1use std::collections::HashMap;
2use std::os::fd::AsRawFd;
3use std::path::PathBuf;
4use std::sync::Arc;
5use std::time::SystemTime;
6
7use serde::{Deserialize, Serialize};
8use tokio::task::JoinHandle;
9
10use crate::context;
11use crate::error::SandboxError;
12pub use crate::http::{http_acl_check, normalize_path, prefix_or_exact_match, HttpRule};
13pub use crate::network::{IpCidr, NetAllow, NetDeny, NetRule, NetTarget, Protocol};
14use crate::protection::{Protection, ProtectionPolicy, ProtectionState, ProtectionStatus};
15
16mod builder;
17pub use builder::SandboxBuilder;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub struct ByteSize(pub u64);
22
23impl ByteSize {
24 pub fn bytes(n: u64) -> Self {
25 ByteSize(n)
26 }
27
28 pub fn kib(n: u64) -> Self {
29 ByteSize(n * 1024)
30 }
31
32 pub fn mib(n: u64) -> Self {
33 ByteSize(n * 1024 * 1024)
34 }
35
36 pub fn gib(n: u64) -> Self {
37 ByteSize(n * 1024 * 1024 * 1024)
38 }
39
40 pub fn parse(s: &str) -> Result<Self, SandboxError> {
41 let s = s.trim();
42 if s.is_empty() {
43 return Err(SandboxError::Invalid("empty byte size string".into()));
44 }
45
46 let last = s.chars().last().unwrap();
48 if last.is_ascii_alphabetic() {
49 let (num_str, suffix) = s.split_at(s.len() - 1);
50 let n: u64 = num_str
51 .trim()
52 .parse()
53 .map_err(|_| SandboxError::Invalid(format!("invalid byte size: {}", s)))?;
54 match suffix.to_ascii_uppercase().as_str() {
55 "K" => Ok(ByteSize::kib(n)),
56 "M" => Ok(ByteSize::mib(n)),
57 "G" => Ok(ByteSize::gib(n)),
58 other => Err(SandboxError::Invalid(format!("unknown byte size suffix: {}", other))),
59 }
60 } else {
61 let n: u64 = s
62 .parse()
63 .map_err(|_| SandboxError::Invalid(format!("invalid byte size: {}", s)))?;
64 Ok(ByteSize(n))
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79pub struct RunAs {
80 pub uid: u32,
81 pub gid: u32,
82}
83
84impl std::str::FromStr for RunAs {
85 type Err = String;
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 let (u, g) = s
88 .split_once(':')
89 .ok_or_else(|| format!("expected UID:GID, got {:?}", s))?;
90 let uid = u.trim().parse::<u32>().map_err(|_| format!("invalid uid {:?}", u))?;
91 let gid = g.trim().parse::<u32>().map_err(|_| format!("invalid gid {:?}", g))?;
92 Ok(RunAs { uid, gid })
93 }
94}
95
96#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
98pub struct Confinement {
99 pub fs_writable: Vec<PathBuf>,
100 pub fs_readable: Vec<PathBuf>,
101}
102
103impl Confinement {
104 pub fn builder() -> ConfinementBuilder {
105 ConfinementBuilder::default()
106 }
107}
108
109#[derive(Default)]
110pub struct ConfinementBuilder {
111 fs_writable: Vec<PathBuf>,
112 fs_readable: Vec<PathBuf>,
113}
114
115impl ConfinementBuilder {
116 pub fn fs_write(mut self, path: impl Into<PathBuf>) -> Self {
117 self.fs_writable.push(path.into());
118 self
119 }
120
121 pub fn fs_read(mut self, path: impl Into<PathBuf>) -> Self {
122 self.fs_readable.push(path.into());
123 self
124 }
125
126 pub fn build(self) -> Confinement {
127 Confinement {
128 fs_writable: self.fs_writable,
129 fs_readable: self.fs_readable,
130 }
131 }
132}
133
134impl TryFrom<&Sandbox> for Confinement {
135 type Error = SandboxError;
136
137 fn try_from(sandbox: &Sandbox) -> Result<Self, Self::Error> {
138 let mut unsupported = Vec::new();
139 if !sandbox.fs_denied.is_empty() { unsupported.push("fs_denied"); }
140 if !sandbox.extra_deny_syscalls.is_empty() { unsupported.push("extra_deny_syscalls"); }
141 if !sandbox.net_allow.is_empty() { unsupported.push("net_allow"); }
142 if !sandbox.net_deny.is_empty() { unsupported.push("net_deny"); }
143 if !sandbox.net_allow_bind.is_default() { unsupported.push("net_allow_bind"); }
144 if !sandbox.net_deny_bind.is_empty() { unsupported.push("net_deny_bind"); }
145 if sandbox.allows_sysv_ipc() { unsupported.push("extra_allow_syscalls=[\"sysv_ipc\"]"); }
146 if !sandbox.http_allow.is_empty() { unsupported.push("http_allow"); }
147 if !sandbox.http_deny.is_empty() { unsupported.push("http_deny"); }
148 if !sandbox.inject.is_empty() { unsupported.push("http_auth"); }
149 if !sandbox.http_ports.is_empty() { unsupported.push("http_ports"); }
150 if sandbox.http_ca.is_some() { unsupported.push("http_ca"); }
151 if sandbox.http_key.is_some() { unsupported.push("http_key"); }
152 if !sandbox.http_inject_ca.is_empty() { unsupported.push("http_inject_ca"); }
153 if sandbox.http_ca_out.is_some() { unsupported.push("http_ca_out"); }
154 if sandbox.max_memory.is_some() { unsupported.push("max_memory"); }
155 if sandbox.max_processes != 64 { unsupported.push("max_processes"); }
156 if sandbox.max_open_files.is_some() { unsupported.push("max_open_files"); }
157 if sandbox.max_cpu.is_some() { unsupported.push("max_cpu"); }
158 if sandbox.random_seed.is_some() { unsupported.push("random_seed"); }
159 if sandbox.time_start.is_some() { unsupported.push("time_start"); }
160 if sandbox.no_randomize_memory { unsupported.push("no_randomize_memory"); }
161 if sandbox.no_huge_pages { unsupported.push("no_huge_pages"); }
162 if sandbox.no_coredump { unsupported.push("no_coredump"); }
163 if sandbox.deterministic_dirs { unsupported.push("deterministic_dirs"); }
164 if sandbox.workdir.is_some() { unsupported.push("workdir"); }
165 if sandbox.cwd.is_some() { unsupported.push("cwd"); }
166 if sandbox.fs_storage.is_some() { unsupported.push("fs_storage"); }
167 if sandbox.max_disk.is_some() { unsupported.push("max_disk"); }
168 if sandbox.on_exit != BranchAction::Commit { unsupported.push("on_exit"); }
169 if sandbox.on_error != BranchAction::Abort { unsupported.push("on_error"); }
170 if !sandbox.fs_mount.is_empty() { unsupported.push("fs_mount"); }
171 if sandbox.chroot.is_some() { unsupported.push("chroot"); }
172 if sandbox.clean_env { unsupported.push("clean_env"); }
173 if !sandbox.env.is_empty() { unsupported.push("env"); }
174 if sandbox.gpu_devices.is_some() { unsupported.push("gpu_devices"); }
175 if sandbox.cpu_cores.is_some() { unsupported.push("cpu_cores"); }
176 if sandbox.num_cpus.is_some() { unsupported.push("num_cpus"); }
177 if sandbox.port_remap { unsupported.push("port_remap"); }
178 if sandbox.user.is_some() { unsupported.push("user"); }
179 if sandbox.policy_fn.is_some() { unsupported.push("policy_fn"); }
180
181 if !unsupported.is_empty() {
182 return Err(SandboxError::UnsupportedForConfine(unsupported.join(", ")));
183 }
184
185 Ok(Self {
186 fs_writable: sandbox.fs_writable.clone(),
187 fs_readable: sandbox.fs_readable.clone(),
188 })
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
194pub enum BranchAction {
195 #[default]
196 Commit,
197 Abort,
198 Keep,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215#[repr(u32)]
216pub enum StdioMode {
217 Inherit = 0,
219 Piped = 1,
221 Null = 2,
223}
224
225#[derive(Debug, Clone, Copy)]
227struct StdioSpec {
228 stdin: StdioMode,
229 stdout: StdioMode,
230 stderr: StdioMode,
231}
232
233impl StdioSpec {
234 fn capture() -> Self {
237 StdioSpec { stdin: StdioMode::Inherit, stdout: StdioMode::Piped, stderr: StdioMode::Piped }
238 }
239
240 fn inherit() -> Self {
242 StdioSpec { stdin: StdioMode::Inherit, stdout: StdioMode::Inherit, stderr: StdioMode::Inherit }
243 }
244}
245
246struct Runtime {
249 name: String,
250 state: RuntimeState,
251 child_pid: Option<i32>,
252 pidfd: Option<std::os::fd::OwnedFd>,
253 notif_handle: Option<JoinHandle<()>>,
254 throttle_handle: Option<JoinHandle<()>>,
255 loadavg_handle: Option<JoinHandle<()>>,
256 _stdout_read: Option<std::os::fd::OwnedFd>,
257 _stderr_read: Option<std::os::fd::OwnedFd>,
258 _stdin_write: Option<std::os::fd::OwnedFd>,
261 seccomp_cow: Option<crate::cow::seccomp::SeccompCowBranch>,
262 supervisor_resource: Option<Arc<tokio::sync::Mutex<crate::seccomp::state::ResourceState>>>,
263 supervisor_cow: Option<Arc<tokio::sync::Mutex<crate::seccomp::state::CowState>>>,
264 supervisor_network: Option<Arc<tokio::sync::Mutex<crate::seccomp::state::NetworkState>>>,
265 ctrl_fd: Option<std::os::fd::OwnedFd>,
266 stdout_pipe: Option<std::os::fd::OwnedFd>,
267 io_overrides: Option<(Option<i32>, Option<i32>, Option<i32>)>,
268 extra_fds: Vec<(i32, i32)>,
269 http_acl_handle: Option<crate::transparent_proxy::HttpAclProxyHandle>,
270 #[allow(clippy::type_complexity)]
271 on_bind: Option<Box<dyn Fn(&HashMap<u16, u16>) + Send + Sync>>,
272 handlers: Vec<(i64, Arc<dyn crate::seccomp::dispatch::Handler>)>,
273 ready_w: Option<std::os::fd::OwnedFd>,
274}
275
276enum RuntimeState {
278 Created,
279 Running,
280 Paused,
281 Stopped(crate::result::ExitStatus),
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
286pub enum BindPorts {
287 Ports(Vec<u16>),
290 All,
292}
293
294impl Default for BindPorts {
295 fn default() -> Self {
296 BindPorts::Ports(Vec::new())
297 }
298}
299
300impl BindPorts {
301 pub fn is_default(&self) -> bool {
303 matches!(self, BindPorts::Ports(p) if p.is_empty())
304 }
305
306 pub fn is_all(&self) -> bool {
308 matches!(self, BindPorts::All)
309 }
310}
311
312#[derive(Serialize, Deserialize)]
314pub struct Sandbox {
315 pub fs_writable: Vec<PathBuf>,
317 pub fs_readable: Vec<PathBuf>,
318 pub fs_denied: Vec<PathBuf>,
319
320 pub extra_deny_syscalls: Vec<String>,
322 pub extra_allow_syscalls: Vec<String>,
323
324 pub protection_policy: ProtectionPolicy,
335
336 pub net_allow: Vec<NetAllow>,
362 pub net_deny: Vec<NetDeny>,
365 pub net_allow_bind: BindPorts,
370 pub net_deny_bind: Vec<u16>,
374 pub http_allow: Vec<HttpRule>,
376 pub http_deny: Vec<HttpRule>,
377 #[serde(skip)]
382 pub(crate) inject: std::sync::Arc<Vec<crate::credential::InjectRule>>,
383 #[serde(skip)]
387 pub(crate) inject_env_strip: Vec<String>,
388 pub http_ports: Vec<u16>,
391 pub http_ca: Option<PathBuf>,
393 pub http_key: Option<PathBuf>,
395 pub http_inject_ca: Vec<PathBuf>,
397 pub http_ca_out: Option<PathBuf>,
400
401 pub max_memory: Option<ByteSize>,
403 pub max_processes: u32,
404 pub max_open_files: Option<u32>,
405 pub max_cpu: Option<u8>,
406
407 pub random_seed: Option<u64>,
409 pub time_start: Option<SystemTime>,
410 pub no_randomize_memory: bool,
411 pub no_huge_pages: bool,
412 pub no_coredump: bool,
413 pub deterministic_dirs: bool,
414
415 pub workdir: Option<PathBuf>,
417 pub cwd: Option<PathBuf>,
418 pub fs_storage: Option<PathBuf>,
419 pub max_disk: Option<ByteSize>,
420 pub on_exit: BranchAction,
421 pub on_error: BranchAction,
422
423 pub fs_mount: Vec<(PathBuf, PathBuf)>,
425 pub fs_mount_ro: Vec<PathBuf>,
428
429 pub chroot: Option<PathBuf>,
431
432 #[serde(skip)]
440 pub in_child_main: Option<fn()>,
441
442 pub clean_env: bool,
443 pub env: HashMap<String, String>,
444 pub gpu_devices: Option<Vec<u32>>,
446
447 pub cpu_cores: Option<Vec<u32>>,
449 pub num_cpus: Option<u32>,
450 pub port_remap: bool,
451
452 pub no_supervisor: bool,
459
460 pub user: Option<RunAs>,
462
463 #[serde(skip)]
465 pub policy_fn: Option<crate::policy_fn::PolicyCallback>,
466
467 #[serde(skip)]
470 pub name: Option<String>,
471
472 #[serde(skip)]
475 init_fn: Option<Box<dyn FnOnce() + Send + 'static>>,
476
477 #[serde(skip)]
480 work_fn: Option<Arc<dyn Fn(u32) + Send + Sync + 'static>>,
481
482 #[serde(skip)]
484 runtime: Option<Box<Runtime>>,
485
486 #[serde(skip)]
489 restore_skipped: Vec<crate::checkpoint::SkippedFd>,
490}
491
492impl std::fmt::Debug for Sandbox {
493 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494 f.debug_struct("Sandbox")
495 .field("fs_readable", &self.fs_readable)
496 .field("fs_writable", &self.fs_writable)
497 .field("max_memory", &self.max_memory)
498 .field("max_processes", &self.max_processes)
499 .field("policy_fn", &self.policy_fn.as_ref().map(|_| "<callback>"))
500 .field("name", &self.name)
501 .field("runtime", &self.runtime.as_ref().map(|_| "<runtime>"))
502 .finish_non_exhaustive()
503 }
504}
505
506impl Clone for Sandbox {
507 fn clone(&self) -> Self {
518 Self {
519 fs_writable: self.fs_writable.clone(),
520 fs_readable: self.fs_readable.clone(),
521 fs_denied: self.fs_denied.clone(),
522 extra_deny_syscalls: self.extra_deny_syscalls.clone(),
523 extra_allow_syscalls: self.extra_allow_syscalls.clone(),
524 protection_policy: self.protection_policy.clone(),
525 net_allow: self.net_allow.clone(),
526 net_deny: self.net_deny.clone(),
527 net_allow_bind: self.net_allow_bind.clone(),
528 net_deny_bind: self.net_deny_bind.clone(),
529 http_allow: self.http_allow.clone(),
530 http_deny: self.http_deny.clone(),
531 inject: self.inject.clone(),
532 inject_env_strip: self.inject_env_strip.clone(),
533 http_ports: self.http_ports.clone(),
534 http_ca: self.http_ca.clone(),
535 http_key: self.http_key.clone(),
536 http_inject_ca: self.http_inject_ca.clone(),
537 http_ca_out: self.http_ca_out.clone(),
538 max_memory: self.max_memory,
539 max_processes: self.max_processes,
540 max_open_files: self.max_open_files,
541 max_cpu: self.max_cpu,
542 random_seed: self.random_seed,
543 time_start: self.time_start,
544 no_randomize_memory: self.no_randomize_memory,
545 no_huge_pages: self.no_huge_pages,
546 no_coredump: self.no_coredump,
547 deterministic_dirs: self.deterministic_dirs,
548 workdir: self.workdir.clone(),
549 cwd: self.cwd.clone(),
550 fs_storage: self.fs_storage.clone(),
551 max_disk: self.max_disk,
552 on_exit: self.on_exit.clone(),
553 on_error: self.on_error.clone(),
554 fs_mount: self.fs_mount.clone(),
555 fs_mount_ro: self.fs_mount_ro.clone(),
556 chroot: self.chroot.clone(),
557 in_child_main: self.in_child_main,
558 clean_env: self.clean_env,
559 env: self.env.clone(),
560 gpu_devices: self.gpu_devices.clone(),
561 cpu_cores: self.cpu_cores.clone(),
562 num_cpus: self.num_cpus,
563 port_remap: self.port_remap,
564 no_supervisor: self.no_supervisor,
565 user: self.user,
566 policy_fn: self.policy_fn.clone(),
567 name: self.name.clone(),
568 init_fn: None,
571 work_fn: self.work_fn.clone(),
573 runtime: None,
575 restore_skipped: Vec::new(),
577 }
578 }
579}
580
581impl Sandbox {
582 pub fn builder() -> SandboxBuilder {
583 SandboxBuilder::default()
584 }
585
586 pub fn allows_sysv_ipc(&self) -> bool {
588 self.extra_allow_syscalls.iter().any(|s| s == "sysv_ipc")
589 }
590
591 pub fn validate(&self) -> Result<(), SandboxError> {
596 Ok(())
597 }
598
599 pub fn active_protections(&self) -> Result<Vec<(Protection, ProtectionStatus)>, crate::error::SandlockError> {
603 let host_abi = crate::landlock::abi_version().map_err(|e| {
604 crate::error::SandlockError::Runtime(crate::error::SandboxRuntimeError::Confinement(e))
605 })?;
606 Ok(Protection::all()
607 .map(|p| (p, ProtectionStatus::resolve(p, host_abi, &self.protection_policy)))
608 .collect())
609 }
610
611 fn rt(&self) -> &Runtime {
616 self.runtime.as_ref().expect("sandbox not started")
617 }
618
619 fn rt_mut(&mut self) -> &mut Runtime {
620 self.runtime.as_mut().expect("sandbox not started")
621 }
622
623 pub fn set_name(&mut self, name: impl Into<String>) {
630 self.name = Some(name.into());
631 }
632
633 pub fn with_name(mut self, name: impl Into<String>) -> Self {
643 self.name = Some(name.into());
644 self
645 }
646
647 pub fn with_init_fn(mut self, f: impl FnOnce() + Send + 'static) -> Self {
652 self.init_fn = Some(Box::new(f));
653 self
654 }
655
656 pub fn with_work_fn(mut self, f: impl Fn(u32) + Send + Sync + 'static) -> Self {
660 self.work_fn = Some(Arc::new(f));
661 self
662 }
663
664 pub fn instance_name(&self) -> Option<&str> {
666 self.runtime.as_ref().map(|r| r.name.as_str())
667 .or_else(|| self.name.as_deref())
668 }
669
670 pub fn pid(&self) -> Option<i32> {
672 self.runtime.as_ref().and_then(|r| r.child_pid)
673 }
674
675 pub fn is_running(&self) -> bool {
677 self.runtime.as_ref().map(|r| {
678 matches!(r.state, RuntimeState::Running | RuntimeState::Paused)
679 }).unwrap_or(false)
680 }
681
682 pub fn pause(&mut self) -> Result<(), crate::error::SandlockError> {
684 use crate::error::SandboxRuntimeError;
685 let pid = self.runtime.as_ref()
686 .and_then(|rt| rt.child_pid)
687 .ok_or(SandboxRuntimeError::NotRunning)?;
688 let ret = unsafe { libc::killpg(pid, libc::SIGSTOP) };
689 if ret < 0 {
690 return Err(SandboxRuntimeError::Io(std::io::Error::last_os_error()).into());
691 }
692 self.rt_mut().state = RuntimeState::Paused;
693 Ok(())
694 }
695
696 pub fn resume(&mut self) -> Result<(), crate::error::SandlockError> {
698 use crate::error::SandboxRuntimeError;
699 let pid = self.runtime.as_ref()
700 .and_then(|rt| rt.child_pid)
701 .ok_or(SandboxRuntimeError::NotRunning)?;
702 let ret = unsafe { libc::killpg(pid, libc::SIGCONT) };
703 if ret < 0 {
704 return Err(SandboxRuntimeError::Io(std::io::Error::last_os_error()).into());
705 }
706 self.rt_mut().state = RuntimeState::Running;
707 Ok(())
708 }
709
710 pub fn kill(&mut self) -> Result<(), crate::error::SandlockError> {
712 use crate::error::SandboxRuntimeError;
713 let pid = self.runtime.as_ref()
714 .and_then(|rt| rt.child_pid)
715 .ok_or(SandboxRuntimeError::NotRunning)?;
716 let ret = unsafe { libc::killpg(pid, libc::SIGKILL) };
717 if ret < 0 {
718 let err = std::io::Error::last_os_error();
719 if err.raw_os_error() != Some(libc::ESRCH) {
720 return Err(SandboxRuntimeError::Io(err).into());
721 }
722 }
723 Ok(())
724 }
725
726 pub fn set_on_bind(&mut self, cb: impl Fn(&HashMap<u16, u16>) + Send + Sync + 'static) {
728 let _ = self.ensure_runtime();
731 self.rt_mut().on_bind = Some(Box::new(cb));
732 }
733
734 pub async fn port_mappings(&self) -> HashMap<u16, u16> {
736 if let Some(ref rt) = self.runtime {
737 if let Some(ref net) = rt.supervisor_network {
738 let ns = net.lock().await;
739 return ns.port_map.virtual_to_real.clone();
740 }
741 }
742 HashMap::new()
743 }
744
745 pub async fn wait(&mut self) -> Result<crate::result::RunResult, crate::error::SandlockError> {
747 use crate::error::SandboxRuntimeError;
748 use crate::result::RunResult;
749
750 let pid = self.rt().child_pid.ok_or(SandboxRuntimeError::NotRunning)?;
751
752 if let RuntimeState::Stopped(ref es) = self.rt().state {
753 return Ok(RunResult {
754 exit_status: es.clone(),
755 stdout: None,
756 stderr: None,
757 });
758 }
759
760 drop(self.rt_mut()._stdin_write.take());
764
765 let exit_status = match self.rt_mut().pidfd.take() {
774 Some(pidfd) => wait_child_exit_via_pidfd(pidfd, pid).await,
775 None => wait_child_exit_blocking(pid).await,
776 };
777
778 self.rt_mut().state = RuntimeState::Stopped(exit_status.clone());
779
780 let rt = self.rt_mut();
781 if let Some(h) = rt.notif_handle.take() { h.abort(); }
782 if let Some(h) = rt.throttle_handle.take() { h.abort(); }
783 if let Some(h) = rt.loadavg_handle.take() { h.abort(); }
784
785 if let Some(ref cow_state) = self.rt().supervisor_cow.clone() {
786 let mut cow = cow_state.lock().await;
787 self.rt_mut().seccomp_cow = cow.branch.take();
788 }
789
790 let stdout = self.rt_mut()._stdout_read.take().map(sandbox_read_fd_to_end);
791 let stderr = self.rt_mut()._stderr_read.take().map(sandbox_read_fd_to_end);
792
793 Ok(RunResult { exit_status, stdout, stderr })
794 }
795
796 pub async fn create(&mut self, cmd: &[&str]) -> Result<(), crate::error::SandlockError> {
802 self.do_create(cmd, true).await
803 }
804
805 pub async fn create_interactive(&mut self, cmd: &[&str]) -> Result<(), crate::error::SandlockError> {
807 self.do_create(cmd, false).await
808 }
809
810 pub fn start(&mut self) -> Result<(), crate::error::SandlockError> {
814 self.do_start()
815 }
816
817 pub async fn spawn(&mut self, cmd: &[&str]) -> Result<(), crate::error::SandlockError> {
823 self.create(cmd).await?;
824 self.start()?;
825 self.wait_until_exec().await
826 }
827
828 pub async fn spawn_interactive(&mut self, cmd: &[&str]) -> Result<(), crate::error::SandlockError> {
830 self.create_interactive(cmd).await?;
831 self.start()?;
832 self.wait_until_exec().await
833 }
834
835 pub async fn popen(
853 &mut self,
854 cmd: &[&str],
855 stdin: StdioMode,
856 stdout: StdioMode,
857 stderr: StdioMode,
858 ) -> Result<Process<'_>, crate::error::SandlockError> {
859 self.do_create_stdio(cmd, StdioSpec { stdin, stdout, stderr }).await?;
860 self.start()?;
865 Ok(Process { sandbox: self })
866 }
867
868 pub async fn restore_interactive(
886 &mut self,
887 cp: &crate::checkpoint::Checkpoint,
888 ) -> Result<Process<'_>, crate::error::SandlockError> {
889 use crate::error::SandboxRuntimeError;
890
891 let exe = if cp.process_state.exe.is_empty() {
897 "/bin/true".to_string()
898 } else {
899 cp.process_state.exe.clone()
900 };
901 self.create_interactive(&[exe.as_str()]).await?;
902 let pid = self.pid().ok_or(SandboxRuntimeError::NotRunning)?;
903
904 let chroot_root = crate::chroot::resolve::resolve_chroot_root(self.chroot.as_deref())?;
916 let mounts = crate::chroot::resolve::resolve_chroot_mounts(&self.fs_mount);
917
918 let cp = cp.clone();
919 let skipped = tokio::task::spawn_blocking(
920 move || -> Result<Vec<crate::checkpoint::SkippedFd>, crate::error::SandlockError> {
921 crate::checkpoint::capture::ptrace_seize(pid).map_err(|e| {
923 SandboxRuntimeError::Child(format!("restore ptrace seize {pid}: {e}"))
924 })?;
925 let skipped = match crate::checkpoint::resume::restore_into(
930 pid, &cp, chroot_root.as_deref(), &mounts,
931 ) {
932 Ok(s) => s,
933 Err(e) => {
934 let _ = crate::checkpoint::capture::ptrace_detach(pid);
935 return Err(e);
936 }
937 };
938 crate::checkpoint::capture::ptrace_detach(pid).map_err(|e| {
942 SandboxRuntimeError::Child(format!("restore ptrace detach {pid}: {e}"))
943 })?;
944 Ok(skipped)
945 },
946 )
947 .await
948 .map_err(|e| SandboxRuntimeError::Child(format!("restore join error: {e}")))??;
949
950 self.restore_skipped = skipped;
951 Ok(Process { sandbox: self })
952 }
953
954 pub fn restore_skipped(&self) -> &[crate::checkpoint::SkippedFd] {
959 &self.restore_skipped
960 }
961
962 async fn wait_until_exec(&self) -> Result<(), crate::error::SandlockError> {
967 use crate::error::SandboxRuntimeError;
968 let pid = self.pid().ok_or(SandboxRuntimeError::NotRunning)?;
969 let Some(our_exe) = std::fs::read_link("/proc/self/exe").ok() else {
970 return Ok(());
971 };
972 let child_link = format!("/proc/{}/exe", pid);
973 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
974 loop {
975 if let Ok(child_exe) = std::fs::read_link(&child_link) {
976 if child_exe != our_exe {
977 return Ok(());
978 }
979 }
980 if std::time::Instant::now() >= deadline {
981 return Err(SandboxRuntimeError::Child(
982 "child did not exec() within 5s".into(),
983 ).into());
984 }
985 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
986 }
987 }
988
989 #[doc(hidden)]
992 pub async fn create_with_io(
993 &mut self,
994 cmd: &[&str],
995 stdin_fd: Option<std::os::unix::io::RawFd>,
996 stdout_fd: Option<std::os::unix::io::RawFd>,
997 stderr_fd: Option<std::os::unix::io::RawFd>,
998 ) -> Result<(), crate::error::SandlockError> {
999 self.ensure_runtime()?;
1000 self.rt_mut().io_overrides = Some((stdin_fd, stdout_fd, stderr_fd));
1001 self.do_create(cmd, false).await
1002 }
1003
1004 #[doc(hidden)]
1006 pub async fn create_with_gather_io(
1007 &mut self,
1008 cmd: &[&str],
1009 stdin_fd: Option<std::os::unix::io::RawFd>,
1010 stdout_fd: Option<std::os::unix::io::RawFd>,
1011 stderr_fd: Option<std::os::unix::io::RawFd>,
1012 extra_fds: Vec<(i32, i32)>,
1013 ) -> Result<(), crate::error::SandlockError> {
1014 self.ensure_runtime()?;
1015 self.rt_mut().io_overrides = Some((stdin_fd, stdout_fd, stderr_fd));
1016 self.rt_mut().extra_fds = extra_fds;
1017 self.do_create(cmd, false).await
1018 }
1019
1020 pub async fn create_with_in_child_main(
1031 &mut self,
1032 name: &str,
1033 extra_fds: Vec<(i32, i32)>,
1034 entrypoint: fn(),
1035 ) -> Result<(), crate::error::SandlockError> {
1036 self.ensure_runtime()?;
1037 self.in_child_main = Some(entrypoint);
1038 self.rt_mut().extra_fds = extra_fds;
1039 self.do_create(&[name], false).await
1040 }
1041
1042 pub(crate) async fn freeze(&self) -> Result<(), crate::error::SandlockError> {
1044 use crate::error::{SandboxRuntimeError, SandlockError};
1045 let rt = self.runtime.as_ref().ok_or(SandlockError::Runtime(SandboxRuntimeError::NotRunning))?;
1046 let pid = rt.child_pid.ok_or(SandlockError::Runtime(SandboxRuntimeError::NotRunning))?;
1047 if let Some(ref resource) = rt.supervisor_resource {
1048 let mut rs = resource.lock().await;
1049 rs.hold_forks = true;
1050 }
1051 unsafe { libc::killpg(pid, libc::SIGSTOP); }
1052 Ok(())
1053 }
1054
1055 pub(crate) async fn thaw(&self) -> Result<(), crate::error::SandlockError> {
1057 use crate::error::{SandboxRuntimeError, SandlockError};
1058 let rt = self.runtime.as_ref().ok_or(SandlockError::Runtime(SandboxRuntimeError::NotRunning))?;
1059 let pid = rt.child_pid.ok_or(SandlockError::Runtime(SandboxRuntimeError::NotRunning))?;
1060 if let Some(ref resource) = rt.supervisor_resource {
1061 let mut rs = resource.lock().await;
1062 rs.hold_forks = false;
1063 rs.held_notif_ids.clear();
1064 }
1065 unsafe { libc::killpg(pid, libc::SIGCONT); }
1066 Ok(())
1067 }
1068
1069 pub async fn checkpoint(&self) -> Result<crate::checkpoint::Checkpoint, crate::error::SandlockError> {
1071 use crate::error::{SandboxRuntimeError, SandlockError};
1072 let pid = self.runtime.as_ref()
1073 .and_then(|rt| rt.child_pid)
1074 .ok_or(SandlockError::Runtime(SandboxRuntimeError::NotRunning))?;
1075 self.checkpoint_pid(pid).await
1076 }
1077
1078 pub async fn checkpoint_pid(&self, target_pid: i32) -> Result<crate::checkpoint::Checkpoint, crate::error::SandlockError> {
1083 use crate::error::{SandboxRuntimeError, SandlockError};
1084 if target_pid <= 0 {
1085 return Err(SandlockError::Runtime(SandboxRuntimeError::NotRunning));
1086 }
1087 self.freeze().await?;
1088 let cp = crate::checkpoint::capture(target_pid, self);
1089 self.thaw().await?;
1090 cp
1091 }
1092
1093 pub async fn run(
1108 &mut self,
1109 cmd: &[&str],
1110 ) -> Result<crate::result::RunResult, crate::error::SandlockError> {
1111 self.do_create(cmd, true).await?;
1112 self.do_start()?;
1113 self.wait().await
1114 }
1115
1116 pub async fn run_interactive(
1118 &mut self,
1119 cmd: &[&str],
1120 ) -> Result<crate::result::RunResult, crate::error::SandlockError> {
1121 self.do_create(cmd, false).await?;
1122 self.do_start()?;
1123 self.wait().await
1124 }
1125
1126 pub async fn run_with_handlers<I, S, H>(
1128 &mut self,
1129 cmd: &[&str],
1130 handlers: I,
1131 ) -> Result<crate::result::RunResult, crate::error::SandlockError>
1132 where
1133 I: IntoIterator<Item = (S, H)>,
1134 S: TryInto<crate::seccomp::syscall::Syscall, Error = crate::seccomp::syscall::SyscallError>,
1135 H: crate::seccomp::dispatch::Handler,
1136 {
1137 let pending = sandbox_collect_handlers(handlers, self)?;
1138 self.ensure_runtime()?;
1139 self.rt_mut().handlers = pending;
1140 self.do_create(cmd, true).await?;
1141 self.do_start()?;
1142 self.wait().await
1143 }
1144
1145 pub async fn run_interactive_with_handlers<I, S, H>(
1147 &mut self,
1148 cmd: &[&str],
1149 handlers: I,
1150 ) -> Result<crate::result::RunResult, crate::error::SandlockError>
1151 where
1152 I: IntoIterator<Item = (S, H)>,
1153 S: TryInto<crate::seccomp::syscall::Syscall, Error = crate::seccomp::syscall::SyscallError>,
1154 H: crate::seccomp::dispatch::Handler,
1155 {
1156 let pending = sandbox_collect_handlers(handlers, self)?;
1157 self.ensure_runtime()?;
1158 self.rt_mut().handlers = pending;
1159 self.do_create(cmd, false).await?;
1160 self.do_start()?;
1161 self.wait().await
1162 }
1163
1164 pub async fn dry_run(
1166 &mut self,
1167 cmd: &[&str],
1168 ) -> Result<crate::dry_run::DryRunResult, crate::error::SandlockError> {
1169 self.on_exit = BranchAction::Keep;
1170 self.on_error = BranchAction::Keep;
1171 self.do_create(cmd, true).await?;
1172 self.do_start()?;
1173 let run_result = self.wait().await?;
1174 let changes = self.collect_changes().await;
1175 self.do_abort().await;
1176 Ok(crate::dry_run::DryRunResult { run_result, changes })
1177 }
1178
1179 pub async fn dry_run_interactive(
1181 &mut self,
1182 cmd: &[&str],
1183 ) -> Result<crate::dry_run::DryRunResult, crate::error::SandlockError> {
1184 self.on_exit = BranchAction::Keep;
1185 self.on_error = BranchAction::Keep;
1186 self.do_create(cmd, false).await?;
1187 self.do_start()?;
1188 let run_result = self.wait().await?;
1189 let changes = self.collect_changes().await;
1190 self.do_abort().await;
1191 Ok(crate::dry_run::DryRunResult { run_result, changes })
1192 }
1193
1194 pub async fn fork(&mut self, n: u32) -> Result<Vec<Sandbox>, crate::error::SandlockError> {
1200 use crate::error::SandboxRuntimeError;
1201 use std::os::fd::{FromRawFd, OwnedFd};
1202
1203 let init_fn = self.init_fn.take()
1206 .ok_or_else(|| SandboxRuntimeError::Child("fork() requires init_fn and work_fn — use SandboxBuilder::init_fn() / work_fn() or Sandbox::with_init_fn() / with_work_fn()".into()))?;
1207 let work_fn = self.work_fn.take()
1208 .ok_or_else(|| SandboxRuntimeError::Child("fork() requires init_fn and work_fn — use SandboxBuilder::init_fn() / work_fn() or Sandbox::with_init_fn() / with_work_fn()".into()))?;
1209
1210 self.ensure_runtime()?;
1212
1213 let sandbox_cfg = self.clone(); let mut ctrl_fds = [0i32; 2];
1216 if unsafe { libc::pipe2(ctrl_fds.as_mut_ptr(), 0) } < 0 {
1217 return Err(SandboxRuntimeError::Io(std::io::Error::last_os_error()).into());
1218 }
1219 let ctrl_parent = unsafe { OwnedFd::from_raw_fd(ctrl_fds[0]) };
1220 let ctrl_child_fd = ctrl_fds[1];
1221
1222 let mut pipe_read_ends: Vec<OwnedFd> = Vec::with_capacity(n as usize);
1223 let mut pipe_write_fds: Vec<i32> = Vec::with_capacity(n as usize);
1224 for _ in 0..n {
1225 let mut pfds = [0i32; 2];
1226 if unsafe { libc::pipe(pfds.as_mut_ptr()) } >= 0 {
1227 pipe_read_ends.push(unsafe { OwnedFd::from_raw_fd(pfds[0]) });
1228 pipe_write_fds.push(pfds[1]);
1229 } else {
1230 pipe_write_fds.push(-1);
1231 }
1232 }
1233
1234 let pid = unsafe { libc::fork() };
1235 if pid < 0 {
1236 unsafe { libc::close(ctrl_child_fd) };
1237 return Err(SandboxRuntimeError::Fork(std::io::Error::last_os_error()).into());
1238 }
1239
1240 if pid == 0 {
1241 drop(ctrl_parent);
1242 unsafe { libc::setpgid(0, 0) };
1243 unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
1244 unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
1245
1246 let _ = crate::landlock::confine(&sandbox_cfg);
1247
1248 let deny = crate::context::blocklist_syscall_numbers(&sandbox_cfg);
1249 let args = crate::context::arg_filters(&sandbox_cfg);
1250 let filter = match crate::seccomp::bpf::assemble_filter(&[], &deny, &args) {
1251 Ok(f) => f,
1252 Err(_) => unsafe { libc::_exit(1) },
1253 };
1254 let _ = crate::seccomp::bpf::install_deny_filter(&filter);
1255
1256 init_fn();
1257
1258 drop(pipe_read_ends);
1259 crate::fork::fork_ready_loop_fn(ctrl_child_fd, n, &*work_fn, &pipe_write_fds);
1260 unsafe { libc::_exit(0) };
1261 }
1262
1263 unsafe { libc::close(ctrl_child_fd) };
1264 for wfd in &pipe_write_fds {
1265 if *wfd >= 0 { unsafe { libc::close(*wfd) }; }
1266 }
1267 self.rt_mut().child_pid = Some(pid);
1268 self.rt_mut().state = RuntimeState::Running;
1269
1270 let ctrl_fd = ctrl_parent.as_raw_fd();
1271 let mut pid_buf = vec![0u8; n as usize * 4];
1272 sandbox_read_exact(ctrl_fd, &mut pid_buf);
1273
1274 let clone_pids: Vec<i32> = pid_buf.chunks(4)
1275 .map(|c| u32::from_be_bytes(c.try_into().unwrap_or([0; 4])) as i32)
1276 .collect();
1277 let live_count = clone_pids.iter().filter(|&&p| p > 0).count();
1278
1279 let mut code_buf = vec![0u8; live_count * 4];
1280 sandbox_read_exact(ctrl_fd, &mut code_buf);
1281 self.rt_mut().ctrl_fd = Some(ctrl_parent);
1282
1283 let mut status = 0i32;
1284 unsafe { libc::waitpid(pid, &mut status, 0) };
1285
1286 let mut code_idx = 0;
1287 let mut clones = Vec::with_capacity(live_count);
1288 let mut pipe_iter = pipe_read_ends.into_iter();
1289
1290 let rt_name = self.rt().name.clone();
1291 for &clone_pid in &clone_pids {
1292 let pipe = pipe_iter.next();
1293 if clone_pid <= 0 { continue; }
1294
1295 let code = i32::from_be_bytes(
1296 code_buf[code_idx * 4..(code_idx + 1) * 4].try_into().unwrap_or([0; 4])
1297 );
1298 code_idx += 1;
1299
1300 let mut clone_sb = sandbox_cfg.clone();
1301 let clone_name = format!("{}-fork-{}", rt_name, clone_pid);
1302 clone_sb.runtime = Some(Box::new(Runtime {
1303 name: clone_name,
1304 state: RuntimeState::Stopped(if code == 0 {
1305 crate::result::ExitStatus::Code(0)
1306 } else if code > 0 {
1307 crate::result::ExitStatus::Code(code)
1308 } else {
1309 crate::result::ExitStatus::Killed
1310 }),
1311 child_pid: Some(clone_pid),
1312 pidfd: None,
1313 notif_handle: None,
1314 throttle_handle: None,
1315 loadavg_handle: None,
1316 _stdout_read: None,
1317 _stderr_read: None,
1318 _stdin_write: None,
1319 seccomp_cow: None,
1320 supervisor_resource: None,
1321 supervisor_cow: None,
1322 supervisor_network: None,
1323 ctrl_fd: None,
1324 stdout_pipe: pipe,
1325 io_overrides: None,
1326 extra_fds: Vec::new(),
1327 http_acl_handle: None,
1328 on_bind: None,
1329 handlers: Vec::new(),
1330 ready_w: None,
1331 }));
1332 clones.push(clone_sb);
1333 }
1334
1335 Ok(clones)
1336 }
1337
1338 pub async fn reduce(
1340 &self,
1341 cmd: &[&str],
1342 clones: &mut [Sandbox],
1343 ) -> Result<crate::result::RunResult, crate::error::SandlockError> {
1344 use crate::error::SandboxRuntimeError;
1345
1346 let mut combined = Vec::new();
1347 for clone in clones.iter_mut() {
1348 if let Some(ref mut rt) = clone.runtime {
1349 if let Some(pipe) = rt.stdout_pipe.take() {
1350 combined.extend_from_slice(&sandbox_read_fd_to_end(pipe));
1351 }
1352 }
1353 }
1354
1355 let mut stdin_fds = [0i32; 2];
1356 if unsafe { libc::pipe2(stdin_fds.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {
1357 return Err(SandboxRuntimeError::Io(std::io::Error::last_os_error()).into());
1358 }
1359
1360 let write_fd = stdin_fds[1];
1361 let write_handle = tokio::task::spawn_blocking(move || {
1362 unsafe {
1363 libc::write(write_fd, combined.as_ptr() as *const _, combined.len());
1364 libc::close(write_fd);
1365 }
1366 });
1367
1368 let base_name = self.instance_name()
1369 .unwrap_or("sandbox")
1370 .to_owned();
1371 let reducer_name = base_name + "-reduce";
1372 let mut reducer = self.clone().with_name(reducer_name);
1373 reducer.ensure_runtime()?;
1374 reducer.rt_mut().io_overrides = Some((Some(stdin_fds[0]), None, None));
1375 reducer.do_create(cmd, true).await?;
1376 reducer.do_start()?;
1377 unsafe { libc::close(stdin_fds[0]) };
1378
1379 let _ = write_handle.await;
1380 reducer.wait().await
1381 }
1382
1383 pub(crate) fn has_unix_fs_gate(&self) -> bool {
1389 !self.fs_readable.is_empty() || !self.fs_writable.is_empty()
1390 }
1391
1392 fn ensure_runtime(&mut self) -> Result<(), crate::error::SandlockError> {
1398 if self.runtime.is_some() {
1399 return Ok(());
1400 }
1401 let name = sandbox_resolve_name(self.name.as_deref())?;
1402 self.runtime = Some(Box::new(Runtime {
1403 name,
1404 state: RuntimeState::Created,
1405 child_pid: None,
1406 pidfd: None,
1407 notif_handle: None,
1408 throttle_handle: None,
1409 loadavg_handle: None,
1410 _stdout_read: None,
1411 _stderr_read: None,
1412 _stdin_write: None,
1413 seccomp_cow: None,
1414 supervisor_resource: None,
1415 supervisor_cow: None,
1416 supervisor_network: None,
1417 ctrl_fd: None,
1418 stdout_pipe: None,
1419 io_overrides: None,
1420 extra_fds: Vec::new(),
1421 http_acl_handle: None,
1422 on_bind: None,
1423 handlers: Vec::new(),
1424 ready_w: None,
1425 }));
1426 Ok(())
1427 }
1428
1429 async fn collect_changes(&self) -> Vec<crate::dry_run::Change> {
1434 if let Some(ref rt) = self.runtime {
1435 if let Some(ref cow) = rt.seccomp_cow {
1436 return cow.changes().unwrap_or_default();
1437 }
1438 }
1439 Vec::new()
1440 }
1441
1442 async fn do_abort(&mut self) {
1443 if let Some(ref mut rt) = self.runtime {
1444 if let Some(ref mut cow) = rt.seccomp_cow {
1445 let _ = cow.abort();
1446 }
1447 }
1448 }
1449
1450 async fn do_create(&mut self, cmd: &[&str], capture: bool) -> Result<(), crate::error::SandlockError> {
1458 let stdio = if capture { StdioSpec::capture() } else { StdioSpec::inherit() };
1459 self.do_create_stdio(cmd, stdio).await
1460 }
1461
1462 async fn do_create_stdio(&mut self, cmd: &[&str], stdio: StdioSpec) -> Result<(), crate::error::SandlockError> {
1463 use std::ffi::CString;
1464 use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
1465 use crate::error::SandboxRuntimeError;
1466 use crate::context::{PipePair, read_u32_fd};
1467 use crate::network;
1468 use crate::seccomp::ctx::SupervisorCtx;
1469 use crate::seccomp::notif::{self, NotifPolicy};
1470 use crate::seccomp::state::{ChrootState, CowState, NetworkState, PolicyFnState, ProcfsState, ResourceState, TimeRandomState};
1471 use crate::sys::syscall;
1472 use std::time::Duration;
1473
1474 self.ensure_runtime()?;
1475
1476 if !matches!(self.rt().state, RuntimeState::Created) {
1477 return Err(SandboxRuntimeError::Child("sandbox already spawned".into()).into());
1478 }
1479
1480 if cmd.is_empty() {
1481 return Err(SandboxRuntimeError::Child("empty command".into()).into());
1482 }
1483
1484 let chroot_root = crate::chroot::resolve::resolve_chroot_root(self.chroot.as_deref())?;
1488
1489 if !self.http_inject_ca.is_empty() {
1494 let mounts = crate::chroot::resolve::resolve_chroot_mounts(&self.fs_mount);
1495 for p in &self.http_inject_ca {
1496 let host = resolve_sandbox_path_to_host(p, chroot_root.as_deref(), &mounts);
1497 if !host.exists() {
1498 return Err(SandboxRuntimeError::Child(format!(
1499 "--http-inject-ca {:?} not found in the sandbox view (resolved to {:?}); \
1500 the CA cannot be injected into it. Point it at the trust bundle the \
1501 workload actually reads (e.g. /etc/ssl/certs/ca-certificates.crt, or \
1502 certifi's cacert.pem).",
1503 p, host
1504 ))
1505 .into());
1506 }
1507 }
1508 }
1509
1510 let c_cmd: Vec<CString> = cmd
1511 .iter()
1512 .map(|s| CString::new(*s).map_err(|_| SandboxRuntimeError::Child("invalid command string".into())))
1513 .collect::<Result<Vec<_>, _>>()?;
1514
1515 let no_supervisor = self.no_supervisor;
1516
1517 let pipes = PipePair::new().map_err(SandboxRuntimeError::Io)?;
1518
1519 let resolved_net_allow = network::resolve_net_allow(&self.net_allow)
1520 .await
1521 .map_err(SandboxRuntimeError::Io)?;
1522 let virtual_etc_hosts = network::compose_virtual_etc_hosts(
1529 self.chroot.as_deref(),
1530 &resolved_net_allow.concrete_host_entries,
1531 );
1532
1533 let mut ca_inject_pem: Option<std::sync::Arc<Vec<u8>>> = None;
1534 if !self.http_allow.is_empty() || !self.http_deny.is_empty() {
1535 let generate = !self.http_inject_ca.is_empty();
1537 let ca_material = crate::transparent_proxy::resolve_ca(
1538 self.http_ca.as_deref(),
1539 self.http_key.as_deref(),
1540 generate,
1541 )
1542 .map_err(SandboxRuntimeError::Io)?;
1543
1544 if let (Some(out), Some(cm)) = (self.http_ca_out.as_deref(), ca_material.as_ref()) {
1546 std::fs::write(out, cm.cert_pem.as_bytes()).map_err(SandboxRuntimeError::Io)?;
1547 }
1548
1549 if !self.http_inject_ca.is_empty() {
1551 if let Some(cm) = ca_material.as_ref() {
1552 ca_inject_pem = Some(std::sync::Arc::new(cm.cert_pem.clone().into_bytes()));
1553 }
1554 }
1555
1556 let (cert_pem, key_pem) = match ca_material.as_ref() {
1557 Some(cm) => (Some(cm.cert_pem.as_str()), Some(cm.key_pem.as_str())),
1558 None => (None, None),
1559 };
1560
1561 let handle = crate::transparent_proxy::spawn_transparent_proxy(
1562 self.http_allow.clone(),
1563 self.http_deny.clone(),
1564 std::sync::Arc::clone(&self.inject),
1565 cert_pem,
1566 key_pem,
1567 )
1568 .await
1569 .map_err(SandboxRuntimeError::Io)?;
1570 self.rt_mut().http_acl_handle = Some(handle);
1571 }
1572
1573 let seccomp_cow_branch = if !no_supervisor && self.workdir.is_some() {
1579 let workdir = self.workdir.as_ref().unwrap().clone();
1580 let storage = self.fs_storage.clone();
1581 let max_disk = self.max_disk.map(|b| b.0).unwrap_or(0);
1582 match crate::cow::seccomp::SeccompCowBranch::create(&workdir, storage.as_deref(), max_disk) {
1583 Ok(branch) => {
1584 self.fs_readable.push(branch.upper_dir().to_path_buf());
1585 Some(branch)
1586 }
1587 Err(e) => {
1588 eprintln!("sandlock: seccomp COW branch creation failed: {}", e);
1589 None
1590 }
1591 }
1592 } else {
1593 None
1594 };
1595
1596 let handler_syscalls: Vec<i64> = self.rt().handlers.iter().map(|(nr, _)| *nr).collect();
1597 let resolved_sandbox_name = self.rt().name.clone();
1598 let resolved = crate::resolved::ResolvedSandbox::from_sandbox(
1599 self,
1600 Some(resolved_sandbox_name.as_str()),
1601 &handler_syscalls,
1602 );
1603
1604 let stdin_p = if stdio.stdin == StdioMode::Piped {
1610 Some(make_cloexec_pipe().map_err(SandboxRuntimeError::Io)?)
1611 } else {
1612 None
1613 };
1614 let stdout_p = if stdio.stdout == StdioMode::Piped {
1615 Some(make_cloexec_pipe().map_err(SandboxRuntimeError::Io)?)
1616 } else {
1617 None
1618 };
1619 let stderr_p = if stdio.stderr == StdioMode::Piped {
1620 Some(make_cloexec_pipe().map_err(SandboxRuntimeError::Io)?)
1621 } else {
1622 None
1623 };
1624
1625 let parent_pid = unsafe { libc::getpid() };
1628
1629 let pid = unsafe { libc::fork() };
1630 if pid < 0 {
1631 return Err(SandboxRuntimeError::Fork(std::io::Error::last_os_error()).into());
1632 }
1633
1634 if pid == 0 {
1635 let io_overrides = self.rt().io_overrides;
1637 if let Some((stdin_fd, stdout_fd, stderr_fd)) = io_overrides {
1638 if let Some(fd) = stdin_fd { unsafe { libc::dup2(fd, 0) }; }
1639 if let Some(fd) = stdout_fd { unsafe { libc::dup2(fd, 1) }; }
1640 if let Some(fd) = stderr_fd { unsafe { libc::dup2(fd, 2) }; }
1641 }
1642
1643 let extra_fds_copy = self.rt().extra_fds.clone();
1644 for &(target_fd, source_fd) in &extra_fds_copy {
1645 unsafe { libc::dup2(source_fd, target_fd) };
1646 }
1647
1648 let safe_in = if stdio.stdin == StdioMode::Piped {
1661 stdin_p.as_ref().map(|(r, _)| unsafe { relocate_high(r.as_raw_fd()) })
1662 } else {
1663 None
1664 };
1665 let safe_out = if stdio.stdout == StdioMode::Piped {
1666 stdout_p.as_ref().map(|(_, w)| unsafe { relocate_high(w.as_raw_fd()) })
1667 } else {
1668 None
1669 };
1670 let safe_err = if stdio.stderr == StdioMode::Piped {
1671 stderr_p.as_ref().map(|(_, w)| unsafe { relocate_high(w.as_raw_fd()) })
1672 } else {
1673 None
1674 };
1675 std::mem::forget(stdin_p);
1676 std::mem::forget(stdout_p);
1677 std::mem::forget(stderr_p);
1678 unsafe {
1679 wire_child_stdio(stdio.stdin, 0, safe_in, libc::O_RDONLY);
1680 wire_child_stdio(stdio.stdout, 1, safe_out, libc::O_WRONLY);
1681 wire_child_stdio(stdio.stderr, 2, safe_err, libc::O_WRONLY);
1682 }
1683
1684 let gather_keep_fds: Vec<i32> = extra_fds_copy.iter().map(|&(target, _)| target).collect();
1685
1686 let extra_syscalls: Vec<u32> = self.rt().handlers
1687 .iter()
1688 .map(|h| h.0 as u32)
1689 .collect();
1690
1691 let sandbox_name = self.rt().name.clone();
1692 let entry = match self.in_child_main {
1695 Some(run) => context::ChildEntry::InProcess { name: c_cmd[0].as_c_str(), run },
1696 None => context::ChildEntry::Exec(&c_cmd),
1697 };
1698 context::confine_child(context::ChildSpawnArgs {
1699 sandbox: self,
1700 entry,
1701 pipes: &pipes,
1702 no_supervisor,
1703 keep_fds: &gather_keep_fds,
1704 sandbox_name: Some(sandbox_name.as_str()),
1705 extra_syscalls: &extra_syscalls,
1706 parent_pid,
1707 });
1708 }
1709
1710 drop(pipes.notif_w);
1712 drop(pipes.ready_r);
1713
1714 self.rt_mut()._stdin_write = stdin_p.map(|(_r, w)| w);
1715 self.rt_mut()._stdout_read = stdout_p.map(|(r, _w)| r);
1716 self.rt_mut()._stderr_read = stderr_p.map(|(r, _w)| r);
1717
1718 self.rt_mut().child_pid = Some(pid);
1719 let pidfd = match syscall::pidfd_open(pid as u32, 0) {
1723 Ok(fd) => Some(fd),
1724 Err(_) => None,
1725 };
1726
1727 let notif_fd_num = read_u32_fd(pipes.notif_r.as_raw_fd())
1728 .map_err(|e| SandboxRuntimeError::Child(format!("read notif fd from child: {}", e)))?;
1729
1730 let is_nested_mode = notif_fd_num == 0;
1731
1732 let notif_fd = if is_nested_mode {
1733 None
1734 } else if let Some(ref pfd) = pidfd {
1735 Some(syscall::pidfd_getfd(pfd, notif_fd_num as i32, 0)
1736 .map_err(|e| SandboxRuntimeError::Child(format!("pidfd_getfd: {}", e)))?)
1737 } else {
1738 let path = format!("/proc/{}/fd/{}", pid, notif_fd_num);
1739 let cpath = CString::new(path).unwrap();
1740 let raw = unsafe { libc::open(cpath.as_ptr(), libc::O_RDWR) };
1741 if raw < 0 {
1742 return Err(SandboxRuntimeError::Child("failed to open notif fd from /proc".into()).into());
1743 }
1744 Some(unsafe { OwnedFd::from_raw_fd(raw) })
1745 };
1746
1747 if let Some(notif_fd) = notif_fd {
1748 if self.time_start.is_some() || self.random_seed.is_some() {
1749 let time_offset = self.time_start.map(|t| crate::time::calculate_time_offset(t));
1750 if let Err(e) = crate::vdso::patch(pid, time_offset, self.random_seed.is_some()) {
1751 eprintln!("sandlock: pre-exec vDSO patching failed (will retry after exec): {}", e);
1752 }
1753 }
1754
1755 let time_offset_val = self.time_start
1756 .map(|t| crate::time::calculate_time_offset(t))
1757 .unwrap_or(0);
1758
1759 let rt_name = self.rt().name.clone();
1760 let notif_policy = NotifPolicy {
1761 max_memory_bytes: self.max_memory.map(|m| m.0).unwrap_or(0),
1762 max_processes: self.max_processes,
1763 has_memory_limit: resolved.features.memory_limit,
1764 has_net_destination_policy: resolved.features.network_destination_policy,
1765 has_bind_denylist: resolved.features.bind_denylist,
1766 has_unix_fs_gate: resolved.features.unix_fs_gate,
1767 has_random_seed: resolved.features.random_seed,
1768 has_time_start: resolved.features.time_start,
1769 argv_safety_required: resolved.features.argv_safety_required,
1770 time_offset: time_offset_val,
1771 num_cpus: self.num_cpus,
1772 port_remap: resolved.features.port_remap,
1773 cow_enabled: resolved.features.cow,
1774 chroot_root: chroot_root.clone(),
1775 chroot_readable: self.fs_readable.clone(),
1776 chroot_writable: self.fs_writable.clone(),
1777 chroot_denied: self.fs_denied.clone(),
1778 chroot_mounts: crate::chroot::resolve::resolve_chroot_mounts(&self.fs_mount),
1779 chroot_mount_ro: self.fs_mount_ro.clone(),
1780 deterministic_dirs: self.deterministic_dirs,
1781 virtual_hostname: Some(rt_name),
1782 has_http_acl: resolved.features.http_acl,
1783 virtual_etc_hosts,
1784 ca_inject_paths: self.http_inject_ca.clone(),
1785 ca_inject_pem: ca_inject_pem.clone(),
1786 };
1787
1788 use rand::SeedableRng;
1789 use rand_chacha::ChaCha8Rng;
1790
1791 let random_state = self.random_seed.map(|seed| ChaCha8Rng::seed_from_u64(seed));
1792 let time_offset = self.time_start.map(|t| crate::time::calculate_time_offset(t));
1793
1794 let time_random_state = TimeRandomState::new(time_offset, random_state);
1795
1796 let mut net_state = NetworkState::new();
1797 if !self.net_deny.is_empty() {
1798 let resolved_deny = network::resolve_net_deny(&self.net_deny);
1799 net_state.tcp_policy = resolved_deny.tcp;
1800 net_state.udp_policy = resolved_deny.udp;
1801 net_state.icmp_policy = resolved_deny.icmp;
1802 } else {
1803 let no_rules = self.net_allow.is_empty();
1804 let policy_from = |resolved: &network::ResolvedNetAllow| {
1805 if no_rules || resolved.any_ip_all_ports {
1806 crate::seccomp::notif::NetworkPolicy::Unrestricted
1807 } else {
1808 use crate::seccomp::notif::PortAllow;
1809 let per_ip = resolved
1810 .per_ip
1811 .iter()
1812 .map(|(ip, ports)| {
1813 let allow = if resolved.per_ip_all_ports.contains(ip) {
1814 PortAllow::Any
1815 } else {
1816 PortAllow::Specific(ports.clone())
1817 };
1818 (*ip, allow)
1819 })
1820 .collect();
1821 crate::seccomp::notif::NetworkPolicy::AllowList {
1822 per_ip,
1823 cidrs: resolved.cidrs.clone(),
1824 any_ip_ports: resolved.any_ip_ports.clone(),
1825 }
1826 }
1827 };
1828 net_state.tcp_policy = policy_from(&resolved_net_allow.tcp);
1829 net_state.udp_policy = policy_from(&resolved_net_allow.udp);
1830 net_state.icmp_policy = policy_from(&resolved_net_allow.icmp);
1831 }
1832 net_state.http_acl_addr = self.rt().http_acl_handle.as_ref().map(|h| h.addr);
1833 net_state.http_acl_ports = self.http_ports.iter().copied().collect();
1834 net_state.http_acl_orig_dest = self.rt().http_acl_handle.as_ref().map(|h| h.orig_dest.clone());
1835 net_state.bind_deny_ports = self.net_deny_bind.iter().copied().collect();
1836 if let Some(cb) = self.rt_mut().on_bind.take() {
1837 net_state.port_map.on_bind = Some(cb);
1838 }
1839
1840 let procfs_state = ProcfsState::new();
1841
1842 let mut res_state = ResourceState::new(
1843 notif_policy.max_memory_bytes,
1844 notif_policy.max_processes,
1845 );
1846 res_state.proc_count = 1;
1847
1848 let mut cow_state = CowState::new();
1849 cow_state.branch = seccomp_cow_branch;
1850
1851 let mut policy_fn_state = PolicyFnState::new();
1852
1853 for path in &self.fs_denied {
1854 policy_fn_state.denied.deny(&path.to_string_lossy());
1857 }
1858
1859 if let Some(ref callback) = self.policy_fn {
1860 let mut allowed_ips: std::collections::HashSet<std::net::IpAddr> =
1861 std::collections::HashSet::new();
1862 for p in [&net_state.tcp_policy, &net_state.udp_policy, &net_state.icmp_policy] {
1863 if let crate::seccomp::notif::NetworkPolicy::AllowList { per_ip, cidrs, .. } = p {
1864 allowed_ips.extend(per_ip.keys().copied());
1865 for (net, _) in cidrs {
1868 if net.is_single_host() {
1869 allowed_ips.insert(net.addr);
1870 }
1871 }
1872 }
1873 }
1874 let live = crate::policy_fn::LivePolicy {
1875 allowed_ips,
1876 max_memory_bytes: notif_policy.max_memory_bytes,
1877 max_processes: notif_policy.max_processes,
1878 };
1879 let ceiling = live.clone();
1880 let live = std::sync::Arc::new(std::sync::RwLock::new(live));
1881 let denied = policy_fn_state.denied.clone();
1882 let pid_overrides = net_state.pid_ip_overrides.clone();
1883 policy_fn_state.live_policy = Some(live.clone());
1884 let tx = crate::policy_fn::spawn_policy_fn(
1885 callback.clone(), live, ceiling, pid_overrides, denied,
1886 );
1887 policy_fn_state.event_tx = Some(tx);
1888 }
1889
1890 let chroot_state = ChrootState::new();
1891
1892 let notif_raw_fd = notif_fd.as_raw_fd();
1893 let child_pidfd_raw = pidfd.as_ref().map(|pfd| pfd.as_raw_fd());
1894
1895 let res_state = Arc::new(tokio::sync::Mutex::new(res_state));
1896 self.rt_mut().supervisor_resource = Some(Arc::clone(&res_state));
1897
1898 let cow_state = Arc::new(tokio::sync::Mutex::new(cow_state));
1899 self.rt_mut().supervisor_cow = Some(Arc::clone(&cow_state));
1900
1901 let net_state = Arc::new(tokio::sync::Mutex::new(net_state));
1902 self.rt_mut().supervisor_network = Some(Arc::clone(&net_state));
1903
1904 let procfs_state = Arc::new(tokio::sync::Mutex::new(procfs_state));
1905 let time_random_state = Arc::new(tokio::sync::Mutex::new(time_random_state));
1906 let policy_fn_state = Arc::new(tokio::sync::Mutex::new(policy_fn_state));
1907 let chroot_state = Arc::new(tokio::sync::Mutex::new(chroot_state));
1908 let processes = Arc::new(crate::seccomp::state::ProcessIndex::new());
1909
1910 let ctx = Arc::new(SupervisorCtx {
1911 resource: Arc::clone(&res_state),
1912 cow: Arc::clone(&cow_state),
1913 procfs: Arc::clone(&procfs_state),
1914 network: Arc::clone(&net_state),
1915 time_random: Arc::clone(&time_random_state),
1916 policy_fn: Arc::clone(&policy_fn_state),
1917 chroot: Arc::clone(&chroot_state),
1918 netlink: Arc::new(crate::netlink::NetlinkState::new()),
1919 processes: Arc::clone(&processes),
1920 policy: Arc::new(notif_policy),
1921 child_pidfd: child_pidfd_raw,
1922 notif_fd: notif_raw_fd,
1923 });
1924
1925 let handlers = std::mem::take(&mut self.rt_mut().handlers);
1926 let (startup_tx, startup_rx) = tokio::sync::oneshot::channel();
1927 self.rt_mut().notif_handle = Some(tokio::spawn(
1928 notif::supervisor(notif_fd, ctx, handlers, startup_tx),
1929 ));
1930 match startup_rx.await {
1937 Ok(Ok(())) => {}
1938 Ok(Err(e)) => return Err(SandboxRuntimeError::Io(e).into()),
1939 Err(_) => {
1940 return Err(SandboxRuntimeError::Child(
1941 "seccomp supervisor exited during startup".into(),
1942 ).into());
1943 }
1944 }
1945
1946 let la_resource = Arc::clone(&res_state);
1947 self.rt_mut().loadavg_handle = Some(tokio::spawn(async move {
1948 let mut interval = tokio::time::interval(Duration::from_secs(5));
1949 interval.tick().await;
1950 loop {
1951 interval.tick().await;
1952 let mut rs = la_resource.lock().await;
1953 let running = rs.proc_count;
1954 rs.load_avg.sample(running);
1955 }
1956 }));
1957 }
1958
1959 if let Some(cpu_pct) = self.max_cpu {
1960 if cpu_pct < 100 {
1961 let child_pid = pid;
1962 self.rt_mut().throttle_handle = Some(tokio::spawn(sandbox_throttle_cpu(child_pid, cpu_pct)));
1963 }
1964 }
1965
1966 self.rt_mut().pidfd = pidfd;
1967 self.rt_mut().ready_w = Some(pipes.ready_w);
1968
1969 Ok(())
1970 }
1971
1972 fn do_start(&mut self) -> Result<(), crate::error::SandlockError> {
1977 use std::os::fd::AsRawFd;
1978 use crate::context::write_u32_fd;
1979 use crate::error::SandboxRuntimeError;
1980
1981 if !matches!(self.rt().state, RuntimeState::Created) {
1982 return Err(SandboxRuntimeError::Child("start() requires a created sandbox".into()).into());
1983 }
1984 let ready_w = self.rt_mut().ready_w.take()
1985 .ok_or_else(|| SandboxRuntimeError::Child("start() called without a prior create()".into()))?;
1986 write_u32_fd(ready_w.as_raw_fd(), 1)
1987 .map_err(|e| SandboxRuntimeError::Child(format!("write ready signal: {}", e)))?;
1988 drop(ready_w);
1989 self.rt_mut().state = RuntimeState::Running;
1990 Ok(())
1991 }
1992}
1993
1994#[must_use = "a Process is a live confined child; call wait() (or kill()) or it runs until the Sandbox is dropped"]
2012pub struct Process<'a> {
2013 sandbox: &'a mut Sandbox,
2014}
2015
2016impl Process<'_> {
2017 pub fn take_stdin(&mut self) -> Option<std::os::fd::OwnedFd> {
2026 self.sandbox.rt_mut()._stdin_write.take()
2027 }
2028
2029 pub fn take_stdout(&mut self) -> Option<std::os::fd::OwnedFd> {
2032 self.sandbox.rt_mut()._stdout_read.take()
2033 }
2034
2035 pub fn take_stderr(&mut self) -> Option<std::os::fd::OwnedFd> {
2038 self.sandbox.rt_mut()._stderr_read.take()
2039 }
2040
2041 pub fn pid(&self) -> Option<i32> {
2044 self.sandbox.pid()
2045 }
2046
2047 pub fn kill(&mut self) -> Result<(), crate::error::SandlockError> {
2051 self.sandbox.kill()
2052 }
2053
2054 pub async fn wait(self) -> Result<crate::result::RunResult, crate::error::SandlockError> {
2060 self.sandbox.wait().await
2061 }
2062}
2063
2064impl Drop for Sandbox {
2069 fn drop(&mut self) {
2070 if let Some(ref mut rt) = self.runtime {
2071 if let Some(pid) = rt.child_pid {
2072 if matches!(rt.state, RuntimeState::Created | RuntimeState::Running | RuntimeState::Paused) {
2073 unsafe { libc::killpg(pid, libc::SIGKILL) };
2074 let mut status: i32 = 0;
2075 unsafe { libc::waitpid(pid, &mut status, 0) };
2076 }
2077 }
2078
2079 if let Some(h) = rt.notif_handle.take() { h.abort(); }
2080 if let Some(h) = rt.throttle_handle.take() { h.abort(); }
2081 if let Some(h) = rt.loadavg_handle.take() { h.abort(); }
2082
2083 let is_error = matches!(
2084 rt.state,
2085 RuntimeState::Stopped(ref s) if !matches!(s, crate::result::ExitStatus::Code(0))
2086 );
2087 let action = if is_error { &self.on_error } else { &self.on_exit };
2088 let action = action.clone();
2089
2090 if let Some(ref mut cow) = rt.seccomp_cow {
2091 match action {
2092 BranchAction::Commit => { let _ = cow.commit(); }
2093 BranchAction::Abort => { let _ = cow.abort(); }
2094 BranchAction::Keep => {}
2095 }
2096 }
2097 }
2098 }
2099}
2100
2101async fn sandbox_throttle_cpu(pid: i32, cpu_pct: u8) {
2106 use std::time::Duration;
2107 let period = Duration::from_millis(100);
2108 let run_time = period * cpu_pct as u32 / 100;
2109 let stop_time = period - run_time;
2110 loop {
2111 tokio::time::sleep(run_time).await;
2112 if unsafe { libc::killpg(pid, libc::SIGSTOP) } < 0 { break; }
2113 tokio::time::sleep(stop_time).await;
2114 if unsafe { libc::killpg(pid, libc::SIGCONT) } < 0 { break; }
2115 }
2116}
2117
2118static NEXT_SANDBOX_NAME: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
2123
2124fn sandbox_resolve_name(name: Option<&str>) -> Result<String, crate::error::SandlockError> {
2125 match name {
2126 Some(n) => sandbox_validate_name(n.to_string()),
2127 None => Ok(format!(
2128 "sandbox-{}-{}",
2129 std::process::id(),
2130 NEXT_SANDBOX_NAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
2131 )),
2132 }
2133}
2134
2135fn sandbox_validate_name(name: String) -> Result<String, crate::error::SandlockError> {
2136 use crate::error::SandboxRuntimeError;
2137 if name.is_empty() {
2138 return Err(SandboxRuntimeError::Child("sandbox name must not be empty".into()).into());
2139 }
2140 if name.len() > 64 {
2141 return Err(SandboxRuntimeError::Child("sandbox name must be at most 64 bytes".into()).into());
2142 }
2143 if name.as_bytes().contains(&0) {
2144 return Err(SandboxRuntimeError::Child("sandbox name must not contain NUL bytes".into()).into());
2145 }
2146 Ok(name)
2147}
2148
2149fn sandbox_read_exact(fd: i32, buf: &mut [u8]) {
2154 let mut off = 0;
2155 while off < buf.len() {
2156 let r = unsafe { libc::read(fd, buf[off..].as_mut_ptr() as *mut _, buf.len() - off) };
2157 if r <= 0 { break; }
2158 off += r as usize;
2159 }
2160}
2161
2162fn make_cloexec_pipe() -> Result<(std::os::fd::OwnedFd, std::os::fd::OwnedFd), std::io::Error> {
2165 use std::os::fd::{FromRawFd, OwnedFd};
2166 let mut fds = [0i32; 2];
2167 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {
2168 return Err(std::io::Error::last_os_error());
2169 }
2170 Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
2171}
2172
2173unsafe fn relocate_high(src: i32) -> i32 {
2182 let hi = libc::fcntl(src, libc::F_DUPFD_CLOEXEC, 3);
2183 if hi >= 0 {
2184 hi
2185 } else {
2186 src
2187 }
2188}
2189
2190unsafe fn wire_child_stdio(mode: StdioMode, target: i32, pipe_src: Option<i32>, devnull_flags: i32) {
2203 match mode {
2204 StdioMode::Inherit => {}
2205 StdioMode::Piped => {
2206 if let Some(src) = pipe_src {
2207 if src == target {
2208 let flags = libc::fcntl(target, libc::F_GETFD);
2212 if flags >= 0 {
2213 libc::fcntl(target, libc::F_SETFD, flags & !libc::FD_CLOEXEC);
2214 }
2215 } else if libc::dup2(src, target) < 0 {
2216 libc::close(target);
2218 libc::close(src);
2219 } else {
2220 libc::close(src);
2221 }
2222 }
2223 }
2224 StdioMode::Null => {
2225 let fd = libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char, devnull_flags);
2227 if fd < 0 {
2228 libc::close(target);
2230 } else if fd == target {
2231 } else if libc::dup2(fd, target) < 0 {
2233 libc::close(target);
2234 libc::close(fd);
2235 } else {
2236 libc::close(fd);
2237 }
2238 }
2239 }
2240}
2241
2242fn sandbox_read_fd_to_end(fd: std::os::fd::OwnedFd) -> Vec<u8> {
2243 use std::io::Read;
2244 use std::os::fd::IntoRawFd;
2245 use std::os::unix::io::FromRawFd;
2246 let mut file = unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) };
2247 let mut buf = Vec::new();
2248 let _ = file.read_to_end(&mut buf);
2249 buf
2250}
2251
2252fn sandbox_wait_status_to_exit(status: i32) -> crate::result::ExitStatus {
2253 use crate::result::ExitStatus;
2254 if libc::WIFEXITED(status) {
2255 ExitStatus::Code(libc::WEXITSTATUS(status))
2256 } else if libc::WIFSIGNALED(status) {
2257 let sig = libc::WTERMSIG(status);
2258 if sig == libc::SIGKILL {
2259 ExitStatus::Killed
2260 } else {
2261 ExitStatus::Signal(sig)
2262 }
2263 } else {
2264 ExitStatus::Killed
2265 }
2266}
2267
2268async fn wait_child_exit_via_pidfd(
2274 pidfd: std::os::unix::io::OwnedFd,
2275 pid: libc::pid_t,
2276) -> crate::result::ExitStatus {
2277 use crate::result::ExitStatus;
2278
2279 let async_fd = match tokio::io::unix::AsyncFd::with_interest(
2280 pidfd,
2281 tokio::io::Interest::READABLE,
2282 ) {
2283 Ok(fd) => fd,
2284 Err(_) => return wait_child_exit_blocking(pid).await,
2285 };
2286
2287 loop {
2288 let mut guard = match async_fd.readable().await {
2290 Ok(g) => g,
2291 Err(_) => return ExitStatus::Killed,
2292 };
2293 let mut status: i32 = 0;
2294 let r = unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) };
2296 if r > 0 {
2297 return sandbox_wait_status_to_exit(status);
2298 }
2299 if r == 0 {
2300 guard.clear_ready();
2302 continue;
2303 }
2304 return ExitStatus::Killed;
2306 }
2307}
2308
2309async fn wait_child_exit_blocking(pid: libc::pid_t) -> crate::result::ExitStatus {
2313 use crate::result::ExitStatus;
2314 tokio::task::spawn_blocking(move || -> ExitStatus {
2315 let mut status: i32 = 0;
2316 loop {
2317 let ret = unsafe { libc::waitpid(pid, &mut status, 0) };
2318 if ret < 0 {
2319 if std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
2320 continue;
2321 }
2322 return ExitStatus::Killed;
2323 }
2324 break;
2325 }
2326 sandbox_wait_status_to_exit(status)
2327 })
2328 .await
2329 .unwrap_or(ExitStatus::Killed)
2330}
2331
2332fn sandbox_collect_handlers<I, S, H>(
2333 handlers: I,
2334 sandbox: &Sandbox,
2335) -> Result<Vec<(i64, Arc<dyn crate::seccomp::dispatch::Handler>)>, crate::error::SandlockError>
2336where
2337 I: IntoIterator<Item = (S, H)>,
2338 S: TryInto<crate::seccomp::syscall::Syscall, Error = crate::seccomp::syscall::SyscallError>,
2339 H: crate::seccomp::dispatch::Handler,
2340{
2341 use crate::seccomp::dispatch::{Handler, HandlerError};
2342
2343 let pending: Vec<(i64, Arc<dyn Handler>)> = handlers
2344 .into_iter()
2345 .map(|(syscall, handler)| {
2346 let nr = syscall.try_into().map_err(HandlerError::from)?.raw();
2347 let h: Arc<dyn Handler> = Arc::new(handler);
2348 Ok::<_, HandlerError>((nr, h))
2349 })
2350 .collect::<Result<_, _>>()?;
2351
2352 let nrs: Vec<i64> = pending.iter().map(|(nr, _)| *nr).collect();
2353 crate::seccomp::dispatch::validate_handler_syscalls_against_policy(&nrs, sandbox)
2354 .map_err(|syscall_nr| HandlerError::OnDenySyscall { syscall_nr })?;
2355
2356 Ok(pending)
2357}
2358
2359fn validate_syscall_names(names: &[String]) -> Result<(), SandboxError> {
2360 let unknown: Vec<&str> = names
2361 .iter()
2362 .map(String::as_str)
2363 .filter(|name| crate::seccomp::syscall::syscall_name_to_nr(name).is_none())
2364 .collect();
2365 if unknown.is_empty() {
2366 Ok(())
2367 } else {
2368 Err(SandboxError::Invalid(format!(
2369 "unknown syscall name(s): {}",
2370 unknown.join(", ")
2371 )))
2372 }
2373}
2374
2375fn parse_allow_bind_ports(specs: &[String], label: &str) -> Result<BindPorts, SandboxError> {
2379 let mut parts = specs.iter().flat_map(|s| s.split(',')).map(str::trim);
2380 if !parts.clone().any(|part| part == "*") {
2381 return Ok(BindPorts::Ports(parse_bind_ports(specs, label)?));
2382 }
2383 if !parts.all(|part| part == "*") {
2384 return Err(SandboxError::Invalid(format!(
2385 "{}: wildcard `*` cannot be combined with port lists",
2386 label
2387 )));
2388 }
2389 Ok(BindPorts::All)
2390}
2391
2392fn parse_bind_ports(specs: &[String], label: &str) -> Result<Vec<u16>, SandboxError> {
2396 let mut ports: std::collections::BTreeSet<u16> = std::collections::BTreeSet::new();
2397 for spec in specs {
2398 for part in spec.split(',') {
2399 let part = part.trim();
2400 if part.is_empty() {
2401 return Err(SandboxError::Invalid(format!(
2402 "{}: empty port in `{}`",
2403 label, spec
2404 )));
2405 }
2406 if part == "*" {
2407 return Err(SandboxError::Invalid(format!(
2408 "{}: wildcard `*` is only supported for --net-allow-bind",
2409 label
2410 )));
2411 }
2412 match part.split_once('-') {
2413 Some((lo, hi)) => {
2414 let lo: u16 = lo.trim().parse().map_err(|_| {
2415 SandboxError::Invalid(format!("{}: invalid port range `{}`", label, part))
2416 })?;
2417 let hi: u16 = hi.trim().parse().map_err(|_| {
2418 SandboxError::Invalid(format!("{}: invalid port range `{}`", label, part))
2419 })?;
2420 if lo > hi {
2421 return Err(SandboxError::Invalid(format!(
2422 "{}: reversed port range `{}` (lo > hi)",
2423 label, part
2424 )));
2425 }
2426 ports.extend(lo..=hi);
2427 }
2428 None => {
2429 let p: u16 = part.parse().map_err(|_| {
2430 SandboxError::Invalid(format!("{}: invalid port `{}`", label, part))
2431 })?;
2432 ports.insert(p);
2433 }
2434 }
2435 }
2436 }
2437 Ok(ports.into_iter().collect())
2438}
2439
2440fn resolve_sandbox_path_to_host(
2445 child_path: &std::path::Path,
2446 chroot_root: Option<&std::path::Path>,
2447 mounts: &[(std::path::PathBuf, std::path::PathBuf)],
2448) -> std::path::PathBuf {
2449 for (virt, host) in mounts {
2450 if let Ok(rest) = child_path.strip_prefix(virt) {
2451 return host.join(rest);
2452 }
2453 }
2454 if let Some(root) = chroot_root {
2455 if let Ok(rest) = child_path.strip_prefix("/") {
2456 return root.join(rest);
2457 }
2458 }
2459 child_path.to_path_buf()
2460}
2461
2462#[cfg(test)]
2463mod tests;