提交 4032b7a4 编写于 作者: A Alex Crichton

std: Synchronize access to global env during `exec`

This commit, after reverting #55359, applies a different fix for #46775
while also fixing #55775. The basic idea was to go back to pre-#55359
libstd, and then fix #46775 in a way that doesn't expose #55775.

The issue described in #46775 boils down to two problems:

* First, the global environment is reset during `exec` but, but if the
  `exec` call fails then the global environment was a dangling pointer
  into free'd memory as the block of memory was deallocated when
  `Command` is dropped. This is fixed in this commit by installing a
  `Drop` stack object which ensures that the `environ` pointer is
  preserved on a failing `exec`.

* Second, the global environment was accessed in an unsynchronized
  fashion during `exec`. This was fixed by ensuring that the
  Rust-specific environment lock is acquired for these system-level
  operations.

Thanks to Alex Gaynor for pioneering the solution here!

Closes #55775
Co-authored-by: NAlex Gaynor <alex.gaynor@gmail.com>
上级 5856797b
...@@ -27,15 +27,12 @@ ...@@ -27,15 +27,12 @@
use ptr; use ptr;
use slice; use slice;
use str; use str;
use sys_common::mutex::Mutex; use sys_common::mutex::{Mutex, MutexGuard};
use sys::cvt; use sys::cvt;
use sys::fd; use sys::fd;
use vec; use vec;
const TMPBUF_SZ: usize = 128; const TMPBUF_SZ: usize = 128;
// We never call `ENV_LOCK.init()`, so it is UB to attempt to
// acquire this mutex reentrantly!
static ENV_LOCK: Mutex = Mutex::new();
extern { extern {
...@@ -408,11 +405,18 @@ pub unsafe fn environ() -> *mut *const *const c_char { ...@@ -408,11 +405,18 @@ pub unsafe fn environ() -> *mut *const *const c_char {
&mut environ &mut environ
} }
pub unsafe fn env_lock() -> MutexGuard<'static> {
// We never call `ENV_LOCK.init()`, so it is UB to attempt to
// acquire this mutex reentrantly!
static ENV_LOCK: Mutex = Mutex::new();
ENV_LOCK.lock()
}
/// Returns a vector of (variable, value) byte-vector pairs for all the /// Returns a vector of (variable, value) byte-vector pairs for all the
/// environment variables of the current process. /// environment variables of the current process.
pub fn env() -> Env { pub fn env() -> Env {
unsafe { unsafe {
let _guard = ENV_LOCK.lock(); let _guard = env_lock();
let mut environ = *environ(); let mut environ = *environ();
let mut result = Vec::new(); let mut result = Vec::new();
while environ != ptr::null() && *environ != ptr::null() { while environ != ptr::null() && *environ != ptr::null() {
...@@ -448,7 +452,7 @@ pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> { ...@@ -448,7 +452,7 @@ pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
// always None as well // always None as well
let k = CString::new(k.as_bytes())?; let k = CString::new(k.as_bytes())?;
unsafe { unsafe {
let _guard = ENV_LOCK.lock(); let _guard = env_lock();
let s = libc::getenv(k.as_ptr()) as *const libc::c_char; let s = libc::getenv(k.as_ptr()) as *const libc::c_char;
let ret = if s.is_null() { let ret = if s.is_null() {
None None
...@@ -464,7 +468,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { ...@@ -464,7 +468,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
let v = CString::new(v.as_bytes())?; let v = CString::new(v.as_bytes())?;
unsafe { unsafe {
let _guard = ENV_LOCK.lock(); let _guard = env_lock();
cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(|_| ()) cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(|_| ())
} }
} }
...@@ -473,7 +477,7 @@ pub fn unsetenv(n: &OsStr) -> io::Result<()> { ...@@ -473,7 +477,7 @@ pub fn unsetenv(n: &OsStr) -> io::Result<()> {
let nbuf = CString::new(n.as_bytes())?; let nbuf = CString::new(n.as_bytes())?;
unsafe { unsafe {
let _guard = ENV_LOCK.lock(); let _guard = env_lock();
cvt(libc::unsetenv(nbuf.as_ptr())).map(|_| ()) cvt(libc::unsetenv(nbuf.as_ptr())).map(|_| ())
} }
} }
......
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
use io::{self, Error, ErrorKind}; use io::{self, Error, ErrorKind};
use libc::{self, c_int, gid_t, pid_t, uid_t}; use libc::{self, c_int, gid_t, pid_t, uid_t};
use ptr; use ptr;
use sys::cvt; use sys::cvt;
use sys::process::process_common::*; use sys::process::process_common::*;
use sys;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Command // Command
...@@ -22,8 +22,6 @@ ...@@ -22,8 +22,6 @@
impl Command { impl Command {
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool) pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
-> io::Result<(Process, StdioPipes)> { -> io::Result<(Process, StdioPipes)> {
use sys;
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX"; const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
let envp = self.capture_env(); let envp = self.capture_env();
...@@ -41,8 +39,21 @@ pub fn spawn(&mut self, default: Stdio, needs_stdin: bool) ...@@ -41,8 +39,21 @@ pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
let (input, output) = sys::pipe::anon_pipe()?; let (input, output) = sys::pipe::anon_pipe()?;
// Whatever happens after the fork is almost for sure going to touch or
// look at the environment in one way or another (PATH in `execvp` or
// accessing the `environ` pointer ourselves). Make sure no other thread
// is accessing the environment when we do the fork itself.
//
// Note that as soon as we're done with the fork there's no need to hold
// a lock any more because the parent won't do anything and the child is
// in its own process.
let result = unsafe {
let _env_lock = sys::os::env_lock();
cvt(libc::fork())?
};
let pid = unsafe { let pid = unsafe {
match cvt(libc::fork())? { match result {
0 => { 0 => {
drop(input); drop(input);
let err = self.do_exec(theirs, envp.as_ref()); let err = self.do_exec(theirs, envp.as_ref());
...@@ -114,7 +125,16 @@ pub fn exec(&mut self, default: Stdio) -> io::Error { ...@@ -114,7 +125,16 @@ pub fn exec(&mut self, default: Stdio) -> io::Error {
} }
match self.setup_io(default, true) { match self.setup_io(default, true) {
Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref()) }, Ok((_, theirs)) => {
unsafe {
// Similar to when forking, we want to ensure that access to
// the environment is synchronized, so make sure to grab the
// environment lock before we try to exec.
let _lock = sys::os::env_lock();
self.do_exec(theirs, envp.as_ref())
}
}
Err(e) => e, Err(e) => e,
} }
} }
...@@ -193,9 +213,6 @@ unsafe fn do_exec( ...@@ -193,9 +213,6 @@ unsafe fn do_exec(
if let Some(ref cwd) = *self.get_cwd() { if let Some(ref cwd) = *self.get_cwd() {
t!(cvt(libc::chdir(cwd.as_ptr()))); t!(cvt(libc::chdir(cwd.as_ptr())));
} }
if let Some(envp) = maybe_envp {
*sys::os::environ() = envp.as_ptr();
}
// emscripten has no signal support. // emscripten has no signal support.
#[cfg(not(any(target_os = "emscripten")))] #[cfg(not(any(target_os = "emscripten")))]
...@@ -231,6 +248,27 @@ unsafe fn do_exec( ...@@ -231,6 +248,27 @@ unsafe fn do_exec(
t!(callback()); t!(callback());
} }
// Although we're performing an exec here we may also return with an
// error from this function (without actually exec'ing) in which case we
// want to be sure to restore the global environment back to what it
// once was, ensuring that our temporary override, when free'd, doesn't
// corrupt our process's environment.
let mut _reset = None;
if let Some(envp) = maybe_envp {
struct Reset(*const *const libc::c_char);
impl Drop for Reset {
fn drop(&mut self) {
unsafe {
*sys::os::environ() = self.0;
}
}
}
_reset = Some(Reset(*sys::os::environ()));
*sys::os::environ() = envp.as_ptr();
}
libc::execvp(self.get_argv()[0], self.get_argv().as_ptr()); libc::execvp(self.get_argv()[0], self.get_argv().as_ptr());
io::Error::last_os_error() io::Error::last_os_error()
} }
...@@ -330,6 +368,8 @@ fn drop(&mut self) { ...@@ -330,6 +368,8 @@ fn drop(&mut self) {
libc::POSIX_SPAWN_SETSIGMASK; libc::POSIX_SPAWN_SETSIGMASK;
cvt(libc::posix_spawnattr_setflags(&mut attrs.0, flags as _))?; cvt(libc::posix_spawnattr_setflags(&mut attrs.0, flags as _))?;
// Make sure we synchronize access to the global `environ` resource
let _env_lock = sys::os::env_lock();
let envp = envp.map(|c| c.as_ptr()) let envp = envp.map(|c| c.as_ptr())
.unwrap_or_else(|| *sys::os::environ() as *const _); .unwrap_or_else(|| *sys::os::environ() as *const _);
let ret = libc::posix_spawnp( let ret = libc::posix_spawnp(
......
...@@ -48,6 +48,23 @@ fn main() { ...@@ -48,6 +48,23 @@ fn main() {
println!("passed"); println!("passed");
} }
"exec-test5" => {
env::set_var("VARIABLE", "ABC");
Command::new("definitely-not-a-real-binary").env("VARIABLE", "XYZ").exec();
assert_eq!(env::var("VARIABLE").unwrap(), "ABC");
println!("passed");
}
"exec-test6" => {
let err = Command::new("echo").arg("passed").env_clear().exec();
panic!("failed to spawn: {}", err);
}
"exec-test7" => {
let err = Command::new("echo").arg("passed").env_remove("PATH").exec();
panic!("failed to spawn: {}", err);
}
_ => panic!("unknown argument: {}", arg), _ => panic!("unknown argument: {}", arg),
} }
return return
...@@ -72,4 +89,23 @@ fn main() { ...@@ -72,4 +89,23 @@ fn main() {
assert!(output.status.success()); assert!(output.status.success());
assert!(output.stderr.is_empty()); assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n"); assert_eq!(output.stdout, b"passed\n");
let output = Command::new(&me).arg("exec-test5").output().unwrap();
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");
if cfg!(target_os = "linux") {
let output = Command::new(&me).arg("exec-test6").output().unwrap();
println!("{:?}", output);
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");
let output = Command::new(&me).arg("exec-test7").output().unwrap();
println!("{:?}", output);
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册