mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
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>
This commit is contained in:
8
.changes/fix-binary-patching.md
Normal file
8
.changes/fix-binary-patching.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"tauri": minor:changes
|
||||
"tauri-cli": minor:changes
|
||||
"tauri-bundler": minor:changes
|
||||
"@tauri-apps/cli": minor:changes
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -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 {
|
||||
#[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 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) {
|
||||
#[cfg(target_os = "linux")]
|
||||
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,22 +111,17 @@ 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.
|
||||
// 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
|
||||
@@ -115,22 +129,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||
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() {
|
||||
|
||||
61
crates/tauri-bundler/src/bundle/kmp/mod.rs
Normal file
61
crates/tauri-bundler/src/bundle/kmp/mod.rs
Normal file
@@ -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
|
||||
}
|
||||
@@ -7,8 +7,3 @@ pub mod appimage;
|
||||
pub mod debian;
|
||||
pub mod freedesktop;
|
||||
pub mod rpm;
|
||||
|
||||
mod util;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use util::patch_binary;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/// Change value of __TAURI_BUNDLE_TYPE static variable to mark which package type it was bundled in
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn patch_binary(
|
||||
binary_path: &std::path::PathBuf,
|
||||
package_type: &crate::PackageType,
|
||||
) -> crate::Result<()> {
|
||||
let mut file_data = std::fs::read(binary_path).expect("Could not read binary file.");
|
||||
|
||||
let elf = match goblin::Object::parse(&file_data)? {
|
||||
goblin::Object::Elf(elf) => elf,
|
||||
_ => return Err(crate::Error::GenericError("Not an ELF file".to_owned())),
|
||||
};
|
||||
|
||||
let offset = find_bundle_type_symbol(elf).ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
let offset = offset as usize;
|
||||
if offset + 3 <= file_data.len() {
|
||||
let chars = &mut file_data[offset..offset + 3];
|
||||
match package_type {
|
||||
crate::PackageType::Deb => chars.copy_from_slice(b"DEB"),
|
||||
crate::PackageType::Rpm => chars.copy_from_slice(b"RPM"),
|
||||
crate::PackageType::AppImage => chars.copy_from_slice(b"APP"),
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"linux".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(binary_path, &file_data)
|
||||
.map_err(|error| crate::Error::BinaryWriteError(error.to_string()))?;
|
||||
} else {
|
||||
return Err(crate::Error::BinaryOffsetOutOfRange);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find address of a symbol in relocations table
|
||||
#[cfg(target_os = "linux")]
|
||||
fn find_bundle_type_symbol(elf: goblin::elf::Elf<'_>) -> Option<i64> {
|
||||
for sym in elf.syms.iter() {
|
||||
if let Some(name) = elf.strtab.get_at(sym.st_name) {
|
||||
if name == "__TAURI_BUNDLE_TYPE" {
|
||||
for reloc in elf.dynrelas.iter() {
|
||||
if reloc.r_offset == sym.st_value {
|
||||
return Some(reloc.r_addend.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -14,5 +14,3 @@ pub use util::{
|
||||
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME,
|
||||
WIX_UPDATER_OUTPUT_FOLDER_NAME,
|
||||
};
|
||||
|
||||
pub use util::patch_binary;
|
||||
|
||||
@@ -77,75 +77,3 @@ pub fn os_bitness<'a>() -> Option<&'a str> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch_binary(binary_path: &PathBuf, package_type: &crate::PackageType) -> crate::Result<()> {
|
||||
let mut file_data = std::fs::read(binary_path)?;
|
||||
|
||||
let pe = match goblin::Object::parse(&file_data)? {
|
||||
goblin::Object::PE(pe) => pe,
|
||||
_ => {
|
||||
return Err(crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, "binary is not a PE file").into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let tauri_bundle_section = pe
|
||||
.sections
|
||||
.iter()
|
||||
.find(|s| s.name().unwrap_or_default() == ".taubndl")
|
||||
.ok_or(crate::Error::MissingBundleTypeVar)?;
|
||||
|
||||
let data_offset = tauri_bundle_section.pointer_to_raw_data as usize;
|
||||
let pointer_size = if pe.is_64 { 8 } else { 4 };
|
||||
let ptr_bytes = file_data
|
||||
.get(data_offset..data_offset + pointer_size)
|
||||
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
|
||||
// `try_into` is safe to `unwrap` here because we have already checked the slice's size through `get`
|
||||
let ptr_value = if pe.is_64 {
|
||||
u64::from_le_bytes(ptr_bytes.try_into().unwrap())
|
||||
} else {
|
||||
u32::from_le_bytes(ptr_bytes.try_into().unwrap()).into()
|
||||
};
|
||||
|
||||
let rdata_section = pe
|
||||
.sections
|
||||
.iter()
|
||||
.find(|s| s.name().unwrap_or_default() == ".rdata")
|
||||
.ok_or_else(|| {
|
||||
crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, ".rdata section not found").into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let rva = ptr_value.checked_sub(pe.image_base as u64).ok_or_else(|| {
|
||||
crate::Error::BinaryParseError(
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid RVA offset").into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// see "Relative virtual address (RVA)" for explanation of offset arithmetic here:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#general-concepts
|
||||
let file_offset = rdata_section.pointer_to_raw_data as usize
|
||||
+ (rva as usize).saturating_sub(rdata_section.virtual_address as usize);
|
||||
|
||||
// Overwrite the string at that offset
|
||||
let string_bytes = file_data
|
||||
.get_mut(file_offset..file_offset + 3)
|
||||
.ok_or(crate::Error::BinaryOffsetOutOfRange)?;
|
||||
match package_type {
|
||||
crate::PackageType::Nsis => string_bytes.copy_from_slice(b"NSS"),
|
||||
crate::PackageType::WindowsMsi => string_bytes.copy_from_slice(b"MSI"),
|
||||
_ => {
|
||||
return Err(crate::Error::InvalidPackageType(
|
||||
package_type.short_name().to_owned(),
|
||||
"windows".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(binary_path, &file_data)
|
||||
.map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -99,19 +99,13 @@ pub enum Error {
|
||||
#[error("Wrong package type {0} for platform {1}")]
|
||||
InvalidPackageType(String, String),
|
||||
/// 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,
|
||||
/// Unsupported architecture.
|
||||
|
||||
@@ -344,23 +344,20 @@ fn resource_dir_from<P: AsRef<std::path::Path>>(
|
||||
// Variable holding the type of bundle the executable is stored in. This is modified by binary
|
||||
// patching during build
|
||||
#[used]
|
||||
#[no_mangle]
|
||||
#[cfg_attr(not(target_vendor = "apple"), link_section = ".taubndl")]
|
||||
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,taubndl")]
|
||||
// Marked as `mut` because it could get optimized away without it,
|
||||
// see https://github.com/tauri-apps/tauri/pull/13812
|
||||
static mut __TAURI_BUNDLE_TYPE: &str = "UNK";
|
||||
static mut __TAURI_BUNDLE_TYPE: &str = "__TAURI_BUNDLE_TYPE_VAR_UNK";
|
||||
|
||||
/// Get the type of the bundle current binary is packaged in.
|
||||
/// If the bundle type is unknown, it returns [`Option::None`].
|
||||
pub fn bundle_type() -> Option<BundleType> {
|
||||
unsafe {
|
||||
match __TAURI_BUNDLE_TYPE {
|
||||
"DEB" => Some(BundleType::Deb),
|
||||
"RPM" => Some(BundleType::Rpm),
|
||||
"APP" => Some(BundleType::AppImage),
|
||||
"MSI" => Some(BundleType::Msi),
|
||||
"NSS" => Some(BundleType::Nsis),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_DEB" => Some(BundleType::Deb),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_RPM" => Some(BundleType::Rpm),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_APP" => Some(BundleType::AppImage),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_MSI" => Some(BundleType::Msi),
|
||||
"__TAURI_BUNDLE_TYPE_VAR_NSS" => Some(BundleType::Nsis),
|
||||
_ => {
|
||||
if cfg!(target_os = "macos") {
|
||||
Some(BundleType::App)
|
||||
|
||||
Reference in New Issue
Block a user