Skip to main content

sandbox_run/
sandbox.rs

1use std::path::{Path, PathBuf};
2
3use crate::error::SandboxError;
4
5/// Protection level for system directories (systemd: ProtectSystem=)
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
7pub enum ProtectSystem {
8    /// No protection
9    #[default]
10    No,
11    /// /usr, /boot, /efi read-only
12    Yes,
13    /// + /etc read-only
14    Full,
15    /// Entire FS read-only except /dev, /proc, /sys
16    Strict,
17}
18
19/// Protection level for home directories (systemd: ProtectHome=)
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
21pub enum ProtectHome {
22    /// No protection
23    #[default]
24    No,
25    /// /home, /root, /run/user inaccessible
26    Yes,
27    /// /home, /root, /run/user read-only
28    ReadOnly,
29}
30
31/// Builder for creating sandbox policies mirroring systemd.exec options.
32///
33/// # Example
34///
35/// ```no_run
36/// use std::path::Path;
37/// use sandbox_run::{Sandbox, ProtectSystem, ProtectHome};
38///
39/// let sandbox = Sandbox::new()
40///     .protect_system(ProtectSystem::Strict)
41///     .protect_home(ProtectHome::Yes)
42///     .read_write_paths([Path::new("/tmp/myapp")])
43///     .no_new_privileges(true)
44///     .limit_nofile(256)
45///     .build()
46///     .unwrap();
47/// ```
48#[derive(Debug, Clone, Default)]
49pub struct Sandbox {
50    // Filesystem protection
51    pub(crate) protect_system: ProtectSystem,
52    pub(crate) protect_home: ProtectHome,
53
54    // Path-based access control
55    pub(crate) read_only_paths: Vec<PathBuf>,
56    pub(crate) read_write_paths: Vec<PathBuf>,
57    pub(crate) inaccessible_paths: Vec<PathBuf>,
58    pub(crate) exec_paths: Vec<PathBuf>,
59    pub(crate) no_exec_paths: Vec<PathBuf>,
60
61    // Network
62    pub(crate) private_network: bool,
63
64    // Privilege control
65    pub(crate) no_new_privileges: bool,
66
67    // Resource limits (None = don't set)
68    pub(crate) limit_nofile: Option<u64>,
69    pub(crate) limit_nproc: Option<u64>,
70    pub(crate) limit_core: Option<u64>,
71    pub(crate) limit_fsize: Option<u64>,
72    pub(crate) limit_data: Option<u64>,
73    pub(crate) limit_stack: Option<u64>,
74    pub(crate) limit_cpu: Option<u64>,
75    pub(crate) limit_memlock: Option<u64>,
76}
77
78impl Sandbox {
79    /// Create a new sandbox builder with default settings (no restrictions).
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    // --- Filesystem Protection ---
85
86    /// Set system directory protection level (systemd: ProtectSystem=)
87    ///
88    /// - `No`: No protection
89    /// - `Yes`: /usr, /boot, /efi read-only
90    /// - `Full`: + /etc read-only
91    /// - `Strict`: Entire filesystem read-only except API filesystems
92    pub fn protect_system(mut self, level: ProtectSystem) -> Self {
93        self.protect_system = level;
94        self
95    }
96
97    /// Set home directory protection level (systemd: ProtectHome=)
98    ///
99    /// - `No`: No protection
100    /// - `Yes`: /home, /root, /run/user inaccessible
101    /// - `ReadOnly`: /home, /root, /run/user read-only
102    pub fn protect_home(mut self, level: ProtectHome) -> Self {
103        self.protect_home = level;
104        self
105    }
106
107    // --- Path Access Control ---
108
109    /// Make paths read-only (systemd: ReadOnlyPaths=)
110    ///
111    /// Writing to these paths will be denied.
112    pub fn read_only_paths<I, P>(mut self, paths: I) -> Self
113    where
114        I: IntoIterator<Item = P>,
115        P: AsRef<Path>,
116    {
117        self.read_only_paths
118            .extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
119        self
120    }
121
122    /// Allow read-write access to paths (systemd: ReadWritePaths=)
123    ///
124    /// Use this to create exceptions within ProtectSystem=strict.
125    pub fn read_write_paths<I, P>(mut self, paths: I) -> Self
126    where
127        I: IntoIterator<Item = P>,
128        P: AsRef<Path>,
129    {
130        self.read_write_paths
131            .extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
132        self
133    }
134
135    /// Make paths completely inaccessible (systemd: InaccessiblePaths=)
136    ///
137    /// Neither reading nor writing will be possible.
138    pub fn inaccessible_paths<I, P>(mut self, paths: I) -> Self
139    where
140        I: IntoIterator<Item = P>,
141        P: AsRef<Path>,
142    {
143        self.inaccessible_paths
144            .extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
145        self
146    }
147
148    /// Allow execution only from these paths (systemd: ExecPaths=)
149    ///
150    /// Binaries outside these paths cannot be executed.
151    pub fn exec_paths<I, P>(mut self, paths: I) -> Self
152    where
153        I: IntoIterator<Item = P>,
154        P: AsRef<Path>,
155    {
156        self.exec_paths
157            .extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
158        self
159    }
160
161    /// Deny execution from these paths (systemd: NoExecPaths=)
162    ///
163    /// Binaries in these paths cannot be executed.
164    pub fn no_exec_paths<I, P>(mut self, paths: I) -> Self
165    where
166        I: IntoIterator<Item = P>,
167        P: AsRef<Path>,
168    {
169        self.no_exec_paths
170            .extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
171        self
172    }
173
174    // --- Network ---
175
176    /// Isolate network access (systemd: PrivateNetwork=)
177    ///
178    /// When enabled, all network access is blocked.
179    pub fn private_network(mut self, enable: bool) -> Self {
180        self.private_network = enable;
181        self
182    }
183
184    // --- Privilege Control ---
185
186    /// Prevent privilege escalation (systemd: NoNewPrivileges=)
187    ///
188    /// When enabled, the process cannot gain new privileges through
189    /// setuid/setgid bits or file capabilities.
190    pub fn no_new_privileges(mut self, enable: bool) -> Self {
191        self.no_new_privileges = enable;
192        self
193    }
194
195    // --- Resource Limits ---
196
197    /// Max open file descriptors (systemd: LimitNOFILE=)
198    pub fn limit_nofile(mut self, limit: u64) -> Self {
199        self.limit_nofile = Some(limit);
200        self
201    }
202
203    /// Max number of processes (systemd: LimitNPROC=)
204    pub fn limit_nproc(mut self, limit: u64) -> Self {
205        self.limit_nproc = Some(limit);
206        self
207    }
208
209    /// Max core dump size in bytes (systemd: LimitCORE=)
210    ///
211    /// Set to 0 to disable core dumps.
212    pub fn limit_core(mut self, limit: u64) -> Self {
213        self.limit_core = Some(limit);
214        self
215    }
216
217    /// Max file size in bytes (systemd: LimitFSIZE=)
218    pub fn limit_fsize(mut self, limit: u64) -> Self {
219        self.limit_fsize = Some(limit);
220        self
221    }
222
223    /// Max data segment size in bytes (systemd: LimitDATA=)
224    pub fn limit_data(mut self, limit: u64) -> Self {
225        self.limit_data = Some(limit);
226        self
227    }
228
229    /// Max stack size in bytes (systemd: LimitSTACK=)
230    pub fn limit_stack(mut self, limit: u64) -> Self {
231        self.limit_stack = Some(limit);
232        self
233    }
234
235    /// Max CPU time in seconds (systemd: LimitCPU=)
236    pub fn limit_cpu(mut self, limit: u64) -> Self {
237        self.limit_cpu = Some(limit);
238        self
239    }
240
241    /// Max locked memory in bytes (systemd: LimitMEMLOCK=)
242    pub fn limit_memlock(mut self, limit: u64) -> Self {
243        self.limit_memlock = Some(limit);
244        self
245    }
246
247    /// Build the sandbox policy.
248    ///
249    /// This validates the configuration and prepares platform-specific data
250    /// needed to apply the sandbox.
251    pub fn build(self) -> Result<SandboxPolicy, SandboxError> {
252        Ok(SandboxPolicy { config: self })
253    }
254}
255
256/// A compiled sandbox policy ready to be applied to a command.
257///
258/// Create this using `Sandbox::build()`.
259#[derive(Debug, Clone)]
260pub struct SandboxPolicy {
261    pub(crate) config: Sandbox,
262}