From c4b1ff893a1efe2dfa72aa4a7b8a45cfd8d8cdbc Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Mon, 13 May 2024 09:05:34 +0200 Subject: [PATCH 1/3] Make `std::env::{set_var, remove_var}` unsafe in edition 2024 Allow calling these functions without `unsafe` blocks in editions up until 2021, but don't trigger the `unused_unsafe` lint for `unsafe` blocks containing these functions. Fixes #27970. Fixes #90308. CC #124866. --- std/src/env.rs | 54 +++++++++++++++++++------------ std/src/sys/pal/hermit/os.rs | 14 +++----- std/src/sys/pal/sgx/os.rs | 4 +-- std/src/sys/pal/solid/os.rs | 4 +-- std/src/sys/pal/teeos/os.rs | 4 +-- std/src/sys/pal/uefi/os.rs | 4 +-- std/src/sys/pal/unix/os.rs | 8 ++--- std/src/sys/pal/unsupported/os.rs | 4 +-- std/src/sys/pal/wasi/os.rs | 4 +-- std/src/sys/pal/windows/os.rs | 8 ++--- std/src/sys/pal/xous/os.rs | 4 +-- std/src/sys/pal/zkvm/os.rs | 4 +-- 12 files changed, 63 insertions(+), 53 deletions(-) diff --git a/std/src/env.rs b/std/src/env.rs index 6f8ac17f12c70..95ee2a91d159e 100644 --- a/std/src/env.rs +++ b/std/src/env.rs @@ -318,11 +318,6 @@ impl Error for VarError { /// /// # Safety /// -/// Even though this function is currently not marked as `unsafe`, it needs to -/// be because invoking it can cause undefined behaviour. The function will be -/// marked `unsafe` in a future version of Rust. This is tracked in -/// [rust#27970](https://github.com/rust-lang/rust/issues/27970). -/// /// This function is safe to call in a single-threaded program. /// /// In multi-threaded programs, you must ensure that are no other threads @@ -331,7 +326,7 @@ impl Error for VarError { /// how to achieve this, but we strongly suggest not using `set_var` or /// `remove_var` in multi-threaded programs at all. /// -/// Most C libraries, including libc itself do not advertise which functions +/// Most C libraries, including libc itself, do not advertise which functions /// read from the environment. Even functions from the Rust standard library do /// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`]. /// @@ -353,15 +348,26 @@ impl Error for VarError { /// use std::env; /// /// let key = "KEY"; -/// env::set_var(key, "VALUE"); +/// unsafe { +/// env::set_var(key, "VALUE"); +/// } /// assert_eq!(env::var(key), Ok("VALUE".to_string())); /// ``` +#[cfg(not(bootstrap))] +#[rustc_deprecated_safe_2024] #[stable(feature = "env", since = "1.0.0")] -pub fn set_var, V: AsRef>(key: K, value: V) { +pub unsafe fn set_var, V: AsRef>(key: K, value: V) { _set_var(key.as_ref(), value.as_ref()) } -fn _set_var(key: &OsStr, value: &OsStr) { +#[cfg(bootstrap)] +#[allow(missing_docs)] +#[stable(feature = "env", since = "1.0.0")] +pub fn set_var, V: AsRef>(key: K, value: V) { + unsafe { _set_var(key.as_ref(), value.as_ref()) } +} + +unsafe fn _set_var(key: &OsStr, value: &OsStr) { os_imp::setenv(key, value).unwrap_or_else(|e| { panic!("failed to set environment variable `{key:?}` to `{value:?}`: {e}") }) @@ -371,11 +377,6 @@ fn _set_var(key: &OsStr, value: &OsStr) { /// /// # Safety /// -/// Even though this function is currently not marked as `unsafe`, it needs to -/// be because invoking it can cause undefined behaviour. The function will be -/// marked `unsafe` in a future version of Rust. This is tracked in -/// [rust#27970](https://github.com/rust-lang/rust/issues/27970). -/// /// This function is safe to call in a single-threaded program. /// /// In multi-threaded programs, you must ensure that are no other threads @@ -384,7 +385,7 @@ fn _set_var(key: &OsStr, value: &OsStr) { /// how to achieve this, but we strongly suggest not using `set_var` or /// `remove_var` in multi-threaded programs at all. /// -/// Most C libraries, including libc itself do not advertise which functions +/// Most C libraries, including libc itself, do not advertise which functions /// read from the environment. Even functions from the Rust standard library do /// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`]. /// @@ -403,22 +404,35 @@ fn _set_var(key: &OsStr, value: &OsStr) { /// /// # Examples /// -/// ``` +/// ```no_run /// use std::env; /// /// let key = "KEY"; -/// env::set_var(key, "VALUE"); +/// unsafe { +/// env::set_var(key, "VALUE"); +/// } /// assert_eq!(env::var(key), Ok("VALUE".to_string())); /// -/// env::remove_var(key); +/// unsafe { +/// env::remove_var(key); +/// } /// assert!(env::var(key).is_err()); /// ``` +#[cfg(not(bootstrap))] +#[rustc_deprecated_safe_2024] #[stable(feature = "env", since = "1.0.0")] -pub fn remove_var>(key: K) { +pub unsafe fn remove_var>(key: K) { _remove_var(key.as_ref()) } -fn _remove_var(key: &OsStr) { +#[cfg(bootstrap)] +#[allow(missing_docs)] +#[stable(feature = "env", since = "1.0.0")] +pub fn remove_var>(key: K) { + unsafe { _remove_var(key.as_ref()) } +} + +unsafe fn _remove_var(key: &OsStr) { os_imp::unsetenv(key) .unwrap_or_else(|e| panic!("failed to remove environment variable `{key:?}`: {e}")) } diff --git a/std/src/sys/pal/hermit/os.rs b/std/src/sys/pal/hermit/os.rs index cc6781238319b..91247d30462f8 100644 --- a/std/src/sys/pal/hermit/os.rs +++ b/std/src/sys/pal/hermit/os.rs @@ -172,18 +172,14 @@ pub fn getenv(k: &OsStr) -> Option { unsafe { ENV.as_ref().unwrap().lock().unwrap().get_mut(k).cloned() } } -pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { - unsafe { - let (k, v) = (k.to_owned(), v.to_owned()); - ENV.as_ref().unwrap().lock().unwrap().insert(k, v); - } +pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { + let (k, v) = (k.to_owned(), v.to_owned()); + ENV.as_ref().unwrap().lock().unwrap().insert(k, v); Ok(()) } -pub fn unsetenv(k: &OsStr) -> io::Result<()> { - unsafe { - ENV.as_ref().unwrap().lock().unwrap().remove(k); - } +pub unsafe fn unsetenv(k: &OsStr) -> io::Result<()> { + ENV.as_ref().unwrap().lock().unwrap().remove(k); Ok(()) } diff --git a/std/src/sys/pal/sgx/os.rs b/std/src/sys/pal/sgx/os.rs index 86f4c7d3d56d6..c021300d4ae33 100644 --- a/std/src/sys/pal/sgx/os.rs +++ b/std/src/sys/pal/sgx/os.rs @@ -157,13 +157,13 @@ pub fn getenv(k: &OsStr) -> Option { get_env_store().and_then(|s| s.lock().unwrap().get(k).cloned()) } -pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { let (k, v) = (k.to_owned(), v.to_owned()); create_env_store().lock().unwrap().insert(k, v); Ok(()) } -pub fn unsetenv(k: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(k: &OsStr) -> io::Result<()> { if let Some(env) = get_env_store() { env.lock().unwrap().remove(k); } diff --git a/std/src/sys/pal/solid/os.rs b/std/src/sys/pal/solid/os.rs index ef35d8788a236..ac90aae4ebe46 100644 --- a/std/src/sys/pal/solid/os.rs +++ b/std/src/sys/pal/solid/os.rs @@ -191,7 +191,7 @@ pub fn getenv(k: &OsStr) -> Option { .flatten() } -pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { run_with_cstr(k.as_bytes(), &|k| { run_with_cstr(v.as_bytes(), &|v| { let _guard = ENV_LOCK.write(); @@ -200,7 +200,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { }) } -pub fn unsetenv(n: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> { run_with_cstr(n.as_bytes(), &|nbuf| { let _guard = ENV_LOCK.write(); cvt_env(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop) diff --git a/std/src/sys/pal/teeos/os.rs b/std/src/sys/pal/teeos/os.rs index e54a92f01f86b..3be0846a6dd4d 100644 --- a/std/src/sys/pal/teeos/os.rs +++ b/std/src/sys/pal/teeos/os.rs @@ -109,11 +109,11 @@ pub fn getenv(_: &OsStr) -> Option { None } -pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { Err(io::Error::new(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) } -pub fn unsetenv(_: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> { Err(io::Error::new(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) } diff --git a/std/src/sys/pal/uefi/os.rs b/std/src/sys/pal/uefi/os.rs index 58838c5876ebd..0b27977df2fde 100644 --- a/std/src/sys/pal/uefi/os.rs +++ b/std/src/sys/pal/uefi/os.rs @@ -203,11 +203,11 @@ pub fn getenv(_: &OsStr) -> Option { None } -pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) } -pub fn unsetenv(_: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) } diff --git a/std/src/sys/pal/unix/os.rs b/std/src/sys/pal/unix/os.rs index 8afc49f52274c..b5c7d30da7bc0 100644 --- a/std/src/sys/pal/unix/os.rs +++ b/std/src/sys/pal/unix/os.rs @@ -675,19 +675,19 @@ pub fn getenv(k: &OsStr) -> Option { .flatten() } -pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { run_with_cstr(k.as_bytes(), &|k| { run_with_cstr(v.as_bytes(), &|v| { let _guard = ENV_LOCK.write(); - cvt(unsafe { libc::setenv(k.as_ptr(), v.as_ptr(), 1) }).map(drop) + cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop) }) }) } -pub fn unsetenv(n: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> { run_with_cstr(n.as_bytes(), &|nbuf| { let _guard = ENV_LOCK.write(); - cvt(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop) + cvt(libc::unsetenv(nbuf.as_ptr())).map(drop) }) } diff --git a/std/src/sys/pal/unsupported/os.rs b/std/src/sys/pal/unsupported/os.rs index 248b34829f2ee..3be98898bbeb9 100644 --- a/std/src/sys/pal/unsupported/os.rs +++ b/std/src/sys/pal/unsupported/os.rs @@ -96,11 +96,11 @@ pub fn getenv(_: &OsStr) -> Option { None } -pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) } -pub fn unsetenv(_: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) } diff --git a/std/src/sys/pal/wasi/os.rs b/std/src/sys/pal/wasi/os.rs index ee377b6ef791d..e96296997e6a9 100644 --- a/std/src/sys/pal/wasi/os.rs +++ b/std/src/sys/pal/wasi/os.rs @@ -244,7 +244,7 @@ pub fn getenv(k: &OsStr) -> Option { .flatten() } -pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { run_with_cstr(k.as_bytes(), &|k| { run_with_cstr(v.as_bytes(), &|v| unsafe { let _guard = env_write_lock(); @@ -253,7 +253,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { }) } -pub fn unsetenv(n: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> { run_with_cstr(n.as_bytes(), &|nbuf| unsafe { let _guard = env_write_lock(); cvt(libc::unsetenv(nbuf.as_ptr())).map(drop) diff --git a/std/src/sys/pal/windows/os.rs b/std/src/sys/pal/windows/os.rs index 64d8b72aed282..483b8b0072c8f 100644 --- a/std/src/sys/pal/windows/os.rs +++ b/std/src/sys/pal/windows/os.rs @@ -302,16 +302,16 @@ pub fn getenv(k: &OsStr) -> Option { .ok() } -pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { let k = to_u16s(k)?; let v = to_u16s(v)?; - cvt(unsafe { c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr()) }).map(drop) + cvt(c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())).map(drop) } -pub fn unsetenv(n: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> { let v = to_u16s(n)?; - cvt(unsafe { c::SetEnvironmentVariableW(v.as_ptr(), ptr::null()) }).map(drop) + cvt(c::SetEnvironmentVariableW(v.as_ptr(), ptr::null())).map(drop) } pub fn temp_dir() -> PathBuf { diff --git a/std/src/sys/pal/xous/os.rs b/std/src/sys/pal/xous/os.rs index 8d2eaee8aa617..9be09eed62989 100644 --- a/std/src/sys/pal/xous/os.rs +++ b/std/src/sys/pal/xous/os.rs @@ -149,11 +149,11 @@ pub fn getenv(_: &OsStr) -> Option { None } -pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) } -pub fn unsetenv(_: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) } diff --git a/std/src/sys/pal/zkvm/os.rs b/std/src/sys/pal/zkvm/os.rs index 759beb2d306b9..e7d6cd52a258e 100644 --- a/std/src/sys/pal/zkvm/os.rs +++ b/std/src/sys/pal/zkvm/os.rs @@ -115,11 +115,11 @@ pub fn getenv(varname: &OsStr) -> Option { Some(OsString::from_inner(os_str::Buf { inner: u8s.to_vec() })) } -pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { +pub unsafe fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) } -pub fn unsetenv(_: &OsStr) -> io::Result<()> { +pub unsafe fn unsetenv(_: &OsStr) -> io::Result<()> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) } From 25007197aef2ab5da57d6ef4a4bc357c123d6b21 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Fri, 24 May 2024 10:04:44 +0200 Subject: [PATCH 2/3] Add note about safety of `std::env::set_var` on Windows --- std/src/env.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/std/src/env.rs b/std/src/env.rs index 95ee2a91d159e..d433caa9d2a45 100644 --- a/std/src/env.rs +++ b/std/src/env.rs @@ -320,11 +320,14 @@ impl Error for VarError { /// /// This function is safe to call in a single-threaded program. /// -/// In multi-threaded programs, you must ensure that are no other threads -/// concurrently writing or *reading*(!) from the environment through functions -/// other than the ones in this module. You are responsible for figuring out -/// how to achieve this, but we strongly suggest not using `set_var` or -/// `remove_var` in multi-threaded programs at all. +/// This function is also always safe to call on Windows, in single-threaded +/// and multi-threaded programs. +/// +/// In multi-threaded programs on other operating systems, you must ensure that +/// are no other threads concurrently writing or *reading*(!) from the +/// environment through functions other than the ones in this module. You are +/// responsible for figuring out how to achieve this, but we strongly suggest +/// not using `set_var` or `remove_var` in multi-threaded programs at all. /// /// Most C libraries, including libc itself, do not advertise which functions /// read from the environment. Even functions from the Rust standard library do @@ -379,6 +382,9 @@ unsafe fn _set_var(key: &OsStr, value: &OsStr) { /// /// This function is safe to call in a single-threaded program. /// +/// This function is also always safe to call on Windows, in single-threaded +/// and multi-threaded programs. +/// /// In multi-threaded programs, you must ensure that are no other threads /// concurrently writing or *reading*(!) from the environment through functions /// other than the ones in this module. You are responsible for figuring out From 4feb88195215095480f94342731b1cc4b103a424 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Fri, 24 May 2024 10:26:04 +0200 Subject: [PATCH 3/3] Elaborate about modifying env vars in multi-threaded programs --- std/src/env.rs | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/std/src/env.rs b/std/src/env.rs index d433caa9d2a45..4d649f8a6f136 100644 --- a/std/src/env.rs +++ b/std/src/env.rs @@ -323,15 +323,20 @@ impl Error for VarError { /// This function is also always safe to call on Windows, in single-threaded /// and multi-threaded programs. /// -/// In multi-threaded programs on other operating systems, you must ensure that -/// are no other threads concurrently writing or *reading*(!) from the -/// environment through functions other than the ones in this module. You are -/// responsible for figuring out how to achieve this, but we strongly suggest -/// not using `set_var` or `remove_var` in multi-threaded programs at all. -/// -/// Most C libraries, including libc itself, do not advertise which functions -/// read from the environment. Even functions from the Rust standard library do -/// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`]. +/// In multi-threaded programs on other operating systems, we strongly suggest +/// not using `set_var` or `remove_var` at all. The exact requirement is: you +/// must ensure that there are no other threads concurrently writing or +/// *reading*(!) the environment through functions or global variables other +/// than the ones in this module. The problem is that these operating systems +/// do not provide a thread-safe way to read the environment, and most C +/// libraries, including libc itself, do not advertise which functions read +/// from the environment. Even functions from the Rust standard library may +/// read the environment without going through this module, e.g. for DNS +/// lookups from [`std::net::ToSocketAddrs`]. No stable guarantee is made about +/// which functions may read from the environment in future versions of a +/// library. All this makes it not practically possible for you to guarantee +/// that no other thread will read the environment, so the only safe option is +/// to not use `set_var` or `remove_var` in multi-threaded programs at all. /// /// Discussion of this unsafety on Unix may be found in: /// @@ -385,15 +390,20 @@ unsafe fn _set_var(key: &OsStr, value: &OsStr) { /// This function is also always safe to call on Windows, in single-threaded /// and multi-threaded programs. /// -/// In multi-threaded programs, you must ensure that are no other threads -/// concurrently writing or *reading*(!) from the environment through functions -/// other than the ones in this module. You are responsible for figuring out -/// how to achieve this, but we strongly suggest not using `set_var` or -/// `remove_var` in multi-threaded programs at all. -/// -/// Most C libraries, including libc itself, do not advertise which functions -/// read from the environment. Even functions from the Rust standard library do -/// that, e.g. for DNS lookups from [`std::net::ToSocketAddrs`]. +/// In multi-threaded programs on other operating systems, we strongly suggest +/// not using `set_var` or `remove_var` at all. The exact requirement is: you +/// must ensure that there are no other threads concurrently writing or +/// *reading*(!) the environment through functions or global variables other +/// than the ones in this module. The problem is that these operating systems +/// do not provide a thread-safe way to read the environment, and most C +/// libraries, including libc itself, do not advertise which functions read +/// from the environment. Even functions from the Rust standard library may +/// read the environment without going through this module, e.g. for DNS +/// lookups from [`std::net::ToSocketAddrs`]. No stable guarantee is made about +/// which functions may read from the environment in future versions of a +/// library. All this makes it not practically possible for you to guarantee +/// that no other thread will read the environment, so the only safe option is +/// to not use `set_var` or `remove_var` in multi-threaded programs at all. /// /// Discussion of this unsafety on Unix may be found in: ///