Skip to content

Possible data race & use-after-free in std::env::var for unix implementation #114949

@Nekrolm

Description

@Nekrolm

Investigating library sources for std::os::env::var/set_var, I have found that the copying of the env variable value is performed not under the lock:

pub fn getenv(k: &OsStr) -> Option<OsString> {
    // environment variables with a nul byte can't be set, so their value is
    // always None as well
    let s = run_with_cstr(k.as_bytes(), |k| {
        let _guard = env_read_lock();
        Ok(unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char)
       // lock is dropped here
    })
    .ok()?;
    if s.is_null() {
        None
    } else {
        // but access is here
        Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec()))
    }
}

So there is a potential data race with setenv.

I tried this code with miri

fn main() {
    let t1 = std::thread::spawn(|| {
        let mut cnt = 0;
        for _ in 0..100000 {
            let var = std::env::var_os("HELLO");
            cnt += var.map(|v| v.len()).unwrap_or_default();
        }
        cnt
    });
    let t2 = std::thread::spawn(|| {
        for i in 0..100000 {
            let value = format!("helloooooooooooooo{i}");
            std::env::set_var("HELLO", &value);
        }
    });

    let cnt = t1.join().expect("ok");
    println!("{cnt}");
    t2.join();
}

And got

error: Undefined Behavior: Data race detected between (1) Read on thread `<unnamed>` and (2) Deallocate on thread `<unnamed>` at alloc3346+0x6. (2) just happened here
   --> /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/unix/os.rs:613:26
    |
613 |             cvt(unsafe { libc::setenv(k.as_ptr(), v.as_ptr(), 1) }).map(drop)
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Deallocate on thread `<unnamed>` at alloc3346+0x6. (2) just happened here
    |
help: and (1) occurred earlier here
   --> src/main.rs:30:23
    |
30  |             let var = std::env::var_os("HELLO");
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span):
    = note: inside closure at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/unix/os.rs:613:26: 613:65
    = note: inside `std::sys::common::small_c_string::run_with_cstr::<(), [closure@std::sys::unix::os::setenv::{closure#0}::{closure#0}]>` at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/common/small_c_string.rs:43:18: 43:22
    = note: inside closure at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/unix/os.rs:611:9: 614:11
    = note: inside `std::sys::common::small_c_string::run_with_cstr::<(), [closure@std::sys::unix::os::setenv::{closure#0}]>` at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/common/small_c_string.rs:43:18: 43:22
    = note: inside `std::sys::unix::os::setenv` at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/unix/os.rs:610:5: 615:7
    = note: inside `std::env::_set_var` at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/env.rs:347:5: 347:31
    = note: inside `std::env::set_var::<&str, &std::string::String>` at /home/dmis/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/env.rs:343:5: 343:43
note: inside closure
   --> src/main.rs:38:13

Have tried to move Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) under the lock, but still got error with miri.

I was not able to trigger the real crash with it yet.

Meta

rustc --version --verbose:

rustc 1.71.1 (eb26296b5 2023-08-03)
binary: rustc
commit-hash: eb26296b556cef10fb713a38f3d16b9886080f26
commit-date: 2023-08-03
host: x86_64-unknown-linux-gnu
release: 1.71.1
LLVM version: 16.0.5

Bug was introduced by this commit: 86974b8 (PR: #93668)
Before that changes, read was under the lock.

Metadata

Metadata

Assignees

Labels

A-processArea: `std::process` and `std::env`C-bugCategory: This is a bug.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.O-unixOperating system: Unix-likeT-libsRelevant to the library team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions