Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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