From 9c7768733fb533f88f1729ca4043433e33f86f16 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 4 Mar 2026 11:39:46 -0600 Subject: [PATCH 1/3] Fix linking on i686 MSVC when using bare linkers --- cargo-auditable/src/object_file.rs | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/cargo-auditable/src/object_file.rs b/cargo-auditable/src/object_file.rs index e8b2e09..36dd8a4 100644 --- a/cargo-auditable/src/object_file.rs +++ b/cargo-auditable/src/object_file.rs @@ -181,6 +181,44 @@ fn create_object_file( abi_version, e_flags, }; + + // Add the COFF `@feat.00` symbol used to communicate linker feature flags. + // + // When linking with /SAFESEH on x86, lld requires that all linker inputs be marked as safe + // exception handling compatible. Our metadata objects masquerade as regular COFF objects and + // are treated as linker inputs, so they need the flag too. + // + // This implementation mirrors the rustc's metadata object generation: + // + // + // See also: + // + // - + // - + if binary_format == BinaryFormat::Coff { + // Disable mangling so the "@feat.00" symbol name is written verbatim. + // CoffI386 mangling adds a `_` prefix which would break this special symbol. + let original_mangling = file.mangling(); + file.set_mangling(write::Mangling::None); + + let mut feature: u64 = 0; + if architecture == Architecture::I386 { + feature |= 1; // IMAGE_FILE_SAFE_EXCEPTION_HANDLER + } + file.add_symbol(Symbol { + name: b"@feat.00".to_vec(), + value: feature, + size: 0, + kind: SymbolKind::Data, + scope: SymbolScope::Compilation, + weak: false, + section: SymbolSection::Absolute, + flags: SymbolFlags::None, + }); + + file.set_mangling(original_mangling); + } + Some(file) } @@ -344,6 +382,65 @@ windows assert_eq!(result.architecture(), Architecture::I386); } + /// Verify that i686 COFF metadata objects contain an absolute `@feat.00` symbol with + /// `IMAGE_FILE_SAFE_EXCEPTION_HANDLER` (bit 0) set. + /// + /// See + #[test] + fn test_create_metadata_file_windows_msvc_i686_has_feat00() { + let rustc_output = br#"debug_assertions +target_arch="x86" +target_endian="little" +target_env="msvc" +target_family="windows" +target_feature="fxsr" +target_feature="sse" +target_feature="sse2" +target_os="windows" +target_pointer_width="32" +target_vendor="pc" +windows +"#; + let target_triple = "i686-pc-windows-msvc"; + let target_info = parse_rustc_target_info(rustc_output); + let contents = b"test audit data"; + let result = create_metadata_file( + &target_info, + target_triple, + contents, + "AUDITABLE_VERSION_INFO", + ) + .expect("should produce an object file for i686-pc-windows-msvc"); + + // Parse the COFF symbol table and verify `@feat.00` has value bit0=1 and absolute section. + let symtab_ptr = u32::from_le_bytes(result[8..12].try_into().unwrap()) as usize; + let sym_count = u32::from_le_bytes(result[12..16].try_into().unwrap()) as usize; + let symbol_size = 18; + + let feat = (0..sym_count).find_map(|i| { + let start = symtab_ptr + i * symbol_size; + let end = start + symbol_size; + let entry = result.get(start..end)?; + if &entry[0..8] != b"@feat.00" { + return None; + } + let value = u32::from_le_bytes(entry[8..12].try_into().unwrap()); + let section_number = i16::from_le_bytes(entry[12..14].try_into().unwrap()); + Some((value, section_number)) + }); + + let (value, section_number) = feat.expect("COFF object for i686 must contain @feat.00"); + assert_eq!( + value & 1, + 1, + "@feat.00 must set IMAGE_FILE_SAFE_EXCEPTION_HANDLER on i686" + ); + assert_eq!( + section_number, -1, + "@feat.00 must be an absolute COFF symbol (section number -1)" + ); + } + #[test] fn test_create_object_file_windows_gnu() { let rustc_output = br#"debug_assertions From c049e9552502b50a75fa9f31824c9e264f81aaae Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 4 Mar 2026 17:52:10 +0000 Subject: [PATCH 2/3] Add bare linker tests for Windows --- .../tests/fixtures/bare_linker/.cargo/config.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml b/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml index b7511fc..e970667 100644 --- a/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml +++ b/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml @@ -32,3 +32,15 @@ rustflags = ["-C", "target-feature=+crt-static"] linker = "rust-lld" rustflags = ["-C", "target-feature=+crt-static"] +# === Not included in the original Rayhunter example === + +# this tests for https://github.com/rust-lang/rust/issues/96498 +[target.i686-pc-windows-msvc] +linker = "rust-lld" + +# this Just Works so we might as well see if it keeps working +[target.x86_64-pc-windows-msvc] +linker = "rust-lld" + +# GNU targets generally don't work with bare linkers; not even rayhunter uses it there. +# So we're not testing windows-gnu or linux-gnu \ No newline at end of file From c0de6a67ebc0914a1afef3a90fd4cf706bb3504f Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 4 Mar 2026 17:56:17 +0000 Subject: [PATCH 3/3] Update comment --- cargo-auditable/tests/it.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cargo-auditable/tests/it.rs b/cargo-auditable/tests/it.rs index bf3a9ae..b30ce1a 100644 --- a/cargo-auditable/tests/it.rs +++ b/cargo-auditable/tests/it.rs @@ -596,9 +596,11 @@ fn test_bare_linker_inner(sbom: bool) { let cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/bare_linker/Cargo.toml"); // The motivating example is https://github.com/EFForg/rayhunter/blob/main/.cargo/config.toml - // and the config file fixture is based on that. - // There doesn't seem to be a way to build with a bare linker for GNU targets, only Apple and Musl, - // so this tests really only does anything on those. + // and the config file fixture is based on that, with some additions. + // + // The config file doesn't specify a bare linker for all targets; + // e.g. GNU doesn't seem to support using a bare linker at all. + // See the config file for the exact list of targets where this is tested. let config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/bare_linker/.cargo/config.toml");