rix/sandbox/
mod.rs

1use nix::env::clearenv;
2use nix::sys::wait;
3use nix::{mount, sched, unistd};
4use std::fs;
5use std::path::{Path, PathBuf};
6use uuid::Uuid;
7
8pub fn run_in_sandbox(
9    new_root: &Path,
10    prepare_sandbox: impl Fn() -> Result<(), String>,
11    run: impl Fn() -> isize,
12) -> Result<i32, String> {
13    let forked_logic = || -> isize {
14        if let Err(err) = unsafe { clearenv() } {
15            eprintln!("Could not clear environment variables: {}", err);
16            return 255;
17        }
18        if let Err(err) = prepare_sandbox() {
19            eprintln!("Error preparing the sandbox: {}", err);
20            return 255;
21        }
22        if let Err(err) = pivot_root(new_root) {
23            eprintln!("Error setting up builder sandbox: {}", err);
24            return 255;
25        }
26        run()
27    };
28
29    let pid = sched::clone(
30        Box::new(forked_logic),
31        &mut vec![0u8; 1024 * 1024],
32        sched::CloneFlags::CLONE_NEWNS | sched::CloneFlags::CLONE_NEWUSER,
33        Some(libc::SIGCHLD),
34    )
35    .map_err(|err| format!("Failed to start the builder process. Error: {}", err))?;
36
37    match wait::waitpid(pid, None) {
38        Ok(wait::WaitStatus::Exited(_, exit_code)) => Ok(exit_code),
39        Ok(wait::WaitStatus::Signaled(_, signal, core_dumped)) => {
40            eprintln!(
41                "Builder killed by signal {} (core dumped: {})",
42                signal, core_dumped,
43            );
44            Ok(255)
45        }
46        Ok(state) => {
47            eprintln!("Unexpected builder process state: {:?}", state);
48            Ok(255)
49        }
50        Err(err) => {
51            eprintln!("Error waiting for the builder process: {}", err);
52            Ok(255)
53        }
54    }
55}
56
57pub fn mount_paths<'a>(
58    paths: impl Iterator<Item = &'a Path>,
59    new_root: &Path,
60) -> Result<(), String> {
61    for path in paths {
62        mount_path(path, new_root)?;
63    }
64    Ok(())
65}
66
67pub fn mount_path(path: &Path, new_root: &Path) -> Result<(), String> {
68    let target_path = prepare_mount_path(path, new_root)?;
69    mount::mount(
70        Some(path),
71        &target_path,
72        None::<&str>,
73        mount::MsFlags::MS_BIND | mount::MsFlags::MS_REC,
74        None::<&str>,
75    )
76    .map_err(|e| {
77        format!(
78            "Failed to bind mount {:?} to {:?}. Error: {}",
79            path, target_path, e
80        )
81    })
82}
83
84pub fn pivot_root(new_root: &Path) -> Result<(), String> {
85    mount_rootfs(new_root)?;
86    let old_root_name = Uuid::new_v4().to_string();
87    let old_root = new_root.join(&old_root_name);
88    let old_root_absolute = Path::new("/").join(old_root_name);
89    fs::create_dir_all(&old_root).map_err(|e| format!("Error creating oldroot: {}", e))?;
90    unistd::pivot_root(new_root, &old_root)
91        .map_err(|e| format!("Error pivoting to new root: {}", e))?;
92    unistd::chdir("/").map_err(|e| format!("Error cd'ing to new root: {}", e))?;
93    mount::umount2(&old_root_absolute, mount::MntFlags::MNT_DETACH)
94        .map_err(|e| format!("Error unmounting old root: {}", e))?;
95    std::fs::remove_dir_all(&old_root_absolute)
96        .map_err(|e| format!("Error removing old root: {}", e))
97}
98
99fn mount_rootfs(new_root: &Path) -> Result<(), String> {
100    // we have to mount the old root as part of requirements of the `pivot_root` syscall.
101    // For more info see: https://man7.org/linux/man-pages/man2/pivot_root.2.html
102    mount::mount(
103        Some("/"),
104        "/",
105        None::<&str>,
106        mount::MsFlags::MS_PRIVATE | mount::MsFlags::MS_REC,
107        None::<&str>,
108    )
109    .map_err(|e| format!("Error mounting old root: {}", e))?;
110
111    mount::mount(
112        Some(new_root),
113        new_root,
114        None::<&str>,
115        mount::MsFlags::MS_BIND | mount::MsFlags::MS_REC,
116        None::<&str>,
117    )
118    .map_err(|e| format!("Failed to mount new root: {}", e))
119}
120
121fn prepare_mount_path(source_path: &Path, new_root: &Path) -> Result<PathBuf, String> {
122    let path_without_root = source_path.strip_prefix("/").map_err(|e| {
123        format!(
124            "Could not remove '/' from path {:?}. Error: {}",
125            source_path, e
126        )
127    })?;
128    let target_path = new_root.join(path_without_root);
129    if source_path.is_dir() {
130        fs::create_dir_all(&target_path)
131            .map_err(|e| format!("Error creating directory {:?}: {}", target_path, e))?;
132    } else {
133        if let Some(parent) = target_path.parent() {
134            fs::create_dir_all(parent).map_err(|e| {
135                format!(
136                    "Error creating parent directories for {:?}: {}",
137                    source_path, e
138                )
139            })?;
140        }
141        fs::write(&target_path, "")
142            .map_err(|e| format!("Error creating empty target file {:?}: {}", source_path, e))?;
143    }
144    return Ok(target_path);
145}