Conversation
rewriteHeaders() rewrites the addresses of the sections it moves back into
.dynamic, but only for DT_VERSYM and DT_VERNEED. DT_VERDEF was missed, so
whenever .gnu.version_d is relocated the tag keeps pointing at the old
address. glibc then walks a stale verdef chain in _dl_check_map_versions,
which can segfault before main() runs.
Add the missing branch next to the existing two:
DT_VERDEF -> .gnu.version_d (mirrors DT_VERNEED -> .gnu.version_r)
Whether the section is relocated depends on the linker's section ordering.
lld places .dynstr after the .gnu.version* sections, so growing .dynstr
drags .gnu.version_d along; GNU ld and gold place it before, and there the
bug is invisible. That is why it has gone unnoticed: the existing tests only
patch objects that *require* symbol versions (.gnu.version_r), never one
that *defines* them.
tests/verdef-relocate.sh covers it. It builds a non-PIE executable that
exports a versioned symbol (-rdynamic plus a version script), applies an
rpath too long to edit in place so the dynamic cluster is relocated, and
asserts DT_VERDEF still points at .gnu.version_d. The linker is chosen by
probing for one whose layout actually moves the section, rather than by
name; if none does, the test skips. Verified to fail on 0.15.2, 0.16.1,
0.17.2, 0.18.0 and 0.19.1, and to pass with this change.
Fixes NixOS#664.
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 #664.
The bug
rewriteHeaders()writes the new addresses of relocated sections back into.dynamic, but only forDT_VERSYMandDT_VERNEED.DT_VERDEFhas nobranch, so whenever
.gnu.version_dis moved the tag keeps pointing at thepre-relocation address. glibc then follows a stale verdef chain in
_dl_check_map_versions, which can segfault beforemain()runs.The fix mirrors the branch that already exists for
.gnu.version_r:Why it has gone unnoticed
Two things have to line up, and the existing tests never line them both up:
Every current test patches objects that have
.gnu.version_r(alreadyhandled); none patches one with
.gnu.version_d..gnu.version_din the way.lld places
.dynstrafter the.gnu.version*sections, so growing.dynstrdrags.gnu.version_dalong. GNU ld and gold place it before,and there the section is never relocated and the bug is invisible.
It is not new — I reproduced it on 0.15.2, 0.16.1, 0.17.2, 0.18.0 and
0.19.1 (master
7688b17). Whether a stale tag actually crashes depends onwhat happens to sit at the old address, so it can also ship silently as a
corrupt-but-running binary; that is the more unpleasant half.
Regression test
tests/verdef-relocate.shbuilds a non-PIE executable exporting a versionedsymbol (
-rdynamicplus a version script), applies an rpath too long to editin place so the dynamic cluster is relocated, and asserts
DT_VERDEFstillpoints at
.gnu.version_d. It also runs the patched binary.The address comparison is the real oracle rather than "does it still run",
because surviving a stale tag is luck.
The linker is chosen by probing for one whose layout actually moves the
section, not by name, so the test starts exercising the bug automatically
if a default toolchain changes ordering — and
exit 77(skip) if none does,rather than passing vacuously.
Verified:
FAIL: DT_VERDEF is '0x2003d8' but .gnu.version_d moved to 0x1fe410make checkon this branch: 70 tests, 66 PASS, 4 SKIP, 0 FAIL.shellcheck tests/*.shis clean.