From 78008af632f35423f2f3d6ba56e8bdd401de5546 Mon Sep 17 00:00:00 2001 From: Luke Craig Date: Tue, 9 Jun 2026 10:09:02 -0400 Subject: [PATCH] =?UTF-8?q?Don't=20patchelf=20the=20musl=20loader=20(libc.?= =?UTF-8?q?so)=20=E2=80=94=20fixes=20dynamic=20binaries=20on=20old=20kerne?= =?UTF-8?q?ls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mk-arch-bundle ran 'patchelf --set-rpath /igloo/dylibs' on every staged ELF, including libc.so (the musl loader, aliased ld-musl-.so.1). musl libc ships with no rpath, so patchelf had to grow .dynamic/.dynstr and appended a fresh PT_LOAD segment. The loader is the PT_INTERP the kernel maps for every dynamic guest binary, and older guest kernels (e.g. Linux 4.10) mishandle that extra RW segment: they never map the loader's BSS, so the first write in musl's __dls2 (storing the 'ldso' global) faults and every dynamic binary segfaults at startup. The loader needs no rpath of its own (NEEDED libs resolve via each program's RUNPATH=/igloo/dylibs), so leave libc.so's stock single-RW-LOAD layout untouched. Other dylibs/tools already had /nix/store rpaths that patchelf rewrites in place, so they are unaffected. --- src/mk-arch-bundle.nix | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mk-arch-bundle.nix b/src/mk-arch-bundle.nix index d178d81..92c9379 100644 --- a/src/mk-arch-bundle.nix +++ b/src/mk-arch-bundle.nix @@ -108,7 +108,18 @@ pkgs.runCommand "penguin-tools-${penguinArch}" copy_dependency "$resolved" done < <(patchelf --print-needed "$source_ref" 2>/dev/null || true) - patchelf --set-rpath "/igloo/dylibs" "$dest" 2>/dev/null || true + # The musl loader (libc.so, aliased as ld-musl-.so.1) is the + # PT_INTERP the *kernel* maps for every dynamic guest binary. It carries + # no rpath of its own, so `patchelf --set-rpath` has to grow .dynamic and + # .dynstr, which makes patchelf append a fresh PT_LOAD. Older guest + # kernels (e.g. Linux 4.10) mishandle that extra RW segment and never map + # the loader's BSS, so the very first write in __dls2 (storing the `ldso` + # global) faults and every dynamic binary segfaults at startup. The loader + # doesn't need an rpath anyway -- NEEDED libs are found via each program's + # own RUNPATH=/igloo/dylibs -- so leave its ELF layout untouched. + if [ "$(basename "$dest")" != "libc.so" ]; then + patchelf --set-rpath "/igloo/dylibs" "$dest" 2>/dev/null || true + fi if [ -n "$interp" ]; then # MIPS targets are flaky when patchelf rewrites the interpreter path.