extrasafe_multiarch/builtins/
systemio.rs1use std::collections::{HashSet, HashMap};
4use std::fs::File;
5use std::os::unix::io::AsRawFd;
6
7#[cfg(feature = "landlock")]
8use std::path::{Path, PathBuf};
9
10use crate::syscalls::Sysno;
11
12#[cfg(feature = "landlock")]
13use crate::LandlockRule;
14#[cfg(feature = "landlock")]
15use crate::landlock::{access, AccessFs, BitFlags};
16
17use crate::{RuleSet, SeccompRule};
18use super::YesReally;
19
20pub(crate) const IO_READ_SYSCALLS: &[Sysno] = &[
21 Sysno::read,
22 Sysno::readv,
23 Sysno::preadv,
24 Sysno::preadv2,
25 Sysno::pread64,
26 Sysno::lseek,
27];
28pub(crate) const IO_WRITE_SYSCALLS: &[Sysno] = &[
29 Sysno::write,
30 Sysno::writev,
31 Sysno::pwritev,
32 Sysno::pwritev2,
33 Sysno::pwrite64,
34 Sysno::fsync,
35 Sysno::fdatasync,
36 Sysno::lseek,
37];
38pub(crate) const IO_OPEN_SYSCALLS: &[Sysno] = &[
39 #[cfg(enabled_arch = "x86_64")]
40 Sysno::open,
41 Sysno::openat,
42 Sysno::openat2
43];
44pub(crate) const IO_IOCTL_SYSCALLS: &[Sysno] = &[Sysno::ioctl, Sysno::fcntl];
45pub(crate) const IO_METADATA_SYSCALLS: &[Sysno] = &[
47 #[cfg(enabled_arch = "x86_64")]
48 Sysno::stat,
49 Sysno::fstat,
50 #[cfg(enabled_arch = "x86_64")]
51 Sysno::newfstatat,
52 #[cfg(any(enabled_arch = "aarch64", enabled_arch = "riscv64"))]
53 Sysno::fstatat,
54 #[cfg(enabled_arch = "x86_64")]
55 Sysno::lstat,
56 Sysno::statx,
57 #[cfg(enabled_arch = "x86_64")]
58 Sysno::getdents,
59 Sysno::getdents64,
60 Sysno::getcwd,
61];
62pub(crate) const IO_CLOSE_SYSCALLS: &[Sysno] = &[Sysno::close, Sysno::close_range];
63pub(crate) const IO_UNLINK_SYSCALLS: &[Sysno] = &[
64 #[cfg(enabled_arch = "x86_64")]
65 Sysno::unlink,
66 Sysno::unlinkat
67];
68
69#[must_use]
78pub struct SystemIO {
79 allowed: HashSet<Sysno>,
81 custom: HashMap<Sysno, Vec<SeccompRule>>,
83 #[cfg(feature = "landlock")]
84 landlock_rules: HashMap<PathBuf, LandlockRule>,
86}
87
88impl SystemIO {
89 pub fn nothing() -> SystemIO {
91 SystemIO {
92 allowed: HashSet::new(),
93 custom: HashMap::new(),
94 #[cfg(feature = "landlock")]
95 landlock_rules: HashMap::new()
96 }
97 }
98
99 pub fn everything() -> SystemIO {
101 SystemIO::nothing()
102 .allow_read()
103 .allow_write()
104 .allow_open().yes_really()
105 .allow_metadata()
106 .allow_unlink()
107 .allow_close()
108 }
109
110 pub fn allow_read(mut self) -> SystemIO {
112 self.allowed.extend(IO_READ_SYSCALLS);
113
114 self
115 }
116
117 pub fn allow_write(mut self) -> SystemIO {
119 self.allowed.extend(IO_WRITE_SYSCALLS);
120
121 self
122 }
123
124 pub fn allow_unlink(mut self) -> SystemIO {
126 self.allowed.extend(IO_UNLINK_SYSCALLS);
127
128 self
129 }
130
131 pub fn allow_open(mut self) -> YesReally<SystemIO> {
139 self.allowed.extend(IO_OPEN_SYSCALLS);
140
141 YesReally::new(self)
142 }
143
144 pub fn allow_open_readonly(mut self) -> SystemIO {
150 const O_WRONLY: u64 = libc::O_WRONLY as u64;
151 const O_RDWR: u64 = libc::O_RDWR as u64;
152 const O_APPEND: u64 = libc::O_APPEND as u64;
153 const O_CREAT: u64 = libc::O_CREAT as u64;
154 const O_EXCL: u64 = libc::O_EXCL as u64;
155 const WRITECREATE: u64 = O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_EXCL;#[cfg(enabled_arch = "x86_64")]
165 {
166 let rule = SeccompRule::new(Sysno::open)
167 .and_condition(seccomp_arg_filter!(arg1 & WRITECREATE == 0));
168 self.custom.entry(Sysno::open)
169 .or_insert_with(Vec::new)
170 .push(rule);
171 }
172
173 let rule = SeccompRule::new(Sysno::openat)
174 .and_condition(seccomp_arg_filter!(arg2 & WRITECREATE == 0));
175 self.custom.entry(Sysno::openat)
176 .or_insert_with(Vec::new)
177 .push(rule);
178
179 self
180 }
181
182 pub fn allow_metadata(mut self) -> SystemIO {
184 self.allowed.extend(IO_METADATA_SYSCALLS);
185
186 self
187 }
188
189 pub fn allow_ioctl(mut self) -> SystemIO {
191 self.allowed.extend(IO_IOCTL_SYSCALLS);
192
193 self
194 }
195
196 pub fn allow_close(mut self) -> SystemIO {
198 self.allowed.extend(IO_CLOSE_SYSCALLS);
199
200 self
201 }
202
203 pub fn allow_stdin(mut self) -> SystemIO {
205 let rule = SeccompRule::new(Sysno::read)
206 .and_condition(seccomp_arg_filter!(arg0 == 0));
207 self.custom.entry(Sysno::read)
208 .or_insert_with(Vec::new)
209 .push(rule);
210
211 self
212 }
213
214 pub fn allow_stdout(mut self) -> SystemIO {
216 let rule = SeccompRule::new(Sysno::write)
217 .and_condition(seccomp_arg_filter!(arg0 == 1));
218 self.custom.entry(Sysno::write)
219 .or_insert_with(Vec::new)
220 .push(rule);
221
222 self
223 }
224
225 pub fn allow_stderr(mut self) -> SystemIO {
227 let rule = SeccompRule::new(Sysno::write)
228 .and_condition(seccomp_arg_filter!(arg0 == 2));
229 self.custom.entry(Sysno::write)
230 .or_insert_with(Vec::new)
231 .push(rule);
232
233 self
234 }
235
236 #[allow(clippy::missing_panics_doc)]
244 pub fn allow_file_read(mut self, file: &File) -> SystemIO {
245 let fd = file.as_raw_fd().try_into().expect("provided fd was negative");
246 for &syscall in IO_READ_SYSCALLS {
247 let rule = SeccompRule::new(syscall)
248 .and_condition(seccomp_arg_filter!(arg0 == fd));
249 self.custom.entry(syscall)
250 .or_insert_with(Vec::new)
251 .push(rule);
252 }
253 for &syscall in IO_METADATA_SYSCALLS {
254 let rule = SeccompRule::new(syscall)
255 .and_condition(seccomp_arg_filter!(arg0 == fd));
256 self.custom.entry(syscall)
257 .or_insert_with(Vec::new)
258 .push(rule);
259 }
260
261 self
262 }
263
264 #[allow(clippy::missing_panics_doc)]
272 pub fn allow_file_write(mut self, file: &File) -> SystemIO {
273 let fd = file.as_raw_fd().try_into().expect("provided fd was negative");
274 let rule = SeccompRule::new(Sysno::write)
275 .and_condition(seccomp_arg_filter!(arg0 == fd));
276 self.custom.entry(Sysno::write)
277 .or_insert_with(Vec::new)
278 .push(rule);
279
280 self
281 }
282}
283
284impl RuleSet for SystemIO {
285 fn simple_rules(&self) -> Vec<crate::syscalls::Sysno> {
286 self.allowed.iter().copied().collect()
287 }
288
289 fn conditional_rules(&self) -> HashMap<crate::syscalls::Sysno, Vec<SeccompRule>> {
290 self.custom.clone()
291 }
292
293 #[cfg(feature = "landlock")]
294 fn landlock_rules(&self) -> Vec<LandlockRule> {
295 self.landlock_rules.values().cloned().collect()
296 }
297
298 fn name(&self) -> &'static str {
299 "SystemIO"
300 }
301}
302
303#[cfg(feature = "landlock")]
306impl SystemIO {
307 fn insert_flags<P: AsRef<Path>>(&mut self, path: P, new_flags: BitFlags<AccessFs>) {
308 let path = path.as_ref().to_path_buf();
309 let _flag = self.landlock_rules.entry(path.clone())
310 .and_modify(|existing_flags| existing_flags.access_rules.insert(new_flags))
311 .or_insert_with(|| LandlockRule::new(&path, new_flags));
312 }
313
314 pub fn allow_read_path<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
321 let new_flags = access::read_path();
322 self.insert_flags(path, new_flags);
323
324 self.allow_close()
326 .allow_read()
327 .allow_metadata()
328 .allow_open().yes_really()
329 }
330
331 pub fn allow_write_file<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
337 let new_flags = access::write_file();
338 self.insert_flags(path, new_flags);
339
340 self.allow_close()
342 .allow_write()
343 .allow_metadata()
344 .allow_open().yes_really()
345 }
346
347 pub fn allow_create_in_dir<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
353 let new_flags = access::create_file() | access::write_file();
356 self.insert_flags(path, new_flags);
357
358 self.allowed.extend(&[Sysno::creat]);
360 self.allow_open().yes_really()
361 }
362
363 pub fn allow_list_dir<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
366 let new_flags = access::list_dir();
367 self.insert_flags(path, new_flags);
368
369 self.allow_metadata()
371 .allow_close()
372 .allow_ioctl()
373 .allow_open().yes_really()
374 }
375
376 pub fn allow_create_dir<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
379 let new_flags = access::create_dir();
380 self.insert_flags(path, new_flags);
381
382 self.allowed.extend(&[Sysno::mkdir, Sysno::mkdirat]);
384 self
385 }
386
387 pub fn allow_remove_file<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
390 let new_flags = access::delete_file();
391 self.insert_flags(path, new_flags);
392
393 self.allowed.extend(&[Sysno::unlink, Sysno::unlinkat]);
395 self
396 }
397
398 pub fn allow_remove_dir<P: AsRef<Path>>(mut self, path: P) -> SystemIO {
407 let new_flags = access::delete_dir();
408 self.insert_flags(path, new_flags);
409
410 self.allowed.extend(&[Sysno::rmdir, Sysno::unlinkat]);
414 self
415 }
416}
417
418#[cfg(feature = "landlock")]
425impl SystemIO {
426 pub fn allow_ssl_files(mut self) -> SystemIO {
431 let new_flags = access::read_path() | access::list_dir();
432 for path in &["/etc/ssl/certs", "/etc/ca-certificates"] {
433 self.insert_flags(path, new_flags);
434 }
435 self.insert_flags("/etc/localtime", access::read_path());
437
438 self.allow_close()
440 .allow_read()
441 .allow_metadata()
442 .allow_open().yes_really()
443 }
444
445 pub fn allow_dns_files(mut self) -> SystemIO {
447 let new_flags = access::read_path();
448 for path in &["/etc/resolv.conf", "/etc/hosts", "/etc/host.conf", "/etc/nsswitch.conf", "/etc/gai.conf"] {
450 self.insert_flags(path, new_flags);
451 }
452 self.allow_close()
454 .allow_read()
455 .allow_metadata()
456 .allow_open().yes_really()
457 }
458}