diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a85ed74..6d4ebc3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -22,10 +22,14 @@ jobs: toolchain: ${{ matrix.toolchain }} profile: minimal override: true - targets: "wasm32-unknown-unknown" + targets: "wasm32-unknown-unknown,i686-pc-windows-msvc" - name: "Test on the native x86_64-pc-windows-mscv" run: cargo test --all-features --workspace - name: "Test when cross-compiling to x86_64-pc-windows-gnu" env: AUDITABLE_TEST_TARGET: "x86_64-pc-windows-gnu" run: cargo test --all-features --workspace + - name: "Test when cross-compiling to i686-pc-windows-msvc" + env: + AUDITABLE_TEST_TARGET: "i686-pc-windows-msvc" + run: cargo test --all-features --workspace diff --git a/cargo-auditable/src/object_file.rs b/cargo-auditable/src/object_file.rs index d4997a9..96b1260 100644 --- a/cargo-auditable/src/object_file.rs +++ b/cargo-auditable/src/object_file.rs @@ -181,6 +181,38 @@ fn create_object_file( abi_version, e_flags, }; + + // Add the COFF `@feat.00` symbol used to communicate linker feature flags. + // + // For i386, bit 0 (`IMAGE_FILE_SAFE_EXCEPTION_HANDLER`) marks the object as SAFESEH-compatible. + // Without this, linkers can reject the object with `/safeseh`. + // + // - + // - + 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) } @@ -322,6 +354,87 @@ windows assert_eq!(result.architecture(), Architecture::X86_64); } + #[test] + fn test_create_object_file_windows_msvc_i686() { + 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 result = create_object_file(&target_info, target_triple).unwrap(); + assert_eq!(result.format(), BinaryFormat::Coff); + 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 diff --git a/cargo-auditable/src/platform_detection.rs b/cargo-auditable/src/platform_detection.rs index 26d7a72..07cdca1 100644 --- a/cargo-auditable/src/platform_detection.rs +++ b/cargo-auditable/src/platform_detection.rs @@ -22,6 +22,10 @@ pub fn is_32bit(target_info: &RustcTargetInfo) -> bool { key_equals(target_info, "target_pointer_width", "32") } +pub fn is_x86(target_info: &RustcTargetInfo) -> bool { + key_equals(target_info, "target_arch", "x86") +} + fn key_equals(target_info: &RustcTargetInfo, key: &str, value: &str) -> bool { target_info.get(key).map(|s| s.as_str()) == Some(value) } diff --git a/cargo-auditable/src/rustc_wrapper.rs b/cargo-auditable/src/rustc_wrapper.rs index 51ffba4..a765c3f 100644 --- a/cargo-auditable/src/rustc_wrapper.rs +++ b/cargo-auditable/src/rustc_wrapper.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ binary_file, collect_audit_data, - platform_detection::{is_apple, is_msvc, is_wasm}, + platform_detection::{is_apple, is_msvc, is_wasm, is_x86}, rustc_arguments::{self, should_embed_audit_data}, target_info, }; @@ -134,7 +134,14 @@ fn rustc_command_with_audit_data(rustc_path: &OsStr) -> Option { command.arg("-Clink-arg=-Wl,-u,_AUDITABLE_VERSION_INFO"); } } else if is_msvc(&target_info) { - command.arg("-Clink-arg=/INCLUDE:AUDITABLE_VERSION_INFO"); + // On x86 MSVC, the `object` crate's CoffI386 mangling adds a `_` + // prefix to global symbols, so the linker must reference the + // decorated name. + if is_x86(&target_info) { + command.arg("-Clink-arg=/INCLUDE:_AUDITABLE_VERSION_INFO"); + } else { + command.arg("-Clink-arg=/INCLUDE:AUDITABLE_VERSION_INFO"); + } } else if is_wasm(&target_info) { // We don't emit the symbol name in WASM, so nothing to do } else { diff --git a/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml b/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml index 1287e6b..debb6d2 100644 --- a/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml +++ b/cargo-auditable/tests/fixtures/bare_linker/.cargo/config.toml @@ -31,3 +31,9 @@ rustflags = ["-C", "target-feature=+crt-static"] [target.x86_64-unknown-linux-musl] 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" \ No newline at end of file