Bump vendored LIEF 0.13.0 -> 0.17.6 and migrate to its API#108
Open
cstrahan wants to merge 1 commit into
Open
Conversation
postject's 3-year-old LIEF (0.13.0) corrupts the dynamic symbol table when it rebuilds a binary with a large .gnu.hash: only some symbol-table `st_name` offsets are updated, the rest keep stale offsets, so exported symbols resolve to truncated/garbage names. For a Node.js SEA this breaks native (raw-V8) addons at runtime with "undefined symbol: _ZN2v8...", because the dlopen'd .node resolves V8 symbols from the host executable by name. This is upstream LIEF bug lief-project/LIEF#1241 ("Corrupted symbol table due to DT_GNU_HASH parsing"): a hardcoded NB_MAX_MASKWORD = 512 cap on the GNU-hash bloom filter. When the filter exceeds 512 maskwords (Node's binary has ~40k dynamic symbols, far past that), the symbol-table rebuild is left incomplete. Fixed by LIEF commit 2d1bbae (in the 0.16.6 -> 0.16.7 range), which removes the cap and reconstructs GnuHash properly; verified here by a version sweep (0.16.6 corrupts, 0.16.7 is clean). Two conditions must coincide to hit it, which is why it looked PIE-specific: injecting a note into a PIE (ET_DYN) binary forces LIEF to relocate .dynsym/.dynstr (the trigger for the symbol-table rebuild), and a >512-maskword .gnu.hash is the defect (#1241). Official non-PIE/ET_EXEC Node builds never relocate .dynstr, so they dodged the bug even on old LIEF; builds that link the node binary as PIE hit it directly. This bumps the dependency to 0.17.6 (latest) via CMake FetchContent and migrates src/postject.cpp to the LIEF 0.16+ API. The injection logic is lifted from Node.js core's own LIEF-based injector, src/node_sea_bin.cc, added in nodejs/node#61167 (Joyee Cheung's work to move SEA blob injection into Node and off the stale postject). Also force-include <cstdlib> globally: LIEF's bundled fmt calls global free() from a template whose definition point doesn't see <cstdlib> under Emscripten, which otherwise fails the wasm build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
postject's 3-year-old LIEF (0.13.0) corrupts the dynamic symbol table when it rebuilds a binary with a large .gnu.hash: only some symbol-table
st_nameoffsets are updated, the rest keep stale offsets, so exported symbols resolve to truncated/garbage names. For a Node.js SEA this breaks native (raw-V8) addons at runtime with "undefined symbol: _ZN2v8...", because the dlopen'd .node resolves V8 symbols from the host executable by name.This is upstream LIEF bug lief-project/LIEF#1241 ("Corrupted symbol table due to DT_GNU_HASH parsing"): a hardcoded NB_MAX_MASKWORD = 512 cap on the GNU-hash bloom filter. When the filter exceeds 512 maskwords (Node's binary has ~40k dynamic symbols, far past that), the symbol-table rebuild is left incomplete. Fixed by LIEF commit 2d1bbae (in the 0.16.6 -> 0.16.7 range), which removes the cap and reconstructs GnuHash properly; verified here by a version sweep (0.16.6 corrupts, 0.16.7 is clean).
Two conditions must coincide to hit it, which is why it looked PIE-specific: injecting a note into a PIE (ET_DYN) binary forces LIEF to relocate .dynsym/.dynstr (the trigger for the symbol-table rebuild), and a >512-maskword .gnu.hash is the defect (#1241). Official non-PIE/ET_EXEC Node builds never relocate .dynstr, so they dodged the bug even on old LIEF; builds that link the node binary as PIE hit it directly.
This bumps the dependency to 0.17.6 (latest) via CMake FetchContent and migrates src/postject.cpp to the LIEF 0.16+ API. The injection logic is lifted from Node.js core's own LIEF-based injector, src/node_sea_bin.cc, added in nodejs/node#61167 (Joyee Cheung's work to move SEA blob injection into Node and off the stale postject).
Also force-include globally: LIEF's bundled fmt calls global free() from a template whose definition point doesn't see under Emscripten, which otherwise fails the wasm build.