### Command builder example with arguments Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html An example demonstrating how to chain builder methods to configure a command with arguments. ```rust use birdcage::process::Command; Command::new("sh").arg("-c").arg("echo hello"); ``` -------------------------------- ### Birdcage Sandbox Example Source: https://docs.rs/birdcage/0.8.1/birdcage This example demonstrates how to use the birdcage crate to create a sandbox, add exceptions for allowed operations, and execute a command within the sandbox. ```APIDOC ## Example Usage of Birdcage Sandbox ### Description This example shows how to initialize a Birdcage sandbox, add exceptions to allow specific file operations or command executions, and then spawn a command within the sandbox to observe its behavior. ### Method N/A (Illustrative Example) ### Endpoint N/A (Rust Crate Usage) ### Parameters N/A ### Request Example ```rust use std::fs; use birdcage::process::Command; use birdcage::{Birdcage, Exception, Sandbox}; // Reads without sandbox work. fs::read_to_string("./Cargo.toml").unwrap(); // Allow access to our test executable. let mut sandbox = Birdcage::new(); sandbox.add_exception(Exception::ExecuteAndRead("/bin/cat".into())).unwrap(); let _ = sandbox.add_exception(Exception::ExecuteAndRead("/lib64".into())); let _ = sandbox.add_exception(Exception::ExecuteAndRead("/lib".into())); // Initialize the sandbox; by default everything is prohibited. let mut command = Command::new("/bin/cat"); command.arg("./Cargo.toml"); let mut child = sandbox.spawn(command).unwrap(); // Reads with sandbox should fail. let status = child.wait().unwrap(); assert!(!status.success()); ``` ### Response #### Success Response (200) N/A (Illustrative Example) #### Response Example N/A (Illustrative Example) ``` -------------------------------- ### Setup Mount Namespace Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/namespaces.rs.html Isolates filesystem access within a new mount namespace. Denies access to paths not explicitly allowed by bind mounts. Requires setup of a new root directory and tmpfs mount. ```rust pub(crate) fn setup_mount_namespace(exceptions: PathExceptions) -> io::Result<()> { // Get target paths for new and old root. let new_root = PathBuf::from(NEW_ROOT); // Ensure new root is available as an empty directory. if !new_root.exists() { fs::create_dir_all(&new_root)?; } // Create C-friendly versions for our paths. let new_root_c = CString::new(new_root.as_os_str().as_bytes()).unwrap(); // Create tmpfs mount for the new root, allowing pivot and ensuring directories // aren't created outside the sandbox. mount_tmpfs(&new_root_c)?; // Sort bind mounts by shortest length, to create parents before their children. let mut bind_mounts: Vec<_> = exceptions.bind_mounts.into_iter().collect(); bind_mounts.sort_unstable_by(|(a_path, a_flags), (b_path, b_flags)| { match a_path.components().count().cmp(&b_path.components().count()) { Ordering::Equal => (a_path, a_flags).cmp(&(b_path, b_flags)), ord => ord, } }); // Bind mount all allowed directories. for (path, flags) in bind_mounts { let src_c = CString::new(path.as_os_str().as_bytes()).unwrap(); // Get bind mount destination. let unrooted_path = path.strip_prefix("/").unwrap(); let dst = new_root.join(unrooted_path); let dst_c = CString::new(dst.as_os_str().as_bytes()).unwrap(); // Create mount target. if let Err(err) = copy_tree(&path, &new_root) { log::error!("skipping birdcage exception {path:?}: {err}"); continue; } // Bind path with full permissions. bind_mount(&src_c, &dst_c)?; // Remount to update permissions. update_mount_flags(&dst_c, flags | MountAttrFlags::NOSUID)?; } // Ensure original symlink paths are available. create_symlinks(&new_root, exceptions.symlinks)?; // Bind mount old procfs. let old_proc_c = CString::new("/proc").unwrap(); let new_proc = new_root.join("proc"); let new_proc_c = CString::new(new_proc.as_os_str().as_bytes()).unwrap(); fs::create_dir_all(&new_proc)?; bind_mount(&old_proc_c, &new_proc_c)?; // Pivot root to `new_root`, placing the old root at the same location. pivot_root(&new_root_c, &new_root_c)?; // Remove old root mounted at /, leaving only the new root at the same location. let root_c = CString::new("/").unwrap(); umount(&root_c)?; // Prevent child mount namespaces from accessing this namespace's mounts. deny_mount_propagation()?; Ok(()) } ``` -------------------------------- ### Get the program path from Command Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Retrieves the program path that was initially provided to Command::new. ```rust use birdcage::process::Command; let cmd = Command::new("echo"); assert_eq!(cmd.get_program(), "echo"); ``` -------------------------------- ### Apply Seccomp Filters Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/seccomp.rs.html The SyscallFilter struct provides an apply method to install seccomp filters, including a whitelist of allowed syscalls and specific restrictions on clone and clone3. ```rust 1//! Seccomp system call filtering. 2 3use std::collections::BTreeMap; 4 5use seccompiler::{ 6 BpfProgram, SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompFilter, 7 SeccompRule, TargetArch, 8}; 9 10use crate::Result; 11 12#[cfg(target_arch = "x86_64")] 13const ARCH: TargetArch = TargetArch::x86_64; 14#[cfg(target_arch = "aarch64")] 15const ARCH: TargetArch = TargetArch::aarch64; 16 17/// Bitmask for the clone syscall seccomp filter. 18/// 19/// A 1 in the bitmask means system calls with this flag set will be denied. 20/// 21/// Filtered flags: 22/// - CLONE_NEWNS = 0x00020000 23/// - CLONE_NEWCGROUP = 0x02000000 24/// - CLONE_NEWUTS = 0x04000000 25/// - CLONE_NEWIPC = 0x08000000 26/// - CLONE_NEWUSER = 0x10000000 27/// - CLONE_NEWPID = 0x20000000 28/// - CLONE_NEWNET = 0x40000000 29const CLONE_NAMESPACE_FILTER: u32 = 0b01111110000000100000000000000000; 30 31/// Seccomp system call filter. 32/// 33/// This filter is aimed at restricting system calls which shouldn't be 34/// executable by an untrusted client. 35#[derive(Default)] 36pub struct SyscallFilter; 37 38impl SyscallFilter { 39 /// Apply the seccomp filter. 40 pub fn apply() -> Result<()> { 41 let mut rules = BTreeMap::new(); 42 43 // Add exceptions for allowed syscalls. 44 for syscall in SYSCALL_WHITELIST { 45 rules.insert(*syscall, Vec::new()); 46 } 47 48 // Add exception for the `clone` syscall. 49 let allow_clone = SeccompCondition::new( 50 0, 51 SeccompCmpArgLen::Qword, 52 SeccompCmpOp::MaskedEq(CLONE_NAMESPACE_FILTER as u64), 53 0, 54 )?; 55 let clone_rule = SeccompRule::new(vec![allow_clone])?; 56 rules.insert(libc::SYS_clone, vec![clone_rule]); 57 58 // Apply seccomp filter. 59 let filter = SeccompFilter::new( 60 rules, 61 // Action performed if no rule matches. 62 SeccompAction::Errno(libc::EACCES as u32), 63 // Action performed if any rule matches. 64 SeccompAction::Allow, 65 ARCH, 66 )?; 67 let program: BpfProgram = filter.try_into()?; 68 seccompiler::apply_filter(&program)?; 69 70 // Change `clone3` syscall error to "not implemented", to force `clone` usage. 71 let mut rules = BTreeMap::new(); 72 rules.insert(libc::SYS_clone3, Vec::new()); 73 let filter = SeccompFilter::new( 74 rules, 75 // Action performed if no rule matches. 76 SeccompAction::Allow, 77 // Action performed if any rule matches. 78 SeccompAction::Errno(libc::ENOSYS as u32), 79 ARCH, 80 )?; 81 let program: BpfProgram = filter.try_into()?; 82 seccompiler::apply_filter(&program)?; 83 84 Ok(()) 85 } 86} ``` -------------------------------- ### Get current process thread count Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Reads and parses /proc/self/status to determine the number of threads currently used by the process. ```rust fn thread_count() -> io::Result { // Read process status from procfs. let status = fs::read_to_string("/proc/self/status")?; // Parse procfs output. let (_, threads_start) = status.split_once("Threads:").ok_or_else(|| { io::Error::new(io::ErrorKind::InvalidData, "/proc/self/status missing \"Threads:\"") })?; let thread_count = threads_start.split_whitespace().next().ok_or_else(|| { io::Error::new(io::ErrorKind::InvalidData, "/proc/self/status output malformed") })?; // Convert to number. let thread_count = thread_count .parse::() .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; Ok(thread_count) } ``` -------------------------------- ### Initialize Sandbox Process Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Uses libc::clone to spawn the sandbox entry point and manages the process initialization arguments. ```rust let init_arg_raw = Box::into_raw(Box::new(init_arg)); let init_pid = libc::clone(sandbox_init, stack_top, flags | libc::SIGCHLD, init_arg_raw as _); if init_pid == -1 { Err(IoError::last_os_error().into()) } else { let mut init_arg = Box::from_raw(init_arg_raw); init_arg.pid = init_pid; Ok(*init_arg) } } } ``` -------------------------------- ### Sandbox::spawn() Method Source: https://docs.rs/birdcage/0.8.1/birdcage/trait.Sandbox.html Sets up the sandbox and spawns a new process. This method sets up the sandbox in the current process before launching the sandboxee. The calling process is not fully sandboxed. Sandboxing may fail if the calling process is not single-threaded, and partial restrictions may affect the calling process after failure. ```rust fn spawn(self, sandboxee: Command) -> Result ``` -------------------------------- ### Initialize a Command Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Create a new Command instance to define the program to be executed. ```rust use birdcage::process::Command; Command::new("sh").arg("-c").arg("echo hello"); ``` ```rust use birdcage::process::Command; Command::new("sh"); ``` -------------------------------- ### Get Child Process ID Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Child.html Retrieves the OS-assigned process identifier for the child. ```rust use birdcage::process::Command; use birdcage::{Birdcage, Sandbox}; let command = Command::new("ls"); if let Ok(child) = Birdcage::new().spawn(command) { println!("Child's ID is {}", child.id()); } else { println!("ls command didn't start"); } ``` -------------------------------- ### Initialize and use a Birdcage sandbox Source: https://docs.rs/birdcage/0.8.1/birdcage/index.html Demonstrates creating a sandbox, adding exceptions for specific paths, and spawning a process that is restricted by the sandbox. ```rust use std::fs; use birdcage::process::Command; use birdcage::{Birdcage, Exception, Sandbox}; // Reads without sandbox work. fs::read_to_string("./Cargo.toml").unwrap(); // Allow access to our test executable. let mut sandbox = Birdcage::new(); sandbox.add_exception(Exception::ExecuteAndRead("/bin/cat".into())).unwrap(); let _ = sandbox.add_exception(Exception::ExecuteAndRead("/lib64".into())); let _ = sandbox.add_exception(Exception::ExecuteAndRead("/lib".into())); // Initialize the sandbox; by default everything is prohibited. let mut command = Command::new("/bin/cat"); command.arg("./Cargo.toml"); let mut child = sandbox.spawn(command).unwrap(); // Reads with sandbox should fail. let status = child.wait().unwrap(); assert!(!status.success()); ``` -------------------------------- ### Get Stdio Handles from File Descriptor Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html A helper method within `ChildReader` to retrieve the mutable `ChildStdout` or `ChildStderr` handle and its corresponding buffer based on a given file descriptor. ```rust /// Get the stdio handles corresponding to a FD. fn stdio_from_fd(&mut self, fd: RawFd) -> (&mut ChildStdout, &mut Vec) { match (self.stdout.as_mut(), self.stderr.as_mut()) { (Some(stdout), _) if stdout.fd.as_raw_fd() == fd => (stdout, &mut self.stdout_buffer), (_, Some(stderr)) if stderr.fd.as_raw_fd() == fd => (stderr, &mut self.stderr_buffer), _ => unreachable!(), } } ``` -------------------------------- ### Configure Stdio Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Set up standard input, output, or error streams for the child process. ```rust use birdcage::process::{Command, Stdio}; ``` -------------------------------- ### Get the exit code of a process Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.ExitStatus.html Use `code` to retrieve the exit code of a process. Returns `None` if the process was terminated by a signal. On Unix, the exit status is truncated to 8 bits. ```rust use std::process::Command; let status = Command::new("mkdir") .arg("projects") .status() .expect("failed to execute mkdir"); match status.code() { Some(code) => println!("Exited with status code: {code}"), None => println!("Process terminated by signal") } ``` -------------------------------- ### Command::new Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Constructs a new Command instance for a specified program. ```APIDOC ## Command::new ### Description Constructs a new `Command` for launching the program at the specified path. ### Parameters - **program** (AsRef) - Required - The path to the program to be executed. ### Request Example ```rust use birdcage::process::Command; Command::new("sh"); ``` ``` -------------------------------- ### Sandbox Entry Point Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html The entry point for the process acting as the init system within the PID namespace. ```rust extern "C" fn sandbox_init(arg: *mut libc::c_void) -> libc::c_int { let init_arg: Box = unsafe { Box::from_raw(arg as _) }; match sandbox_init_inner(*init_arg) { Ok(exit_code) => exit_code, Err(err) => { eprintln!("sandboxing failure: {err}"); 1 }, } } ``` -------------------------------- ### Retrieve Command Program Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Returns the path to the program configured in the Command. ```rust use birdcage::process::Command; let cmd = Command::new("echo"); assert_eq!(cmd.get_program(), "echo"); ``` -------------------------------- ### Define Process Initialization Arguments Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Structure holding configuration for the sandboxed process, including file descriptors and namespace identifiers. ```rust struct ProcessInitArg { path_exceptions: PathExceptions, sandboxee: Command, parent_euid: Uid, parent_egid: Gid, // FDs used by the child process. stdin_rx: Option, stdout_tx: Option, stderr_tx: Option, exit_signal_tx: OwnedFd, // FDs passed to the child for closing them. stdin_tx: Option, stdout_rx: Option, stderr_rx: Option, exit_signal_rx: OwnedFd, pid: i32, } ``` ```rust impl ProcessInitArg { fn new( sandbox: LinuxSandbox, sandboxee: Command, exit_signal: (OwnedFd, OwnedFd), stdin: (Option, Option), stdout: (Option, Option), stderr: (Option, Option), ) -> Self { // Get EUID/EGID outside of the namespaces. let parent_euid = rustix::process::geteuid(); let parent_egid = rustix::process::getegid(); Self { parent_euid, parent_egid, sandboxee, ``` -------------------------------- ### Command Configuration Methods Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Methods for configuring standard input, output, and error handles for a child process. ```APIDOC ## Command::stdin ### Description Configures the child process's standard input (stdin) handle. ### Parameters - **cfg** (T: Into) - Required - The configuration for the stdin handle. ## Command::stdout ### Description Configures the child process's standard output (stdout) handle. Defaults to inherit. ### Parameters - **cfg** (T: Into) - Required - The configuration for the stdout handle. ## Command::stderr ### Description Configures the child process's standard error (stderr) handle. Defaults to inherit. ### Parameters - **cfg** (T: Into) - Required - The configuration for the stderr handle. ``` -------------------------------- ### Sandbox::new() Method Source: https://docs.rs/birdcage/0.8.1/birdcage/trait.Sandbox.html Initializes a new sandboxing environment. This method is part of the Sandbox trait. ```rust fn new() -> Self; ``` -------------------------------- ### Configure Sandbox Environment Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Handles file descriptor cleanup, namespace mapping, and process spawning within the sandbox. ```rust fn sandbox_init_inner(mut init_arg: ProcessInitArg) -> io::Result { // Close all unused FDs. init_arg.stdin_tx.take(); init_arg.stdout_rx.take(); init_arg.stderr_rx.take(); drop(init_arg.exit_signal_rx); // Hook up stdio to parent process. if let Some(stdin_pipe) = &mut init_arg.stdin_rx { rustix::stdio::dup2_stdin(stdin_pipe)?; } if let Some(stdout_pipe) = &init_arg.stdout_tx { rustix::stdio::dup2_stdout(stdout_pipe)?; } if let Some(stderr_pipe) = &init_arg.stderr_tx { rustix::stdio::dup2_stderr(stderr_pipe)?; } // Map root UID and GID. namespaces::map_ids(init_arg.parent_euid.as_raw(), init_arg.parent_egid.as_raw(), 0, 0)?; // Isolate filesystem using a mount namespace. namespaces::setup_mount_namespace(init_arg.path_exceptions)?; // Create new procfs directory. let new_proc_c = CString::new("/proc")?; namespaces::mount_proc(&new_proc_c)?; // Drop root user mapping. namespaces::create_user_namespace( init_arg.parent_euid.as_raw(), init_arg.parent_egid.as_raw(), Namespaces::empty(), )?; // Setup system call filters. SyscallFilter::apply().map_err(|err| IoError::new(IoErrorKind::Other, err))?; // Block suid/sgid. // // This is also blocked by our bind mount's MS_NOSUID flag, so we're just // doubling-down here. rustix::thread::set_no_new_privs(true)?; // Spawn sandboxed process. let mut std_command = std::process::Command::from(init_arg.sandboxee); std_command.stdin(std::process::Stdio::inherit()); std_command.stdout(std::process::Stdio::inherit()); std_command.stderr(std::process::Stdio::inherit()); let child = std_command.spawn()?; // Reap zombie children. let child_pid = Pid::from_raw(child.id() as i32); loop { // Wait for any child to exit. match rustix::process::wait(WaitOptions::empty())? { Some((pid, status)) if Some(pid) == child_pid => match status.terminating_signal() { Some(signal) => { // Send exit signal to parent. rustix::io::write(init_arg.exit_signal_tx, &signal.to_le_bytes())?; return Ok(1); }, None => return Ok(status.exit_status().unwrap_or(1) as i32), }, Some(_) => (), None => unreachable!("none without nohang"), } } } ``` -------------------------------- ### Create Bind Mount in Rust Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/namespaces.rs.html Creates a recursive bind mount from a source to a destination using libc::mount. ```rust fn bind_mount(src: &CStr, dst: &CStr) -> io::Result<()> { let flags = MountFlags::BIND | MountFlags::RECURSIVE; let res = unsafe { libc::mount(src.as_ptr(), dst.as_ptr(), ptr::null(), flags.bits(), ptr::null()) }; if res == 0 { Ok(()) } else { Err(IoError::last_os_error()) } } ``` -------------------------------- ### Create a new Command Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Constructs a new Command for launching a program. The program path can be absolute or will be searched in the system's PATH. ```rust use birdcage::process::Command; Command::new("sh"); ``` -------------------------------- ### Command::stdin, stdout, stderr Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Configures the standard input, output, and error streams for the child process. ```APIDOC ## Command::stdin / stdout / stderr ### Description Configures the handle for the child process's standard streams. Defaults to inherit. ### Parameters - **cfg** (Into) - Required - The configuration for the stream (e.g., Stdio::null()). ### Request Example ```rust use birdcage::process::{Command, Stdio}; Command::new("ls").stdout(Stdio::null()); ``` ``` -------------------------------- ### Spawn Sandbox Init Process Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Internal function to initialize the child process using clone and namespace isolation. ```rust /// Create sandbox child process. /// /// This function uses `clone` to setup the sandbox's init process with user /// namespace isolations in place. /// /// Returns PID of the child process if successful. fn spawn_sandbox_init(init_arg: ProcessInitArg, allow_networking: bool) -> Result { unsafe { // Initialize child process stack memory. let stack_size = 1024 * 1024; let child_stack = libc::mmap( ptr::null_mut(), stack_size, libc::PROT_READ | libc::PROT_WRITE, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK, -1, 0, ); if child_stack == libc::MAP_FAILED { return Err(IoError::last_os_error().into()); } // Stack grows downward on all relevant Linux processors. let stack_top = child_stack.add(stack_size); // Construct clone flags with required namespaces. let mut flags = libc::CLONE_NEWIPC | libc::CLONE_NEWNS | libc::CLONE_NEWPID | libc::CLONE_NEWUSER; if !allow_networking { flags |= libc::CLONE_NEWNET; } // Spawn sandbox init process. ``` -------------------------------- ### Map Process IDs in Rust Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/namespaces.rs.html Use this function after creating a user namespace to set up UID and GID mappings for the current process. It writes to `/proc/self/uid_map` and `/proc/self/gid_map`, and denies setgroups. ```rust pub fn map_ids( parent_euid: u32, parent_egid: u32, child_uid: u32, child_gid: u32, ) -> io::Result<()> { let uid_map = format!( "{child_uid} {parent_euid} 1\n"); let gid_map = format!( "{child_gid} {parent_egid} 1\n"); fs::write("/proc/self/uid_map", uid_map.as_bytes())?; fs::write("/proc/self/setgroups", b"deny")?; fs::write("/proc/self/gid_map", gid_map.as_bytes())?; Ok(()) } ``` -------------------------------- ### Stdio Configuration Methods Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Methods to define how standard streams (stdin, stdout, stderr) are handled for a child process. ```APIDOC ## Stdio::piped() ### Description Creates a new pipe to connect the parent and child processes, allowing for bidirectional communication. ### Method Static Method ## Stdio::inherit() ### Description The child process inherits the corresponding parent descriptor. ### Method Static Method ## Stdio::null() ### Description The stream is ignored, effectively attaching it to /dev/null. ### Method Static Method ``` -------------------------------- ### Configure Command stdout Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Configures the standard output for the child process. Defaults to inheriting from the parent process. ```rust use birdcage::process::{Command, Stdio}; Command::new("ls").stdout(Stdio::null()); ``` -------------------------------- ### Spawn and Wait for Child Process Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Child.html Spawns a child process using a sandbox and waits for its completion. ```rust use birdcage::process::Command; use birdcage::{Birdcage, Sandbox}; let mut cmd = Command::new("/bin/cat"); cmd.arg("file.txt"); let mut child = Birdcage::new().spawn(cmd).expect("failed to execute child"); let ecode = child.wait().expect("failed to wait on child"); assert!(ecode.success()); ``` -------------------------------- ### Configure Command stdin Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Configures the standard input for the child process. Defaults to inheriting from the parent process. ```rust use birdcage::process::{Command, Stdio}; Command::new("ls").stdin(Stdio::null()); ``` -------------------------------- ### Take Child Stdin Handle Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Demonstrates taking the stdin handle from a child process to avoid partial moves. ```rust let stdin = child.stdin.take().unwrap(); ``` -------------------------------- ### Command::stdin Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Configures the standard input (stdin) for the child process. Defaults to inheriting the parent's stdin. ```APIDOC ## DELETE /api/users/{id} ### Description Deletes a user by their ID. ### Method DELETE ### Endpoint /api/users/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the user to delete. ### Response #### Success Response (204) No content is returned upon successful deletion. #### Error Response (404) - **error** (string) - A message indicating the user was not found. #### Response Example ```json { "error": "User not found." } ``` ``` -------------------------------- ### Add Arguments to Command Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Use arg or args to pass parameters to the child process. Arguments are passed literally and are not processed by a shell. ```rust # birdcage::process::Command::new("sh") .arg("-C /path/to/repo") # ; ``` ```rust # birdcage::process::Command::new("sh") .arg("-C") .arg("/path/to/repo") # ; ``` ```rust use birdcage::process::Command; Command::new("ls").arg("-l").arg("-a"); ``` ```rust use birdcage::process::Command; Command::new("ls").args(["-l", "-a"]); ``` -------------------------------- ### Define Path Exceptions Structure Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Defines the storage for bind mounts and symlink mappings required for sandbox path permissions. ```rust /// Path permissions required for the sandbox. #[derive(Default)] pub(crate) struct PathExceptions { bind_mounts: HashMap, symlinks: Vec<(PathBuf, PathBuf)>, } ``` -------------------------------- ### LinuxSandbox Implementation Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html The primary struct for configuring and spawning a sandboxed process on Linux. ```rust //! Linux sandboxing. use std::collections::HashMap; use std::ffi::CString; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::os::fd::OwnedFd; use std::os::unix::ffi::OsStrExt; use std::path::{Component, Path, PathBuf}; use std::{env, fs, io, ptr}; use rustix::pipe::pipe; use rustix::process::{Gid, Pid, Uid, WaitOptions}; use crate::error::{Error, Result}; use crate::linux::namespaces::{MountAttrFlags, Namespaces}; use crate::linux::seccomp::SyscallFilter; use crate::{Child, Command, Exception, Sandbox}; mod namespaces; mod seccomp; /// Linux sandboxing. #[derive(Default)] pub struct LinuxSandbox { env_exceptions: Vec, path_exceptions: PathExceptions, allow_networking: bool, full_env: bool, } impl Sandbox for LinuxSandbox { fn new() -> Self { Self::default() } fn add_exception(&mut self, exception: Exception) -> Result<&mut Self> { match exception { Exception::Read(path) => self.path_exceptions.update(path, false, false)?, Exception::WriteAndRead(path) => self.path_exceptions.update(path, true, false)?, Exception::ExecuteAndRead(path) => self.path_exceptions.update(path, false, true)?, Exception::Environment(key) => self.env_exceptions.push(key), Exception::FullEnvironment => self.full_env = true, Exception::Networking => self.allow_networking = true, } Ok(self) } fn spawn(self, sandboxee: Command) -> Result { // Ensure calling process is not multi-threaded. assert!( thread_count().unwrap_or(0) == 1, "`Sandbox::spawn` must be called from a single-threaded process" ); // Remove environment variables. if !self.full_env { crate::restrict_env_variables(&self.env_exceptions); } // Create pipes to hook up init's stdio. let stdin_pipe = sandboxee.stdin.make_pipe(true)?; let stdout_pipe = sandboxee.stdout.make_pipe(false)?; let stderr_pipe = sandboxee.stderr.make_pipe(false)?; let exit_signal_pipe = pipe().map_err(IoError::from)?; // Spawn isolated sandbox PID 1. let allow_networking = self.allow_networking; let init_arg = ProcessInitArg::new( self, sandboxee, exit_signal_pipe, stdin_pipe, stdout_pipe, stderr_pipe, ); let init_arg = spawn_sandbox_init(init_arg, allow_networking)?; // Deconstruct init args, dropping unused FDs. let (pid, stdin_tx, stdout_rx, stderr_rx, exit_signal_rx) = { let ProcessInitArg { // Extract used fields. pid, stdin_tx, stdout_rx, stderr_rx, exit_signal_rx, // Deconstruct all remaining fields to manually drop them. path_exceptions: _x0, exit_signal_tx: _x1, parent_euid: _x2, parent_egid: _x3, stdout_tx: _x4, stderr_tx: _x5, sandboxee: _x6, stdin_rx: _x7, } = init_arg; (pid, stdin_tx, stdout_rx, stderr_rx, exit_signal_rx) }; let child = Child::new(pid, exit_signal_rx, stdin_tx, stdout_rx, stderr_rx)?; Ok(child) } } ``` -------------------------------- ### Create Child Process with Stdio Pipes Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Internal constructor for creating a `Child` instance, taking ownership of the process ID and its standard input, output, and error pipes. ```rust pub(crate) fn new( pid: i32, exit_signal: OwnedFd, stdin: Option, stdout: Option, stderr: Option, ) -> io::Result { Ok(Self { exit_signal, pid: pid as u32, stdin: stdin.map(ChildStdin::new).transpose()?, stdout: stdout.map(ChildStdout::new).transpose()?, stderr: stderr.map(ChildStderr::new).transpose()?, }) } ``` -------------------------------- ### Replicate Directory Tree Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/namespaces.rs.html Recursively copies a directory tree from a source path to a destination path. Creates missing empty directories and preserves their permissions. If the source is a file, an empty file with matching permissions is created at the destination. ```rust fn copy_tree(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { let mut dst = dst.as_ref().to_path_buf(); let mut src_sub = PathBuf::new(); let src = src.as_ref(); ``` -------------------------------- ### Birdcage Sandbox API Source: https://docs.rs/birdcage/0.8.1/src/birdcage/lib.rs.html Core methods for initializing a sandbox, defining access exceptions, and spawning sandboxed processes. ```APIDOC ## Sandbox::new() ### Description Initializes a new sandboxing environment for the current platform (Linux or macOS). ### Method Constructor ### Parameters None --- ## Sandbox::add_exception(exception: Exception) ### Description Adds a new exception rule to the sandbox, allowing access to specific resources that would otherwise be prohibited. ### Parameters - **exception** (Exception) - Required - The rule to add (e.g., Read, WriteAndRead, ExecuteAndRead, Environment, Networking). --- ## Sandbox::spawn(sandboxee: Command) ### Description Sets up the sandbox and spawns a new process. Note that this should be called from a single-threaded process to avoid sandboxing failures. ### Parameters - **sandboxee** (Command) - Required - The command to execute within the sandbox. ### Response - **Result** - Returns a handle to the spawned child process or an error if initialization fails. ``` -------------------------------- ### Check exit status with exit_ok Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Output.html Demonstrates using the nightly-only exit_ok method to verify process success. ```rust #![feature(exit_status_error)] use std::process::Command; assert!(Command::new("false").output().unwrap().exit_ok().is_err()); ``` -------------------------------- ### Create Symlinks Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/namespaces.rs.html Creates missing symlinks within a new root filesystem. Ignores symlinks if a parent directory is already bind-mounted. Ensures parent directories exist before creating the symlink. ```rust fn create_symlinks(new_root: &Path, symlinks: Vec<(PathBuf, PathBuf)>) -> io::Result<()> { for (symlink, target) in symlinks { // Ignore symlinks if a parent bind mount exists. let unrooted_path = symlink.strip_prefix("/").unwrap(); let dst = new_root.join(unrooted_path); if dst.symlink_metadata().is_ok() { continue; } // Create all parent directories. let parent = match symlink.parent() { Some(parent) => parent, None => continue, }; copy_tree(parent, new_root)?; // Create the symlink. unixfs::symlink(target, dst)?; } Ok(()) } ``` -------------------------------- ### Command::arg and Command::args Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Command.html Methods to add single or multiple arguments to the command execution. ```APIDOC ## Command::arg / Command::args ### Description Adds arguments to pass to the program. Arguments are passed literally and not processed by a shell. ### Parameters - **arg** (AsRef) - Required (for arg) - A single argument to add. - **args** (IntoIterator) - Required (for args) - A collection of arguments to add. ### Request Example ```rust Command::new("ls").arg("-l").arg("-a"); Command::new("ls").args(["-l", "-a"]); ``` ``` -------------------------------- ### Command::args Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Adds multiple arguments to the command. Arguments are passed literally and not interpreted by a shell. ```APIDOC ## PUT /api/users/{id} ### Description Updates an existing user's information. ### Method PUT ### Endpoint /api/users/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the user to update. #### Request Body - **username** (string) - Optional - The new username for the user. - **email** (string) - Optional - The new email address for the user. ### Request Example ```json { "email": "john.doe.updated@example.com" } ``` ### Response #### Success Response (200) - **id** (string) - The unique identifier for the user. - **username** (string) - The updated username of the user. - **email** (string) - The updated email address of the user. #### Response Example ```json { "id": "user-12345", "username": "johndoe", "email": "john.doe.updated@example.com" } ``` #### Error Response (404) - **error** (string) - A message indicating the user was not found. #### Response Example ```json { "error": "User not found." } ``` ``` -------------------------------- ### Configure Standard Output Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Sets the standard output handle for a child process. ```rust use birdcage::process::{Command, Stdio}; Command::new("ls").stdout(Stdio::null()); ``` -------------------------------- ### Sandbox Trait Methods Source: https://docs.rs/birdcage/0.8.1/birdcage/trait.Sandbox.html API documentation for the required methods of the Sandbox trait. ```APIDOC ## fn new() ### Description Setup the sandboxing environment. ## fn add_exception(&mut self, exception: Exception) -> Result<&mut Self> ### Description Add a new exception to the sandbox. Exceptions added for symlinks will also automatically apply to the symlink’s target. ### Parameters - **exception** (Exception) - Required - The exception to add to the sandbox. ## fn spawn(self, sandboxee: Command) -> Result ### Description Setup sandbox and spawn a new process. This will setup the sandbox in the CURRENT process, before launching the sandboxee. It is recommended to create a separate process before calling this method as the calling process is not fully sandboxed. ### Parameters - **sandboxee** (Command) - Required - The command to execute within the sandbox. ### Errors - Sandboxing will fail if the calling process is not single-threaded. After failure, the calling process might still be affected by partial sandboxing restrictions. ``` -------------------------------- ### Create a piped stdin for a command Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Stdio.html Use `Stdio::piped()` for stdin to send data to a child process. Ensure to handle potential deadlocks by reading stdout/stderr concurrently if writing large amounts of data. ```rust use std::io::Write; use birdcage::process::{Command, Stdio}; use birdcage::{Birdcage, Sandbox}; let mut cmd = Command::new("rev"); cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); let mut child = Birdcage::new().spawn(cmd).expect("Failed to spawn child process"); let mut stdin = child.stdin.take().expect("Failed to open stdin"); std::thread::spawn(move || { stdin.write_all("Hello, world!".as_bytes()).expect("Failed to write to stdin"); }); let output = child.wait_with_output().expect("Failed to read stdout"); assert_eq!(String::from_utf8_lossy(&output.stdout), "!dlrow ,olleH"); ``` -------------------------------- ### Mount tmpfs Filesystem in Rust Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/namespaces.rs.html Mounts a new tmpfs filesystem at the specified destination. Uses libc::mount with appropriate flags and type. ```rust fn mount_tmpfs(dst: &CStr) -> io::Result<()> { let flags = MountFlags::empty(); let fstype = CString::new("tmpfs").unwrap(); let res = unsafe { libc::mount(ptr::null(), dst.as_ptr(), fstype.as_ptr(), flags.bits(), ptr::null()) }; if res == 0 { Ok(()) } else { Err(IoError::last_os_error()) } } ``` -------------------------------- ### Stdio::piped Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Stdio.html Creates a new pipe to connect the parent and child processes. ```APIDOC ## pub fn piped() -> Self ### Description A new pipe should be arranged to connect the parent and child processes. ### Request Example ```rust use birdcage::process::{Command, Stdio}; Command::new("echo").arg("Hello, world!").stdout(Stdio::piped()); ``` ``` -------------------------------- ### Update Path Exceptions Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/mod.rs.html Updates bind mount permissions for a given path, canonicalizing it first and tracking symlinks if present. ```rust impl PathExceptions { /// Add or modify a path's exceptions. /// /// This will add a new bind mount for the canonical path with the specified /// permission if it does not exist already. /// /// If the bind mount already exists, it will *ADD* the additional /// permissions. fn update(&mut self, path: PathBuf, write: bool, execute: bool) -> Result<()> { // Use canonical path for indexing. // // This ensures that a symlink and its target are treated like the same path for // exceptions. // // If the home path cannot be accessed, we ignore the exception. let canonical_path = match path.canonicalize() { Ok(path) => path, Err(_) => return Err(Error::InvalidPath(path)), }; // Store original symlink path to create it if necessary. if path_has_symlinks(&path) { // Normalize symlink's path. let absolute = absolute(&path)?; let normalized = normalize_path(&absolute); self.symlinks.push((normalized, canonical_path.clone())); } // Update bind mount's permission flags. let flags = self .bind_mounts .entry(canonical_path) .or_insert(MountAttrFlags::RDONLY | MountAttrFlags::NOEXEC); if write { flags.remove(MountAttrFlags::RDONLY); } if execute { flags.remove(MountAttrFlags::NOEXEC); } Ok(()) } } ``` -------------------------------- ### Child Process Management Source: https://docs.rs/birdcage/0.8.1/src/birdcage/process/linux.rs.html Managing the lifecycle of a spawned child process, including waiting for exit. ```APIDOC ## Child ### Description Represents a running or exited child process. Note that Birdcage does not automatically wait on child processes; developers must call `wait()` to prevent zombie processes. ### Fields - **stdin** (Option) - The handle for writing to the child's stdin, if captured. - **stdout** (Option) - The handle for reading from the child's stdout, if captured. ### Methods - **wait()** - Blocks the parent process until the child has exited. ``` -------------------------------- ### Define Syscall Whitelist Source: https://docs.rs/birdcage/0.8.1/src/birdcage/linux/seccomp.rs.html A list of system calls that are unconditionally allowed for networking, with architecture-specific entries. ```rust 89const SYSCALL_WHITELIST: &[libc::c_long] = &[ 90 libc::SYS_read, 91 libc::SYS_write, 92 #[cfg(target_arch = "x86_64")] 93 libc::SYS_open, 94 libc::SYS_close, 95 #[cfg(target_arch = "x86_64")] 96 libc::SYS_stat, 97 libc::SYS_fstat, 98 #[cfg(target_arch = "x86_64")] 99 libc::SYS_lstat, 100 #[cfg(target_arch = "x86_64")] 101 libc::SYS_poll, 102 libc::SYS_lseek, 103 libc::SYS_mmap, 104 libc::SYS_mprotect, 105 libc::SYS_munmap, 106 libc::SYS_brk, 107 libc::SYS_rt_sigaction, 108 libc::SYS_rt_sigprocmask, 109 libc::SYS_rt_sigreturn, 110 libc::SYS_ioctl, 111 libc::SYS_pread64, 112 libc::SYS_pwrite64, 113 libc::SYS_readv, 114 libc::SYS_writev, 115 #[cfg(target_arch = "x86_64")] 116 libc::SYS_access, 117 #[cfg(target_arch = "x86_64")] 118 libc::SYS_pipe, 119 #[cfg(target_arch = "x86_64")] 120 libc::SYS_select, 121 libc::SYS_sched_yield, 122 libc::SYS_mremap, 123 libc::SYS_msync, 124 libc::SYS_mincore, 125 libc::SYS_madvise, 126 libc::SYS_shmget, 127 libc::SYS_shmat, 128 libc::SYS_shmctl, 129 libc::SYS_dup, 130 #[cfg(target_arch = "x86_64")] 131 libc::SYS_dup2, 132 #[cfg(target_arch = "x86_64")] 133 libc::SYS_pause, 134 libc::SYS_nanosleep, 135 libc::SYS_getitimer, 136 #[cfg(target_arch = "x86_64")] 137 libc::SYS_alarm, 138 libc::SYS_setitimer, 139 libc::SYS_getpid, 140 #[cfg(target_arch = "x86_64")] 141 libc::SYS_sendfile, 142 libc::SYS_connect, 143 libc::SYS_accept, 144 libc::SYS_sendto, 145 libc::SYS_recvfrom, 146 libc::SYS_sendmsg, 147 libc::SYS_recvmsg, 148 libc::SYS_shutdown, 149 libc::SYS_bind, 150 libc::SYS_listen, 151 libc::SYS_getsockname, 152 libc::SYS_getpeername, 153 libc::SYS_setsockopt, 154 libc::SYS_getsockopt, 155 #[cfg(target_arch = "x86_64")] 156 libc::SYS_fork, 157 #[cfg(target_arch = "x86_64")] 158 libc::SYS_vfork, 159 libc::SYS_execve, 160]; ``` -------------------------------- ### Create a piped stdout for a command Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Stdio.html Use `Stdio::piped()` to create a new pipe for the stdout of a child process. This allows the parent and child to communicate via this stream. ```rust use birdcage::process::{Command, Stdio}; Command::new("echo").arg("Hello, world!").stdout(Stdio::piped()); ``` -------------------------------- ### Take Child I/O Handles Source: https://docs.rs/birdcage/0.8.1/birdcage/process/struct.Child.html Use take() on stdin, stdout, or stderr to avoid partially moving the child struct. ```rust let stdin = child.stdin.take().unwrap(); ``` ```rust let stdout = child.stdout.take().unwrap(); ``` ```rust let stderr = child.stderr.take().unwrap(); ```