Skip to content

Commit 5eb2145

Browse files
dylan-conwayclaude
andauthored
fix(compile): use 8-byte header for embedded section to ensure bytecode alignment (#25377)
## 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 <[email protected]>
1 parent cde167c commit 5eb2145

File tree

5 files changed

+37
-32
lines changed

5 files changed

+37
-32
lines changed

src/StandaloneModuleGraph.zig

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,24 +119,26 @@ pub const StandaloneModuleGraph = struct {
119119
};
120120

121121
const Macho = struct {
122-
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u32;
122+
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u64;
123123

124124
pub fn getData() ?[]const u8 {
125125
if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
126126
if (length.* < 8) {
127127
return null;
128128
}
129129

130+
// BlobHeader has 8 bytes size (u64), so data starts at offset 8.
131+
const data_offset = @sizeOf(u64);
130132
const slice_ptr: [*]const u8 = @ptrCast(length);
131-
return slice_ptr[4..][0..length.*];
133+
return slice_ptr[data_offset..][0..length.*];
132134
}
133135

134136
return null;
135137
}
136138
};
137139

138140
const PE = struct {
139-
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u32;
141+
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u64;
140142
pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
141143

142144
pub fn getData() ?[]const u8 {

src/bun.js/bindings/c-bindings.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -910,14 +910,14 @@ extern "C" void Bun__signpost_emit(os_log_t log, os_signpost_type_t type, os_sig
910910

911911
extern "C" {
912912
struct BlobHeader {
913-
uint32_t size;
913+
uint64_t size; // 64-bit to ensure data[] starts at 8-byte aligned offset (required for bytecode cache)
914914
uint8_t data[];
915915
} __attribute__((aligned(BLOB_HEADER_ALIGNMENT)));
916916
}
917917

918918
extern "C" BlobHeader __attribute__((section("__BUN,__bun"))) BUN_COMPILED = { 0, 0 };
919919

920-
extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength()
920+
extern "C" uint64_t* Bun__getStandaloneModuleGraphMachoLength()
921921
{
922922
return &BUN_COMPILED.size;
923923
}
@@ -927,7 +927,7 @@ extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength()
927927
#include <windows.h>
928928
#include <winnt.h>
929929

930-
static uint32_t* pe_section_size = nullptr;
930+
static uint64_t* pe_section_size = nullptr;
931931
static uint8_t* pe_section_data = nullptr;
932932

933933
// Helper function to find and map the .bun section
@@ -949,9 +949,10 @@ static bool initializePESection()
949949
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
950950
if (strncmp((char*)sectionHeader->Name, ".bun", 4) == 0) {
951951
// Found the .bun section
952+
// Section format: 8 bytes size (uint64_t) + data
952953
BYTE* sectionData = (BYTE*)hModule + sectionHeader->VirtualAddress;
953-
pe_section_size = (uint32_t*)sectionData;
954-
pe_section_data = sectionData + sizeof(uint32_t);
954+
pe_section_size = (uint64_t*)sectionData;
955+
pe_section_data = sectionData + sizeof(uint64_t); // Skip size (8)
955956
return true;
956957
}
957958
sectionHeader++;
@@ -960,7 +961,7 @@ static bool initializePESection()
960961
return false;
961962
}
962963

963-
extern "C" uint32_t Bun__getStandaloneModuleGraphPELength()
964+
extern "C" uint64_t Bun__getStandaloneModuleGraphPELength()
964965
{
965966
if (!initializePESection()) return 0;
966967
return pe_section_size ? *pe_section_size : 0;

src/macho.zig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub const MachoFile = struct {
4444
const PAGE_SIZE: u64 = 1 << 12;
4545
const HASH_SIZE: usize = 32; // SHA256 = 32 bytes
4646

47-
const header_size = @sizeOf(u32);
47+
const header_size = @sizeOf(u64);
4848
const total_size = header_size + data.len;
4949
const aligned_size = alignSize(total_size, blob_alignment);
5050

@@ -164,14 +164,14 @@ pub const MachoFile = struct {
164164
const prev_after_bun_slice = prev_data_slice[original_segsize..];
165165
bun.memmove(after_bun_slice, prev_after_bun_slice);
166166

167-
// Now we copy the u32 size header
168-
std.mem.writeInt(u32, self.data.items[original_fileoff..][0..4], @intCast(data.len), .little);
167+
// Now we copy the u64 size header (8 bytes for alignment)
168+
std.mem.writeInt(u64, self.data.items[original_fileoff..][0..8], @intCast(data.len), .little);
169169

170170
// Now we copy the data itself
171-
@memcpy(self.data.items[original_fileoff + 4 ..][0..data.len], data);
171+
@memcpy(self.data.items[original_fileoff + 8 ..][0..data.len], data);
172172

173173
// Lastly, we zero any of the padding that was added
174-
const padding_bytes = self.data.items[original_fileoff..][data.len + 4 .. aligned_size];
174+
const padding_bytes = self.data.items[original_fileoff..][data.len + 8 .. aligned_size];
175175
@memset(padding_bytes, 0);
176176

177177
if (code_sign_cmd) |cs| {

src/pe.zig

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,11 @@ pub const PEFile = struct {
500500
}
501501
}
502502

503-
// Check for overflow before adding 4
504-
if (data_to_embed.len > std.math.maxInt(u32) - 4) {
503+
// Check for overflow before adding 8
504+
if (data_to_embed.len > std.math.maxInt(u32) - 8) {
505505
return error.Overflow;
506506
}
507-
const payload_len = @as(u32, @intCast(data_to_embed.len + 4)); // 4 for LE length prefix
507+
const payload_len = @as(u32, @intCast(data_to_embed.len + 8)); // 8 for LE length prefix
508508
const raw_size = try alignUpU32(payload_len, opt.file_alignment);
509509
const new_va = try alignUpU32(last_va_end, opt.section_alignment);
510510
const new_raw = try alignUpU32(last_file_end, opt.file_alignment);
@@ -536,9 +536,9 @@ pub const PEFile = struct {
536536
std.mem.copyForwards(u8, self.data.items[new_sh_off .. new_sh_off + @sizeOf(SectionHeader)], std.mem.asBytes(&sh));
537537

538538
// 8. Write payload
539-
// At data[new_raw ..]: write LE length prefix then data
540-
std.mem.writeInt(u32, self.data.items[new_raw..][0..4], @intCast(data_to_embed.len), .little);
541-
@memcpy(self.data.items[new_raw + 4 ..][0..data_to_embed.len], data_to_embed);
539+
// At data[new_raw ..]: write u64 LE length prefix, then data
540+
std.mem.writeInt(u64, self.data.items[new_raw..][0..8], @intCast(data_to_embed.len), .little);
541+
@memcpy(self.data.items[new_raw + 8 ..][0..data_to_embed.len], data_to_embed);
542542

543543
// 9. Update headers
544544
// Get fresh pointers after resize
@@ -573,7 +573,8 @@ pub const PEFile = struct {
573573
const section_headers = try self.getSectionHeaders();
574574
for (section_headers) |section| {
575575
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
576-
if (section.size_of_raw_data < @sizeOf(u32)) {
576+
// Header: 8 bytes size (u64)
577+
if (section.size_of_raw_data < @sizeOf(u64)) {
577578
return error.InvalidBunSection;
578579
}
579580

@@ -585,36 +586,37 @@ pub const PEFile = struct {
585586
}
586587

587588
const section_data = self.data.items[section.pointer_to_raw_data..][0..section.size_of_raw_data];
588-
const data_size = std.mem.readInt(u32, section_data[0..4], .little);
589+
const data_size = std.mem.readInt(u64, section_data[0..8], .little);
589590

590-
if (data_size + @sizeOf(u32) > section.size_of_raw_data) {
591+
if (data_size + @sizeOf(u64) > section.size_of_raw_data) {
591592
return error.InvalidBunSection;
592593
}
593594

594-
return section_data[4..][0..data_size];
595+
// Data starts at offset 8 (after u64 size)
596+
return section_data[8..][0..data_size];
595597
}
596598
}
597599
return error.BunSectionNotFound;
598600
}
599601

600602
/// Get the length of the Bun section data
601-
pub fn getBunSectionLength(self: *const PEFile) !u32 {
603+
pub fn getBunSectionLength(self: *const PEFile) !u64 {
602604
const section_headers = try self.getSectionHeaders();
603605
for (section_headers) |section| {
604606
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
605-
if (section.size_of_raw_data < @sizeOf(u32)) {
607+
if (section.size_of_raw_data < @sizeOf(u64)) {
606608
return error.InvalidBunSection;
607609
}
608610

609611
// Bounds check
610612
if (section.pointer_to_raw_data >= self.data.items.len or
611-
section.pointer_to_raw_data + @sizeOf(u32) > self.data.items.len)
613+
section.pointer_to_raw_data + @sizeOf(u64) > self.data.items.len)
612614
{
613615
return error.InvalidBunSection;
614616
}
615617

616618
const section_data = self.data.items[section.pointer_to_raw_data..];
617-
return std.mem.readInt(u32, section_data[0..4], .little);
619+
return std.mem.readInt(u64, section_data[0..8], .little);
618620
}
619621
}
620622
return error.BunSectionNotFound;

test/regression/issue/pe-codesigning-integrity.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ describe.if(isWindows)("PE codesigning integrity", () => {
119119
// Read the .bun section data
120120
const sectionData = new Uint8Array(this.buffer, bunSection.pointerToRawData, bunSection.sizeOfRawData);
121121

122-
// First 4 bytes should be the data size
123-
const dataSize = new DataView(sectionData.buffer, bunSection.pointerToRawData).getUint32(0, true);
122+
// First 8 bytes should be the data size (u64 for 8-byte alignment)
123+
const dataSize = Number(new DataView(sectionData.buffer, bunSection.pointerToRawData).getBigUint64(0, true));
124124

125125
// Validate the size is reasonable - it should match or be close to virtual size
126126
if (dataSize > bunSection.sizeOfRawData || dataSize === 0) {
@@ -133,8 +133,8 @@ describe.if(isWindows)("PE codesigning integrity", () => {
133133
throw new Error(`Invalid .bun section: data size ${dataSize} exceeds virtual size ${bunSection.virtualSize}`);
134134
}
135135

136-
// Extract the actual embedded data (skip the 4-byte size header)
137-
const embeddedData = sectionData.slice(4, 4 + dataSize);
136+
// Extract the actual embedded data (skip the 8-byte size header)
137+
const embeddedData = sectionData.slice(8, 8 + dataSize);
138138

139139
return {
140140
section: bunSection,

0 commit comments

Comments
 (0)