Skip to content

Update DT_VERDEF when .gnu.version_d is relocated - #665

Open
Wenri wants to merge 1 commit into
NixOS:masterfrom
Wenri:patchelf-verdef
Open

Wenri wants to merge 1 commit into
NixOS:masterfrom
Wenri:patchelf-verdef

Conversation

@Wenri

@Wenri Wenri commented Sep 18, 2026

Copy link
Copy Markdown

Fixes #664.

The bug

rewriteHeaders() writes the new addresses of relocated sections back into
.dynamic, but only for DT_VERSYM and DT_VERNEED. DT_VERDEF has no
branch, so whenever .gnu.version_d is moved the tag keeps pointing at the
pre-relocation address. glibc then follows a stale verdef chain in
_dl_check_map_versions, which can segfault before main() runs.

The fix mirrors the branch that already exists for .gnu.version_r:

else if (d_tag == DT_VERDEF)
    dyn->d_un.d_ptr = findSectionHeader(".gnu.version_d").sh_addr;

Why it has gone unnoticed

Two things have to line up, and the existing tests never line them both up:

  1. The object must define symbol versions, not merely require them.
    Every current test patches objects that have .gnu.version_r (already
    handled); none patches one with .gnu.version_d.
  2. The linker's section ordering must put .gnu.version_d in the way.
    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 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 on
what 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.sh builds a non-PIE executable exporting 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. 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:

result
upstream 0.19.1, 0.15.2 FAIL: DT_VERDEF is '0x2003d8' but .gnu.version_d moved to 0x1fe410
with this patch PASS
no suitable linker SKIP (77)

make check on this branch: 70 tests, 66 PASS, 4 SKIP, 0 FAIL.
shellcheck tests/*.sh is clean.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

patchelf does not update DT_VERDEF when it relocates .gnu.version_d, so glibc segfaults in _dl_check_map_versions

1 participant