fix(elf): preserve relative relocations by appending notes in-place - #72
Merged
Conversation
The ELF `append` path rebuilt the whole binary through
`object::build::elf::Builder`, which reconstructs the file purely from its
section table. That is lossy for modern relocatable executables:
* `object` 0.36 has no `SHT_RELR` support and errors out on any binary
that still carries a `.relr.dyn` section header; and
* any segment bytes not covered by a surviving section are silently
dropped on write.
When a binary's section headers are stripped (as `deno compile`'s
release-linux base is), the `.relr.dyn` relative relocations live only in a
`PT_LOAD` referenced by `DT_RELR` and get dropped by the rebuild. With the
v8 149.4.0 bump adding the first load-bearing relative relocation, a C++
static-init guard's mutex pointer is left un-relocated, deadlocking at
startup (`__cxa_guard_acquire failed to acquire mutex`).
Replace the full rebuild with an in-place, `patchelf --add-note` style
append: keep every original byte, write the note plus an enlarged copy of
the program header table past EOF, add a `PT_LOAD` mapping that region and a
`PT_NOTE` pointing at the note, and repoint `e_phoff`/`e_phnum` (and
`PT_PHDR`). The section header table is never touched, so `.relr.dyn` and
all other relocations survive unchanged.
Adds a regression test asserting byte/section-header preservation and a
discoverable SUI `PT_NOTE`.
The surgical `append` no longer creates a `.note.sui` section — the note is
discoverable only through its PT_NOTE program header (which is what the
runtime `find_section`/`dl_iterate_phdr` path uses, and the only mechanism
that survives a full section-header strip).
Update the ELF note tests accordingly:
* find_section_in_bytes falls through to program headers even when a
section table is present, instead of bailing out.
* test_elf_note_mapped_and_preserves verifies a PT_NOTE carries the SUI
note, that a PT_LOAD maps it, and that the GNU note is preserved.
* test_elf_note_does_not_overlap_bss derives the note's mapped range from
its PT_NOTE program header.
The PT_NOTE-only append left the note unreachable after a subsequent `strip`: BFD-based tools rebuild the file from its section table and discard bytes that no section describes. Restore the previous guarantee by also adding a real SHF_ALLOC SHT_NOTE section for the note, but do it surgically: the original section header table and .shstrtab are copied (enlarged) past EOF and the ELF header is repointed at them — the originals, and every relocation, are left byte-for-byte intact. A fully stripped binary (no section header table) keeps the note via its PT_NOTE program header alone, which is all that is possible there and all the runtime needs. Updates test_elf_append_preserves_original_bytes for the relocated section table (e_shoff moves past EOF, e_shnum grows by one).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #72 +/- ##
==========================================
+ Coverage 68.95% 72.00% +3.05%
==========================================
Files 3 3
Lines 963 1036 +73
==========================================
+ Hits 664 746 +82
+ Misses 299 290 -9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Problem
Elf::appendrebuilt the entire binary throughobject::build::elf::Builder, which reconstructs the file purely from its section table. That is lossy for modern relocatable executables:object0.36 has noSHT_RELRsupport —Builder::readerrors (Unsupported section type 13) on any binary carrying a.relr.dynsection header.So when a binary's section headers are stripped (as
deno compile's release-linux base is), the.relr.dynrelative relocations survive only in aPT_LOADreferenced byDT_RELR— and the rebuild drops them.The v8 149.4.0 bump added the first load-bearing relative relocation (a C++ static-init guard's mutex pointer). Left un-relocated, it deadlocks/aborts at startup:
Only reproduces on
release-linux-x86_64, where the strip is aggressive enough to leave.relr.dynoutside the section table. Verified locally against object 0.36.3: flipping a fixture section toSHT_RELRmakesBuilder::readfail, andBuilder::writeonly round-trips section-backed bytes.Fix
Replace the full rebuild with an in-place,
patchelf --add-notestyle append that keeps every original byte:PT_LOADmapping that region and aPT_NOTEpointing at the note, and repointe_phoff/e_phnum+PT_PHDRat the new table (so the loader's load-bias math,AT_PHDR - PT_PHDR.p_vaddr, stays correct). This is what the runtimefind_section/dl_iterate_phdrreads.SHF_ALLOCSHT_NOTE.note.suisection — relocating + growing the section header table and.shstrtabpast EOF — so the note survives a laterstrip(BFD tools rebuild from sections). A fully stripped binary keeps the note viaPT_NOTEalone.The original program/section header tables,
.shstrtab, segment contents, and all relocations are copied (enlarged), never edited in place — so.relr.dynsurvives unchanged.Tests
test_elfexecutes the produced binary on Linux CI — exercises the relocatedPT_PHDR. ✅test_elf_note_survives_stripconfirms the note is recoverable after GNUstrip. ✅test_elf_note_mapped_and_preserves/test_elf_note_does_not_overlap_bssrewritten to verify the note via itsPT_NOTEprogram header. ✅test_elf_append_preserves_original_bytes: all bytes past the ELF header preserved, section table relocated + grew by one, program header table grew by two, SUI note discoverable. ✅All four CI runners green.
Note: no version bump included — left for a follow-up release when deno bumps the libsui pin.