|
8 | 8 | // option. This file may not be copied, modified, or distributed
|
9 | 9 | // except according to those terms.
|
10 | 10 |
|
| 11 | +use env; |
| 12 | +use ffi::CString; |
11 | 13 | use io::{self, Error, ErrorKind};
|
12 | 14 | use libc::{self, c_int, gid_t, pid_t, uid_t};
|
13 | 15 | use ptr;
|
@@ -39,13 +41,15 @@ impl Command {
|
39 | 41 | return Ok((ret, ours))
|
40 | 42 | }
|
41 | 43 |
|
| 44 | + let possible_paths = self.compute_possible_paths(envp.as_ref()); |
| 45 | + |
42 | 46 | let (input, output) = sys::pipe::anon_pipe()?;
|
43 | 47 |
|
44 | 48 | let pid = unsafe {
|
45 | 49 | match cvt(libc::fork())? {
|
46 | 50 | 0 => {
|
47 | 51 | drop(input);
|
48 |
| - let err = self.do_exec(theirs, envp.as_ref()); |
| 52 | + let err = self.do_exec(theirs, envp.as_ref(), possible_paths); |
49 | 53 | let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
|
50 | 54 | let bytes = [
|
51 | 55 | (errno >> 24) as u8,
|
@@ -113,12 +117,48 @@ impl Command {
|
113 | 117 | "nul byte found in provided data")
|
114 | 118 | }
|
115 | 119 |
|
| 120 | + let possible_paths = self.compute_possible_paths(envp.as_ref()); |
116 | 121 | match self.setup_io(default, true) {
|
117 |
| - Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref()) }, |
| 122 | + Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref(), possible_paths) }, |
118 | 123 | Err(e) => e,
|
119 | 124 | }
|
120 | 125 | }
|
121 | 126 |
|
| 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, |
| 146 | + } |
| 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); |
| 158 | + } |
| 159 | + return Some(possible_paths); |
| 160 | + } |
| 161 | + |
122 | 162 | // And at this point we've reached a special time in the life of the
|
123 | 163 | // child. The child must now be considered hamstrung and unable to
|
124 | 164 | // do anything other than syscalls really. Consider the following
|
@@ -152,7 +192,8 @@ impl Command {
|
152 | 192 | unsafe fn do_exec(
|
153 | 193 | &mut self,
|
154 | 194 | stdio: ChildPipes,
|
155 |
| - maybe_envp: Option<&CStringArray> |
| 195 | + maybe_envp: Option<&CStringArray>, |
| 196 | + maybe_possible_paths: Option<Vec<CString>>, |
156 | 197 | ) -> io::Error {
|
157 | 198 | use sys::{self, cvt_r};
|
158 | 199 |
|
@@ -193,9 +234,6 @@ impl Command {
|
193 | 234 | if let Some(ref cwd) = *self.get_cwd() {
|
194 | 235 | t!(cvt(libc::chdir(cwd.as_ptr())));
|
195 | 236 | }
|
196 |
| - if let Some(envp) = maybe_envp { |
197 |
| - *sys::os::environ() = envp.as_ptr(); |
198 |
| - } |
199 | 237 |
|
200 | 238 | // emscripten has no signal support.
|
201 | 239 | #[cfg(not(any(target_os = "emscripten")))]
|
@@ -231,8 +269,53 @@ impl Command {
|
231 | 269 | t!(callback());
|
232 | 270 | }
|
233 | 271 |
|
234 |
| - libc::execvp(self.get_argv()[0], self.get_argv().as_ptr()); |
235 |
| - io::Error::last_os_error() |
| 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 | + } |
| 303 | + } |
| 304 | + } |
| 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() |
| 317 | + } |
| 318 | + } |
236 | 319 | }
|
237 | 320 |
|
238 | 321 | #[cfg(not(any(target_os = "macos", target_os = "freebsd",
|
|
0 commit comments