C stdlib isn't threadsafe and even safe Rust didn't save us

Non-thread-safe environment APIs in C/POSIX

  • Core issue: setenv / unsetenv (and putenv, environ) are not required to be reentrant or thread‑safe by POSIX; many implementations aren’t.
  • getenv is also tricky: the returned pointer may be invalidated by later env calls; POSIX/ISO C give weak guarantees.
  • Exposing extern char **environ makes it impossible to fully “fix” things without breaking ABI: code can mutate the environment behind libc’s back.

Impact on Rust and other languages

  • Rust’s std::env::set_var originally wrapped setenv as a safe function; it’s now documented as unsafe on most non‑Windows platforms and will require unsafe in Rust 2024.
  • Rust can’t synchronize with C libraries that call getenv/setenv directly, so a Rust-only mutex cannot fix the problem.
  • Even a Rust‑native libc or alternative runtime doesn’t help if C/FFI code still uses the platform libc and its environment.

Can libc or POSIX fix this?

  • One camp: libc should make env operations safe by default (e.g., mutexes, copy‑on‑write, per‑thread views, never freeing old strings, Solaris/illumos-style fixes, glibc’s recent “leak instead of crash” change).
  • Counterarguments:
    • The API shape (shared pointers, environ) fundamentally prevents full MT safety.
    • Retrofitting safety risks breaking existing programs or causing unbounded leaks.
    • Standards explicitly say these calls need not be thread‑safe; changing that is hard.

Environment variables as mutable global state

  • Strong sentiment that env vars should be read-only after startup, like argv.
  • Many argue setenv “shouldn’t have existed” or should be reserved for building child-process environments, not intra-process configuration.
  • Others note legitimate cases (e.g., dlopen’ing libraries configured via env, allocators/sanitizers, shells) but agree mutation after threads start is dangerous.

Workarounds and safer patterns

  • Common recommendations:
    • Read env exactly once at startup, copy into your own data structure, then treat as immutable.
    • Avoid libraries that mutate env at runtime; prefer APIs that accept explicit config instead of reading env.
    • In extreme cases, override getenv/setenv via LD_PRELOAD or use alternative libcs that leak instead of corrupting.
  • Some suggest new, explicitly thread-safe env APIs (buffer‑based or copy‑returning), but migration and coexistence with environ remain unsolved.

Broader reflections

  • Debate over “safe by default” vs performance and historical constraints.
  • Comparisons to Windows’ and JVM’s environment handling, which are effectively thread-safe or immutable, seen as vindicating stricter designs.