Skip to content

Commit 7d3b9b1

Browse files
committed
Auto merge of #55939 - alexcrichton:path-regression-again, r=sfackler
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
2 parents 6f93e93 + 4032b7a commit 7d3b9b1

File tree

4 files changed

+83
-106
lines changed

4 files changed

+83
-106
lines changed

src/libstd/sys/unix/os.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@ use path::{self, PathBuf};
2727
use ptr;
2828
use slice;
2929
use str;
30-
use sys_common::mutex::Mutex;
30+
use sys_common::mutex::{Mutex, MutexGuard};
3131
use sys::cvt;
3232
use sys::fd;
3333
use vec;
3434

3535
const TMPBUF_SZ: usize = 128;
36-
// We never call `ENV_LOCK.init()`, so it is UB to attempt to
37-
// acquire this mutex reentrantly!
38-
static ENV_LOCK: Mutex = Mutex::new();
3936

4037

4138
extern {
@@ -408,11 +405,18 @@ pub unsafe fn environ() -> *mut *const *const c_char {
408405
&mut environ
409406
}
410407

408+
pub unsafe fn env_lock() -> MutexGuard<'static> {
409+
// We never call `ENV_LOCK.init()`, so it is UB to attempt to
410+
// acquire this mutex reentrantly!
411+
static ENV_LOCK: Mutex = Mutex::new();
412+
ENV_LOCK.lock()
413+
}
414+
411415
/// Returns a vector of (variable, value) byte-vector pairs for all the
412416
/// environment variables of the current process.
413417
pub fn env() -> Env {
414418
unsafe {
415-
let _guard = ENV_LOCK.lock();
419+
let _guard = env_lock();
416420
let mut environ = *environ();
417421
let mut result = Vec::new();
418422
while environ != ptr::null() && *environ != ptr::null() {
@@ -448,7 +452,7 @@ pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
448452
// always None as well
449453
let k = CString::new(k.as_bytes())?;
450454
unsafe {
451-
let _guard = ENV_LOCK.lock();
455+
let _guard = env_lock();
452456
let s = libc::getenv(k.as_ptr()) as *const libc::c_char;
453457
let ret = if s.is_null() {
454458
None
@@ -464,7 +468,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
464468
let v = CString::new(v.as_bytes())?;
465469

466470
unsafe {
467-
let _guard = ENV_LOCK.lock();
471+
let _guard = env_lock();
468472
cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(|_| ())
469473
}
470474
}
@@ -473,7 +477,7 @@ pub fn unsetenv(n: &OsStr) -> io::Result<()> {
473477
let nbuf = CString::new(n.as_bytes())?;
474478

475479
unsafe {
476-
let _guard = ENV_LOCK.lock();
480+
let _guard = env_lock();
477481
cvt(libc::unsetenv(nbuf.as_ptr())).map(|_| ())
478482
}
479483
}

src/libstd/sys/unix/process/process_common.rs

-8
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ impl Command {
141141
pub fn get_argv(&self) -> &Vec<*const c_char> {
142142
&self.argv.0
143143
}
144-
#[cfg(not(target_os = "fuchsia"))]
145-
pub fn get_program(&self) -> &CString {
146-
return &self.program;
147-
}
148144

149145
#[allow(dead_code)]
150146
pub fn get_cwd(&self) -> &Option<CString> {
@@ -248,10 +244,6 @@ impl CStringArray {
248244
pub fn as_ptr(&self) -> *const *const c_char {
249245
self.ptrs.as_ptr()
250246
}
251-
#[cfg(not(target_os = "fuchsia"))]
252-
pub fn get_items(&self) -> &[CString] {
253-
return &self.items;
254-
}
255247
}
256248

257249
fn construct_envp(env: BTreeMap<DefaultEnvKey, OsString>, saw_nul: &mut bool) -> CStringArray {

src/libstd/sys/unix/process/process_unix.rs

+47-90
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use env;
12-
use ffi::CString;
1311
use io::{self, Error, ErrorKind};
1412
use libc::{self, c_int, gid_t, pid_t, uid_t};
1513
use ptr;
16-
1714
use sys::cvt;
1815
use sys::process::process_common::*;
16+
use sys;
1917

2018
////////////////////////////////////////////////////////////////////////////////
2119
// Command
@@ -24,8 +22,6 @@ use sys::process::process_common::*;
2422
impl Command {
2523
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
2624
-> io::Result<(Process, StdioPipes)> {
27-
use sys;
28-
2925
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
3026

3127
let envp = self.capture_env();
@@ -41,15 +37,26 @@ impl Command {
4137
return Ok((ret, ours))
4238
}
4339

44-
let possible_paths = self.compute_possible_paths(envp.as_ref());
45-
4640
let (input, output) = sys::pipe::anon_pipe()?;
4741

42+
// Whatever happens after the fork is almost for sure going to touch or
43+
// look at the environment in one way or another (PATH in `execvp` or
44+
// accessing the `environ` pointer ourselves). Make sure no other thread
45+
// is accessing the environment when we do the fork itself.
46+
//
47+
// Note that as soon as we're done with the fork there's no need to hold
48+
// a lock any more because the parent won't do anything and the child is
49+
// in its own process.
50+
let result = unsafe {
51+
let _env_lock = sys::os::env_lock();
52+
cvt(libc::fork())?
53+
};
54+
4855
let pid = unsafe {
49-
match cvt(libc::fork())? {
56+
match result {
5057
0 => {
5158
drop(input);
52-
let err = self.do_exec(theirs, envp.as_ref(), possible_paths);
59+
let err = self.do_exec(theirs, envp.as_ref());
5360
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
5461
let bytes = [
5562
(errno >> 24) as u8,
@@ -117,46 +124,19 @@ impl Command {
117124
"nul byte found in provided data")
118125
}
119126

120-
let possible_paths = self.compute_possible_paths(envp.as_ref());
121127
match self.setup_io(default, true) {
122-
Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref(), possible_paths) },
123-
Err(e) => e,
124-
}
125-
}
128+
Ok((_, theirs)) => {
129+
unsafe {
130+
// Similar to when forking, we want to ensure that access to
131+
// the environment is synchronized, so make sure to grab the
132+
// environment lock before we try to exec.
133+
let _lock = sys::os::env_lock();
126134

127-
fn compute_possible_paths(&self, maybe_envp: Option<&CStringArray>) -> Option<Vec<CString>> {
128-
let program = self.get_program().as_bytes();
129-
if program.contains(&b'/') {
130-
return None;
131-
}
132-
// Outside the match so we can borrow it for the lifetime of the function.
133-
let parent_path = env::var("PATH").ok();
134-
let paths = match maybe_envp {
135-
Some(envp) => {
136-
match envp.get_items().iter().find(|var| var.as_bytes().starts_with(b"PATH=")) {
137-
Some(p) => &p.as_bytes()[5..],
138-
None => return None,
139-
}
140-
},
141-
// maybe_envp is None if the process isn't changing the parent's env at all.
142-
None => {
143-
match parent_path.as_ref() {
144-
Some(p) => p.as_bytes(),
145-
None => return None,
135+
self.do_exec(theirs, envp.as_ref())
146136
}
147-
},
148-
};
149-
150-
let mut possible_paths = vec![];
151-
for path in paths.split(|p| *p == b':') {
152-
let mut binary_path = Vec::with_capacity(program.len() + path.len() + 1);
153-
binary_path.extend_from_slice(path);
154-
binary_path.push(b'/');
155-
binary_path.extend_from_slice(program);
156-
let c_binary_path = CString::new(binary_path).unwrap();
157-
possible_paths.push(c_binary_path);
137+
}
138+
Err(e) => e,
158139
}
159-
return Some(possible_paths);
160140
}
161141

162142
// And at this point we've reached a special time in the life of the
@@ -192,8 +172,7 @@ impl Command {
192172
unsafe fn do_exec(
193173
&mut self,
194174
stdio: ChildPipes,
195-
maybe_envp: Option<&CStringArray>,
196-
maybe_possible_paths: Option<Vec<CString>>,
175+
maybe_envp: Option<&CStringArray>
197176
) -> io::Error {
198177
use sys::{self, cvt_r};
199178

@@ -269,53 +248,29 @@ impl Command {
269248
t!(callback());
270249
}
271250

272-
// If the program isn't an absolute path, and our environment contains a PATH var, then we
273-
// implement the PATH traversal ourselves so that it honors the child's PATH instead of the
274-
// parent's. This mirrors the logic that exists in glibc's execvpe, except using the
275-
// child's env to fetch PATH.
276-
match maybe_possible_paths {
277-
Some(possible_paths) => {
278-
let mut pending_error = None;
279-
for path in possible_paths {
280-
libc::execve(
281-
path.as_ptr(),
282-
self.get_argv().as_ptr(),
283-
maybe_envp.map(|envp| envp.as_ptr()).unwrap_or_else(|| *sys::os::environ())
284-
);
285-
let err = io::Error::last_os_error();
286-
match err.kind() {
287-
io::ErrorKind::PermissionDenied => {
288-
// If we saw a PermissionDenied, and none of the other entries in
289-
// $PATH are successful, then we'll return the first EACCESS we see.
290-
if pending_error.is_none() {
291-
pending_error = Some(err);
292-
}
293-
},
294-
// Errors which indicate we failed to find a file are ignored and we try
295-
// the next entry in the path.
296-
io::ErrorKind::NotFound | io::ErrorKind::TimedOut => {
297-
continue
298-
},
299-
// Any other error means we found a file and couldn't execute it.
300-
_ => {
301-
return err;
302-
}
251+
// Although we're performing an exec here we may also return with an
252+
// error from this function (without actually exec'ing) in which case we
253+
// want to be sure to restore the global environment back to what it
254+
// once was, ensuring that our temporary override, when free'd, doesn't
255+
// corrupt our process's environment.
256+
let mut _reset = None;
257+
if let Some(envp) = maybe_envp {
258+
struct Reset(*const *const libc::c_char);
259+
260+
impl Drop for Reset {
261+
fn drop(&mut self) {
262+
unsafe {
263+
*sys::os::environ() = self.0;
303264
}
304265
}
305-
if let Some(err) = pending_error {
306-
return err;
307-
}
308-
return io::Error::from_raw_os_error(libc::ENOENT);
309-
},
310-
_ => {
311-
libc::execve(
312-
self.get_argv()[0],
313-
self.get_argv().as_ptr(),
314-
maybe_envp.map(|envp| envp.as_ptr()).unwrap_or_else(|| *sys::os::environ())
315-
);
316-
return io::Error::last_os_error()
317266
}
267+
268+
_reset = Some(Reset(*sys::os::environ()));
269+
*sys::os::environ() = envp.as_ptr();
318270
}
271+
272+
libc::execvp(self.get_argv()[0], self.get_argv().as_ptr());
273+
io::Error::last_os_error()
319274
}
320275

321276
#[cfg(not(any(target_os = "macos", target_os = "freebsd",
@@ -413,6 +368,8 @@ impl Command {
413368
libc::POSIX_SPAWN_SETSIGMASK;
414369
cvt(libc::posix_spawnattr_setflags(&mut attrs.0, flags as _))?;
415370

371+
// Make sure we synchronize access to the global `environ` resource
372+
let _env_lock = sys::os::env_lock();
416373
let envp = envp.map(|c| c.as_ptr())
417374
.unwrap_or_else(|| *sys::os::environ() as *const _);
418375
let ret = libc::posix_spawnp(

src/test/run-pass/command-exec.rs

+24
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ fn main() {
5555
println!("passed");
5656
}
5757

58+
"exec-test6" => {
59+
let err = Command::new("echo").arg("passed").env_clear().exec();
60+
panic!("failed to spawn: {}", err);
61+
}
62+
63+
"exec-test7" => {
64+
let err = Command::new("echo").arg("passed").env_remove("PATH").exec();
65+
panic!("failed to spawn: {}", err);
66+
}
67+
5868
_ => panic!("unknown argument: {}", arg),
5969
}
6070
return
@@ -84,4 +94,18 @@ fn main() {
8494
assert!(output.status.success());
8595
assert!(output.stderr.is_empty());
8696
assert_eq!(output.stdout, b"passed\n");
97+
98+
if cfg!(target_os = "linux") {
99+
let output = Command::new(&me).arg("exec-test6").output().unwrap();
100+
println!("{:?}", output);
101+
assert!(output.status.success());
102+
assert!(output.stderr.is_empty());
103+
assert_eq!(output.stdout, b"passed\n");
104+
105+
let output = Command::new(&me).arg("exec-test7").output().unwrap();
106+
println!("{:?}", output);
107+
assert!(output.status.success());
108+
assert!(output.stderr.is_empty());
109+
assert_eq!(output.stdout, b"passed\n");
110+
}
87111
}

0 commit comments

Comments
 (0)