sandbox_rs/execution/
init.rs1use nix::sys::signal::{SigHandler, Signal, signal};
4use nix::unistd::execv;
5use std::ffi::CString;
6use std::process::exit;
7
8pub struct SandboxInit {
10 pub program: String,
12 pub args: Vec<String>,
13}
14
15impl SandboxInit {
16 pub fn new(program: String, args: Vec<String>) -> Self {
18 Self { program, args }
19 }
20
21 pub fn run(&self) -> ! {
24 Self::setup_signals();
26
27 Self::mount_procfs();
28 Self::mount_sysfs();
29
30 self.exec_user_program();
32 }
33
34 fn setup_signals() {
36 unsafe {
38 let _ = signal(Signal::SIGCHLD, SigHandler::SigIgn);
39 let _ = signal(Signal::SIGTERM, SigHandler::SigDfl);
40 }
41 }
42
43 fn mount_procfs() {
44 let _ = std::fs::create_dir("/proc");
45 let _ = std::process::Command::new("mount")
46 .args(["-t", "proc", "proc", "/proc"])
47 .output();
48 }
49
50 fn mount_sysfs() {
51 let _ = std::fs::create_dir("/sys");
52 let _ = std::process::Command::new("mount")
53 .args(["-t", "sysfs", "sysfs", "/sys"])
54 .output();
55 }
56
57 fn exec_user_program(&self) -> ! {
59 let program_cstr = match CString::new(self.program.clone()) {
60 Ok(s) => s,
61 Err(_) => {
62 eprintln!("Invalid program name");
63 exit(1);
64 }
65 };
66
67 let args_cstr: Vec<CString> = self
68 .args
69 .iter()
70 .map(|arg| CString::new(arg.clone()).unwrap_or_else(|_| CString::new("").unwrap()))
71 .collect();
72
73 let args_refs: Vec<&CString> = vec![&program_cstr]
74 .into_iter()
75 .chain(args_cstr.iter())
76 .collect();
77
78 match execv(&program_cstr, &args_refs) {
79 Ok(_) => {
80 exit(0);
82 }
83 Err(e) => {
84 eprintln!("Failed to execute program: {}", e);
85 exit(1);
86 }
87 }
88 }
89
90 pub fn reap_children() {
92 use nix::sys::wait::{WaitStatus, waitpid};
93 use nix::unistd::Pid;
94
95 loop {
96 match waitpid(
97 Pid::from_raw(-1),
98 Some(nix::sys::wait::WaitPidFlag::WNOHANG),
99 ) {
100 Ok(WaitStatus::Exited(pid, _status)) => {
101 eprintln!("[init] Child {} exited", pid);
102 }
103 Ok(WaitStatus::Signaled(pid, signal, _core)) => {
104 eprintln!("[init] Child {} killed by {:?}", pid, signal);
105 }
106 Ok(WaitStatus::StillAlive) => break,
107 Ok(_) => continue,
108 Err(_) => break,
109 }
110 }
111 }
112}
113
114pub fn generate_init_script(program: &str, args: &[&str]) -> String {
117 format!(
118 r#"#!/bin/sh
119set -e
120
121# Mount essential filesystems
122mkdir -p /proc /sys /dev /tmp
123
124# Don't mount if already mounted (in case of nested)
125mountpoint -q /proc || mount -t proc proc /proc
126mountpoint -q /sys || mount -t sysfs sysfs /sys
127
128# Execute program
129exec "{}" {}
130"#,
131 program,
132 args.join(" ")
133 )
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_init_creation() {
142 let init = SandboxInit::new("/bin/echo".to_string(), vec!["hello".to_string()]);
143
144 assert_eq!(init.program, "/bin/echo");
145 assert_eq!(init.args.len(), 1);
146 assert_eq!(init.args[0], "hello");
147 }
148
149 #[test]
150 fn test_init_with_multiple_args() {
151 let init = SandboxInit::new(
152 "/bin/echo".to_string(),
153 vec![
154 "hello".to_string(),
155 "world".to_string(),
156 "from".to_string(),
157 "init".to_string(),
158 ],
159 );
160
161 assert_eq!(init.args.len(), 4);
162 }
163
164 #[test]
165 fn test_generate_init_script_simple() {
166 let script = generate_init_script("/bin/echo", &["hello"]);
167
168 assert!(script.contains("#!/bin/sh"));
169 assert!(script.contains("/proc"));
170 assert!(script.contains("/bin/echo"));
171 assert!(script.contains("hello"));
172 }
173
174 #[test]
175 fn test_generate_init_script_multiple_args() {
176 let script = generate_init_script("/bin/echo", &["hello", "world"]);
177
178 assert!(script.contains("hello"));
179 assert!(script.contains("world"));
180 assert!(script.contains("exec"));
181 }
182
183 #[test]
184 fn test_generate_init_script_contains_mounts() {
185 let script = generate_init_script("/usr/bin/test", &[]);
186
187 assert!(script.contains("mount -t proc"));
188 assert!(script.contains("mount -t sysfs"));
189 assert!(script.contains("mkdir -p /proc /sys /dev /tmp"));
190 }
191
192 #[test]
193 fn test_init_empty_args() {
194 let init = SandboxInit::new("/bin/sh".to_string(), Vec::new());
195
196 assert!(init.args.is_empty());
197 }
198
199 #[test]
200 fn test_mount_helpers_are_best_effort() {
201 SandboxInit::mount_procfs();
202 SandboxInit::mount_sysfs();
203 }
204
205 #[test]
206 fn test_setup_signals_runs() {
207 let original_sigchld = unsafe { signal(Signal::SIGCHLD, SigHandler::SigDfl) };
209
210 SandboxInit::setup_signals();
212
213 unsafe {
215 let _ = signal(
216 Signal::SIGCHLD,
217 original_sigchld.unwrap_or(SigHandler::SigDfl),
218 );
219 }
220 }
221
222 #[test]
223 fn test_generate_init_script_with_special_args() {
224 let script = generate_init_script("/bin/sh", &["-c", "echo hello"]);
225
226 assert!(script.contains("/bin/sh"));
227 assert!(script.contains("-c"));
228 assert!(script.contains("echo hello"));
229 }
230
231 #[test]
232 fn test_generate_init_script_complex_program() {
233 let script = generate_init_script("/usr/bin/python3", &["-u", "script.py"]);
234
235 assert!(script.contains("/usr/bin/python3"));
236 assert!(script.contains("-u"));
237 assert!(script.contains("script.py"));
238 }
239
240 #[test]
241 fn test_generate_init_script_many_args() {
242 let args = vec!["arg1", "arg2", "arg3", "arg4", "arg5"];
243 let script = generate_init_script("/bin/cat", &args);
244
245 for arg in &args {
246 assert!(script.contains(arg));
247 }
248 assert!(script.contains("/bin/cat"));
249 }
250
251 #[test]
252 fn test_generate_init_script_format() {
253 let script = generate_init_script("/bin/ls", &["-la"]);
254
255 assert!(script.starts_with("#!/bin/sh"));
256 assert!(script.contains("set -e"));
257 assert!(script.contains("mkdir -p"));
258 assert!(script.contains("exec"));
259 }
260
261 #[test]
262 fn test_init_program_stored_correctly() {
263 let program = "/usr/bin/python3".to_string();
264 let init = SandboxInit::new(program.clone(), vec![]);
265
266 assert_eq!(init.program, program);
267 }
268
269 #[test]
270 fn test_init_args_stored_correctly() {
271 let args = vec!["arg1".to_string(), "arg2".to_string(), "arg3".to_string()];
272 let init = SandboxInit::new("/bin/test".to_string(), args.clone());
273
274 assert_eq!(init.args, args);
275 }
276
277 #[test]
278 fn test_init_clone() {
279 let init1 = SandboxInit::new("/bin/echo".to_string(), vec!["test".to_string()]);
280
281 let init2 = SandboxInit::new(init1.program.clone(), init1.args.clone());
282
283 assert_eq!(init1.program, init2.program);
284 assert_eq!(init1.args, init2.args);
285 }
286
287 #[test]
288 fn test_generate_init_script_empty_program() {
289 let script = generate_init_script("", &[]);
290
291 assert!(script.starts_with("#!/bin/sh"));
292 assert!(script.contains("exec \"\""));
293 }
294
295 #[test]
296 fn test_generate_init_script_preserves_shell_syntax() {
297 let script = generate_init_script("/bin/echo", &["hello"]);
298
299 assert!(script.contains("#!/bin/sh"));
300 assert!(script.contains("set -e"));
301 assert!(script.contains("mkdir -p"));
302 assert!(script.contains("exec"));
303 }
304}