Skip to content

Commit 3d237f7

Browse files
committed
Add std::os::unix::process::CommandExt::chroot to safely chroot a child process
This adds a `chroot` method to the `CommandExt` extension trait for the `Command` builder, to set a directory to chroot into. This will chroot the child process into that directory right before calling chdir for the `Command`'s working directory. To avoid allowing a process to have a working directory outside of the chroot, if the `Command` does not yet have a working directory set, `chroot` will set its working directory to "/".
1 parent c51b9b6 commit 3d237f7

File tree

4 files changed

+45
-0
lines changed

4 files changed

+45
-0
lines changed

library/std/src/os/unix/process.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use cfg_if::cfg_if;
88

99
use crate::ffi::OsStr;
1010
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
11+
use crate::path::Path;
1112
use crate::sealed::Sealed;
1213
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
1314
use crate::{io, process, sys};
@@ -197,6 +198,16 @@ pub trait CommandExt: Sealed {
197198
/// ```
198199
#[stable(feature = "process_set_process_group", since = "1.64.0")]
199200
fn process_group(&mut self, pgroup: i32) -> &mut process::Command;
201+
202+
/// Set the root of the child process. This calls `chroot` in the child process before executing
203+
/// the command.
204+
///
205+
/// This happens before changing to the directory specified with `Command::current_dir`, and
206+
/// that directory will be relative to the new root. If no directory has been specified with
207+
/// `Command::current_dir`, this will set the directory to `/`, to avoid leaving the current
208+
/// directory outside the chroot.
209+
#[unstable(feature = "process_chroot", issue = "none")]
210+
fn chroot<P: AsRef<Path>>(&mut self, dir: P) -> &mut process::Command;
200211
}
201212

202213
#[stable(feature = "rust1", since = "1.0.0")]
@@ -242,6 +253,11 @@ impl CommandExt for process::Command {
242253
self.as_inner_mut().pgroup(pgroup);
243254
self
244255
}
256+
257+
fn chroot<P: AsRef<Path>>(&mut self, dir: P) -> &mut process::Command {
258+
self.as_inner_mut().chroot(dir.as_ref());
259+
self
260+
}
245261
}
246262

247263
/// Unix-specific extensions to [`process::ExitStatus`] and

library/std/src/sys/pal/unix/process/process_common.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub struct Command {
9393

9494
program_kind: ProgramKind,
9595
cwd: Option<CString>,
96+
chroot: Option<CString>,
9697
uid: Option<uid_t>,
9798
gid: Option<gid_t>,
9899
saw_nul: bool,
@@ -187,6 +188,7 @@ impl Command {
187188
program_kind,
188189
env: Default::default(),
189190
cwd: None,
191+
chroot: None,
190192
uid: None,
191193
gid: None,
192194
saw_nul,
@@ -211,6 +213,7 @@ impl Command {
211213
program_kind,
212214
env: Default::default(),
213215
cwd: None,
216+
chroot: None,
214217
uid: None,
215218
gid: None,
216219
saw_nul,
@@ -259,6 +262,12 @@ impl Command {
259262
pub fn pgroup(&mut self, pgroup: pid_t) {
260263
self.pgroup = Some(pgroup);
261264
}
265+
pub fn chroot(&mut self, dir: &Path) {
266+
self.chroot = Some(os2c(dir.as_os_str(), &mut self.saw_nul));
267+
if self.cwd.is_none() {
268+
self.cwd(&OsStr::new("/"));
269+
}
270+
}
262271

263272
#[cfg(target_os = "linux")]
264273
pub fn create_pidfd(&mut self, val: bool) {
@@ -331,6 +340,10 @@ impl Command {
331340
pub fn get_pgroup(&self) -> Option<pid_t> {
332341
self.pgroup
333342
}
343+
#[allow(dead_code)]
344+
pub fn get_chroot(&self) -> Option<&CStr> {
345+
self.chroot.as_deref()
346+
}
334347

335348
pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
336349
&mut self.closures

library/std/src/sys/pal/unix/process/process_unix.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,15 @@ impl Command {
328328
cvt(libc::setuid(u as uid_t))?;
329329
}
330330
}
331+
if let Some(chroot) = self.get_chroot() {
332+
#[cfg(not(target_os = "fuchsia"))]
333+
cvt(libc::chroot(chroot.as_ptr()))?;
334+
#[cfg(target_os = "fuchsia")]
335+
return Err(io::const_error!(
336+
io::ErrorKind::Unsupported,
337+
"chroot not supported by fuchsia"
338+
));
339+
}
331340
if let Some(cwd) = self.get_cwd() {
332341
cvt(libc::chdir(cwd.as_ptr()))?;
333342
}
@@ -448,6 +457,7 @@ impl Command {
448457
|| (self.env_saw_path() && !self.program_is_path())
449458
|| !self.get_closures().is_empty()
450459
|| self.get_groups().is_some()
460+
|| self.get_chroot().is_some()
451461
{
452462
return Ok(None);
453463
}

library/std/src/sys/pal/unix/process/process_vxworks.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ impl Command {
2727
"nul byte found in provided data",
2828
));
2929
}
30+
if self.get_chroot().is_some() {
31+
return Err(io::const_error!(
32+
ErrorKind::Unsupported,
33+
"chroot not supported by vxworks",
34+
));
35+
}
3036
let (ours, theirs) = self.setup_io(default, needs_stdin)?;
3137
let mut p = Process { pid: 0, status: None };
3238

0 commit comments

Comments
 (0)