Skip to content

fix-elf: don't write RPATH onto ld-linux-*.so.* (loader SIGSEGVs at startup) #345

@tannevaled

Description

@tannevaled

Summary

The post-install SLOW rpath fixes pass in fix-elf writes a transitive-deps RPATH chain onto every ELF in the install directory, including ld-linux-*.so.*. The loader parses its own RPATH at startup — before anything else is resolvable — and SIGSEGVs (exit 139), making the bottle unusable as a dynamic linker.

Surfaced building gnu.org/glibc (CI run 26226054550) but the bug applies to any package that ships its own ld-linux-*.so.*.

Reproduction

In the brewkit test sandbox of a glibc bottle:

$ "$LIBDIR/$LDSO" --version
Segmentation fault         "$LIBDIR/$LDSO" --version
$ echo $?
139
$ "$LIBDIR/$LDSO" /bin/true   # any binary, same result
Segmentation fault

readelf -d on the installed ld-linux-aarch64.so.1:

0x000000000000000f (RPATH)  Library rpath: [
  $ORIGIN/../../../../../sourceware.org/bzip2/v1:
  $ORIGIN/../../../../../lz4.org/v1:
  $ORIGIN/../../../../../curl.se/ca-certs/v2026:
  $ORIGIN/../../../../binutils/v2:
  $ORIGIN/../../../../gmp/v6:
  $ORIGIN/../../../../mpfr/v4:
  $ORIGIN/../../../../mpc/v1:
  …(46 entries total)…
]
0x000000000000000e (SONAME) Library soname: [ld-linux-aarch64.so.1]

Full log: https://github.com/pkgxdev/pantry/actions/runs/26226054550/job/77173340700 (search for "ld.so --version exit:").

Why it's wrong

ld.so is special: it's both the kernel-recognized program loader AND a shared object, and it bootstraps itself from nothing. It MUST NOT have RPATH / RUNPATH for two reasons:

  1. No NEEDED entries to resolveld.so has no DT_NEEDED; RPATH is dead weight at best.
  2. Parsed before any libraries exist — the loader resolves its own RPATH during early startup, before TLS/PLT/GOT are fully set up. A non-trivial RPATH (especially with $ORIGIN/... going to directories that may not exist on the consumer's machine) blows up the relocation phase.

Nix and the glibc upstream test suite both explicitly strip RPATH from ld.so post-link for this reason.

Workaround in the recipe

For pkgxdev/pantry#12968 I switched the recipe's test to a static-linked binary, sidestepping our own ld.so entirely:

test:
  script:
    - gcc -static -o test-static test.c \
        -nostdinc -isystem {{prefix}}/include \
        -B "$LIBDIR" -L "$LIBDIR"
    - out=$(./test-static)

That gets the recipe to PASS in CI but it means the bottle's dynamic-loader path is never exercised by brewkit's test step. Cross-distro verification has to happen out-of-band (we did it on Alpine 3.18, Debian 11, Ubuntu 22.04 across 9 glibc versions × 2 arches — see projects/gnu.org/glibc/README.md).

I also tried patchelf --remove-rpath on ld.so at test time — clears the RPATH cleanly per readelf -d, but the loader still SIGSEGVs. So fix-elf is doing more damage to ld.so than just RPATH pollution — likely relocation tables or program-header offsets get rewritten. That's a deeper investigation than I want to gate the glibc PR on.

Proposed fix

In fix-elf.ts (or whichever pass is responsible for the SLOW rpath fixes), skip any ELF whose basename matches ld-linux-*.so.* or ld-*.so.*:

const LOADER_RE = /^ld[-.].*\.so(\.\d+)*$/;
if (LOADER_RE.test(path.basename())) {
  console.info(`skip ${path}: loader (must have no RPATH)`);
  continue;
}

(Same pattern applies to fix-machos.rb and macOS's dyld, though I haven't tested whether it has the same problem.)

Open questions

  1. Should we additionally never touch INIT/FINI/RELA/RELR/HASH etc. on the loader? Just leaving its dynamic section verbatim is probably the safest default.
  2. Is there a brewkit skip: … directive the recipe could use today as a stop-gap (e.g. skip: linux-loader) before this is fixed in code?

Happy to PR if there's appetite — point me at the right file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions