Change:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --Investigating library sources for , I have found that the copying of the env variable value is performed not under the lock: So there is a potential data race with setenv. I tried this code with miri And got Have tried to move under the lock, but still got error with miri. I was not able to trigger the real crash with it yet. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: Bug was introduced by this commit: (PR: ) Before that changes, read was under the lock. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOT_END --
claim
I think this happened because implicit scoping of the lock guard, which makes it easy to shorten its lifetime the rwlock isn't coupled to the resource via a lifetime, so it's easy for the extracted pointer to be used longer than the lock is valid The first one could be made more visible by linting to add an explicit to make its lifetime more explicit in the code when a critical section ends. Especially when guard is otherwise unused which indicates that the lock doesn't directly own the resource it protects. The second one could be solved by using a lifetime-tagged raw pointer and coupling that lifetime to the guard.
Side note: even when fixed this assumes all other read/writes acquire Rust's lock and is potentially unsound if they do not. This has often been discussed with a view to making unsafe. Arguably if that happens then the lock would be redundant, although it could also be argued it's still useful to help avoid some footguns even if you can't make it totally safe.
That's
That PR did not add a test, did it? Would be good to add one in (those are run in Miri regularly). We probably don't want iterations though, that will take forever. 100 should easily be enough.
Something like ?
Yeah something like that. For Miri it's not really necessary to do this "sum of the lengths" computation, not sure why that would be particularly susceptible to a data race. The one existing concurrency test uses , so probably it would make sense to use the same attribute here.

Discussion:
static ENV_LOCK: RwLock<()> = RwLock::new(()); <ins> pub fn env_read_lock() -> impl Drop { ENV_LOCK.read().unwrap_or_else(PoisonError::into_inner) } </ins> pub struct Env { iter: vec::IntoIter<(OsString, OsString)>, }

} unsafe { <del> let _guard = ENV_LOCK.read(); </del> <ins> let _guard = env_read_lock(); </ins> let mut result = Vec::new(); if !environ.is_null() { while !(*environ).is_null() {

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 <del> let s = run_with_cstr(k.as_bytes(), |k| { let _guard = ENV_LOCK.read(); Ok(unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char) }) .ok()?; </del> <ins> run_with_cstr(k.as_bytes(), |k| { let _guard = env_read_lock(); let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char; </ins> <del> if s.is_null() { None } else { Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) } </del> <ins> if v.is_null() { Ok(None) } else { // SAFETY: `v` cannot be mutated while executing this line since we've a read lock let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec(); Ok(Some(OsStringExt::from_vec(bytes))) } }) .ok() .flatten() </ins> } pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {

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 <del> let s = run_with_cstr(k.as_bytes(), |k| { </del> <ins> run_with_cstr(k.as_bytes(), |k| { </ins> let _guard = env_read_lock(); <del> Ok(unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char) </del> <ins> let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char; if v.is_null() { Ok(None) } else { // SAFETY: `v` cannot be mutated while executing this line since we've a read lock let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec(); Ok(Some(OsStringExt::from_vec(bytes))) } </ins> }) <del> .ok()?; if s.is_null() { None } else { Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) } </del> <ins> .ok() .flatten() </ins> } pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {

} pub fn getenv(k: &OsStr) -> Option<OsString> { <del> let s = run_with_cstr(k.as_bytes(), |k| unsafe { </del> <ins> // environment variables with a nul byte can't be set, so their value is // always None as well run_with_cstr(k.as_bytes(), |k| { </ins> let _guard = env_read_lock(); <del> Ok(libc::getenv(k.as_ptr()) as *const libc::c_char) </del> <ins> let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char; if v.is_null() { Ok(None) } else { // SAFETY: `v` cannot be mutated while executing this line since we've a read lock let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec(); Ok(Some(OsStringExt::from_vec(bytes))) } </ins> }) <del> .ok()?; if s.is_null() { None } else { Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) } </del> <ins> .ok() .flatten() </ins> } pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {