Repository navigation
rust-lld default on x86_64-unknown-linux-gnu produces binaries without RUNPATH on NixOS #162781
Description
Activity
- addedneeds-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
on Sep 14, 2026 I think documenting this issue and its workaround is a good first step, but special-casing NixOS would make it impossible to compile a binary targeting another Linux distro on NixOS without the use of a sysroot and custom compilation flags. So NixOS would have to become a separate target to have NixOS-specific linking behavior by default.
- addedO-NixOSOperating system: NixOS, https://nixos.org/Operating system: NixOS, https://nixos.org/A-linkageArea: linking into static, shared libraries and binariesArea: linking into static, shared libraries and binariesand removedneeds-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
on Sep 15, 2026 Is this with a rustup distributed rustc or a NixOS distributed one? I would expect the latter to contain patches to ensure everything is properly linked.
rustup distributed rust lld
I'm going to patch it into my own linker which has an AI optimized Wild fork
I'm using nixos, but I can't reproduce your issue. Do I read correctly that you're not in a nix shell, and have instead manually set up
PKG_CONFIG_PATHto point to the relevant packages?@maxdexh You read it correctly — I was outside any
nix shell/nix develop, withPKG_CONFIG_PATHpointed manually at the store's.pcdirectories. But I don't think the shell vs. manual-PKG_CONFIG_PATHdistinction is what separates us. Here's the full context (lifted from the session where I hit this and worked around it), plus a self-contained repro.What I was actually doing when I hit it
I build a Rust CLI (
bosn) that ships a Linux Python wheel through a custom maturin/PEP 517 backend. That backend runslddon the built CLI to copy the OpenSSL sidecars (_copy_linux_openssl). On NixOS,lddprints:libssl.so.3 => not found libcrypto.so.3 => not foundso the backend matched
notas the source path and literally triedcopy2("not", …). On the Ubuntu CI runnerlddresolves real paths, so that lane stays green. That is the real-world trigger — not a synthetic test.Minimal repro
Environment: NixOS 26.05, rustup stable
rustc 1.95.0 (59807616e 2026-04-14), LLVM 22.1.2, nixpkgsgcc-wrapper-15.2.0ascc. Outside any dev shell,PKG_CONFIG_PATHpointing at the store's openssl/zlib/sqlite.pcdirs.Cargo.toml:[package] name = "devlibs-rusttest" version = "0.1.0" edition = "2021" [dependencies] openssl-sys = "=0.9.117" libz-sys = { version = "=1.1.29", default-features = false, features = ["libc"] } libsqlite3-sys = "=0.38.2"
src/main.rs:fn main() { unsafe { openssl_sys::init(); let v = std::ffi::CStr::from_ptr(openssl_sys::OpenSSL_version(0)); let z = std::ffi::CStr::from_ptr(libz_sys::zlibVersion()); let s = std::ffi::CStr::from_ptr(libsqlite3_sys::sqlite3_libversion()); println!("openssl={v:?} zlib={z:?} sqlite={s:?}"); } }
Build and inspect:
cargo build readelf -p .comment target/debug/devlibs-rusttest | grep Linker # Linker: LLD 22.1.2 readelf -d target/debug/devlibs-rusttest | grep RUNPATH # (nothing) ldd target/debug/devlibs-rusttest # libssl.so.3 => not found, libcrypto.so.3 => not found, libsqlite3.so => not found ./target/debug/devlibs-rusttest # error while loading shared libraries: libsqlite3.so (exit 127)
Note the
cargo builditself exits 0 — it links cleanly and only dies at run time. TheNEEDEDentries are all present (libsqlite3.so,libssl.so.3,libcrypto.so.3); there is just noRUNPATH.Opt out of the self-contained linker:
RUSTFLAGS="-Clinker-features=-lld -Clink-self-contained=-linker" cargo build readelf -d target/debug/devlibs-rusttest | grep RUNPATH # [/nix/store/…-sqlite-3.51.2/lib:/nix/store/…-openssl-3.6.3/lib:…] ./target/debug/devlibs-rusttest # openssl="OpenSSL 3.6.3 …" zlib="1.3.2" sqlite="3.51.2" (exit 0)
Controlled A/B on the same machine — same crate, same nixpkgs
cc, only the linker differs:RUSTFLAGS Linker RUNPATH lddnot-foundrun (default) rust-lld ( Linker: LLD 22.1.2)none 3 exit 127 -Clinker-features=-lld -Clink-self-contained=-linkerGNU ld via ccstore lib dirs 0 exit 0 Why the shell/
PKG_CONFIG_PATHdistinction isn't the differentiatorOn NixOS the
RUNPATHis written by the nixpkgscc/ldwrapper, which takes the-Ldirectories it sees and emits a matching-rpath. rustc does not readNIX_LDFLAGSitself. Whether the-Lflags arrive viaNIX_LDFLAGS(a shell's setup hooks) or via the build script'scargo:rustc-link-search(pkg-config), they only tell the linker where to find the.soat link time. The runtime path still has to come from the wrapper.With
-Clink-self-contained=+linker(the 1.90 default), rustc invokes its bundledrust-llddirectly and never callscc, so the wrapper — and therefore the-rpathinjection — never runs. That's why it links cleanly but dies at startup, in a shell or out of one. Plain C links through the sameccstill get a correctRUNPATH, which is the contrast that isolated it.So if you can't reproduce, my guess is your rustc isn't actually on the self-contained-lld path. One line settles it:
readelf -p .comment <bin> | grep -i linker
LLD 22.x→ self-contained lld and the bug should reproduce;GNU ld/cc→ your toolchain still goes through the wrapper (e.g. nixpkgs rustc built withrust.lld = false, or an older channel), which is why it works for you. I'm on rustup stable 1.95.0.What I shipped as a workaround
Rather than a global
LD_LIBRARY_PATH(which would outrank every Nix program's own RUNPATH), I bake an explicit-rpathfor a dev-library set into theccwrapper'scc-ldflags:withDevLibraries = cc: cc.override (old: { extraBuildCommands = (old.extraBuildCommands or "") + '' echo "-idirafter ${devLibraryEnv}/include" >> $out/nix-support/cc-cflags echo "-L${devLibraryEnv}/lib -rpath ${devLibraryEnv}/lib" >> $out/nix-support/cc-ldflags ''; });
That's
zackees/nixos@91d74c1(PR zackees/nixos#19). It reaches any linker the wrapper invokes —rust-lldincluded — and post-fix the Rust binary still readsLinker: LLD 22.1.2but now has aRUNPATH,lddresolves 0 missing, and it runs. So-rpathincc-ldflagsis the wrapper-native fix that survives the self-contained linker.
This comment was generated by
clud, a custom agent made by Zach Vorhies who reviewed and authorized this post.I tried your reproducer with
nix-shell -p pkg-config openssl zlib sqlite(withrustupfrom nixpkgs) and nothing was out of the ordinary. So you (or your agent, idk who actually wrote that comment) are missing something. As it stands, I cannot reproduce your issue.You are in the nix-shell to add the package. But it's unclear if you are trying to link while not in the nix-shell.
> nix-shell -p pkg-config openssl zlib sqlite > cargo run Compiling playground v0.1.0 (/home/max/Repos/rust/playground) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s Running `target/debug/playground` openssl="OpenSSL 3.6.3 9 Jun 2026" zlib="1.3.2" sqlite="3.51.2" > exit > target/debug/playground openssl="OpenSSL 3.6.3 9 Jun 2026" zlib="1.3.2" sqlite="3.51.2" > readelf -p .comment target/debug/playground | grep Linker [ 48] Linker: LLD 23.1.1 (/checkout/src/llvm-project/llvm 1b9c0d5ff9bbe7634aead059efe6b11a7eeba145) > readelf -d target/debug/playground | grep RUNPATH 0x000000000000001d (RUNPATH) Library runpath: [/nix/store/dhafmhsa7fps9i05yh226y9n8b70hmsf-shell/lib:/nix/store/483x61iy35irm4wr2b7dwzihljhp6da2-zlib-1.3.2/lib:/nix/store/pvxx7066ymzmmrlcfn7yl6kcbqvbc8lw-openssl-3.6.3/lib:/nix/store/0zyx0wg82m6pghdbjkrk5q4jvfq402wz-sqlite-3.51.2/lib:/nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib:/nix/store/7vafhlh0lmcvi75jfyy09qwr4m3x1ks3-gcc-15.2.0-lib/lib] > ldd target/debug/playground linux-vdso.so.1 (0x00007f6a56a0b000) libsqlite3.so => /nix/store/0zyx0wg82m6pghdbjkrk5q4jvfq402wz-sqlite-3.51.2/lib/libsqlite3.so (0x00007f6a56810000) libssl.so.3 => /nix/store/pvxx7066ymzmmrlcfn7yl6kcbqvbc8lw-openssl-3.6.3/lib/libssl.so.3 (0x00007f6a566f6000) libcrypto.so.3 => /nix/store/pvxx7066ymzmmrlcfn7yl6kcbqvbc8lw-openssl-3.6.3/lib/libcrypto.so.3 (0x00007f6a56000000) libgcc_s.so.1 => /nix/store/7vafhlh0lmcvi75jfyy09qwr4m3x1ks3-gcc-15.2.0-lib/lib/libgcc_s.so.1 (0x00007f6a566c9000) libc.so.6 => /nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib/libc.so.6 (0x00007f6a55c00000) /nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib/ld-linux-x86-64.so.2 => /nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib64/ld-linux-x86-64.so.2 (0x00007f6a56a0d000) libm.so.6 => /nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib/libm.so.6 (0x00007f6a55f08000) libz.so.1 => /nix/store/483x61iy35irm4wr2b7dwzihljhp6da2-zlib-1.3.2/lib/libz.so.1 (0x00007f6a566a8000) libdl.so.2 => /nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib/libdl.so.2 (0x00007f6a566a3000) libpthread.so.0 => /nix/store/avld9cdn23zab2ssl30h2r6444rqh6ms-glibc-2.42-67/lib/libpthread.so.0 (0x00007f6a5669e000)
Reacted by Zachary Vorhies@maxdexh Thanks for the full output, that settled it. You can't reproduce because you're using nixpkgs'
rustup, and nixpkgs already patches this bug away at toolchain install time.nixpkgs'
rustuppackage carries0001-dynamically-patchelf-binaries.patch(NixOS/nixpkgs#314268, merged June 2024). Itsnix_wrap_lldmoves every installedld.lldinto a siblinggcc-ld-unwrapped/directory and writes a shim in its place:#!/usr/bin/env bash export PROG=".../bin/gcc-ld-unwrapped/ld.lld" "/nix/store/...-rustup-1.29.0/nix-support/ld-wrapper.sh" "$@"
That
ld-wrapper.shis nixpkgs' bintools wrapper, the component that turns each-L /nix/store/...into-rpath. So on your machine rust-lld still gets nixpkgs' RUNPATH injection. Your RUNPATH shows its signature: the nix-shell's own-shell/libfirst (fromNIX_LDFLAGS), then the-Ldirs, then glibc and gcc-lib last.I installed rustup via the rustup.rs installer. Its
gcc-ld/ld.lldis the raw ELF from Rust CI and there is nogcc-ld-unwrapped/. Nothing wraps it.To confirm it was that file and nothing else, I ran your exact
nix-shell -p pkg-config openssl zlib sqliteon my machine with nixpkgs rustup in an isolatedRUSTUP_HOME, samerustc 1.95.0, and swapped only that one file:gcc-ld/ld.lldLinker RUNPATH lddnot foundrun nixpkgs shim LLD 22.1.2 shell, sqlite, zlib, openssl, glibc, gcc-lib 0 exit 0 raw upstream ELF LLD 22.1.2 <shell>/libonly3 exit 127 Same result with the rustup.rs toolchain inside your shell (exit 127), and with a plain C link through
cc -fuse-ld=lld -B<gcc-ld>(exit 127), so this is neither the shell norPKG_CONFIG_PATH.Two corrections to what I wrote earlier:
- rustc does invoke
cc.rustc --print link-argsshows"cc" ... "-B<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld" "-fuse-ld=lld". What gets bypassed is the bintoolsldwrapper, because-Bplus-fuse-ld=lldmakes collect2 resolveld.lldfrom the Rust sysroot instead of resolvingldthrough the wrapper. That is also why putting-rpathin the cc wrapper'scc-ldflagsworks. readelf -p .commentdoes not tell the two cases apart, since both say LLD. The check that does:
head -c4 "$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld/ld.lld" # ELF -> rustup.rs toolchain, reproduces # #!/u -> nixpkgs rustup shim, does not
So the behaviour is real for anyone on NixOS who installs rustup the way rustup.rs documents, and nixpkgs has handled it downstream for its own package. That narrows what is left for rustc: at most a note next to the lld default that on NixOS you want nixpkgs'
rustup(or-Clinker-features=-lld -Clink-self-contained=-linker), which is in line with @ds84182's point that NixOS-specific linking belongs in nixpkgs.
This comment was generated by
clud, a custom agent made by Zach Vorhies who reviewed and authorized this post.- rustc does invoke
So disclosure, this is happening with a tool for compile optimization in rust, so many tools are pre-compiled archives that the tool is providing.
I'm going to have to rule out now that it's not cross linux binaries being provided.
This looks like a work around has been provided in a pre tool and the Rust LLD path fix should be migrated here as well.
TL;DR
The bug is real but only reaches people who install Rust with the rustup.rs installer on NixOS. nixpkgs' own
rustuppackage already patches it. My tool installs upstream rustup, so it is affected, and I will carry the nixpkgs fix there. The only thing left for rust-lang is a documentation note next to the lld default.Background
Since 1.90, rustc on
x86_64-unknown-linux-gnudrivesccwith-fuse-ld=lld -B <sysroot>/.../gcc-ld, which makes gcc pick Rust's bundledld.lldinstead of theldon PATH. On NixOS theldon PATH is a wrapper that turns every-L /nix/store/...into-rpath, and that wrapper is the only thing that gives a binary its RUNPATH. Skip it and the binary links cleanly, then fails at startup withlibssl.so.3: cannot open shared object file.nixpkgs fixed this on its side in June 2024 (NixOS/nixpkgs#314268). Its
rustuppackage replaces every installedgcc-ld/ld.lldwith a shim that runs the nixpkgs ld wrapper around the real lld. @maxdexh uses that package, so rust-lld still gets RUNPATH injection and the bug does not appear. I use the rustup.rs installer, whoseld.lldis the raw upstream binary.What was ruled out
My tool ships precompiled toolchain bundles, so I checked whether they were the cause. They are not. The failing binary carries a
/nix/store/...-glibc-2.42interpreter andLinker: LLD 22.1.2, which is the stock rustup.rs toolchain through NixOS's owncc. The bundled cross toolchain only engages with an explicit--target, and on that path it uses the bundle's GNU ld with-C link-self-contained=no, so rust-lld never runs there.Discriminator
head -c4 "$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld/ld.lld" # ELF -> rustup.rs toolchain, reproduces # #!/u -> nixpkgs rustup shim, does not
What happens next
- I will migrate the nixpkgs
ld.lldwrapping into my tool, since it installs its own upstream rustup and misses the nixpkgs fix. - I am not proposing rustc carry NixOS-specific linking behavior, in line with @ds84182's point.
- For rust-lang, the remaining ask is a note next to the lld default: rustup.rs toolchains on NixOS need either nixpkgs'
rustupor-Clinker-features=-lld -Clink-self-contained=-linker.
This comment was generated by
clud, a custom agent made by Zach Vorhies who reviewed and authorized this post.- I will migrate the nixpkgs
If you're using
rustupfrom the install script instead of nixpkgs, then I'm honestly surprised you even got it to run a compiler. Last time I tried that I couldn't even get through the installer. I would not expect an unpatched compiler to work on NixOS. Also note that the nixpkgs PR you linked is from a compiler team member.Things seem to have improved and now works with this last fix.
- addedC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.
on Sep 15, 2026 - added 2 commits that reference this issue
on Sep 16, 2026 - added a commit that references this issue
on Sep 16, 2026 - added a commit that references this issue
on Sep 20, 2026 - added 2 commits that reference this issue
on Oct 1, 2026
Since 1.90 (#140525) rustc links
x86_64-unknown-linux-gnubinaries with the self-contained rust-lld. On NixOS this silently changes the output of any crate that links a system shared library: the binary no longer gets aRUNPATH, and it fails to start.Background
NixOS has no
/usr/lib. ThecconPATHis a nixpkgs wrapper whose companionldwrapper appends-rpath <dir>for each-Ldirectory in/nix/storethat provides a linked-llibrary (ld-wrapper.sh). That is how every binary linked there finds its libraries at run time.rust-lld is not reached through that
ldwrapper, so noRUNPATHis written. Theccwrapper still passes-dynamic-linkerfor the Nix store glibc, so the binary is not a "foreign" one either, and NixOS'snix-ldfallback does not apply. The result links cleanly and dies at startup.Reproduction
NixOS 26.05, rustup stable
rustc 1.95.0 (59807616e 2026-04-14), LLVM 22.1.2, nixpkgsgcc-wrapper-15.2.0ascc. Outside any nix-shell, withPKG_CONFIG_PATHpointing at the store's openssl/zlib/sqlite.pcdirectories:The same build through the same
cc, opting out of the self-contained linker:lddnot foundThe same happens with
clang-sys(libclang.so.21.1: cannot open shared object file), and with tools that inspect the result: a Python wheel backend that runslddto bundle OpenSSL failed becauselddreported it missing.What would help
Any one of these:
ccis a nixpkgs wrapper (e.g. it has anix-support/directory beside it), don't pick the self-contained linker, or warn thatRUNPATHhandling is lost.ld.Workarounds (verified)
-Clinker-features=-lld -Clink-self-contained=-linkerrestores the wrapper's behaviour.-rpath <dir>in the cc wrapper'scc-ldflags, which the cc wrapper passes to whatever linker it calls, rust-lld included. That is what this machine does now: zackees/nixos@91d74c1Related: NixOS/nixpkgs#24744 (lld has no nixpkgs wrapper).