-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
fix: issues when patching binary files #14521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
8bf7d51
dad191c
ec35668
66b64b1
88d8064
2d32f71
662a1ba
7c786b3
3799c5b
ff5ab53
6adcbcc
4035869
ad7335b
7595a70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| "tauri": patch:changes | ||
| "tauri-cli": patch:changes | ||
| "tauri-bundler": patch:changes | ||
| "@tauri-apps/cli": patch:changes | ||
| --- | ||
|
|
||
| Change the way bundle type information is added to binary files. Intead of looking up value of a variable we simply look for default value. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,8 @@ | |
| // SPDX-License-Identifier: MIT | ||
|
|
||
| mod category; | ||
| #[cfg(any(target_os = "linux", target_os = "windows"))] | ||
| mod kmp; | ||
| #[cfg(target_os = "linux")] | ||
| mod linux; | ||
| #[cfg(target_os = "macos")] | ||
|
|
@@ -15,29 +17,46 @@ mod windows; | |
|
|
||
| use tauri_utils::{display_path, platform::Target as TargetPlatform}; | ||
|
|
||
| #[cfg(any(target_os = "linux", target_os = "windows"))] | ||
| const BUNDLE_VAR_TOKEN: &[u8] = b"__TAURI_BUNDLE_TYPE_VAR_UNK"; | ||
| /// Patch a binary with bundle type information | ||
| #[cfg(any(target_os = "linux", target_os = "windows"))] | ||
| fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> { | ||
| match package_type { | ||
| let mut file_data = std::fs::read(binary).expect("Could not read binary file."); | ||
|
|
||
| if let Some(bundle_var_index) = kmp::index_of(BUNDLE_VAR_TOKEN, &file_data) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't look at it closely yet, but I think this would fail when patching the binary for the second bundle type right? (e.g
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was an issue with double signing the binaries or patching already signed binary (I don't remember exactly) and the fix was to copy the original binary before each bundle so this should be fine. I tested on linux with standard deb,rpm and appImage and didn't get any errors.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you remember if after the bundling is all done the binary in target/release/ will be a fresh one or the one from the last patch?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've double checked and something is not right. I will fix it so that we always patch and bundle a copy of the binary and at the end we're left with the original, unpatched one.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am still a bit concerned about this approach, like if we stopped at one of these steps (user pressed ctrl-c or we had an error somewhere), there's no way to recover/bundle again other than rebuilding the app's binary through cargo, and since we're matching any string in the binary, although unlikely, we risk replacing the wrong things In my opinion, this method should not be used for the binary patching/main detection method
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ah, didn't think of that, that should probably fix this problem, it might leave files in the temp folder but I guess if that's somewhere inside
Fair, I just don't really like that chance to be honest 😂
I messed up the words when moving them, I was thinking it shouldn't be the 'main binary patching/detection' method (e.g. use the current one and adding this for a backup)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ah my bad. I mean honestly fair enough! we can keep both 🤷
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I remembered correctly we do this but it's just for Windows right now. I'm assuming simply extending this approach to Linux will be enough, right? It's basically boils down to the same issue. I will work on this later today.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the future we should look into the "work on temp copy only" approach but that's not urgent
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So just to be clear, are we going to use this method for binary patching exclusively or do we want to use this as a fallback? (e.g. the find link section thing -> find the string and replace) |
||
| #[cfg(target_os = "linux")] | ||
| PackageType::AppImage | PackageType::Deb | PackageType::Rpm => { | ||
| log::info!( | ||
| "Patching binary {:?} for type {}", | ||
| binary, | ||
| package_type.short_name() | ||
| ); | ||
| linux::patch_binary(binary, package_type)?; | ||
| } | ||
| PackageType::Nsis | PackageType::WindowsMsi => { | ||
| log::info!( | ||
| "Patching binary {:?} for type {}", | ||
| binary, | ||
| package_type.short_name() | ||
| ); | ||
| windows::patch_binary(binary, package_type)?; | ||
| } | ||
| _ => (), | ||
| } | ||
| let bundle_type = match package_type { | ||
| crate::PackageType::Deb => b"__TAURI_BUNDLE_TYPE_VAR_DEB", | ||
| crate::PackageType::Rpm => b"__TAURI_BUNDLE_TYPE_VAR_RPM", | ||
| crate::PackageType::AppImage => b"__TAURI_BUNDLE_TYPE_VAR_APP", | ||
| _ => { | ||
| return Err(crate::Error::InvalidPackageType( | ||
| package_type.short_name().to_owned(), | ||
| "Linux".to_owned(), | ||
| )) | ||
| } | ||
| }; | ||
| #[cfg(target_os = "windows")] | ||
| let bundle_type = match package_type { | ||
| crate::PackageType::Nsis => b"__TAURI_BUNDLE_TYPE_VAR_NSS", | ||
| crate::PackageType::WindowsMsi => b"__TAURI_BUNDLE_TYPE_VAR_MSI", | ||
| _ => { | ||
| return Err(crate::Error::InvalidPackageType( | ||
| package_type.short_name().to_owned(), | ||
| "Windows".to_owned(), | ||
| )) | ||
| } | ||
| }; | ||
|
|
||
| file_data[bundle_var_index..bundle_var_index + BUNDLE_VAR_TOKEN.len()] | ||
| .copy_from_slice(bundle_type); | ||
|
|
||
| std::fs::write(binary, &file_data) | ||
| .map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?; | ||
| } else { | ||
| return Err(crate::Error::MissingBundleTypeVar); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -92,45 +111,32 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> { | |
| .expect("Main binary missing in settings"); | ||
| let main_binary_path = settings.binary_path(main_binary); | ||
|
|
||
| // When packaging multiple binary types, we make a copy of the unsigned main_binary so that we can | ||
| // restore it after each package_type step. This avoids two issues: | ||
| // We make a copy of the unsigned main_binary so that we can restore it after each package_type step. | ||
kandrelczyk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // This allows us to patch the binary correctly and avoids two issues: | ||
| // - modifying a signed binary without updating its PE checksum can break signature verification | ||
| // - codesigning tools should handle calculating+updating this, we just need to ensure | ||
| // (re)signing is performed after every `patch_binary()` operation | ||
| // - signing an already-signed binary can result in multiple signatures, causing verification errors | ||
| let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows) | ||
| && settings.windows().can_sign() | ||
| && package_types.len() > 1; | ||
| let mut unsigned_main_binary_copy = tempfile::tempfile()?; | ||
| if main_binary_reset_required { | ||
| let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?; | ||
| std::io::copy(&mut unsigned_main_binary, &mut unsigned_main_binary_copy)?; | ||
| } | ||
| // TODO: change this to work on a copy while preserving the main binary unchanged | ||
| let mut main_binary_copy = tempfile::tempfile()?; | ||
| let mut main_binary_orignal = std::fs::File::open(&main_binary_path)?; | ||
| std::io::copy(&mut main_binary_orignal, &mut main_binary_copy)?; | ||
|
|
||
| let mut main_binary_signed = false; | ||
| let mut bundles = Vec::<Bundle>::new(); | ||
| for package_type in &package_types { | ||
| // bundle was already built! e.g. DMG already built .app | ||
| if bundles.iter().any(|b| b.package_type == *package_type) { | ||
| continue; | ||
| } | ||
|
|
||
| #[cfg(any(target_os = "linux", target_os = "windows"))] | ||
| if let Err(e) = patch_binary(&main_binary_path, package_type) { | ||
| 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"); | ||
| } | ||
|
|
||
| // sign main binary for every package type after patch | ||
| if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() { | ||
| if main_binary_signed && main_binary_reset_required { | ||
| let mut signed_main_binary = std::fs::OpenOptions::new() | ||
| .write(true) | ||
| .truncate(true) | ||
| .open(&main_binary_path)?; | ||
| unsigned_main_binary_copy.seek(SeekFrom::Start(0))?; | ||
| std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?; | ||
| } | ||
| windows::sign::try_sign(&main_binary_path, settings)?; | ||
| main_binary_signed = true; | ||
| } | ||
|
|
||
| let bundle_paths = match package_type { | ||
|
|
@@ -172,6 +178,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> { | |
| package_type: package_type.to_owned(), | ||
| bundle_paths, | ||
| }); | ||
|
|
||
| // Restore unsigned and unpatched binary | ||
| let mut modified_main_binary = std::fs::OpenOptions::new() | ||
| .write(true) | ||
| .truncate(true) | ||
| .open(&main_binary_path)?; | ||
| main_binary_copy.seek(SeekFrom::Start(0))?; | ||
| std::io::copy(&mut main_binary_copy, &mut modified_main_binary)?; | ||
| } | ||
|
|
||
| if let Some(updater) = settings.updater() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle> | ||
| // Copyright 2019-2024 Tauri Programme within The Commons Conservancy | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| // Knuth–Morris–Pratt algorithm | ||
| // based on https://github.com/howeih/rust_kmp | ||
| #[cfg(any(target_os = "linux", target_os = "windows"))] | ||
| pub fn index_of(pattern: &[u8], target: &[u8]) -> Option<usize> { | ||
| let failure_function = find_failure_function(pattern); | ||
|
|
||
| let mut t_i: usize = 0; | ||
| let mut p_i: usize = 0; | ||
| let target_len = target.len(); | ||
| let mut result_idx = None; | ||
| let pattern_len = pattern.len(); | ||
|
|
||
| while (t_i < target_len) && (p_i < pattern_len) { | ||
| if target[t_i] == pattern[p_i] { | ||
| if result_idx.is_none() { | ||
| result_idx.replace(t_i); | ||
| } | ||
| t_i += 1; | ||
| p_i += 1; | ||
| if p_i >= pattern_len { | ||
| return result_idx; | ||
| } | ||
| } else { | ||
| if p_i == 0 { | ||
| p_i = 0; | ||
| t_i += 1; | ||
| } else { | ||
| p_i = failure_function[p_i - 1]; | ||
| } | ||
| result_idx = None; | ||
| } | ||
| } | ||
| None | ||
| } | ||
|
|
||
| #[cfg(any(target_os = "linux", target_os = "windows"))] | ||
| fn find_failure_function(pattern: &[u8]) -> Vec<usize> { | ||
| let mut i = 1; | ||
| let mut j = 0; | ||
| let pattern_length = pattern.len(); | ||
| let end_i = pattern_length - 1; | ||
| let mut failure_function = vec![0usize; pattern_length]; | ||
| while i <= end_i { | ||
| if pattern[i] == pattern[j] { | ||
| failure_function[i] = j + 1; | ||
| i += 1; | ||
| j += 1; | ||
| } else if j == 0 { | ||
| failure_function[i] = 0; | ||
| i += 1; | ||
| } else { | ||
| j = failure_function[j - 1]; | ||
| } | ||
| } | ||
| failure_function | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,19 +99,13 @@ pub enum Error { | |
| #[error("Wrong package type {0} for platform {1}")] | ||
| InvalidPackageType(String, String), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason we change this to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I merged both Linux and Windows cases in one function so I thought the platform validation doesn't apply anymore but after thinking about it I split it again |
||
| /// Bundle type symbol missing in binary | ||
| #[cfg_attr( | ||
| target_os = "linux", | ||
| 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)") | ||
| )] | ||
| #[cfg_attr( | ||
| not(target_os = "linux"), | ||
| error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date") | ||
| )] | ||
| #[error("__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date")] | ||
| MissingBundleTypeVar, | ||
| /// Failed to write binary file changed | ||
| #[error("Failed to write binary file changes: `{0}`")] | ||
| BinaryWriteError(String), | ||
| /// Invalid offset while patching binary file | ||
| #[deprecated] | ||
| #[error("Invalid offset while patching binary file")] | ||
| BinaryOffsetOutOfRange, | ||
kandrelczyk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// Unsupported architecture. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FabianLars do we want to delay this to next minor? since this isn't compatible with our current tauri and cli minor versions combo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds reasonable