Skip to content

Lower the .bun section alignment on ELF so PT_LOAD segments do not overlap - #40756

Merged
Jarred-Sumner merged 7 commits into
mainfrom
farm/189d6f64/elf-pt-load-overlap
Aug 28, 2026
Merged

Jarred-Sumner merged 7 commits into
mainfrom
farm/189d6f64/elf-pt-load-overlap

Conversation

@robobun

@robobun robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • A UPX-compressed bun build --compile executable fails on linux with SyntaxError: Invalid character: '\0' (Using UPX to compress a single file executable generated with bun 1.4 fails #40752). The uncompressed executable runs.
  • The .bun section is declared with 16KB alignment (BLOB_HEADER_ALIGNMENT in src/jsc/bindings/c-bindings.cpp). That raises the RW PT_LOAD's p_align to 0x4000 while every other segment stays at 0x1000, and lld assigns the RW p_vaddr so that round_down(p_vaddr, p_align) overlaps the last pages of the executable segment. Linux execve ignores p_align, so the plain binary runs. UPX's stub honors it, maps the overlap, and the embedded module source reads back corrupted.

Fix

  • Keep BLOB_HEADER_ALIGNMENT at 16KB on Mach-O, where exe_format/macho.rs expands the __BUN segment in place at that alignment. Lower it to 8 on ELF.
  • On ELF the section only ever holds the 8-byte header. exe_format/elf.rs appends the --compile payload at a page-aligned virtual address it computes itself, so nothing depends on the section alignment.
  • With the fix every PT_LOAD has p_align 0x1000 and the strict mapping ranges no longer intersect. A UPX 5.2.0-packed debug build starts and runs correctly.
  • Verified: test/bundler/compile-elf-segment-layout.test.ts (both tests fail on bun 1.4.1 with a 0x3000 overlap). Also bun-build-compile.test.ts, compile-asset-bunfs.test.ts, tls-segment-size.test.ts, and regression/issue/29290.test.ts.

Background

  • A PT_LOAD program header tells the loader to map a file range at p_vaddr. p_align declares the mapping granularity: a strict loader maps [round_down(p_vaddr, p_align), round_up(p_vaddr + p_memsz, p_align)). A segment's p_align is the largest alignment of any section inside it.
  • Linux maps at page granularity and ignores p_align, which hides the overlap. UPX's decompression stub performs the mapping itself and honors p_align. The UPX maintainer's analysis is in UPX does not decompress correctly bun bundles with bun 1.4.0 upx/upx#18906.
  • The .bun section holds the standalone module graph header. At --compile time bun appends the payload past every existing mapping and grows the RW PT_LOAD to cover it (exe_format/elf.rs).
Notes

Layout of the official bun-v1.4.1 linux-x64 binary (readelf -lW), which --compile inherits:

LOAD  0x000000  0x0000000000200000  ...  0x147c3d0 0x147c3d0 R   0x1000
LOAD  0x147d200 0x0000000001743200  ...  0x31f6ed0 0x31f6ed0 R E 0x1000
LOAD  0x46770d0 0x000000000493b0d0  ...  0x0a6f50  0x266dd8  RW  0x4000

The R E segment's strict mapping ends at round_up(0x1743200 + 0x31f6ed0, 0x1000) = 0x493b000. The RW segment's strict mapping starts at round_down(0x493b0d0, 0x4000) = 0x4938000. Overlap: 0x3000. Bun 1.4.0 has the same shape with a 0x2000 overlap, and 1.4.0 + UPX 5.2.0 reproduces the reporter's failure reliably.

After the fix the debug build links as:

LOAD  0x000000  0x0000000000200000 ... R   0x1000
LOAD  0x7e12800 0x0000000008013800 ... R E 0x1000
LOAD  0x190b5290 0x00000000192b7290 ... RW  0x1000

End-to-end check: UPX refuses files over its size cap, and a debug --compile output is 816MB, so the packed run used a stripped copy of the fixed debug bun (484MB). upx -1 packed it and the packed binary runs --version and -e correctly. With the old layout the stub maps the RW segment over the executable segment's tail, so any packed binary with this shape is corrupt. The full compiled-app repro needs a release-linked binary.

The three --bytecode tests in bun-build-compile.test.ts time out on this builder with and without the change (checked by stashing the fix and rerunning). They are container-speed failures, not caused by this diff.

aarch64 is unaffected: its segments already use p_align 0x10000, which is larger than the old 16KB, so the RW p_align does not change there.


no test proof · iteration 1 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/binary/tls-segment-size.test.ts, test/bundler/compile-elf-segment-layout.test.ts

…erlap

The 16KB alignment raised the RW PT_LOAD's p_align to 0x4000 while the
other segments stayed at 0x1000, and lld assigned the RW p_vaddr so that
round_down(p_vaddr, p_align) overlapped the executable segment's last
pages. The kernel ignores p_align at execve, but a loader that honors it
(UPX's stub) maps the overlap and corrupts the image.

On ELF the section only holds the 8-byte header: exe_format/elf.rs
appends the --compile payload at a page-aligned vaddr, so 8-byte
alignment suffices. Mach-O keeps 16KB; exe_format/macho.rs expands the
__BUN segment in place at that alignment.

Fixes #40752
@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 82885fb8-db44-42a7-af6c-6213b28e3393

📥 Commits

Reviewing files that changed from the base of the PR and between d9b4c7b and 3781acd.

📒 Files selected for processing (4)
  • src/jsc/bindings/c-bindings.cpp
  • test/bundler/compile-elf-segment-layout.test.ts
  • test/harness.ts
  • test/js/bun/binary/tls-segment-size.test.ts

Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.


Walkthrough

The change makes ELF blob-header alignment platform-specific. It adds shared ELF64 program-header parsing utilities and uses them in segment-layout and TLS-size regression tests.

Changes

ELF alignment and validation

Layer / File(s) Summary
Shared ELF64 program-header parsing
test/harness.ts
Adds exact-offset reads and endian-aware ELF64 program-header parsing with validation and descriptor cleanup.
Platform-specific blob alignment regression coverage
src/jsc/bindings/c-bindings.cpp, test/bundler/compile-elf-segment-layout.test.ts
Uses 16 KiB blob-header alignment on Darwin and 8-byte alignment on Linux and FreeBSD. Tests validate PT_LOAD layout for Bun and compiled executables.
TLS segment test migration
test/js/bun/binary/tls-segment-size.test.ts
Replaces local ELF parsing with the shared harness utilities and reads PT_TLS.memsz from parsed program headers.

Suggested reviewers: alii, cirospaciari, jarred-sumner

Merge Risk: ⚪ Minimal · up to 3781a

The change is narrowly scoped to ELF alignment behavior, with no actionable merge-blocking risk remaining after normal checks and review.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: lowering ELF .bun section alignment to prevent PT_LOAD overlap.
Description check ✅ Passed The description explains the problem, fix, technical background, and verification results in detail. It does not use the template headings "What does this PR do?" and "How did you verify your code wor…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the problem, fix, technical background, and verification results in detail. It does not use the template headings "What does this PR do?" and "How did you verify your code works?", but it provides the required information under equivalent sections.


Comment @coderabbitai help to get the list of available commands.

Comment thread src/jsc/bindings/c-bindings.cpp Outdated
Comment thread src/jsc/bindings/c-bindings.cpp Outdated
@robobun

robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:28 AM PT - Aug 28th, 2026

⏳ @autofix-ci[bot], your commit 3781acd is still building in Build #107559, but has 1 failures so far (All Failures):

Comment thread src/jsc/bindings/c-bindings.cpp
Comment thread src/jsc/bindings/c-bindings.cpp
Comment thread test/bundler/compile-elf-segment-layout.test.ts Outdated
Comment thread test/bundler/compile-elf-segment-layout.test.ts
Comment thread test/bundler/compile-elf-segment-layout.test.ts Outdated
Comment thread test/bundler/compile-elf-segment-layout.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — thanks for addressing the earlier feedback (stdout drain, FreeBSD gating, and hoisting the ELF program-header reader into harness.ts).

What was reviewed:

  • Confirmed src/exe_format/elf.rs computes its own page-aligned new_vaddr (align_up(max_vaddr_end, page_size)) and keeps p_align unchanged when growing the RW segment, so nothing on the ELF path depends on the old 16KB section alignment; BlobHeader/BLOB_HEADER_ALIGNMENT is otherwise only referenced from macho.rs, where the 16KB value is preserved.
  • The 180_000 per-test timeout matches existing precedent in test/bundler/bun-build-compile.test.ts and bun-build-api.test.ts for --compile tests, so I'm no longer flagging it.
  • Checked the shared readElf64ProgramHeaders against the removed inline parser in tls-segment-size.test.ts — behavior-preserving, and the new test's overlap check guards against a vacuous pass with expect(loads.length).toBeGreaterThan(1).
Extended reasoning...

Overview

The PR lowers BLOB_HEADER_ALIGNMENT from 16 * 1024 to 8 on ELF targets (Linux/FreeBSD) while keeping 16 * 1024 on Darwin, fixing a PT_LOAD segment overlap under strict-p_align loaders like UPX's decompression stub (#40752). Supporting changes hoist preadExact and a new readElf64ProgramHeaders helper into test/harness.ts, refactor tls-segment-size.test.ts to consume the shared helper, and add test/bundler/compile-elf-segment-layout.test.ts asserting the non-overlap invariant on both the bun binary and a fresh --compile output.

Security risks

None. The change adjusts a link-time section alignment constant and adds test infrastructure. No user input handling, auth, crypto, or network paths are touched. Lowering section alignment cannot introduce a memory-safety issue — the struct itself is alignas(BLOB_HEADER_ALIGNMENT) with a single size_t, and 8-byte alignment satisfies size_t's natural alignment.

Level of scrutiny

Moderate. The native change is a single conditional #define, but it affects how every ELF bun binary links, so I verified the load-bearing claim: src/exe_format/elf.rs places the --compile payload at align_up(max_vaddr_end, page_size) and only grows the existing RW segment's filesz/memsz without touching p_align, and a repo-wide grep shows BlobHeader/BLOB_HEADER_ALIGNMENT is otherwise referenced only in macho.rs (which retains 16KB) and StandaloneModuleGraph.rs. Nothing on the ELF path reads the section alignment. The aarch64 note in the PR (segments already at p_align 0x10000) matches elf.rs's page_size table.

Other factors

This is my fifth look at the PR. Commits 6e62802f and e2e36cb3 addressed three of my four prior inline comments (stdout pipe drained, !(isLinux || isFreeBSD) gating, ELF reader deduplicated into harness). The remaining 180_000 timeout follows established precedent in sibling --compile tests (bun-build-compile.test.ts, bun-build-api.test.ts), which is a legitimate outlier per root CLAUDE.md — bun build --compile under debug+ASAN copies the entire multi-hundred-MB binary. The new test uses tempDir, bunEnv, bunExe(), await using, drains all pipes concurrently, asserts stdout/stderr before exit code, and guards against vacuous passes with expect(loads.length).toBeGreaterThan(1). No CODEOWNERS cover the changed paths. Exit reason was dry_streak.

@Jarred-Sumner
Jarred-Sumner merged commit 5ad6455 into main Aug 28, 2026
5 of 6 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/189d6f64/elf-pt-load-overlap branch August 28, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants