fix(elf): keep the relocated program header table reachable via AT_PHDR - #78
Merged
Conversation
`append` moves the program header table into a new PT_LOAD past EOF and packed
it right above the highest existing vaddr, giving that segment a different
file-offset-to-vaddr bias than the first PT_LOAD. Loaders that recompute
`AT_PHDR` from the segment table (the kernel, ld.so via an explicit
interpreter) cope, but a loader that trusts the classic invariant
AT_PHDR = load_bias + (first_load.p_vaddr - first_load.p_offset) + e_phoff
lands ~tens of MB off the real table, reads garbage program headers, and dies
before `main`. That is what happens to `deno compile` binaries run directly
under gVisor / Google Cloud Run (denoland/deno#35700):
Inconsistency detected by ld.so: rtld.c: rtld_setup_main_map:
Assertion `GL(dl_rtld_map).l_libname' failed!
(and a plain SIGSEGV for stripped binaries). Running through the interpreter
explicitly — `ld-linux-x86-64.so.2 ./app` — works, because ld.so then reads
`e_phoff` from the file.
Pin the new segment's virtual address to `new_phoff + first_load_bias` so it
carries the same bias as the first PT_LOAD, making the naive `AT_PHDR`
computation correct. `new_phoff` is bumped when needed so the vaddr still
clears every existing segment (and the note still lands past .bss).
Reproduced and verified fixed under gVisor for PIE (+RELR), non-PIE, large
.bss, and section-header-stripped inputs; the fix also composes with the
eu-strip section cover. Adds a regression test asserting the AT_PHDR invariant.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #78 +/- ##
==========================================
+ Coverage 72.53% 72.82% +0.28%
==========================================
Files 3 3
Lines 1067 1078 +11
==========================================
+ Hits 774 785 +11
Misses 293 293 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
littledivy
added a commit
to denoland/deno
that referenced
this pull request
Jul 2, 2026
…d Run (#35701) Fixes #35700. ## Problem A `deno compile` binary with a native npm addon crashes at startup on Google Cloud Run (`debian:bookworm-slim`, run directly as PID 1): ``` Inconsistency detected by ld.so: rtld.c: rtld_setup_main_map: Assertion `GL(dl_rtld_map).l_libname' failed! ``` (a plain `Uncaught signal: 11` for the reduced repro). Invoking through the loader explicitly — `CMD ["/lib64/ld-linux-x86-64.so.2", "/app/main"]` — works. ## Root cause Cloud Run runs under **gVisor**. libsui's in-place `Elf::append` relocates the program header table into a new `PT_LOAD` past EOF, packed just above the highest existing vaddr — giving that segment a different file-offset→vaddr bias than the first `PT_LOAD` (~23 MB for the deno base). gVisor computes `AT_PHDR = load_bias + (first_load.p_vaddr - first_load.p_offset) + e_phoff`, so it lands tens of MB off the real table, reads garbage program headers, and dies before `main`. The Linux kernel recomputes `AT_PHDR` from the segment table, so it only breaks in the sandbox. The native addon is incidental — a trivial `console.log` binary reproduces it. ## Fix libsui 0.16.3 (denoland/sui#78) pins the relocated segment's vaddr to `new_phoff + first_load_bias`, preserving the first load's bias so the naive `AT_PHDR` computation is correct. ## Verification Reproduced under gVisor (`runsc`) on Linux x86_64: - the compiled `@node-rs/bcrypt` repro built with the current libsui crashes with the exact assertion; built with 0.16.3 it runs (`gVisor exit=0`). - also verified for PIE (+RELR), non-PIE, large `.bss`, and section-header-stripped inputs, and that it composes with the eu-strip fix (an eu-stripped binary also runs under gVisor). The fix carries an `AT_PHDR`-invariant regression test in libsui.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes denoland/deno#35700.
Problem
deno compilebinaries crash at startup when run directly under gVisor / Google Cloud Run:(a plain SIGSEGV for section-header-stripped binaries). Running through the interpreter explicitly —
ld-linux-x86-64.so.2 ./app— works.Root cause
appendrelocates the program header table into a newPT_LOADpast EOF, packed right above the highest existing vaddr. That gives the new segment a different file-offset→vaddr bias than the firstPT_LOAD(for the deno base, ~23 MB different). Loaders that recomputeAT_PHDRfrom the segment table (the Linux kernel, ld.so via an explicit interpreter) cope, but a loader that trusts the classic invariant— which gVisor does — lands tens of MB off the real table, reads garbage program headers, and dies before
main. The native-addon detail in the original report is incidental: a trivialconsole.logbinary reproduces it.Fix
Pin the new segment's virtual address to
new_phoff + first_load_biasso it carries the same bias as the firstPT_LOAD, making the naiveAT_PHDRcomputation correct.new_phoffis bumped when needed so the vaddr still clears every existing segment (and the note still lands past.bss).Verified
Reproduced under gVisor (
runsc) with elfutils/glibc from Ubuntu; the exact assertion fires before the fix and is gone after, for PIE (+RELR), non-PIE, large.bss, and section-header-stripped inputs. Composes with the eu-strip section cover (an eu-stripped binary also runs under gVisor). Adds a regression test asserting theAT_PHDRinvariant.