Skip to content

std: fix FreeBSD environ() null deref under crt-static#159097

Closed
valentynkit wants to merge 1 commit into
rust-lang:mainfrom
valentynkit:fix/freebsd-crt-static-environ
Closed

std: fix FreeBSD environ() null deref under crt-static#159097
valentynkit wants to merge 1 commit into
rust-lang:mainfrom
valentynkit:fix/freebsd-crt-static-environ

Conversation

@valentynkit

@valentynkit valentynkit commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

The bug

On FreeBSD, environ() resolves the symbol with dlsym(RTLD_DEFAULT, "environ"), which needs a dynamic symbol table. A -C target-feature=+crt-static executable has none, so dlsym returns null, and every caller that walks the environment array dereferences that return before the null check further down, which only guards the array contents rather than the pointer itself. So std::env::vars/vars_os and Command::spawn segfault on a statically linked FreeBSD binary. std::env::var is unaffected, since it goes through getenv.

The dlsym accessor was added in #153718 to make environ resolve in cdylibs linked with -Wl,--no-undefined. That fixed the shared-library case but regressed the static-executable one, which is the only build of the two that lacks the dynamic symbol table dlsym depends on.

The fix

When dlsym returns null, fall back to a weakly linked reference to environ. rustc lowers an extern_weak static to an indirection global that holds the symbol's address, so reading the static yields the address of environ, or null if nothing defines it. This is the same mechanism the weak! macro in sys/pal/unix/weak uses for function symbols.

The fallback covers both cases the accessor has to satisfy: in a static executable it resolves because crt1.o defines environ, and a weak undefined reference still links cleanly under -Wl,--no-undefined, the constraint dlsym was introduced for.

Testing

No test. x86_64-unknown-freebsd is Tier 2, and its only CI job is a Linux-hosted cross-compile dist build, so FreeBSD binaries never execute in CI regardless of what ships. #151132 and #153718 (the PR this repairs) both landed FreeBSD sys fixes without one.

Fixes #158939

r? libs

On FreeBSD, environ() looks up the environ symbol with dlsym(RTLD_DEFAULT, "environ"), which needs a dynamic symbol table. A statically linked executable (-C target-feature=+crt-static) has none, so dlsym returns null and every caller that walks the environment array (std::env::vars, Command::spawn) derefs it unchecked and segfaults.

Fall back to a weakly linked reference to environ when dlsym fails. Reading an extern_weak static yields the symbol's address, or null when nothing defines it, so the fallback resolves in a static executable (crt1.o defines environ) while a cdylib linked with -Wl,--no-undefined still links, the same constraint the dlsym lookup was introduced to satisfy.
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 10, 2026
@sbogomolov

Copy link
Copy Markdown
Contributor

Author of #158939 here. I missed this draft when I opened #159112, sorry for the overlap.

Both PRs use the same extern_weak mechanism with the order flipped: #159112 reads the weak reference first, so executables never call dlsym at all, and it falls back to dlsym only inside shared libraries, where the weak reference stays null. It also moves the null check in env() up a level so it guards the pointer environ() returns rather than the table contents, and it has been run-tested on FreeBSD 14.4-RELEASE: the reproducer from the issue segfaults built with stock 1.96.0 and passes against the patched std.

Happy to resolve this either way: if you'd like to fold the env() guard into this PR, I'll close mine, or the libs team can just pick one.

@valentynkit
valentynkit marked this pull request as ready for review July 11, 2026 07:15
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 11, 2026
@rust-bors

rust-bors Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

☔ The latest upstream changes (presumably #159192) made this pull request unmergeable. Please resolve the merge conflicts by rebasing.

@valentynkit

Copy link
Copy Markdown
Contributor Author

Closing this one as it was already fixed by the author of the issue #158939 in #159112.

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

std::env::vars and Command::spawn segfault in crt-static binaries on FreeBSD since 1.96

4 participants