about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
authorJosh Triplett <josh@joshtriplett.org>2025-02-27 22:00:15 +0000
committerJosh Triplett <josh@joshtriplett.org>2025-05-20 18:25:05 +0200
commita3cf6f640828647e34afe96a626b3b4f6bbb22b1 (patch)
treed358007b1f6aa3b6d79eaac3abe480c2f0958b1a /library/std/src
parentf8e9e7636aabcbc29345d9614432d15b3c0c4ec7 (diff)
downloadrust-a3cf6f640828647e34afe96a626b3b4f6bbb22b1.tar.gz
rust-a3cf6f640828647e34afe96a626b3b4f6bbb22b1.zip
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 "/".
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/os/unix/process.rs16
-rw-r--r--library/std/src/sys/process/unix/common.rs13
-rw-r--r--library/std/src/sys/process/unix/unix.rs10
-rw-r--r--library/std/src/sys/process/unix/vxworks.rs6
4 files changed, 45 insertions, 0 deletions
diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs
index 7c3fa7d6507..27866badfbe 100644
--- a/library/std/src/os/unix/process.rs
+++ b/library/std/src/os/unix/process.rs
@@ -8,6 +8,7 @@ use cfg_if::cfg_if;
 
 use crate::ffi::OsStr;
 use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
+use crate::path::Path;
 use crate::sealed::Sealed;
 use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
 use crate::{io, process, sys};
@@ -197,6 +198,16 @@ pub trait CommandExt: Sealed {
     /// ```
     #[stable(feature = "process_set_process_group", since = "1.64.0")]
     fn process_group(&mut self, pgroup: i32) -> &mut process::Command;
+
+    /// Set the root of the child process. This calls `chroot` in the child process before executing
+    /// the command.
+    ///
+    /// This happens before changing to the directory specified with `Command::current_dir`, and
+    /// that directory will be relative to the new root. If no directory has been specified with
+    /// `Command::current_dir`, this will set the directory to `/`, to avoid leaving the current
+    /// directory outside the chroot.
+    #[unstable(feature = "process_chroot", issue = "none")]
+    fn chroot<P: AsRef<Path>>(&mut self, dir: P) -> &mut process::Command;
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -242,6 +253,11 @@ impl CommandExt for process::Command {
         self.as_inner_mut().pgroup(pgroup);
         self
     }
+
+    fn chroot<P: AsRef<Path>>(&mut self, dir: P) -> &mut process::Command {
+        self.as_inner_mut().chroot(dir.as_ref());
+        self
+    }
 }
 
 /// Unix-specific extensions to [`process::ExitStatus`] and
diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs
index a9c2510e6d4..e205a839005 100644
--- a/library/std/src/sys/process/unix/common.rs
+++ b/library/std/src/sys/process/unix/common.rs
@@ -88,6 +88,7 @@ pub struct Command {
 
     program_kind: ProgramKind,
     cwd: Option<CString>,
+    chroot: Option<CString>,
     uid: Option<uid_t>,
     gid: Option<gid_t>,
     saw_nul: bool,
@@ -182,6 +183,7 @@ impl Command {
             program_kind,
             env: Default::default(),
             cwd: None,
+            chroot: None,
             uid: None,
             gid: None,
             saw_nul,
@@ -206,6 +208,7 @@ impl Command {
             program_kind,
             env: Default::default(),
             cwd: None,
+            chroot: None,
             uid: None,
             gid: None,
             saw_nul,
@@ -254,6 +257,12 @@ impl Command {
     pub fn pgroup(&mut self, pgroup: pid_t) {
         self.pgroup = Some(pgroup);
     }
+    pub fn chroot(&mut self, dir: &Path) {
+        self.chroot = Some(os2c(dir.as_os_str(), &mut self.saw_nul));
+        if self.cwd.is_none() {
+            self.cwd(&OsStr::new("/"));
+        }
+    }
 
     #[cfg(target_os = "linux")]
     pub fn create_pidfd(&mut self, val: bool) {
@@ -326,6 +335,10 @@ impl Command {
     pub fn get_pgroup(&self) -> Option<pid_t> {
         self.pgroup
     }
+    #[allow(dead_code)]
+    pub fn get_chroot(&self) -> Option<&CStr> {
+        self.chroot.as_deref()
+    }
 
     pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
         &mut self.closures
diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs
index 1b3bd2de265..4f595ac9a1c 100644
--- a/library/std/src/sys/process/unix/unix.rs
+++ b/library/std/src/sys/process/unix/unix.rs
@@ -323,6 +323,15 @@ impl Command {
                 cvt(libc::setuid(u as uid_t))?;
             }
         }
+        if let Some(chroot) = self.get_chroot() {
+            #[cfg(not(target_os = "fuchsia"))]
+            cvt(libc::chroot(chroot.as_ptr()))?;
+            #[cfg(target_os = "fuchsia")]
+            return Err(io::const_error!(
+                io::ErrorKind::Unsupported,
+                "chroot not supported by fuchsia"
+            ));
+        }
         if let Some(cwd) = self.get_cwd() {
             cvt(libc::chdir(cwd.as_ptr()))?;
         }
@@ -447,6 +456,7 @@ impl Command {
             || (self.env_saw_path() && !self.program_is_path())
             || !self.get_closures().is_empty()
             || self.get_groups().is_some()
+            || self.get_chroot().is_some()
         {
             return Ok(None);
         }
diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs
index fab3b36ebf3..f33b4a375da 100644
--- a/library/std/src/sys/process/unix/vxworks.rs
+++ b/library/std/src/sys/process/unix/vxworks.rs
@@ -27,6 +27,12 @@ impl Command {
                 "nul byte found in provided data",
             ));
         }
+        if self.get_chroot().is_some() {
+            return Err(io::const_error!(
+                ErrorKind::Unsupported,
+                "chroot not supported by vxworks",
+            ));
+        }
         let (ours, theirs) = self.setup_io(default, needs_stdin)?;
         let mut p = Process { pid: 0, status: None };