mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -119,7 +119,7 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Macho = 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 {
|
pub fn getData() ?[]const u8 {
|
||||||
if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
|
if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
|
||||||
@@ -127,8 +127,10 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
return null;
|
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);
|
const slice_ptr: [*]const u8 = @ptrCast(length);
|
||||||
return slice_ptr[4..][0..length.*];
|
return slice_ptr[data_offset..][0..length.*];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -136,7 +138,7 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PE = 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 extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
|
||||||
|
|
||||||
pub fn getData() ?[]const u8 {
|
pub fn getData() ?[]const u8 {
|
||||||
|
|||||||
@@ -910,14 +910,14 @@ extern "C" void Bun__signpost_emit(os_log_t log, os_signpost_type_t type, os_sig
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
struct BlobHeader {
|
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[];
|
uint8_t data[];
|
||||||
} __attribute__((aligned(BLOB_HEADER_ALIGNMENT)));
|
} __attribute__((aligned(BLOB_HEADER_ALIGNMENT)));
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" BlobHeader __attribute__((section("__BUN,__bun"))) BUN_COMPILED = { 0, 0 };
|
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;
|
return &BUN_COMPILED.size;
|
||||||
}
|
}
|
||||||
@@ -927,7 +927,7 @@ extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength()
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winnt.h>
|
#include <winnt.h>
|
||||||
|
|
||||||
static uint32_t* pe_section_size = nullptr;
|
static uint64_t* pe_section_size = nullptr;
|
||||||
static uint8_t* pe_section_data = nullptr;
|
static uint8_t* pe_section_data = nullptr;
|
||||||
|
|
||||||
// Helper function to find and map the .bun section
|
// 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++) {
|
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
|
||||||
if (strncmp((char*)sectionHeader->Name, ".bun", 4) == 0) {
|
if (strncmp((char*)sectionHeader->Name, ".bun", 4) == 0) {
|
||||||
// Found the .bun section
|
// Found the .bun section
|
||||||
|
// Section format: 8 bytes size (uint64_t) + data
|
||||||
BYTE* sectionData = (BYTE*)hModule + sectionHeader->VirtualAddress;
|
BYTE* sectionData = (BYTE*)hModule + sectionHeader->VirtualAddress;
|
||||||
pe_section_size = (uint32_t*)sectionData;
|
pe_section_size = (uint64_t*)sectionData;
|
||||||
pe_section_data = sectionData + sizeof(uint32_t);
|
pe_section_data = sectionData + sizeof(uint64_t); // Skip size (8)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
sectionHeader++;
|
sectionHeader++;
|
||||||
@@ -960,7 +961,7 @@ static bool initializePESection()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" uint32_t Bun__getStandaloneModuleGraphPELength()
|
extern "C" uint64_t Bun__getStandaloneModuleGraphPELength()
|
||||||
{
|
{
|
||||||
if (!initializePESection()) return 0;
|
if (!initializePESection()) return 0;
|
||||||
return pe_section_size ? *pe_section_size : 0;
|
return pe_section_size ? *pe_section_size : 0;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ pub const MachoFile = struct {
|
|||||||
const PAGE_SIZE: u64 = 1 << 12;
|
const PAGE_SIZE: u64 = 1 << 12;
|
||||||
const HASH_SIZE: usize = 32; // SHA256 = 32 bytes
|
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 total_size = header_size + data.len;
|
||||||
const aligned_size = alignSize(total_size, blob_alignment);
|
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..];
|
const prev_after_bun_slice = prev_data_slice[original_segsize..];
|
||||||
bun.memmove(after_bun_slice, prev_after_bun_slice);
|
bun.memmove(after_bun_slice, prev_after_bun_slice);
|
||||||
|
|
||||||
// Now we copy the u32 size header
|
// Now we copy the u64 size header (8 bytes for alignment)
|
||||||
std.mem.writeInt(u32, self.data.items[original_fileoff..][0..4], @intCast(data.len), .little);
|
std.mem.writeInt(u64, self.data.items[original_fileoff..][0..8], @intCast(data.len), .little);
|
||||||
|
|
||||||
// Now we copy the data itself
|
// 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
|
// 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);
|
@memset(padding_bytes, 0);
|
||||||
|
|
||||||
if (code_sign_cmd) |cs| {
|
if (code_sign_cmd) |cs| {
|
||||||
|
|||||||
30
src/pe.zig
30
src/pe.zig
@@ -500,11 +500,11 @@ pub const PEFile = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for overflow before adding 4
|
// Check for overflow before adding 8
|
||||||
if (data_to_embed.len > std.math.maxInt(u32) - 4) {
|
if (data_to_embed.len > std.math.maxInt(u32) - 8) {
|
||||||
return error.Overflow;
|
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 raw_size = try alignUpU32(payload_len, opt.file_alignment);
|
||||||
const new_va = try alignUpU32(last_va_end, opt.section_alignment);
|
const new_va = try alignUpU32(last_va_end, opt.section_alignment);
|
||||||
const new_raw = try alignUpU32(last_file_end, opt.file_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));
|
std.mem.copyForwards(u8, self.data.items[new_sh_off .. new_sh_off + @sizeOf(SectionHeader)], std.mem.asBytes(&sh));
|
||||||
|
|
||||||
// 8. Write payload
|
// 8. Write payload
|
||||||
// At data[new_raw ..]: write LE length prefix then data
|
// At data[new_raw ..]: write u64 LE length prefix, then data
|
||||||
std.mem.writeInt(u32, self.data.items[new_raw..][0..4], @intCast(data_to_embed.len), .little);
|
std.mem.writeInt(u64, self.data.items[new_raw..][0..8], @intCast(data_to_embed.len), .little);
|
||||||
@memcpy(self.data.items[new_raw + 4 ..][0..data_to_embed.len], data_to_embed);
|
@memcpy(self.data.items[new_raw + 8 ..][0..data_to_embed.len], data_to_embed);
|
||||||
|
|
||||||
// 9. Update headers
|
// 9. Update headers
|
||||||
// Get fresh pointers after resize
|
// Get fresh pointers after resize
|
||||||
@@ -573,7 +573,8 @@ pub const PEFile = struct {
|
|||||||
const section_headers = try self.getSectionHeaders();
|
const section_headers = try self.getSectionHeaders();
|
||||||
for (section_headers) |section| {
|
for (section_headers) |section| {
|
||||||
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
|
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;
|
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 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 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;
|
return error.BunSectionNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the length of the Bun section data
|
/// 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();
|
const section_headers = try self.getSectionHeaders();
|
||||||
for (section_headers) |section| {
|
for (section_headers) |section| {
|
||||||
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
|
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;
|
return error.InvalidBunSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bounds check
|
// Bounds check
|
||||||
if (section.pointer_to_raw_data >= self.data.items.len or
|
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;
|
return error.InvalidBunSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
const section_data = self.data.items[section.pointer_to_raw_data..];
|
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;
|
return error.BunSectionNotFound;
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ describe.if(isWindows)("PE codesigning integrity", () => {
|
|||||||
// Read the .bun section data
|
// Read the .bun section data
|
||||||
const sectionData = new Uint8Array(this.buffer, bunSection.pointerToRawData, bunSection.sizeOfRawData);
|
const sectionData = new Uint8Array(this.buffer, bunSection.pointerToRawData, bunSection.sizeOfRawData);
|
||||||
|
|
||||||
// First 4 bytes should be the data size
|
// First 8 bytes should be the data size (u64 for 8-byte alignment)
|
||||||
const dataSize = new DataView(sectionData.buffer, bunSection.pointerToRawData).getUint32(0, true);
|
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
|
// Validate the size is reasonable - it should match or be close to virtual size
|
||||||
if (dataSize > bunSection.sizeOfRawData || dataSize === 0) {
|
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}`);
|
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)
|
// Extract the actual embedded data (skip the 8-byte size header)
|
||||||
const embeddedData = sectionData.slice(4, 4 + dataSize);
|
const embeddedData = sectionData.slice(8, 8 + dataSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
section: bunSection,
|
section: bunSection,
|
||||||
|
|||||||
Reference in New Issue
Block a user