bob/sandbox/
sandbox_linux.rs1use crate::sandbox::Sandbox;
18use anyhow::Context;
19use std::fs;
20use std::os::unix::process::CommandExt;
21use std::path::Path;
22use std::process::{Command, ExitStatus, Stdio};
23use tracing::{debug, info, warn};
24
25impl Sandbox {
26 pub fn mount_bindfs(
27 &self,
28 src: &Path,
29 dest: &Path,
30 opts: &[&str],
31 ) -> anyhow::Result<Option<ExitStatus>> {
32 fs::create_dir_all(dest)?;
33 let cmd = "/bin/mount";
34 let mut mount_opts = vec!["bind"];
36 mount_opts.extend(opts.iter().copied());
37 let opts_str = mount_opts.join(",");
38 Ok(Some(
39 Command::new(cmd)
40 .arg("-o")
41 .arg(&opts_str)
42 .arg(src)
43 .arg(dest)
44 .process_group(0)
45 .status()
46 .context(format!("Unable to execute {}", cmd))?,
47 ))
48 }
49
50 pub fn mount_devfs(
51 &self,
52 _src: &Path,
53 dest: &Path,
54 opts: &[&str],
55 ) -> anyhow::Result<Option<ExitStatus>> {
56 fs::create_dir_all(dest)?;
57 let cmd = "/bin/mount";
58 Ok(Some(
59 Command::new(cmd)
60 .arg("-t")
61 .arg("devtmpfs")
62 .args(opts)
63 .arg("devtmpfs")
64 .arg(dest)
65 .process_group(0)
66 .status()
67 .context(format!("Unable to execute {}", cmd))?,
68 ))
69 }
70
71 pub fn mount_fdfs(
72 &self,
73 _src: &Path,
74 dest: &Path,
75 opts: &[&str],
76 ) -> anyhow::Result<Option<ExitStatus>> {
77 fs::create_dir_all(dest)?;
78 let cmd = "/bin/mount";
79 let mut mount_opts = vec!["bind"];
81 mount_opts.extend(opts.iter().copied());
82 let opts_str = mount_opts.join(",");
83 Ok(Some(
84 Command::new(cmd)
85 .arg("-o")
86 .arg(&opts_str)
87 .arg("/dev/fd")
88 .arg(dest)
89 .process_group(0)
90 .status()
91 .context(format!("Unable to execute {}", cmd))?,
92 ))
93 }
94
95 pub fn mount_nfs(
96 &self,
97 src: &Path,
98 dest: &Path,
99 opts: &[&str],
100 ) -> anyhow::Result<Option<ExitStatus>> {
101 fs::create_dir_all(dest)?;
102 let cmd = "/bin/mount";
103 Ok(Some(
104 Command::new(cmd)
105 .arg("-t")
106 .arg("nfs")
107 .args(opts)
108 .arg(src)
109 .arg(dest)
110 .process_group(0)
111 .status()
112 .context(format!("Unable to execute {}", cmd))?,
113 ))
114 }
115
116 pub fn mount_procfs(
117 &self,
118 _src: &Path,
119 dest: &Path,
120 opts: &[&str],
121 ) -> anyhow::Result<Option<ExitStatus>> {
122 fs::create_dir_all(dest)?;
123 let cmd = "/bin/mount";
124 Ok(Some(
125 Command::new(cmd)
126 .arg("-t")
127 .arg("proc")
128 .args(opts)
129 .arg("proc")
130 .arg(dest)
131 .process_group(0)
132 .status()
133 .context(format!("Unable to execute {}", cmd))?,
134 ))
135 }
136
137 pub fn mount_tmpfs(
138 &self,
139 _src: &Path,
140 dest: &Path,
141 opts: &[&str],
142 ) -> anyhow::Result<Option<ExitStatus>> {
143 fs::create_dir_all(dest)?;
144 let cmd = "/bin/mount";
145 let mut args = vec!["-t", "tmpfs"];
146 let mut mount_opts: Vec<String> = vec![];
148 for opt in opts {
149 if opt.starts_with("size=") || opt.starts_with("mode=") {
150 mount_opts.push(opt.to_string());
151 }
152 }
153 if !mount_opts.is_empty() {
154 args.push("-o");
155 }
156 let opts_str = mount_opts.join(",");
157 Ok(Some(
158 Command::new(cmd)
159 .args(&args)
160 .arg(if !mount_opts.is_empty() { &opts_str } else { "" })
161 .arg("tmpfs")
162 .arg(dest)
163 .process_group(0)
164 .status()
165 .context(format!("Unable to execute {}", cmd))?,
166 ))
167 }
168
169 fn unmount_common(
170 &self,
171 dest: &Path,
172 ) -> anyhow::Result<Option<ExitStatus>> {
173 let cmd = "/bin/umount";
174 Ok(Some(
178 Command::new(cmd)
179 .arg(dest)
180 .stdout(Stdio::null())
181 .stderr(Stdio::null())
182 .process_group(0)
183 .status()
184 .context(format!("Unable to execute {}", cmd))?,
185 ))
186 }
187
188 pub fn unmount_bindfs(
189 &self,
190 dest: &Path,
191 ) -> anyhow::Result<Option<ExitStatus>> {
192 self.unmount_common(dest)
193 }
194
195 pub fn unmount_devfs(
196 &self,
197 dest: &Path,
198 ) -> anyhow::Result<Option<ExitStatus>> {
199 self.unmount_common(dest)
200 }
201
202 pub fn unmount_fdfs(
203 &self,
204 dest: &Path,
205 ) -> anyhow::Result<Option<ExitStatus>> {
206 self.unmount_common(dest)
207 }
208
209 pub fn unmount_nfs(
210 &self,
211 dest: &Path,
212 ) -> anyhow::Result<Option<ExitStatus>> {
213 self.unmount_common(dest)
214 }
215
216 pub fn unmount_procfs(
217 &self,
218 dest: &Path,
219 ) -> anyhow::Result<Option<ExitStatus>> {
220 self.unmount_common(dest)
221 }
222
223 pub fn unmount_tmpfs(
224 &self,
225 dest: &Path,
226 ) -> anyhow::Result<Option<ExitStatus>> {
227 self.unmount_common(dest)
228 }
229
230 pub fn kill_processes(&self, sandbox: &Path) {
236 for iteration in 0..super::KILL_PROCESSES_MAX_RETRIES {
237 let mut killed: Vec<i32> = Vec::new();
238
239 if let Ok(procs) = procfs::process::all_processes() {
241 for proc in procs.flatten() {
242 if Self::process_uses_path(&proc, sandbox).is_some() {
243 killed.push(proc.pid);
244 unsafe {
245 libc::kill(proc.pid, libc::SIGKILL);
246 }
247 }
248 }
249 }
250
251 if killed.is_empty() {
252 debug!(retries = iteration, "No processes found in sandbox");
253 return;
254 }
255
256 let pids: Vec<String> =
257 killed.iter().map(|p| p.to_string()).collect();
258 info!(pids = %pids.join(" "), "Killed processes using sandbox");
259
260 let delay_ms = super::KILL_PROCESSES_INITIAL_DELAY_MS << iteration;
262 std::thread::sleep(std::time::Duration::from_millis(delay_ms));
263 }
264 let proc_info = Self::get_process_info(sandbox);
266 warn!(
267 max_retries = super::KILL_PROCESSES_MAX_RETRIES,
268 remaining = %proc_info,
269 "Gave up killing processes after max retries"
270 );
271 }
272
273 fn get_process_info(sandbox: &Path) -> String {
275 let mut info = Vec::new();
276 if let Ok(procs) = procfs::process::all_processes() {
277 for proc in procs.flatten() {
278 if Self::process_uses_path(&proc, sandbox).is_some() {
279 let cmdline = proc
280 .cmdline()
281 .map(|c| c.join(" "))
282 .unwrap_or_else(|_| String::from("?"));
283 info.push(format!("pid={} cmd='{}'", proc.pid, cmdline));
284 }
285 }
286 }
287 if info.is_empty() { String::from("(none)") } else { info.join(", ") }
288 }
289
290 fn process_uses_path(
293 proc: &procfs::process::Process,
294 dir: &Path,
295 ) -> Option<String> {
296 if let Ok(cwd) = proc.cwd() {
298 if cwd.starts_with(dir) {
299 return Some(format!("cwd={}", cwd.display()));
300 }
301 }
302
303 if let Ok(root) = proc.root() {
305 if root.starts_with(dir) {
306 return Some(format!("root={}", root.display()));
307 }
308 }
309
310 if let Ok(fds) = proc.fd() {
312 for fd in fds.flatten() {
313 if let procfs::process::FDTarget::Path(path) = fd.target {
314 if path.starts_with(dir) {
315 return Some(format!("fd={}", path.display()));
316 }
317 }
318 }
319 }
320
321 None
322 }
323}