From 5eb2145b3104f48eadd601518904e56aaa9937bf Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Sat, 6 Dec 2025 16:37:09 -0800 Subject: [PATCH] fix(compile): use 8-byte header for embedded section to ensure bytecode alignment (#25377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Change the size header in embedded Mach-O and PE sections from `u32` (4 bytes) to `u64` (8 bytes) - Ensures the data payload starts at an 8-byte aligned offset, which is required for the bytecode cache ## Test plan - [x] Test standalone compilation on macOS - [ ] Test standalone compilation on Windows 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 --- src/StandaloneModuleGraph.zig | 8 +++-- src/bun.js/bindings/c-bindings.cpp | 13 ++++---- src/macho.zig | 10 +++---- src/pe.zig | 30 ++++++++++--------- .../issue/pe-codesigning-integrity.test.ts | 8 ++--- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index 6d8c57ed48..bfc1ffdb54 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -119,7 +119,7 @@ pub const StandaloneModuleGraph = struct { }; const Macho = struct { - pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u32; + pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u64; pub fn getData() ?[]const u8 { if (Bun__getStandaloneModuleGraphMachoLength()) |length| { @@ -127,8 +127,10 @@ pub const StandaloneModuleGraph = struct { return null; } + // BlobHeader has 8 bytes size (u64), so data starts at offset 8. + const data_offset = @sizeOf(u64); const slice_ptr: [*]const u8 = @ptrCast(length); - return slice_ptr[4..][0..length.*]; + return slice_ptr[data_offset..][0..length.*]; } return null; @@ -136,7 +138,7 @@ pub const StandaloneModuleGraph = struct { }; const PE = struct { - pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u32; + pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u64; pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8; pub fn getData() ?[]const u8 { diff --git a/src/bun.js/bindings/c-bindings.cpp b/src/bun.js/bindings/c-bindings.cpp index 0ec3d0b025..b3db3e7000 100644 --- a/src/bun.js/bindings/c-bindings.cpp +++ b/src/bun.js/bindings/c-bindings.cpp @@ -910,14 +910,14 @@ extern "C" void Bun__signpost_emit(os_log_t log, os_signpost_type_t type, os_sig extern "C" { struct BlobHeader { - uint32_t size; + uint64_t size; // 64-bit to ensure data[] starts at 8-byte aligned offset (required for bytecode cache) uint8_t data[]; } __attribute__((aligned(BLOB_HEADER_ALIGNMENT))); } extern "C" BlobHeader __attribute__((section("__BUN,__bun"))) BUN_COMPILED = { 0, 0 }; -extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength() +extern "C" uint64_t* Bun__getStandaloneModuleGraphMachoLength() { return &BUN_COMPILED.size; } @@ -927,7 +927,7 @@ extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength() #include #include -static uint32_t* pe_section_size = nullptr; +static uint64_t* pe_section_size = nullptr; static uint8_t* pe_section_data = nullptr; // Helper function to find and map the .bun section @@ -949,9 +949,10 @@ static bool initializePESection() for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) { if (strncmp((char*)sectionHeader->Name, ".bun", 4) == 0) { // Found the .bun section + // Section format: 8 bytes size (uint64_t) + data BYTE* sectionData = (BYTE*)hModule + sectionHeader->VirtualAddress; - pe_section_size = (uint32_t*)sectionData; - pe_section_data = sectionData + sizeof(uint32_t); + pe_section_size = (uint64_t*)sectionData; + pe_section_data = sectionData + sizeof(uint64_t); // Skip size (8) return true; } sectionHeader++; @@ -960,7 +961,7 @@ static bool initializePESection() return false; } -extern "C" uint32_t Bun__getStandaloneModuleGraphPELength() +extern "C" uint64_t Bun__getStandaloneModuleGraphPELength() { if (!initializePESection()) return 0; return pe_section_size ? *pe_section_size : 0; diff --git a/src/macho.zig b/src/macho.zig index 64db7f5d09..9e8060b6e2 100644 --- a/src/macho.zig +++ b/src/macho.zig @@ -44,7 +44,7 @@ pub const MachoFile = struct { const PAGE_SIZE: u64 = 1 << 12; const HASH_SIZE: usize = 32; // SHA256 = 32 bytes - const header_size = @sizeOf(u32); + const header_size = @sizeOf(u64); const total_size = header_size + data.len; const aligned_size = alignSize(total_size, blob_alignment); @@ -164,14 +164,14 @@ pub const MachoFile = struct { const prev_after_bun_slice = prev_data_slice[original_segsize..]; bun.memmove(after_bun_slice, prev_after_bun_slice); - // Now we copy the u32 size header - std.mem.writeInt(u32, self.data.items[original_fileoff..][0..4], @intCast(data.len), .little); + // Now we copy the u64 size header (8 bytes for alignment) + std.mem.writeInt(u64, self.data.items[original_fileoff..][0..8], @intCast(data.len), .little); // Now we copy the data itself - @memcpy(self.data.items[original_fileoff + 4 ..][0..data.len], data); + @memcpy(self.data.items[original_fileoff + 8 ..][0..data.len], data); // Lastly, we zero any of the padding that was added - const padding_bytes = self.data.items[original_fileoff..][data.len + 4 .. aligned_size]; + const padding_bytes = self.data.items[original_fileoff..][data.len + 8 .. aligned_size]; @memset(padding_bytes, 0); if (code_sign_cmd) |cs| { diff --git a/src/pe.zig b/src/pe.zig index 3de66febc1..03ef9b60e6 100644 --- a/src/pe.zig +++ b/src/pe.zig @@ -500,11 +500,11 @@ pub const PEFile = struct { } } - // Check for overflow before adding 4 - if (data_to_embed.len > std.math.maxInt(u32) - 4) { + // Check for overflow before adding 8 + if (data_to_embed.len > std.math.maxInt(u32) - 8) { return error.Overflow; } - const payload_len = @as(u32, @intCast(data_to_embed.len + 4)); // 4 for LE length prefix + const payload_len = @as(u32, @intCast(data_to_embed.len + 8)); // 8 for LE length prefix const raw_size = try alignUpU32(payload_len, opt.file_alignment); const new_va = try alignUpU32(last_va_end, opt.section_alignment); const new_raw = try alignUpU32(last_file_end, opt.file_alignment); @@ -536,9 +536,9 @@ pub const PEFile = struct { std.mem.copyForwards(u8, self.data.items[new_sh_off .. new_sh_off + @sizeOf(SectionHeader)], std.mem.asBytes(&sh)); // 8. Write payload - // At data[new_raw ..]: write LE length prefix then data - std.mem.writeInt(u32, self.data.items[new_raw..][0..4], @intCast(data_to_embed.len), .little); - @memcpy(self.data.items[new_raw + 4 ..][0..data_to_embed.len], data_to_embed); + // At data[new_raw ..]: write u64 LE length prefix, then data + std.mem.writeInt(u64, self.data.items[new_raw..][0..8], @intCast(data_to_embed.len), .little); + @memcpy(self.data.items[new_raw + 8 ..][0..data_to_embed.len], data_to_embed); // 9. Update headers // Get fresh pointers after resize @@ -573,7 +573,8 @@ pub const PEFile = struct { const section_headers = try self.getSectionHeaders(); for (section_headers) |section| { if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) { - if (section.size_of_raw_data < @sizeOf(u32)) { + // Header: 8 bytes size (u64) + if (section.size_of_raw_data < @sizeOf(u64)) { return error.InvalidBunSection; } @@ -585,36 +586,37 @@ pub const PEFile = struct { } const section_data = self.data.items[section.pointer_to_raw_data..][0..section.size_of_raw_data]; - const data_size = std.mem.readInt(u32, section_data[0..4], .little); + const data_size = std.mem.readInt(u64, section_data[0..8], .little); - if (data_size + @sizeOf(u32) > section.size_of_raw_data) { + if (data_size + @sizeOf(u64) > section.size_of_raw_data) { return error.InvalidBunSection; } - return section_data[4..][0..data_size]; + // Data starts at offset 8 (after u64 size) + return section_data[8..][0..data_size]; } } return error.BunSectionNotFound; } /// Get the length of the Bun section data - pub fn getBunSectionLength(self: *const PEFile) !u32 { + pub fn getBunSectionLength(self: *const PEFile) !u64 { const section_headers = try self.getSectionHeaders(); for (section_headers) |section| { if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) { - if (section.size_of_raw_data < @sizeOf(u32)) { + if (section.size_of_raw_data < @sizeOf(u64)) { return error.InvalidBunSection; } // Bounds check if (section.pointer_to_raw_data >= self.data.items.len or - section.pointer_to_raw_data + @sizeOf(u32) > self.data.items.len) + section.pointer_to_raw_data + @sizeOf(u64) > self.data.items.len) { return error.InvalidBunSection; } const section_data = self.data.items[section.pointer_to_raw_data..]; - return std.mem.readInt(u32, section_data[0..4], .little); + return std.mem.readInt(u64, section_data[0..8], .little); } } return error.BunSectionNotFound; diff --git a/test/regression/issue/pe-codesigning-integrity.test.ts b/test/regression/issue/pe-codesigning-integrity.test.ts index ffd6305365..7b1f5bcc97 100644 --- a/test/regression/issue/pe-codesigning-integrity.test.ts +++ b/test/regression/issue/pe-codesigning-integrity.test.ts @@ -119,8 +119,8 @@ describe.if(isWindows)("PE codesigning integrity", () => { // Read the .bun section data const sectionData = new Uint8Array(this.buffer, bunSection.pointerToRawData, bunSection.sizeOfRawData); - // First 4 bytes should be the data size - const dataSize = new DataView(sectionData.buffer, bunSection.pointerToRawData).getUint32(0, true); + // First 8 bytes should be the data size (u64 for 8-byte alignment) + const dataSize = Number(new DataView(sectionData.buffer, bunSection.pointerToRawData).getBigUint64(0, true)); // Validate the size is reasonable - it should match or be close to virtual size if (dataSize > bunSection.sizeOfRawData || dataSize === 0) { @@ -133,8 +133,8 @@ describe.if(isWindows)("PE codesigning integrity", () => { throw new Error(`Invalid .bun section: data size ${dataSize} exceeds virtual size ${bunSection.virtualSize}`); } - // Extract the actual embedded data (skip the 4-byte size header) - const embeddedData = sectionData.slice(4, 4 + dataSize); + // Extract the actual embedded data (skip the 8-byte size header) + const embeddedData = sectionData.slice(8, 8 + dataSize); return { section: bunSection,