Skip to content

Commit 0575dd2

Browse files
kandrelczykLegend-MasterFabianLars
authored
fix(bundler): patch bundle type via string replacement (#14521)
Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com> Co-authored-by: FabianLars <github@fabianlars.de>
1 parent eccff97 commit 0575dd2

File tree

9 files changed

+130
-194
lines changed

9 files changed

+130
-194
lines changed

.changes/fix-binary-patching.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"tauri": minor:changes
3+
"tauri-cli": minor:changes
4+
"tauri-bundler": minor:changes
5+
"@tauri-apps/cli": minor:changes
6+
---
7+
8+
Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.

crates/tauri-bundler/src/bundle.rs

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// SPDX-License-Identifier: MIT
55

66
mod category;
7+
#[cfg(any(target_os = "linux", target_os = "windows"))]
8+
mod kmp;
79
#[cfg(target_os = "linux")]
810
mod linux;
911
#[cfg(target_os = "macos")]
@@ -15,29 +17,46 @@ mod windows;
1517

1618
use tauri_utils::{display_path, platform::Target as TargetPlatform};
1719

20+
#[cfg(any(target_os = "linux", target_os = "windows"))]
21+
const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK";
1822
/// Patch a binary with bundle type information
23+
#[cfg(any(target_os = "linux", target_os = "windows"))]
1924
fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {
20-
match package_type {
25+
let mut file_data = std::fs::read(binary).expect("Could not read binary file.");
26+
27+
if let Some(bundle_var_index) = kmp::index_of(BUNDLE_VAR_TOKEN, &file_data) {
2128
#[cfg(target_os = "linux")]
22-
PackageType::AppImage | PackageType::Deb | PackageType::Rpm => {
23-
log::info!(
24-
"Patching binary {:?} for type {}",
25-
binary,
26-
package_type.short_name()
27-
);
28-
linux::patch_binary(binary, package_type)?;
29-
}
30-
PackageType::Nsis | PackageType::WindowsMsi => {
31-
log::info!(
32-
"Patching binary {:?} for type {}",
33-
binary,
34-
package_type.short_name()
35-
);
36-
windows::patch_binary(binary, package_type)?;
37-
}
38-
_ => (),
39-
}
29+
let bundle_type = match package_type {
30+
crate::PackageType::Deb => b"__TAURI_BUNDLE_TYPE_VAR_DEB",
31+
crate::PackageType::Rpm => b"__TAURI_BUNDLE_TYPE_VAR_RPM",
32+
crate::PackageType::AppImage => b"__TAURI_BUNDLE_TYPE_VAR_APP",
33+
_ => {
34+
return Err(crate::Error::InvalidPackageType(
35+
package_type.short_name().to_owned(),
36+
"Linux".to_owned(),
37+
))
38+
}
39+
};
40+
#[cfg(target_os = "windows")]
41+
let bundle_type = match package_type {
42+
crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS",
43+
crate::PackageType::WindowsMsi => b"__TAURI_BUNDLE_TYPE_VAR_MSI",
44+
_ => {
45+
return Err(crate::Error::InvalidPackageType(
46+
package_type.short_name().to_owned(),
47+
"Windows".to_owned(),
48+
))
49+
}
50+
};
4051

52+
file_data[bundle_var_index..bundle_var_index + BUNDLE_VAR_TOKEN.len()]
53+
.copy_from_slice(bundle_type);
54+
55+
std::fs::write(binary, &file_data)
56+
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
57+
} else {
58+
return Err(crate::Error::MissingBundleTypeVar);
59+
}
4160
Ok(())
4261
}
4362

@@ -92,45 +111,32 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
92111
.expect("Main binary missing in settings");
93112
let main_binary_path = settings.binary_path(main_binary);
94113

95-
// When packaging multiple binary types, we make a copy of the unsigned main_binary so that we can
96-
// restore it after each package_type step. This avoids two issues:
114+
// We make a copy of the unsigned main_binary so that we can restore it after each package_type step.
115+
// This allows us to patch the binary correctly and avoids two issues:
97116
// - modifying a signed binary without updating its PE checksum can break signature verification
98117
// - codesigning tools should handle calculating+updating this, we just need to ensure
99118
// (re)signing is performed after every `patch_binary()` operation
100119
// - signing an already-signed binary can result in multiple signatures, causing verification errors
101-
let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows)
102-
&& settings.windows().can_sign()
103-
&& package_types.len() > 1;
104-
let mut unsigned_main_binary_copy = tempfile::tempfile()?;
105-
if main_binary_reset_required {
106-
let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?;
107-
std::io::copy(&mut unsigned_main_binary, &mut unsigned_main_binary_copy)?;
108-
}
120+
// TODO: change this to work on a copy while preserving the main binary unchanged
121+
let mut main_binary_copy = tempfile::tempfile()?;
122+
let mut main_binary_orignal = std::fs::File::open(&main_binary_path)?;
123+
std::io::copy(&mut main_binary_orignal, &mut main_binary_copy)?;
109124

110-
let mut main_binary_signed = false;
111125
let mut bundles = Vec::<Bundle>::new();
112126
for package_type in &package_types {
113127
// bundle was already built! e.g. DMG already built .app
114128
if bundles.iter().any(|b| b.package_type == *package_type) {
115129
continue;
116130
}
117131

132+
#[cfg(any(target_os = "linux", target_os = "windows"))]
118133
if let Err(e) = patch_binary(&main_binary_path, package_type) {
119134
log::warn!("Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues");
120135
}
121136

122137
// sign main binary for every package type after patch
123138
if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
124-
if main_binary_signed && main_binary_reset_required {
125-
let mut signed_main_binary = std::fs::OpenOptions::new()
126-
.write(true)
127-
.truncate(true)
128-
.open(&main_binary_path)?;
129-
unsigned_main_binary_copy.seek(SeekFrom::Start(0))?;
130-
std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?;
131-
}
132139
windows::sign::try_sign(&main_binary_path, settings)?;
133-
main_binary_signed = true;
134140
}
135141

136142
let bundle_paths = match package_type {
@@ -172,6 +178,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
172178
package_type: package_type.to_owned(),
173179
bundle_paths,
174180
});
181+
182+
// Restore unsigned and unpatched binary
183+
let mut modified_main_binary = std::fs::OpenOptions::new()
184+
.write(true)
185+
.truncate(true)
186+
.open(&main_binary_path)?;
187+
main_binary_copy.seek(SeekFrom::Start(0))?;
188+
std::io::copy(&mut main_binary_copy, &mut modified_main_binary)?;
175189
}
176190

177191
if let Some(updater) = settings.updater() {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
2+
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
3+
// SPDX-License-Identifier: Apache-2.0
4+
// SPDX-License-Identifier: MIT
5+
6+
// Knuth–Morris–Pratt algorithm
7+
// based on https://github.com/howeih/rust_kmp
8+
#[cfg(any(target_os = "linux", target_os = "windows"))]
9+
pub fn index_of(pattern: &[u8], target: &[u8]) -> Option<usize> {
10+
let failure_function = find_failure_function(pattern);
11+
12+
let mut t_i: usize = 0;
13+
let mut p_i: usize = 0;
14+
let target_len = target.len();
15+
let mut result_idx = None;
16+
let pattern_len = pattern.len();
17+
18+
while (t_i < target_len) && (p_i < pattern_len) {
19+
if target[t_i] == pattern[p_i] {
20+
if result_idx.is_none() {
21+
result_idx.replace(t_i);
22+
}
23+
t_i += 1;
24+
p_i += 1;
25+
if p_i >= pattern_len {
26+
return result_idx;
27+
}
28+
} else {
29+
if p_i == 0 {
30+
p_i = 0;
31+
t_i += 1;
32+
} else {
33+
p_i = failure_function[p_i - 1];
34+
}
35+
result_idx = None;
36+
}
37+
}
38+
None
39+
}
40+
41+
#[cfg(any(target_os = "linux", target_os = "windows"))]
42+
fn find_failure_function(pattern: &[u8]) -> Vec<usize> {
43+
let mut i = 1;
44+
let mut j = 0;
45+
let pattern_length = pattern.len();
46+
let end_i = pattern_length - 1;
47+
let mut failure_function = vec![0usize; pattern_length];
48+
while i <= end_i {
49+
if pattern[i] == pattern[j] {
50+
failure_function[i] = j + 1;
51+
i += 1;
52+
j += 1;
53+
} else if j == 0 {
54+
failure_function[i] = 0;
55+
i += 1;
56+
} else {
57+
j = failure_function[j - 1];
58+
}
59+
}
60+
failure_function
61+
}

crates/tauri-bundler/src/bundle/linux/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,3 @@ pub mod appimage;
77
pub mod debian;
88
pub mod freedesktop;
99
pub mod rpm;
10-
11-
mod util;
12-
13-
#[cfg(target_os = "linux")]
14-
pub use util::patch_binary;

crates/tauri-bundler/src/bundle/linux/util.rs

Lines changed: 0 additions & 59 deletions
This file was deleted.

crates/tauri-bundler/src/bundle/windows/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,3 @@ pub use util::{
1414
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME,
1515
WIX_UPDATER_OUTPUT_FOLDER_NAME,
1616
};
17-
18-
pub use util::patch_binary;

crates/tauri-bundler/src/bundle/windows/util.rs

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -77,75 +77,3 @@ pub fn os_bitness<'a>() -> Option<&'a str> {
7777
_ => None,
7878
}
7979
}
80-
81-
pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) -> crate::Result<()> {
82-
let mut file_data = std::fs::read(binary_path)?;
83-
84-
let pe = match goblin::Object::parse(&file_data)? {
85-
goblin::Object::PE(pe) => pe,
86-
_ => {
87-
return Err(crate::Error::BinaryParseError(
88-
std::io::Error::new(std::io::ErrorKind::InvalidInput, "binary is not a PE file").into(),
89-
));
90-
}
91-
};
92-
93-
let tauri_bundle_section = pe
94-
.sections
95-
.iter()
96-
.find(|s| s.name().unwrap_or_default() == ".taubndl")
97-
.ok_or(crate::Error::MissingBundleTypeVar)?;
98-
99-
let data_offset = tauri_bundle_section.pointer_to_raw_data as usize;
100-
let pointer_size = if pe.is_64 { 8 } else { 4 };
101-
let ptr_bytes = file_data
102-
.get(data_offset..data_offset + pointer_size)
103-
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
104-
// `try_into` is safe to `unwrap` here because we have already checked the slice's size through `get`
105-
let ptr_value = if pe.is_64 {
106-
u64::from_le_bytes(ptr_bytes.try_into().unwrap())
107-
} else {
108-
u32::from_le_bytes(ptr_bytes.try_into().unwrap()).into()
109-
};
110-
111-
let rdata_section = pe
112-
.sections
113-
.iter()
114-
.find(|s| s.name().unwrap_or_default() == ".rdata")
115-
.ok_or_else(|| {
116-
crate::Error::BinaryParseError(
117-
std::io::Error::new(std::io::ErrorKind::InvalidInput, ".rdata section not found").into(),
118-
)
119-
})?;
120-
121-
let rva = ptr_value.checked_sub(pe.image_base as u64).ok_or_else(|| {
122-
crate::Error::BinaryParseError(
123-
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid RVA offset").into(),
124-
)
125-
})?;
126-
127-
// see "Relative virtual address (RVA)" for explanation of offset arithmetic here:
128-
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#general-concepts
129-
let file_offset = rdata_section.pointer_to_raw_data as usize
130-
+ (rva as usize).saturating_sub(rdata_section.virtual_address as usize);
131-
132-
// Overwrite the string at that offset
133-
let string_bytes = file_data
134-
.get_mut(file_offset..file_offset + 3)
135-
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
136-
match package_type {
137-
crate::PackageType::Nsis => string_bytes.copy_from_slice(b"NSS"),
138-
crate::PackageType::WindowsMsi => string_bytes.copy_from_slice(b"MSI"),
139-
_ => {
140-
return Err(crate::Error::InvalidPackageType(
141-
package_type.short_name().to_owned(),
142-
"windows".to_owned(),
143-
));
144-
}
145-
}
146-
147-
std::fs::write(binary_path, &file_data)
148-
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
149-
150-
Ok(())
151-
}

crates/tauri-bundler/src/error.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,13 @@ pub enum Error {
9999
#[error("Wrong package type {0} for platform {1}")]
100100
InvalidPackageType(String, String),
101101
/// Bundle type symbol missing in binary
102-
#[cfg_attr(
103-
target_os = "linux",
104-
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date and that symbol stripping is disabled (https://doc.rust-lang.org/cargo/reference/profiles.html#strip)")
105-
)]
106-
#[cfg_attr(
107-
not(target_os = "linux"),
108-
error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")
109-
)]
102+
#[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")]
110103
MissingBundleTypeVar,
111104
/// Failed to write binary file changed
112105
#[error("Failed to write binary file changes: `{0}`")]
113106
BinaryWriteError(String),
114107
/// Invalid offset while patching binary file
108+
#[deprecated]
115109
#[error("Invalid offset while patching binary file")]
116110
BinaryOffsetOutOfRange,
117111
/// Unsupported architecture.

0 commit comments

Comments
 (0)