Skip to content

Fix segfault in env access in statically linked FreeBSD binaries#159112

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
sbogomolov:freebsd-static-environ
Jul 13, 2026
Merged

Fix segfault in env access in statically linked FreeBSD binaries#159112
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
sbogomolov:freebsd-static-environ

Conversation

@sbogomolov

@sbogomolov sbogomolov commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

View all comments

On FreeBSD environ is provided by the startup object linked into every executable (crt1.o) rather than by libc.so, so #153718 switched std to a dlsym lookup to keep shared libraries linkable with -Wl,--no-undefined (#153451). But dlsym needs a dynamic symbol table, which statically linked executables do not have: it returns null, and both env::vars_os and the Command spawn paths dereference it (#158939).

Resolve environ through a weak reference first: crt1.o defines it in every executable, statically linked ones included, and a weak undefined symbol does not break shared library links. Fall back to dlsym only when the weak reference is null, i.e. inside a shared library, where the dynamic lookup is available. Also stop dereferencing a null table pointer in env(), so an unresolvable environ degrades to an empty environment instead of a crash.

The extern_weak reference is the same mechanism std already uses for __dso_handle and __cxa_thread_atexit_impl in sys/thread_local/destructors/linux_like.rs.

Testing

CI cannot execute FreeBSD tests (the target is cross-compiled only), so:

  • ./x check library/std --target x86_64-unknown-freebsd and a host check pass.
  • The reproduction from std::env::vars and Command::spawn segfault in crt-static binaries on FreeBSD since 1.96 #158939, built with -C target-feature=+crt-static, was executed on FreeBSD 14.4-RELEASE (amd64): built with stock 1.96.0 it segfaults on vars_os() (SIGSEGV, exit 139); built against std from this branch it iterates the environment, spawns a child with an inherited environment, and exits cleanly.
  • Downstream, the crash was originally isolated and worked around in a production daemon whose CI runs the exact affected configuration (static, LTO) on a real FreeBSD kernel per commit; that harness stands ready to validate a nightly containing this fix.

This is a regression from stable to stable (1.95 -> 1.96) and may be worth a beta backport.

Fixes #158939

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 11, 2026
@rustbot

rustbot commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome! The Rust Project is excited to review your changes, and you should hear from @clarfonthey (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue
Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ChrisDenton, libs
  • @ChrisDenton, libs expanded to 13 candidates
  • Random selection from 6 candidates

@rustbot

This comment has been minimized.

@sbogomolov
sbogomolov force-pushed the freebsd-static-environ branch from 422f653 to d0615ff Compare July 11, 2026 00:48
@sbogomolov

Copy link
Copy Markdown
Contributor Author

@rustbot label +regression-from-stable-to-stable

@rustbot rustbot added regression-from-stable-to-stable Performance or correctness regression from one stable version to another. I-prioritize Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical} labels Jul 11, 2026
@sbogomolov

sbogomolov commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Note: #159097 is an earlier draft PR for the same issue, using the same extern_weak mechanism with the lookup order flipped (dlsym first). Differences here: the weak reference is read first so executables skip dlsym entirely, the env() null check now guards the right pointer, and the fix is run-tested (see Testing above). I've left a note there; happy to consolidate in either direction.

Comment thread library/std/src/sys/env/unix.rs
Comment thread library/std/src/sys/env/unix.rs Outdated
@theemathas theemathas removed regression-from-stable-to-stable Performance or correctness regression from one stable version to another. I-prioritize Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical} labels Jul 11, 2026
@sbogomolov
sbogomolov force-pushed the freebsd-static-environ branch from d0615ff to 31ae9ac Compare July 11, 2026 11:10
Comment thread library/std/src/sys/env/unix.rs Outdated
std resolves environ through dlsym on FreeBSD so that shared libraries
can link, but a statically linked executable has no dynamic symbol
table: the lookup returns null and the env iteration dereferences it.
Take a weak reference instead: it binds at link time in any executable,
and in a shared library the runtime linker resolves it against the
environ the executable exports. Treat a null environ as an empty
environment instead of dereferencing it.
@sbogomolov
sbogomolov force-pushed the freebsd-static-environ branch from 31ae9ac to 6194bec Compare July 11, 2026 16:06
@sbogomolov
sbogomolov requested review from bjorn3 and clarfonthey July 11, 2026 16:13
Comment thread library/std/src/sys/env/unix.rs
@sbogomolov

Copy link
Copy Markdown
Contributor Author

All comments were addressed, all discussions resolved.
@bjorn3 @clarfonthey let me know if you want anything else changed.

@sbogomolov
sbogomolov requested a review from bjorn3 July 12, 2026 14:23
@clarfonthey

Copy link
Copy Markdown
Contributor

LGTM, will wait for @bjorn3 to give it a look and then can merge.

@bjorn3

bjorn3 commented Jul 12, 2026

Copy link
Copy Markdown
Member

LGTM

@clarfonthey

Copy link
Copy Markdown
Contributor

@bors r+ rollup

@rust-bors

rust-bors Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 6194bec has been approved by clarfonthey

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 12, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jul 12, 2026
… r=clarfonthey

Fix segfault in env access in statically linked FreeBSD binaries

On FreeBSD `environ` is provided by the startup object linked into every executable (crt1.o) rather than by libc.so, so rust-lang#153718 switched std to a dlsym lookup to keep shared libraries linkable with `-Wl,--no-undefined` (rust-lang#153451). But dlsym needs a dynamic symbol table, which statically linked executables do not have: it returns null, and both `env::vars_os` and the `Command` spawn paths dereference it (rust-lang#158939).

Resolve `environ` through a weak reference first: crt1.o defines it in every executable, statically linked ones included, and a weak undefined symbol does not break shared library links. Fall back to dlsym only when the weak reference is null, i.e. inside a shared library, where the dynamic lookup is available. Also stop dereferencing a null table pointer in `env()`, so an unresolvable `environ` degrades to an empty environment instead of a crash.

The `extern_weak` reference is the same mechanism std already uses for `__dso_handle` and `__cxa_thread_atexit_impl` in `sys/thread_local/destructors/linux_like.rs`.

## Testing

CI cannot execute FreeBSD tests (the target is cross-compiled only), so:

- `./x check library/std --target x86_64-unknown-freebsd` and a host check pass.
- The reproduction from rust-lang#158939, built with `-C target-feature=+crt-static`, was executed on FreeBSD 14.4-RELEASE (amd64): built with stock 1.96.0 it segfaults on `vars_os()` (SIGSEGV, exit 139); built against std from this branch it iterates the environment, spawns a child with an inherited environment, and exits cleanly.
- Downstream, the crash was originally isolated and worked around in a production daemon whose CI runs the exact affected configuration (static, LTO) on a real FreeBSD kernel per commit; that harness stands ready to validate a nightly containing this fix.

This is a regression from stable to stable (1.95 -> 1.96) and may be worth a beta backport.

Fixes rust-lang#158939
rust-bors Bot pushed a commit that referenced this pull request Jul 12, 2026
…uwer

Rollup of 7 pull requests

Successful merges:

 - #158732 (Apply MCP 1003 and move diagnostics.rs into its own module)
 - #159010 (Simplify the unwind crate)
 - #159131 (bootstrap: Allow path-based skipping of the coverage test suite)
 - #159134 (std: fix panic in Windows Stdin::read_vectored with pending surrogate)
 - #159178 (Print duration of BOLT instrumentation and optimization steps)
 - #159112 (Fix segfault in env access in statically linked FreeBSD binaries)
 - #159124 (compiletest: use VecDeque::pop_front_if)
rust-bors Bot pushed a commit that referenced this pull request Jul 12, 2026
…uwer

Rollup of 6 pull requests

Successful merges:

 - #158732 (Apply MCP 1003 and move diagnostics.rs into its own module)
 - #159131 (bootstrap: Allow path-based skipping of the coverage test suite)
 - #159134 (std: fix panic in Windows Stdin::read_vectored with pending surrogate)
 - #159178 (Print duration of BOLT instrumentation and optimization steps)
 - #159112 (Fix segfault in env access in statically linked FreeBSD binaries)
 - #159124 (compiletest: use VecDeque::pop_front_if)
@rust-bors
rust-bors Bot merged commit 21e2e77 into rust-lang:main Jul 13, 2026
13 checks passed
@rustbot rustbot added this to the 1.99.0 milestone Jul 13, 2026
rust-timer added a commit that referenced this pull request Jul 13, 2026
Rollup merge of #159112 - sbogomolov:freebsd-static-environ, r=clarfonthey

Fix segfault in env access in statically linked FreeBSD binaries

On FreeBSD `environ` is provided by the startup object linked into every executable (crt1.o) rather than by libc.so, so #153718 switched std to a dlsym lookup to keep shared libraries linkable with `-Wl,--no-undefined` (#153451). But dlsym needs a dynamic symbol table, which statically linked executables do not have: it returns null, and both `env::vars_os` and the `Command` spawn paths dereference it (#158939).

Resolve `environ` through a weak reference first: crt1.o defines it in every executable, statically linked ones included, and a weak undefined symbol does not break shared library links. Fall back to dlsym only when the weak reference is null, i.e. inside a shared library, where the dynamic lookup is available. Also stop dereferencing a null table pointer in `env()`, so an unresolvable `environ` degrades to an empty environment instead of a crash.

The `extern_weak` reference is the same mechanism std already uses for `__dso_handle` and `__cxa_thread_atexit_impl` in `sys/thread_local/destructors/linux_like.rs`.

## Testing

CI cannot execute FreeBSD tests (the target is cross-compiled only), so:

- `./x check library/std --target x86_64-unknown-freebsd` and a host check pass.
- The reproduction from #158939, built with `-C target-feature=+crt-static`, was executed on FreeBSD 14.4-RELEASE (amd64): built with stock 1.96.0 it segfaults on `vars_os()` (SIGSEGV, exit 139); built against std from this branch it iterates the environment, spawns a child with an inherited environment, and exits cleanly.
- Downstream, the crash was originally isolated and worked around in a production daemon whose CI runs the exact affected configuration (static, LTO) on a real FreeBSD kernel per commit; that harness stands ready to validate a nightly containing this fix.

This is a regression from stable to stable (1.95 -> 1.96) and may be worth a beta backport.

Fixes #158939
@RalfJung RalfJung mentioned this pull request Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. 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

5 participants