Skip to content
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

Mask non-reproducible linker artifacts in libs #2661

Merged
merged 5 commits into from Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file modified crates/targets/aarch64_msvc/lib/windows.0.52.0.lib
Binary file not shown.
Binary file modified crates/targets/i686_msvc/lib/windows.0.52.0.lib
Binary file not shown.
Binary file modified crates/targets/x86_64_msvc/lib/windows.0.52.0.lib
Binary file not shown.
302 changes: 263 additions & 39 deletions crates/tools/msvc/src/main.rs
Expand Up @@ -41,6 +41,7 @@ changes can sneak in undetected.
let mut cmd = std::process::Command::new("lib");
cmd.current_dir(&output);
cmd.arg("/nologo");
cmd.arg("/Brepro");
cmd.arg("/out:windows.lib");

for library in libraries.keys() {
Expand All @@ -53,45 +54,7 @@ changes can sneak in undetected.
std::fs::remove_file(output.join(format!("{library}.lib"))).unwrap();
}

// Clear out timestamps in the resulting library.
let mut archive = std::fs::File::options()
.read(true)
.write(true)
.open(output.join("windows.lib"))
.unwrap();
let len = archive.metadata().unwrap().len();
let mut header = [0u8; 8];
archive.read_exact(&mut header).unwrap();
assert_eq!(&header, b"!<arch>\n");
for num in 1.. {
archive.seek(SeekFrom::Current(16)).unwrap(); // identifier
assert_eq!(archive.write(b"-1 ").unwrap(), 12); // replace archive timestamp
let mut buf = [0u8; 32];
archive.read_exact(&mut buf).unwrap(); // remainder of the archive member header
let mut size = i64::from_str(
std::str::from_utf8(
buf[20..][..10]
.split(u8::is_ascii_whitespace)
.next()
.unwrap(),
)
.unwrap(),
)
.unwrap();
// The first two members are indexes, skip them.
if num > 3 {
archive.read_exact(&mut buf[..4]).unwrap(); // member header
let timestamp_offset = if buf[..4] == [0, 0, 0xff, 0xff] { 4 } else { 0 };
archive.seek(SeekFrom::Current(timestamp_offset)).unwrap();
archive.write_all(&[0; 4]).unwrap(); // replace member timestamp
size -= timestamp_offset + 8;
}
if archive.seek(SeekFrom::Current((size + 1) & !1)).unwrap() >= len {
break;
}
}
archive.flush().unwrap();
drop(archive);
make_reproducible(&output.join("windows.lib"));

std::fs::remove_dir_all(format!("crates/targets/{platform}/lib")).unwrap();
std::fs::create_dir_all(format!("crates/targets/{platform}/lib")).unwrap();
Expand Down Expand Up @@ -191,3 +154,264 @@ EXPORTS
path.push(format!("{library}.obj"));
std::fs::remove_file(&path).unwrap();
}

fn make_reproducible(lib: &std::path::Path) {
/*
The linker emits the following non-reproducible
elements during lib generation:
* archive member header timestamps (set to -1 via /Brepro)
* import header time/date stamps
* coff file header time/date stamps
* compiler version coff symbols
These must overwritten to ensure reproducibility
across linker versions and time.
*/

let mut archive = std::fs::File::options()
.read(true)
.write(true)
.open(lib)
.unwrap();
let len = archive.metadata().unwrap().len();
let mut header = [0u8; 8];
archive.read_exact(&mut header).unwrap();
assert_eq!(&header, b"!<arch>\n");
loop {
let mut buf = [0u8; 32];

//
// Archive member header
//

// Name
archive.read_exact(&mut buf[..16]).unwrap();
let skip_member = match &buf[..4] {
b"/ " => true, // Linker member
b"// " => true, // Long names table
_ => false,
};

// Timestamp, User ID, Group ID, and Mode
archive.seek(SeekFrom::Current(12 + 6 + 6 + 8)).unwrap();

// Size, sans header
archive.read_exact(&mut buf[..10]).unwrap();
let size = i64::from_str(
std::str::from_utf8(buf.split(u8::is_ascii_whitespace).next().unwrap()).unwrap(),
)
.unwrap();

// End of archive member header marker
archive.read_exact(&mut buf[..2]).unwrap();
assert_eq!(&buf[..2], [0x60, 0x0A]);

if !skip_member {
let start = archive.stream_position().unwrap();

// Machine type
archive.read_exact(&mut buf[..2]).unwrap();

if buf[..2] == [0x00, 0x00] {
//
// Import header
//

// Signature 2
archive.read_exact(&mut buf[..2]).unwrap();
assert_eq!(&buf[..2], [0xFF, 0xFF]);

// Version and Machine
archive.seek(SeekFrom::Current(4)).unwrap();

// Timestamp (zero out)
archive.write_all(&[0u8; 4]).unwrap();

// ... ignore rest
} else {
//
// COFF File Header
//
// Section count
archive.read_exact(&mut buf[..2]).unwrap();
let sections = u16::from_le_bytes((&buf[..2]).try_into().unwrap());

// Timestamp (zero out)
archive.write_all(&[0u8; 4]).unwrap();

// Symbol table offset (relative to end of archive member header)
archive.read_exact(&mut buf[..4]).unwrap();
let offset = u32::from_le_bytes((&buf[..4]).try_into().unwrap()) - 12;

if offset != 0 {
//
// Symbol table
//
let last_position = archive.stream_position().unwrap();
archive.seek(SeekFrom::Current(offset.into())).unwrap();

// Name
archive.read_exact(&mut buf[..8]).unwrap();
let is_compiler_id = std::str::from_utf8(&buf[..8]).unwrap() == "@comp.id";

// Value
if is_compiler_id {
archive.write_all(&[0; 4]).unwrap();
}

archive.seek(SeekFrom::Start(last_position)).unwrap();
}

// ... and rest
archive.seek(SeekFrom::Current(8)).unwrap();

for _ in 1..sections {
//
// Section Header
//

// Name
archive.read_exact(&mut buf[..8]).unwrap();
let is_symbols = std::str::from_utf8(&buf[..8]).unwrap() == ".debug$S";

// Virtual size and address
archive.seek(SeekFrom::Current(8)).unwrap();

// Size of data
archive.seek(SeekFrom::Current(4)).unwrap();

// Pointer to raw data
archive.read_exact(&mut buf[..4]).unwrap();
let start_of_data = u32::from_le_bytes((&buf[..4]).try_into().unwrap())
+ u32::try_from(start).unwrap();

// ... and rest
archive.seek(SeekFrom::Current(16)).unwrap();

if is_symbols {
//
// $$SYMBOLS
//

let last_position = archive.stream_position().unwrap();
archive.seek(SeekFrom::Start(start_of_data.into())).unwrap();

// Signature (CV_SIGNATURE_C11)
archive.read_exact(&mut buf[..4]).unwrap();
assert_eq!(&buf[..4], [0x02, 0x00, 0x00, 0x00]);

loop {
// Record length
archive.read_exact(&mut buf[..2]).unwrap();
let length = u16::from_le_bytes((&buf[..2]).try_into().unwrap());

// Index
archive.read_exact(&mut buf[..2]).unwrap();
let index = u16::from_le_bytes((&buf[..2]).try_into().unwrap());

// Skip non S_COMPILE2 records
if index != 0x1013 {
archive.seek(SeekFrom::Current(-2 + length as i64)).unwrap();
continue;
}

// Flags, Machine
archive.seek(SeekFrom::Current(4 + 2)).unwrap();

// Frontend major_minor_build and Backend major_minor_build (zero out version)
archive.write_all(&[0; 12]).unwrap();

archive.seek(SeekFrom::Start(last_position)).unwrap();
break;
}
}
}
}

archive.seek(SeekFrom::Start(start)).unwrap();
}

if archive.seek(SeekFrom::Current((size + 1) & !1)).unwrap() >= len {
break;
}
}
archive.flush().unwrap();
drop(archive);
}

#[test]
fn test_make_reproducible() {
for (machine, offset) in [("x86", 0), ("x64", 12), ("arm64", 12)] {
let mut def = std::fs::File::create("test.def").unwrap();
def.write_all(
format!(
r#"
LIBRARY long-library-name-for-placement-in-long-names-table.dll
EXPORTS
A=A.#1
B=B.#2
C=C.#3
"#
)
.as_bytes(),
)
.unwrap();
drop(def);

let mut cmd = std::process::Command::new("lib");
cmd.arg("/nologo");
cmd.arg("/Brepro");
cmd.arg(format!("/machine:{machine}"));
cmd.arg(format!("/out:test.lib"));
cmd.arg(format!("/def:test.def"));
cmd.output().unwrap();

let mut archive = std::fs::File::options()
.read(true)
.write(true)
.open("test.lib")
.unwrap();

let mut buf = [0; 24];

make_reproducible(&std::path::Path::new("test.lib"));

// Archive member header timestamp
archive.seek(SeekFrom::Start(0x18)).unwrap();
archive.read_exact(&mut buf[..12]).unwrap();
assert_eq!(&buf[..12], b"-1 ");

// COFF file header time/date stamp
archive.seek(SeekFrom::Start(0x322 - offset)).unwrap();
archive.read_exact(&mut buf[..4]).unwrap();
assert_eq!(&buf[..4], [0x00, 0x00, 0x00, 0x00]);

// Compiler version symbol
archive.seek(SeekFrom::Start(0x481 - offset)).unwrap();
archive.read_exact(&mut buf[..12]).unwrap();
assert_eq!(&buf[..12], b"@comp.id\0\0\0\0");

// Import header time/date stamp
archive.seek(SeekFrom::Start(0x493 - offset)).unwrap();
archive.read_exact(&mut buf[..4]).unwrap();
assert_eq!(&buf[..4], [0x00, 0x00, 0x00, 0x00]);

// Sampling to ensure lib is consistent
archive.seek(SeekFrom::Start(0xB4)).unwrap();
archive.read_exact(&mut buf[..24]).unwrap();
assert_eq!(&buf[..24], b"__NULL_IMPORT_DESCRIPTOR");

archive.seek(SeekFrom::Start(0x26E - offset)).unwrap();
archive.read_exact(&mut buf[..4]).unwrap();
assert_eq!(&buf[..4], b"// ");

archive.seek(SeekFrom::Start(0x405 - offset)).unwrap();
archive.read_exact(&mut buf[..18]).unwrap();
assert_eq!(&buf[..18], b"Microsoft (R) LINK");

drop(archive);

std::fs::remove_file("test.lib").unwrap();
std::fs::remove_file("test.exp").unwrap();
std::fs::remove_file("test.def").unwrap();
}
}