1use std::collections::{HashMap, HashSet};
6use std::future::Future;
7use std::io;
8use std::net::IpAddr;
9use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
10use std::pin::Pin;
11use std::sync::Arc;
12
13use crate::error::NotifError;
14use crate::arch;
15use crate::sys::structs::{
16 SeccompNotif, SeccompNotifAddfd, SeccompNotifResp,
17 SECCOMP_ADDFD_FLAG_SEND, SECCOMP_IOCTL_NOTIF_ADDFD, SECCOMP_IOCTL_NOTIF_ID_VALID, SECCOMP_IOCTL_NOTIF_RECV,
18 SECCOMP_IOCTL_NOTIF_SEND, SECCOMP_IOCTL_NOTIF_SET_FLAGS,
19 SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP, SECCOMP_USER_NOTIF_FLAG_CONTINUE,
20 ENOMEM,
21};
22
23pub struct OnInjectSuccess(pub Box<dyn FnOnce(i32) + Send + Sync>);
34
35impl std::fmt::Debug for OnInjectSuccess {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.write_str("OnInjectSuccess(<callback>)")
38 }
39}
40
41impl OnInjectSuccess {
42 pub fn new<F: FnOnce(i32) + Send + Sync + 'static>(f: F) -> Self {
43 Self(Box::new(f))
44 }
45}
46
47pub struct Deferred(Pin<Box<dyn Future<Output = NotifAction> + Send + 'static>>);
64
65unsafe impl Sync for Deferred {}
75
76impl std::fmt::Debug for Deferred {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.write_str("Deferred(<future>)")
79 }
80}
81
82impl Deferred {
83 pub fn new<F: Future<Output = NotifAction> + Send + 'static>(f: F) -> Self {
84 Self(Box::pin(f))
85 }
86
87 pub async fn run(self) -> NotifAction {
90 self.0.await
91 }
92}
93
94#[derive(Debug)]
96pub enum NotifAction {
97 Continue,
99 Errno(i32),
101 InjectFd { srcfd: RawFd, targetfd: i32 },
103 InjectFdSend { srcfd: OwnedFd, newfd_flags: u32 },
108 InjectFdSendTracked {
113 srcfd: OwnedFd,
114 newfd_flags: u32,
115 on_success: OnInjectSuccess,
116 },
117 ReturnValue(i64),
119 Hold,
121 Kill { sig: i32, pgid: i32 },
124 Defer(Deferred),
129}
130
131impl NotifAction {
132 pub fn defer<F: Future<Output = NotifAction> + Send + 'static>(fut: F) -> Self {
135 NotifAction::Defer(Deferred::new(fut))
136 }
137
138 pub fn inject_bytes(content: &[u8]) -> NotifAction {
153 match content_memfd(content, true) {
154 Ok(fd) => NotifAction::InjectFdSend {
155 srcfd: fd,
156 newfd_flags: libc::O_CLOEXEC as u32,
157 },
158 Err(_) => NotifAction::Errno(libc::EIO),
159 }
160 }
161}
162
163pub fn content_memfd(content: &[u8], seal: bool) -> io::Result<OwnedFd> {
177 use std::io::{Seek, SeekFrom, Write};
178 use std::os::unix::io::FromRawFd;
179
180 let flags = if seal {
181 (libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING) as u32
182 } else {
183 libc::MFD_CLOEXEC as u32
184 };
185 let memfd = crate::sys::syscall::memfd_create("sandlock-content", flags)?;
186
187 {
190 let raw = memfd.as_raw_fd();
191 let mut file = unsafe { std::fs::File::from_raw_fd(raw) };
192 let res = file
193 .write_all(content)
194 .and_then(|()| file.seek(SeekFrom::Start(0)).map(|_| ()));
195 std::mem::forget(file); res?;
197 }
198
199 if seal {
200 let seals =
202 libc::F_SEAL_SEAL | libc::F_SEAL_WRITE | libc::F_SEAL_GROW | libc::F_SEAL_SHRINK;
203 unsafe { libc::fcntl(memfd.as_raw_fd(), libc::F_ADD_SEALS, seals) };
204 }
205
206 Ok(memfd)
207}
208
209fn finalize_deferred(action: NotifAction) -> NotifAction {
214 match action {
215 NotifAction::Defer(_) => NotifAction::Errno(libc::EIO),
216 other => other,
217 }
218}
219
220#[derive(Debug, Clone)]
228pub enum PortAllow {
229 Any,
231 Specific(HashSet<u16>),
233}
234
235#[derive(Debug, Clone)]
237pub enum NetworkPolicy {
238 Unrestricted,
245 AllowList {
248 per_ip: HashMap<IpAddr, PortAllow>,
251 cidrs: Vec<(crate::network::IpCidr, PortAllow)>,
255 any_ip_ports: HashSet<u16>,
258 },
259 DenyList {
262 cidrs: Vec<(crate::network::IpCidr, PortAllow)>,
265 any_ip_ports: HashSet<u16>,
267 deny_all: bool,
270 },
271}
272
273impl NetworkPolicy {
274 pub fn allows(&self, ip: IpAddr, port: u16) -> bool {
276 match self {
277 NetworkPolicy::Unrestricted => true,
278 NetworkPolicy::AllowList { per_ip, cidrs, any_ip_ports } => {
279 if any_ip_ports.contains(&port) {
280 return true;
281 }
282 match per_ip.get(&ip) {
283 Some(PortAllow::Any) => return true,
284 Some(PortAllow::Specific(s)) if s.contains(&port) => return true,
285 _ => {}
286 }
287 for (net, allowed) in cidrs {
288 if net.contains(ip) {
289 match allowed {
290 PortAllow::Any => return true,
291 PortAllow::Specific(s) => {
292 if s.contains(&port) {
293 return true;
294 }
295 }
296 }
297 }
298 }
299 false
300 }
301 NetworkPolicy::DenyList { cidrs, any_ip_ports, deny_all } => {
302 if *deny_all {
303 return false;
304 }
305 if any_ip_ports.contains(&port) {
306 return false;
307 }
308 for (net, denied) in cidrs {
309 if net.contains(ip) {
310 match denied {
311 PortAllow::Any => return false,
312 PortAllow::Specific(s) => {
313 if s.contains(&port) {
314 return false;
315 }
316 }
317 }
318 }
319 }
320 true
321 }
322 }
323 }
324}
325
326pub(crate) fn is_path_denied_for_notif(
337 policy_fn_state: &super::state::PolicyFnState,
338 notif: &SeccompNotif,
339 notif_fd: RawFd,
340) -> bool {
341 if let Some(path) = resolve_path_for_notif(notif, notif_fd) {
342 if is_denied_with_symlink_resolve(policy_fn_state, &path) {
343 return true;
344 }
345 }
346 if let Some(path) = resolve_second_path_for_notif(notif, notif_fd) {
348 if is_denied_with_symlink_resolve(policy_fn_state, &path) {
349 return true;
350 }
351 }
352 false
353}
354
355fn is_denied_with_symlink_resolve(
361 policy_fn_state: &super::state::PolicyFnState,
362 path: &str,
363) -> bool {
364 if policy_fn_state.is_path_denied(path) {
366 return true;
367 }
368 if let Ok(real) = std::fs::canonicalize(path) {
370 if policy_fn_state.is_path_denied(&real.to_string_lossy()) {
371 return true;
372 }
373 if let Some(id) = super::state::file_id_of_path(&real.to_string_lossy()) {
379 if policy_fn_state.is_id_denied(&id) {
380 return true;
381 }
382 }
383 }
384 false
385}
386
387const RESOLVE_NO_MAGICLINKS: u64 = 0x02;
391
392#[repr(C)]
394struct OpenHow {
395 flags: u64,
396 mode: u64,
397 resolve: u64,
398}
399
400fn last_errno(fallback: i32) -> i32 {
401 io::Error::last_os_error().raw_os_error().unwrap_or(fallback)
402}
403
404fn openat2_at(dirfd: RawFd, path: &std::ffi::CStr, flags: u64, mode: u64, resolve: u64)
406 -> Result<OwnedFd, i32>
407{
408 use std::os::unix::io::FromRawFd;
409 let how = OpenHow { flags, mode, resolve };
410 let fd = unsafe {
411 libc::syscall(
412 arch::SYS_OPENAT2,
413 dirfd,
414 path.as_ptr(),
415 &how as *const OpenHow,
416 std::mem::size_of::<OpenHow>(),
417 )
418 } as i32;
419 if fd < 0 {
420 Err(last_errno(libc::ENOENT))
421 } else {
422 Ok(unsafe { OwnedFd::from_raw_fd(fd) })
423 }
424}
425
426fn open_base_dir(pid: u32, dirfd: i64) -> Result<OwnedFd, i32> {
430 use std::os::unix::io::FromRawFd;
431 if dirfd as i32 == libc::AT_FDCWD {
432 let cwd = std::ffi::CString::new(format!("/proc/{}/cwd", pid)).map_err(|_| libc::EINVAL)?;
433 let fd = unsafe {
434 libc::open(cwd.as_ptr(), libc::O_PATH | libc::O_DIRECTORY | libc::O_CLOEXEC)
435 };
436 if fd < 0 {
437 return Err(last_errno(libc::EACCES));
438 }
439 Ok(unsafe { OwnedFd::from_raw_fd(fd) })
440 } else {
441 dup_fd_from_pid(pid, dirfd as i32).map_err(|_| libc::EBADF)
442 }
443}
444
445fn realpath_of_fd(fd: RawFd) -> Option<std::path::PathBuf> {
447 std::fs::read_link(format!("/proc/self/fd/{}", fd)).ok()
448}
449
450
451fn path_under_any(path: &std::path::Path, list: &[std::path::PathBuf]) -> bool {
452 list.iter().any(|p| path.starts_with(p))
453}
454
455fn deny_open_verdict(
462 realpath: &std::path::Path,
463 flags: u64,
464 policy: &NotifPolicy,
465 pfs: &super::state::PolicyFnState,
466) -> Option<i32> {
467 if pfs.is_path_denied(&realpath.to_string_lossy()) {
468 return Some(libc::EACCES);
469 }
470 let acc = flags as i32 & libc::O_ACCMODE;
471 let is_write = acc == libc::O_WRONLY
472 || acc == libc::O_RDWR
473 || (flags & libc::O_TRUNC as u64) != 0
474 || (flags & libc::O_CREAT as u64) != 0;
475 let allowed = if is_write {
476 path_under_any(realpath, &policy.chroot_writable)
477 } else {
478 path_under_any(realpath, &policy.chroot_readable)
479 || path_under_any(realpath, &policy.chroot_writable)
480 };
481 if allowed { None } else { Some(libc::EACCES) }
482}
483
484struct OpenArgs {
486 dirfd: i64,
487 path_ptr: u64,
488 flags: u64,
489 mode: u64,
490 resolve: u64,
492}
493
494fn decode_open_args(notif: &SeccompNotif, notif_fd: RawFd) -> Option<OpenArgs> {
499 let a = ¬if.data.args;
500 let nr = notif.data.nr as i64;
501 if nr == libc::SYS_openat {
502 Some(OpenArgs { dirfd: a[0] as i64, path_ptr: a[1], flags: a[2], mode: a[3], resolve: 0 })
503 } else if nr == arch::SYS_OPENAT2 {
504 let how_ptr = a[2];
507 let want = (a[3] as usize).min(std::mem::size_of::<OpenHow>());
508 if how_ptr == 0 || want < 16 {
509 return None; }
511 let bytes = read_child_mem(notif_fd, notif.id, notif.pid, how_ptr, want).ok()?;
512 if bytes.len() < 16 {
513 return None;
514 }
515 let flags = u64::from_ne_bytes(bytes[0..8].try_into().ok()?);
516 let mode = u64::from_ne_bytes(bytes[8..16].try_into().ok()?);
517 let resolve = if bytes.len() >= 24 {
518 u64::from_ne_bytes(bytes[16..24].try_into().ok()?)
519 } else {
520 0
521 };
522 Some(OpenArgs { dirfd: a[0] as i64, path_ptr: a[1], flags, mode, resolve })
523 } else {
524 Some(OpenArgs { dirfd: libc::AT_FDCWD as i64, path_ptr: a[0], flags: a[1], mode: a[2], resolve: 0 })
526 }
527}
528
529fn inject_open_result(raw_fd: i32, flags: u64) -> NotifAction {
532 use std::os::unix::io::FromRawFd;
533 if raw_fd < 0 {
534 return NotifAction::Errno(last_errno(libc::EACCES));
535 }
536 let owned = unsafe { OwnedFd::from_raw_fd(raw_fd) };
537 let newfd_flags = if flags & libc::O_CLOEXEC as u64 != 0 {
538 libc::O_CLOEXEC as u32
539 } else {
540 0
541 };
542 NotifAction::InjectFdSend { srcfd: owned, newfd_flags }
543}
544
545fn reopen_existing_on_behalf(
549 probe: OwnedFd,
550 flags: u64,
551 policy: &NotifPolicy,
552 pfs: &super::state::PolicyFnState,
553) -> NotifAction {
554 if (flags & libc::O_CREAT as u64) != 0 && (flags & libc::O_EXCL as u64) != 0 {
556 return NotifAction::Errno(libc::EEXIST);
557 }
558 let realpath = match realpath_of_fd(probe.as_raw_fd()) {
559 Some(p) => p,
560 None => return NotifAction::Errno(libc::EACCES),
561 };
562 if let Some(errno) = deny_open_verdict(&realpath, flags, policy, pfs) {
563 return NotifAction::Errno(errno);
564 }
565 if let Some(id) = super::state::file_id_of_fd(probe.as_raw_fd()) {
570 if pfs.is_id_denied(&id) {
571 return NotifAction::Errno(libc::EACCES);
572 }
573 }
574 let reopen_flags =
576 flags as i32 & !(libc::O_CREAT | libc::O_EXCL | libc::O_PATH | libc::O_NOFOLLOW);
577 let proc_path = match std::ffi::CString::new(format!("/proc/self/fd/{}", probe.as_raw_fd())) {
578 Ok(c) => c,
579 Err(_) => return NotifAction::Errno(libc::EIO),
580 };
581 let fd = unsafe { libc::open(proc_path.as_ptr(), reopen_flags) };
582 inject_open_result(fd, flags)
583}
584
585fn create_new_on_behalf(
589 base: &OwnedFd,
590 path: &str,
591 flags: u64,
592 mode: u64,
593 resolve: u64,
594 policy: &NotifPolicy,
595 pfs: &super::state::PolicyFnState,
596) -> NotifAction {
597 let p = std::path::Path::new(path);
598 let file_name = match p.file_name() {
599 Some(f) => f,
600 None => return NotifAction::Errno(libc::ENOENT),
601 };
602 let parent = p.parent().unwrap_or(std::path::Path::new("."));
603 let parent_str = match parent.to_str() {
604 Some("") | None => ".",
605 Some(s) => s,
606 };
607 let c_parent = match std::ffi::CString::new(parent_str) {
608 Ok(c) => c,
609 Err(_) => return NotifAction::Errno(libc::EINVAL),
610 };
611 let parent_fd = match openat2_at(
612 base.as_raw_fd(),
613 &c_parent,
614 (libc::O_PATH | libc::O_DIRECTORY | libc::O_CLOEXEC) as u64,
615 0,
616 RESOLVE_NO_MAGICLINKS | resolve,
617 ) {
618 Ok(f) => f,
619 Err(e) => return NotifAction::Errno(e),
620 };
621 let parent_real = match realpath_of_fd(parent_fd.as_raw_fd()) {
622 Some(p) => p,
623 None => return NotifAction::Errno(libc::EACCES),
624 };
625 if let Some(errno) = deny_open_verdict(&parent_real.join(file_name), flags, policy, pfs) {
626 return NotifAction::Errno(errno);
627 }
628 let c_name = match std::ffi::CString::new(file_name.as_encoded_bytes()) {
629 Ok(c) => c,
630 Err(_) => return NotifAction::Errno(libc::EINVAL),
631 };
632 let create_flags = flags as i32 & !(libc::O_PATH | libc::O_NOFOLLOW);
633 let fd = unsafe { libc::openat(parent_fd.as_raw_fd(), c_name.as_ptr(), create_flags, mode) };
634 inject_open_result(fd, flags)
635}
636
637fn on_behalf_open_for_deny(
644 notif: &SeccompNotif,
645 policy: &NotifPolicy,
646 pfs: &super::state::PolicyFnState,
647 notif_fd: RawFd,
648) -> NotifAction {
649 if policy.chroot_readable.is_empty() && policy.chroot_writable.is_empty() {
653 return NotifAction::Continue;
654 }
655
656 let OpenArgs { dirfd, path_ptr, flags, mode, resolve } =
657 match decode_open_args(notif, notif_fd) {
658 Some(a) => a,
659 None => return NotifAction::Continue, };
661
662 let path = match read_child_cstr(notif_fd, notif.id, notif.pid, path_ptr, 4096) {
663 Some(p) => p,
664 None => return NotifAction::Continue, };
666 let c_path = match std::ffi::CString::new(path.clone()) {
667 Ok(c) => c,
668 Err(_) => return NotifAction::Errno(libc::EINVAL),
669 };
670 let base = match open_base_dir(notif.pid, dirfd) {
671 Ok(b) => b,
672 Err(e) => return NotifAction::Errno(e),
673 };
674
675 let probe_flags = (libc::O_PATH | libc::O_CLOEXEC) as u64 | (flags & libc::O_NOFOLLOW as u64);
678 match openat2_at(base.as_raw_fd(), &c_path, probe_flags, 0, RESOLVE_NO_MAGICLINKS | resolve) {
679 Ok(probe) => reopen_existing_on_behalf(probe, flags, policy, pfs),
680 Err(errno) if errno == libc::ENOENT && (flags & libc::O_CREAT as u64) != 0 => {
681 create_new_on_behalf(&base, &path, flags, mode, resolve, policy, pfs)
682 }
683 Err(errno) => NotifAction::Errno(errno),
684 }
685}
686
687fn tgid_of(tid: u32) -> Option<u32> {
689 let status = std::fs::read_to_string(format!("/proc/{}/status", tid)).ok()?;
690 status
691 .lines()
692 .find_map(|l| l.strip_prefix("Tgid:").and_then(|r| r.trim().parse().ok()))
693}
694
695pub(crate) fn dup_fd_from_pid(pid: u32, target_fd: i32) -> io::Result<OwnedFd> {
704 use crate::sys::syscall::{pidfd_getfd, pidfd_open};
705 let pidfd = pidfd_open(pid, 0).or_else(|e| match tgid_of(pid) {
706 Some(tgid) if tgid != pid => pidfd_open(tgid, 0),
707 _ => Err(e),
708 })?;
709 pidfd_getfd(&pidfd, target_fd, 0)
710}
711
712pub struct NotifPolicy {
718 pub max_memory_bytes: u64,
719 pub max_processes: u32,
720 pub has_memory_limit: bool,
721 pub has_net_destination_policy: bool,
730 pub has_bind_denylist: bool,
734 pub has_unix_fs_gate: bool,
740 pub has_random_seed: bool,
741 pub has_time_start: bool,
742 pub argv_safety_required: bool,
749 pub time_offset: i64,
750 pub num_cpus: Option<u32>,
751 pub port_remap: bool,
752 pub cow_enabled: bool,
753 pub chroot_root: Option<std::path::PathBuf>,
754 pub chroot_readable: Vec<std::path::PathBuf>,
756 pub chroot_writable: Vec<std::path::PathBuf>,
758 pub chroot_denied: Vec<std::path::PathBuf>,
760 pub chroot_mounts: Vec<(std::path::PathBuf, std::path::PathBuf)>,
762 pub chroot_mount_ro: Vec<std::path::PathBuf>,
767 pub deterministic_dirs: bool,
768 pub virtual_hostname: Option<String>,
769 pub has_http_acl: bool,
770 pub virtual_etc_hosts: String,
775 pub ca_inject_paths: Vec<std::path::PathBuf>,
777 pub ca_inject_pem: Option<std::sync::Arc<Vec<u8>>>,
780}
781
782impl NotifPolicy {
783 pub(crate) fn ip_connect_supervised(&self, dest_is_loopback: bool) -> bool {
799 self.has_net_destination_policy || (self.port_remap && dest_is_loopback)
800 }
801}
802
803fn recv_notif(fd: RawFd) -> io::Result<SeccompNotif> {
810 let mut notif: SeccompNotif = unsafe { std::mem::zeroed() };
811 let ret = unsafe {
812 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_RECV as libc::c_ulong, &mut notif as *mut _)
813 };
814 if ret < 0 {
815 Err(io::Error::last_os_error())
816 } else {
817 Ok(notif)
818 }
819}
820
821enum NotifFdState {
823 Pending,
826 Empty,
829 Terminal,
834}
835
836fn probe_notif_fd(fd: RawFd) -> NotifFdState {
847 let mut pfd = libc::pollfd {
848 fd,
849 events: libc::POLLIN,
850 revents: 0,
851 };
852 let r = unsafe { libc::poll(&mut pfd, 1, 0) };
853 if r > 0 && (pfd.revents & libc::POLLIN) != 0 {
854 return NotifFdState::Pending;
855 }
856 if r < 0 || (pfd.revents & (libc::POLLHUP | libc::POLLERR | libc::POLLNVAL)) != 0 {
857 return NotifFdState::Terminal;
858 }
859 NotifFdState::Empty
860}
861
862fn respond_continue(fd: RawFd, id: u64) -> io::Result<()> {
864 let resp = SeccompNotifResp {
865 id,
866 val: 0,
867 error: 0,
868 flags: SECCOMP_USER_NOTIF_FLAG_CONTINUE,
869 };
870 send_resp_raw(fd, &resp)
871}
872
873fn respond_errno(fd: RawFd, id: u64, errno: i32) -> io::Result<()> {
875 let resp = SeccompNotifResp {
876 id,
877 val: 0,
878 error: -errno,
879 flags: 0,
880 };
881 send_resp_raw(fd, &resp)
882}
883
884fn respond_value(fd: RawFd, id: u64, val: i64) -> io::Result<()> {
886 let resp = SeccompNotifResp {
887 id,
888 val,
889 error: 0,
890 flags: 0,
891 };
892 send_resp_raw(fd, &resp)
893}
894
895fn inject_failure_resp(id: u64) -> SeccompNotifResp {
903 SeccompNotifResp {
904 id,
905 val: 0,
906 error: -libc::EACCES,
907 flags: 0,
908 }
909}
910
911fn inject_fd_and_send(fd: RawFd, id: u64, srcfd: RawFd, newfd_flags: u32) -> io::Result<i32> {
917 let addfd = SeccompNotifAddfd {
918 id,
919 flags: SECCOMP_ADDFD_FLAG_SEND,
920 srcfd: srcfd as u32,
921 newfd: 0, newfd_flags,
923 };
924 let ret = unsafe {
925 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong, &addfd as *const _)
926 };
927 if ret < 0 {
928 Err(io::Error::last_os_error())
929 } else {
930 Ok(ret as i32)
931 }
932}
933
934fn inject_fd(fd: RawFd, id: u64, srcfd: RawFd, targetfd: i32) -> io::Result<()> {
937 let addfd = SeccompNotifAddfd {
938 id,
939 flags: 0,
940 srcfd: srcfd as u32,
941 newfd: targetfd as u32,
942 newfd_flags: 0,
943 };
944 let ret = unsafe {
945 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong, &addfd as *const _)
946 };
947 if ret < 0 {
948 Err(io::Error::last_os_error())
949 } else {
950 Ok(())
951 }
952}
953
954fn send_resp_raw(fd: RawFd, resp: &SeccompNotifResp) -> io::Result<()> {
956 let ret = unsafe {
957 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND as libc::c_ulong, resp as *const _)
958 };
959 if ret < 0 {
960 Err(io::Error::last_os_error())
961 } else {
962 Ok(())
963 }
964}
965
966pub(crate) fn id_valid(fd: RawFd, id: u64) -> io::Result<()> {
969 let ret = unsafe {
970 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_ID_VALID as libc::c_ulong, &id as *const _)
971 };
972 if ret < 0 {
973 Err(io::Error::last_os_error())
974 } else {
975 Ok(())
976 }
977}
978
979fn try_set_sync_wakeup(fd: RawFd) {
981 let flags: u64 = SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP as u64;
982 unsafe {
983 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_SET_FLAGS as libc::c_ulong, &flags as *const _);
984 }
985}
986
987fn read_child_mem_vm(pid: u32, addr: u64, len: usize) -> Result<Vec<u8>, NotifError> {
993 let mut buf = vec![0u8; len];
994 let local_iov = libc::iovec {
995 iov_base: buf.as_mut_ptr() as *mut libc::c_void,
996 iov_len: len,
997 };
998 let remote_iov = libc::iovec {
999 iov_base: addr as *mut libc::c_void,
1000 iov_len: len,
1001 };
1002 let ret = unsafe {
1003 libc::process_vm_readv(pid as i32, &local_iov, 1, &remote_iov, 1, 0)
1004 };
1005 if ret < 0 {
1006 Err(NotifError::ChildMemoryRead(io::Error::last_os_error()))
1007 } else {
1008 buf.truncate(ret as usize);
1009 Ok(buf)
1010 }
1011}
1012
1013fn write_child_mem_vm(pid: u32, addr: u64, data: &[u8]) -> Result<(), NotifError> {
1015 let local_iov = libc::iovec {
1016 iov_base: data.as_ptr() as *mut libc::c_void,
1017 iov_len: data.len(),
1018 };
1019 let remote_iov = libc::iovec {
1020 iov_base: addr as *mut libc::c_void,
1021 iov_len: data.len(),
1022 };
1023 let ret = unsafe {
1024 libc::process_vm_writev(pid as i32, &local_iov, 1, &remote_iov, 1, 0)
1025 };
1026 if ret < 0 {
1027 Err(NotifError::ChildMemoryRead(io::Error::last_os_error()))
1028 } else if (ret as usize) < data.len() {
1029 Err(NotifError::ChildMemoryRead(io::Error::new(
1030 io::ErrorKind::WriteZero,
1031 format!("short write: {} of {} bytes", ret, data.len()),
1032 )))
1033 } else {
1034 Ok(())
1035 }
1036}
1037
1038pub fn read_child_mem(
1048 notif_fd: RawFd,
1049 id: u64,
1050 pid: u32,
1051 addr: u64,
1052 len: usize,
1053) -> Result<Vec<u8>, NotifError> {
1054 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
1055 let result = read_child_mem_vm(pid, addr, len)?;
1056 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
1057 Ok(result)
1058}
1059
1060pub fn read_child_cstr(
1075 notif_fd: RawFd,
1076 id: u64,
1077 pid: u32,
1078 addr: u64,
1079 max_len: usize,
1080) -> Option<String> {
1081 if addr == 0 || max_len == 0 {
1082 return None;
1083 }
1084
1085 const PAGE_SIZE: u64 = 4096;
1086 let mut result = Vec::with_capacity(max_len.min(256));
1087 let mut cur = addr;
1088 while result.len() < max_len {
1089 let page_remaining = PAGE_SIZE - (cur % PAGE_SIZE);
1090 let remaining = max_len - result.len();
1091 let to_read = page_remaining.min(remaining as u64) as usize;
1092 let bytes = read_child_mem(notif_fd, id, pid, cur, to_read).ok()?;
1093 if let Some(nul) = bytes.iter().position(|&b| b == 0) {
1094 result.extend_from_slice(&bytes[..nul]);
1095 return String::from_utf8(result).ok();
1096 }
1097 result.extend_from_slice(&bytes);
1098 cur += to_read as u64;
1099 }
1100
1101 String::from_utf8(result).ok()
1102}
1103
1104pub fn write_child_mem(
1111 notif_fd: RawFd,
1112 id: u64,
1113 pid: u32,
1114 addr: u64,
1115 data: &[u8],
1116) -> Result<(), NotifError> {
1117 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
1118 write_child_mem_vm(pid, addr, data)?;
1119 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
1120 Ok(())
1121}
1122
1123pub fn write_child_mem_force(
1150 notif_fd: RawFd,
1151 id: u64,
1152 pid: u32,
1153 addr: u64,
1154 data: &[u8],
1155) -> Result<(), NotifError> {
1156 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
1157 write_child_mem_proc(pid, addr, data)?;
1158 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
1159 Ok(())
1160}
1161
1162fn write_child_mem_proc(pid: u32, addr: u64, data: &[u8]) -> Result<(), NotifError> {
1169 use std::os::unix::fs::FileExt;
1170 let mem = std::fs::OpenOptions::new()
1171 .write(true)
1172 .open(format!("/proc/{}/mem", pid))
1173 .map_err(NotifError::ChildMemoryWrite)?;
1174 mem.write_all_at(data, addr)
1175 .map_err(NotifError::ChildMemoryWrite)?;
1176 Ok(())
1177}
1178
1179fn send_response(fd: RawFd, id: u64, action: NotifAction) -> io::Result<()> {
1185 match action {
1186 NotifAction::Continue => respond_continue(fd, id),
1187 NotifAction::Errno(errno) => respond_errno(fd, id, errno),
1188 NotifAction::InjectFd { srcfd, targetfd } => {
1189 inject_fd(fd, id, srcfd, targetfd)?;
1190 respond_continue(fd, id)
1191 }
1192 NotifAction::InjectFdSend { srcfd, newfd_flags } => {
1193 match inject_fd_and_send(fd, id, srcfd.as_raw_fd(), newfd_flags) {
1199 Ok(_new_fd) => Ok(()),
1200 Err(_) => send_resp_raw(fd, &inject_failure_resp(id)),
1201 }
1202 }
1203 NotifAction::InjectFdSendTracked { srcfd, newfd_flags, on_success } => {
1204 match inject_fd_and_send(fd, id, srcfd.as_raw_fd(), newfd_flags) {
1205 Ok(new_fd) => {
1206 (on_success.0)(new_fd);
1207 Ok(())
1208 }
1209 Err(_) => send_resp_raw(fd, &inject_failure_resp(id)),
1210 }
1211 }
1212 NotifAction::ReturnValue(val) => respond_value(fd, id, val),
1213 NotifAction::Hold => Ok(()), NotifAction::Defer(_) => {
1215 debug_assert!(false, "Defer reached send_response; should be intercepted earlier");
1219 respond_errno(fd, id, libc::EIO)
1220 }
1221 NotifAction::Kill { sig, pgid } => {
1222 unsafe { libc::killpg(pgid, sig) };
1225 respond_errno(fd, id, ENOMEM)
1226 }
1227 }
1228}
1229
1230fn maybe_patch_vdso(pid: i32, procfs: &mut super::state::ProcfsState, policy: &NotifPolicy) {
1236 let base = match crate::vdso::find_vdso_base(pid) {
1237 Ok(addr) => addr,
1238 Err(_) => return,
1239 };
1240 if base == procfs.vdso_patched_addr {
1241 return; }
1243 let time_offset = if policy.has_time_start { Some(policy.time_offset) } else { None };
1244 if crate::vdso::patch(pid, time_offset, policy.has_random_seed).is_ok() {
1245 procfs.vdso_patched_addr = base;
1246 }
1247}
1248
1249fn syscall_name(nr: i64) -> &'static str {
1255 match nr {
1256 n if n == libc::SYS_openat => "openat",
1257 n if n == libc::SYS_connect => "connect",
1258 n if n == libc::SYS_sendto => "sendto",
1259 n if n == libc::SYS_sendmsg => "sendmsg",
1260 n if n == libc::SYS_sendmmsg => "sendmmsg",
1261 n if n == libc::SYS_bind => "bind",
1262 n if n == libc::SYS_clone => "clone",
1263 n if n == libc::SYS_clone3 => "clone3",
1264 n if Some(n) == arch::sys_vfork() => "vfork",
1265 n if Some(n) == arch::sys_fork() => "fork",
1266 n if n == libc::SYS_execve => "execve",
1267 n if n == libc::SYS_execveat => "execveat",
1268 n if n == libc::SYS_mmap => "mmap",
1269 n if n == libc::SYS_munmap => "munmap",
1270 n if n == libc::SYS_brk => "brk",
1271 n if n == libc::SYS_getrandom => "getrandom",
1272 n if n == libc::SYS_unlinkat => "unlinkat",
1273 n if n == libc::SYS_mkdirat => "mkdirat",
1274 _ => "unknown",
1275 }
1276}
1277
1278fn syscall_category(nr: i64) -> crate::policy_fn::SyscallCategory {
1280 use crate::policy_fn::SyscallCategory;
1281 match nr {
1282 n if n == libc::SYS_openat || n == libc::SYS_unlinkat
1283 || n == libc::SYS_mkdirat || n == libc::SYS_renameat2
1284 || n == libc::SYS_symlinkat || n == libc::SYS_linkat
1285 || n == libc::SYS_fchmodat || n == libc::SYS_fchownat
1286 || n == libc::SYS_truncate || n == libc::SYS_readlinkat
1287 || n == libc::SYS_newfstatat || n == libc::SYS_statx
1288 || n == libc::SYS_faccessat || n == libc::SYS_getdents64
1289 || Some(n) == arch::sys_getdents() => SyscallCategory::File,
1290 n if n == libc::SYS_connect || n == libc::SYS_sendto
1291 || n == libc::SYS_sendmsg || n == libc::SYS_sendmmsg
1292 || n == libc::SYS_bind
1293 || n == libc::SYS_getsockname => SyscallCategory::Network,
1294 n if n == libc::SYS_clone || n == libc::SYS_clone3
1295 || Some(n) == arch::sys_vfork() || Some(n) == arch::sys_fork()
1296 || n == libc::SYS_execve || n == libc::SYS_execveat => SyscallCategory::Process,
1297 n if n == libc::SYS_mmap || n == libc::SYS_munmap
1298 || n == libc::SYS_brk || n == libc::SYS_mremap
1299 => SyscallCategory::Memory,
1300 _ => SyscallCategory::File, }
1302}
1303
1304fn read_ppid(pid: u32) -> Option<u32> {
1306 let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
1307 let close_paren = stat.rfind(')')?;
1310 let rest = &stat[close_paren + 2..]; let fields: Vec<&str> = rest.split_whitespace().collect();
1312 fields.get(1)?.parse().ok()
1314}
1315
1316fn read_path_for_event(notif: &SeccompNotif, addr: u64, notif_fd: RawFd) -> Option<String> {
1318 if addr == 0 { return None; }
1319 let bytes = read_child_mem(notif_fd, notif.id, notif.pid, addr, 256).ok()?;
1320 let nul = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
1321 String::from_utf8(bytes[..nul].to_vec()).ok()
1322}
1323
1324fn normalize_path(path: &std::path::Path) -> String {
1325 use std::path::{Component, PathBuf};
1326
1327 let mut normalized = PathBuf::new();
1328 let absolute = path.is_absolute();
1329 if absolute {
1330 normalized.push("/");
1331 }
1332
1333 for component in path.components() {
1334 match component {
1335 Component::RootDir | Component::CurDir => {}
1336 Component::ParentDir => {
1337 normalized.pop();
1338 }
1339 Component::Normal(part) => normalized.push(part),
1340 Component::Prefix(_) => {}
1341 }
1342 }
1343
1344 if normalized.as_os_str().is_empty() {
1345 if absolute { "/".into() } else { ".".into() }
1346 } else {
1347 normalized.to_string_lossy().into_owned()
1348 }
1349}
1350
1351fn resolve_at_path_for_event(notif: &SeccompNotif, dirfd: i64, path: &str) -> Option<String> {
1352 use std::path::Path;
1353
1354 if Path::new(path).is_absolute() {
1355 return Some(normalize_path(Path::new(path)));
1356 }
1357
1358 let dirfd32 = dirfd as i32;
1359 let base = if dirfd32 == libc::AT_FDCWD {
1360 std::fs::read_link(format!("/proc/{}/cwd", notif.pid)).ok()?
1361 } else {
1362 std::fs::read_link(format!("/proc/{}/fd/{}", notif.pid, dirfd32)).ok()?
1363 };
1364
1365 Some(normalize_path(&base.join(path)))
1366}
1367
1368fn resolve_path_for_notif(notif: &SeccompNotif, notif_fd: RawFd) -> Option<String> {
1369 let nr = notif.data.nr as i64;
1370 match nr {
1371 n if n == libc::SYS_openat || n == arch::SYS_OPENAT2 => {
1372 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1375 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
1376 }
1377 n if Some(n) == arch::sys_open() || n == libc::SYS_execve => {
1378 let path = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
1379 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1380 }
1381 n if n == libc::SYS_execveat => {
1382 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1383 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
1384 }
1385 n if n == libc::SYS_linkat => {
1388 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1389 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
1390 }
1391 n if n == libc::SYS_renameat2 => {
1394 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1395 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
1396 }
1397 n if Some(n) == arch::sys_link() => {
1403 let path = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
1404 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1405 }
1406 n if Some(n) == arch::sys_rename() => {
1408 let path = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
1409 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1410 }
1411 _ => None,
1412 }
1413}
1414
1415fn resolve_second_path_for_notif(notif: &SeccompNotif, notif_fd: RawFd) -> Option<String> {
1419 let nr = notif.data.nr as i64;
1420 match nr {
1421 n if n == libc::SYS_renameat2 => {
1423 let path = read_path_for_event(notif, notif.data.args[3], notif_fd)?;
1424 resolve_at_path_for_event(notif, notif.data.args[2] as i64, &path)
1425 }
1426 n if n == libc::SYS_linkat => {
1430 let path = read_path_for_event(notif, notif.data.args[3], notif_fd)?;
1431 resolve_at_path_for_event(notif, notif.data.args[2] as i64, &path)
1432 }
1433 n if Some(n) == arch::sys_rename() => {
1435 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1436 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1437 }
1438 n if Some(n) == arch::sys_link() => {
1440 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1441 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1442 }
1443 _ => None,
1444 }
1445}
1446
1447fn read_sockaddr_for_event(notif: &SeccompNotif, addr: u64, len: usize, notif_fd: RawFd)
1449 -> (Option<std::net::IpAddr>, Option<u16>)
1450{
1451 if addr == 0 || len < 4 { return (None, None); }
1452 let bytes = match read_child_mem(notif_fd, notif.id, notif.pid, addr, len.min(128)) {
1453 Ok(b) => b,
1454 Err(_) => return (None, None),
1455 };
1456 if bytes.len() < 4 { return (None, None); }
1457 let family = u16::from_ne_bytes([bytes[0], bytes[1]]);
1458 let port = u16::from_be_bytes([bytes[2], bytes[3]]);
1459 let ip = match family as u32 {
1460 f if f == crate::sys::structs::AF_INET && bytes.len() >= 8 => {
1461 Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
1462 bytes[4], bytes[5], bytes[6], bytes[7],
1463 )))
1464 }
1465 f if f == crate::sys::structs::AF_INET6 && bytes.len() >= 24 => {
1466 let mut addr = [0u8; 16];
1467 addr.copy_from_slice(&bytes[8..24]);
1468 Some(std::net::IpAddr::V6(std::net::Ipv6Addr::from(addr)))
1469 }
1470 _ => None,
1471 };
1472 (ip, if port > 0 { Some(port) } else { None })
1473}
1474
1475fn read_argv_for_event(notif: &SeccompNotif, argv_ptr: u64, notif_fd: RawFd) -> Option<Vec<String>> {
1478 if argv_ptr == 0 { return None; }
1479 let mut args = Vec::new();
1480 let ptr_size = std::mem::size_of::<u64>();
1481
1482 for i in 0..64u64 {
1483 let ptr_addr = argv_ptr + i * ptr_size as u64;
1484 let ptr_bytes = read_child_mem(notif_fd, notif.id, notif.pid, ptr_addr, ptr_size).ok()?;
1485 let str_ptr = u64::from_ne_bytes(ptr_bytes[..8].try_into().ok()?);
1486 if str_ptr == 0 { break; } if let Some(s) = read_path_for_event(notif, str_ptr, notif_fd) {
1489 args.push(s);
1490 } else {
1491 break;
1492 }
1493 }
1494
1495 if args.is_empty() { None } else { Some(args) }
1496}
1497
1498fn resolve_held_gate(
1505 received: Option<crate::policy_fn::Verdict>,
1506) -> Option<crate::policy_fn::Verdict> {
1507 match received {
1508 Some(v) => Some(v),
1509 None => Some(crate::policy_fn::Verdict::Deny),
1510 }
1511}
1512
1513async fn emit_policy_event(
1516 notif: &SeccompNotif,
1517 action: &NotifAction,
1518 policy_fn_state: &Arc<tokio::sync::Mutex<super::state::PolicyFnState>>,
1519 notif_fd: RawFd,
1520) -> Option<crate::policy_fn::Verdict> {
1521 let pfs = policy_fn_state.lock().await;
1522 let tx = match pfs.event_tx.as_ref() {
1523 Some(tx) => tx.clone(),
1524 None => return None,
1525 };
1526 drop(pfs);
1527
1528 let nr = notif.data.nr as i64;
1529 let denied = matches!(action, NotifAction::Errno(_));
1530 let name = syscall_name(nr);
1531 let category = syscall_category(nr);
1532 let parent_pid = read_ppid(notif.pid);
1533
1534 let mut host = None;
1551 let mut port = None;
1552 let mut size = None;
1553 let mut argv = None;
1554
1555 if !denied && (nr == libc::SYS_execve || nr == libc::SYS_execveat) {
1556 let argv_ptr = if nr == libc::SYS_execveat {
1559 notif.data.args[2]
1560 } else {
1561 notif.data.args[1]
1562 };
1563 argv = read_argv_for_event(notif, argv_ptr, notif_fd);
1564 }
1565
1566 if nr == libc::SYS_connect || nr == libc::SYS_sendto || nr == libc::SYS_bind {
1567 let addr_ptr = notif.data.args[1];
1569 let addr_len = notif.data.args[2] as usize;
1570 let (h, p) = read_sockaddr_for_event(notif, addr_ptr, addr_len, notif_fd);
1571 host = h;
1572 port = p;
1573 }
1574
1575 if nr == libc::SYS_mmap {
1576 size = Some(notif.data.args[1]);
1578 }
1579
1580 let event = crate::policy_fn::SyscallEvent {
1581 syscall: name.to_string(),
1582 category,
1583 pid: notif.pid,
1584 parent_pid,
1585 host,
1586 port,
1587 size,
1588 argv,
1589 denied,
1590 };
1591
1592 let is_held = nr == libc::SYS_execve || nr == libc::SYS_execveat
1595 || nr == libc::SYS_connect || nr == libc::SYS_sendto
1596 || nr == libc::SYS_bind || nr == libc::SYS_openat;
1597
1598 if is_held {
1599 let (gate_tx, gate_rx) = tokio::sync::oneshot::channel();
1600 let _ = tx.send(crate::policy_fn::PolicyEvent {
1601 event,
1602 gate: Some(gate_tx),
1603 });
1604 let received = match tokio::time::timeout(std::time::Duration::from_secs(5), gate_rx).await {
1605 Ok(Ok(verdict)) => Some(verdict),
1606 _ => None, };
1608 resolve_held_gate(received)
1609 } else {
1610 let _ = tx.send(crate::policy_fn::PolicyEvent {
1611 event,
1612 gate: None,
1613 });
1614 None
1615 }
1616}
1617
1618const DEFER_MAX_INFLIGHT: usize = 64;
1628
1629const DEFER_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
1634
1635async fn run_deferred_within(deferred: Deferred, limit: std::time::Duration) -> NotifAction {
1641 match tokio::time::timeout(limit, deferred.run()).await {
1642 Ok(action) => finalize_deferred(action),
1643 Err(_) => {
1644 eprintln!(
1645 "sandlock: deferred handler exceeded {:?}; failing syscall with EIO",
1646 limit
1647 );
1648 NotifAction::Errno(libc::EIO)
1649 }
1650 }
1651}
1652
1653fn spawn_deferred(
1659 fd: RawFd,
1660 id: u64,
1661 deferred: Deferred,
1662 permit: tokio::sync::OwnedSemaphorePermit,
1663) {
1664 tokio::spawn(async move {
1665 let _permit = permit; let action = run_deferred_within(deferred, DEFER_TIMEOUT).await;
1667 let _ = send_response(fd, id, action);
1668 });
1669}
1670
1671async fn handle_notification(
1672 notif: SeccompNotif,
1673 ctx: &Arc<super::ctx::SupervisorCtx>,
1674 dispatch_table: &super::dispatch::DispatchTable,
1675 fd: RawFd,
1676 defer_sem: &Arc<tokio::sync::Semaphore>,
1677) {
1678 let policy = &ctx.policy;
1679
1680 crate::resource::register_child_if_new(ctx, notif.pid as i32).await;
1686
1687 if policy.has_time_start || policy.has_random_seed {
1689 let mut pfs = ctx.procfs.lock().await;
1690 maybe_patch_vdso(notif.pid as i32, &mut pfs, policy);
1691 }
1692
1693 let mut action = {
1695 let nr = notif.data.nr as i64;
1696 let mut path_check_nrs = vec![
1700 libc::SYS_openat, arch::SYS_OPENAT2, libc::SYS_execve, libc::SYS_execveat,
1701 libc::SYS_linkat, libc::SYS_renameat2,
1702 ];
1703 path_check_nrs.extend([
1704 arch::sys_open(), arch::sys_link(), arch::sys_rename(),
1705 ].into_iter().flatten());
1706 let should_precheck_denied = policy.chroot_root.is_none()
1707 && path_check_nrs.contains(&nr);
1708 if should_precheck_denied {
1709 let pfs = ctx.policy_fn.lock().await;
1710 if is_path_denied_for_notif(&pfs, ¬if, fd) {
1711 NotifAction::Errno(libc::EACCES)
1712 } else {
1713 let has_denied = pfs.has_denied_paths();
1714 drop(pfs);
1715 let action = dispatch_table.dispatch(notif, fd).await;
1718 let is_openat_family = nr == libc::SYS_openat
1727 || nr == arch::SYS_OPENAT2
1728 || Some(nr) == arch::sys_open();
1729 if matches!(action, NotifAction::Continue) && is_openat_family && has_denied {
1730 let pfs = ctx.policy_fn.lock().await;
1731 on_behalf_open_for_deny(¬if, policy, &pfs, fd)
1732 } else {
1733 action
1734 }
1735 }
1736 } else {
1737 dispatch_table.dispatch(notif, fd).await
1738 }
1739 };
1740
1741 let nr = notif.data.nr as i64;
1742 let fork_counted = matches!(action, NotifAction::Continue)
1743 && crate::resource::fork_counted_on_continue(¬if, fd);
1744
1745 let mut exec_freeze = None;
1759 if matches!(action, NotifAction::Continue)
1760 && policy.argv_safety_required
1761 && crate::freeze::requires_freeze_on_continue(nr)
1762 {
1763 match crate::freeze::freeze_sandbox_for_execve(
1764 &ctx.processes,
1765 notif.pid as i32,
1766 ) {
1767 Ok(outcome) => {
1768 exec_freeze = Some(outcome);
1769 }
1770 Err(e) => {
1771 eprintln!(
1772 "sandlock: argv-safety freeze failed for pid {}: {} \
1773 — denying execve to preserve TOCTOU invariant",
1774 notif.pid, e
1775 );
1776 action = NotifAction::Errno(libc::EPERM);
1777 }
1778 }
1779 }
1780
1781 if let Some(verdict) = emit_policy_event(¬if, &action, &ctx.policy_fn, fd).await {
1785 use crate::policy_fn::Verdict;
1786 match verdict {
1787 Verdict::Deny => { action = NotifAction::Errno(libc::EPERM); }
1788 Verdict::DenyWith(errno) => { action = NotifAction::Errno(errno); }
1789 Verdict::Audit => { }
1790 Verdict::Allow => {}
1791 }
1792 }
1793
1794 if fork_counted && !matches!(action, NotifAction::Continue) {
1795 crate::resource::rollback_fork_count(&ctx.resource).await;
1796 }
1797
1798 let mut creation_trace = None;
1803 if matches!(action, NotifAction::Continue)
1804 && crate::resource::requires_process_creation_tracking(¬if, fd, policy)
1805 {
1806 match crate::resource::prepare_process_creation_tracking(ctx, notif.pid as i32).await {
1807 Ok(trace) => {
1808 creation_trace = Some(trace);
1809 }
1810 Err(e) => {
1811 eprintln!(
1812 "sandlock: process-creation tracking failed for pid {}: {} \
1813 — denying fork-like syscall to preserve argv TOCTOU invariant",
1814 notif.pid, e
1815 );
1816 if fork_counted {
1817 crate::resource::rollback_fork_count(&ctx.resource).await;
1818 }
1819 action = NotifAction::Errno(libc::EPERM);
1820 }
1821 }
1822 }
1823
1824 if let NotifAction::Defer(deferred) = action {
1835 if crate::freeze::requires_freeze_on_continue(nr)
1836 || crate::resource::requires_process_creation_tracking(¬if, fd, policy)
1837 {
1838 let _ = send_response(fd, notif.id, NotifAction::Errno(libc::EPERM));
1839 return;
1840 }
1841 match Arc::clone(defer_sem).try_acquire_owned() {
1842 Ok(permit) => spawn_deferred(fd, notif.id, deferred, permit),
1843 Err(_) => {
1846 let _ = send_response(fd, notif.id, NotifAction::Errno(libc::EAGAIN));
1847 }
1848 }
1849 return;
1850 }
1851
1852 let exec_continued = exec_freeze.is_some() && matches!(action, NotifAction::Continue);
1854 let send_result = send_response(fd, notif.id, action);
1855
1856 if let Some(trace) = creation_trace {
1857 if send_result.is_ok() {
1858 match crate::resource::finish_process_creation_tracking(trace).await {
1859 Ok(true) => {}
1860 Ok(false) => {
1861 crate::resource::rollback_fork_count(&ctx.resource).await;
1862 }
1863 Err(e) => {
1864 crate::resource::rollback_fork_count(&ctx.resource).await;
1865 eprintln!(
1866 "sandlock: process-creation tracking completion failed for pid {}: {}",
1867 notif.pid, e
1868 );
1869 }
1870 }
1871 } else {
1872 crate::resource::rollback_fork_count(&ctx.resource).await;
1873 crate::resource::abort_process_creation_tracking(trace).await;
1874 }
1875 }
1876
1877 if let Some(freeze) = exec_freeze {
1878 if exec_continued && send_result.is_ok() {
1879 crate::freeze::detach_peers(&freeze.peer_tids);
1880 } else {
1881 crate::freeze::detach_all(&freeze);
1882 }
1883 }
1884}
1885
1886pub async fn supervisor(
1898 notif_fd: OwnedFd,
1899 ctx: Arc<super::ctx::SupervisorCtx>,
1900 pending_handlers: Vec<(i64, std::sync::Arc<dyn super::dispatch::Handler>)>,
1901 startup: tokio::sync::oneshot::Sender<io::Result<()>>,
1902) {
1903 let async_fd = match tokio::io::unix::AsyncFd::with_interest(
1906 notif_fd,
1907 tokio::io::Interest::READABLE,
1908 ) {
1909 Ok(fd) => fd,
1910 Err(err) => {
1911 let _ = startup.send(Err(err));
1912 return;
1913 }
1914 };
1915 let fd = async_fd.get_ref().as_raw_fd();
1916
1917 let dispatch_table = Arc::new(super::dispatch::build_dispatch_table(
1919 &ctx.policy,
1920 &ctx.resource,
1921 &ctx,
1922 pending_handlers,
1923 ));
1924
1925 try_set_sync_wakeup(fd);
1927
1928 let _ = startup.send(Ok(()));
1932
1933 let gc = tokio::spawn(process_index_gc(Arc::clone(&ctx.processes)));
1939
1940 let defer_sem = Arc::new(tokio::sync::Semaphore::new(DEFER_MAX_INFLIGHT));
1944
1945 'outer: loop {
1955 let mut ready = match async_fd.readable().await {
1956 Ok(r) => r,
1957 Err(_) => break 'outer,
1958 };
1959 ready.clear_ready();
1960 drop(ready);
1961
1962 loop {
1963 match probe_notif_fd(fd) {
1964 NotifFdState::Pending => {
1965 let notif = match recv_notif(fd) {
1966 Ok(n) => n,
1967 Err(e) if e.raw_os_error() == Some(libc::EINTR) => continue,
1968 Err(_) => break 'outer,
1969 };
1970 handle_notification(notif, &ctx, &dispatch_table, fd, &defer_sem).await;
1971 }
1972 NotifFdState::Empty => break,
1973 NotifFdState::Terminal => break 'outer,
1974 }
1975 }
1976 }
1977
1978 gc.abort();
1979}
1980
1981async fn process_index_gc(processes: Arc<super::state::ProcessIndex>) {
1985 let interval = std::time::Duration::from_secs(300);
1986 loop {
1987 tokio::time::sleep(interval).await;
1988 if processes.len() == 0 {
1989 continue;
1990 }
1991 processes.prune_dead();
1992 }
1993}
1994
1995pub(crate) fn spawn_pid_watcher(
2005 ctx: Arc<super::ctx::SupervisorCtx>,
2006 key: super::state::PidKey,
2007 pidfd: std::os::unix::io::OwnedFd,
2008) {
2009 tokio::spawn(async move {
2010 let async_fd = match tokio::io::unix::AsyncFd::with_interest(
2011 pidfd,
2012 tokio::io::Interest::READABLE,
2013 ) {
2014 Ok(f) => f,
2015 Err(_) => {
2016 cleanup_pid(&ctx, key).await;
2022 return;
2023 }
2024 };
2025 let _ = async_fd.readable().await;
2028 cleanup_pid(&ctx, key).await;
2029 });
2031}
2032
2033pub(crate) async fn cleanup_pid(ctx: &super::ctx::SupervisorCtx, key: super::state::PidKey) {
2039 ctx.processes.unregister(key);
2040}
2041
2042#[cfg(test)]
2047mod tests {
2048 use super::*;
2049 use std::os::unix::io::FromRawFd;
2050
2051 fn gettid() -> u32 {
2052 (unsafe { libc::syscall(libc::SYS_gettid) }) as u32
2053 }
2054
2055 #[test]
2056 fn inject_failure_response_denies_not_continues() {
2057 let resp = inject_failure_resp(123);
2061 assert_eq!(resp.id, 123);
2062 assert_eq!(
2063 resp.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE,
2064 0,
2065 "fd-injection failure must not respond with CONTINUE"
2066 );
2067 assert_ne!(resp.error, 0, "fd-injection failure must be a denial");
2068 assert_eq!(resp.error, -libc::EACCES);
2069 }
2070
2071 #[test]
2072 fn held_gate_no_decision_denies() {
2073 use crate::policy_fn::Verdict;
2074 assert!(matches!(resolve_held_gate(None), Some(Verdict::Deny)));
2077 }
2078
2079 #[test]
2080 fn held_gate_passes_through_callback_verdict() {
2081 use crate::policy_fn::Verdict;
2082 assert!(matches!(
2084 resolve_held_gate(Some(Verdict::Allow)),
2085 Some(Verdict::Allow)
2086 ));
2087 assert!(matches!(
2088 resolve_held_gate(Some(Verdict::Deny)),
2089 Some(Verdict::Deny)
2090 ));
2091 assert!(matches!(
2092 resolve_held_gate(Some(Verdict::DenyWith(13))),
2093 Some(Verdict::DenyWith(13))
2094 ));
2095 }
2096
2097 #[test]
2098 fn tgid_of_main_thread_is_own_pid() {
2099 assert_eq!(tgid_of(gettid()), Some(std::process::id()));
2101 }
2102
2103 #[test]
2104 fn tgid_of_worker_thread_resolves_to_process() {
2105 let (tid_tx, tid_rx) = std::sync::mpsc::channel();
2107 let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
2108 let h = std::thread::spawn(move || {
2109 tid_tx.send(gettid()).unwrap();
2110 done_rx.recv().ok(); });
2112 let worker_tid = tid_rx.recv().unwrap();
2113 let pid = std::process::id();
2114 assert_ne!(worker_tid, pid, "worker tid must differ from pid");
2115 assert_eq!(tgid_of(worker_tid), Some(pid));
2116 done_tx.send(()).ok();
2117 h.join().unwrap();
2118 }
2119
2120 #[test]
2121 fn dup_fd_from_pid_handles_worker_thread_fd() {
2122 use std::os::unix::io::AsRawFd;
2123 let (info_tx, info_rx) = std::sync::mpsc::channel();
2127 let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
2128 let h = std::thread::spawn(move || {
2129 let f = std::fs::File::open("/dev/null").unwrap();
2130 info_tx.send((gettid(), f.as_raw_fd())).unwrap();
2131 done_rx.recv().ok();
2132 drop(f);
2133 });
2134 let (worker_tid, fd) = info_rx.recv().unwrap();
2135 let dup = dup_fd_from_pid(worker_tid, fd);
2136 done_tx.send(()).ok();
2137 h.join().unwrap();
2138 assert!(dup.is_ok(), "dup_fd_from_pid for a worker-thread fd failed: {:?}", dup.err());
2139 }
2140
2141 #[test]
2142 fn read_child_cstr_returns_none_for_null_addr_or_zero_max_len() {
2143 assert!(read_child_cstr(-1, 0, 0, 0, 4096).is_none());
2145 assert!(read_child_cstr(-1, 0, 0, 0xdeadbeef, 0).is_none());
2147 }
2148
2149 #[test]
2150 fn test_notif_action_debug() {
2151 let _ = format!("{:?}", NotifAction::Continue);
2153 let _ = format!("{:?}", NotifAction::Errno(1));
2154 let _ = format!("{:?}", NotifAction::InjectFd { srcfd: 3, targetfd: 4 });
2155 let test_fd = unsafe { OwnedFd::from_raw_fd(libc::dup(2)) };
2157 let _ = format!("{:?}", NotifAction::InjectFdSend { srcfd: test_fd, newfd_flags: 0 });
2158 let _ = format!("{:?}", NotifAction::ReturnValue(42));
2159 let _ = format!("{:?}", NotifAction::Hold);
2160 let _ = format!("{:?}", NotifAction::Kill { sig: 9, pgid: 1 });
2161 let _ = format!("{:?}", NotifAction::defer(async { NotifAction::Continue }));
2162 }
2163
2164 #[tokio::test]
2165 async fn deferred_future_need_not_be_sync() {
2166 use std::cell::Cell;
2171 let action = NotifAction::defer(async move {
2172 let counter = Cell::new(0);
2173 counter.set(counter.get() + 41);
2174 tokio::task::yield_now().await; NotifAction::ReturnValue(counter.get() + 1)
2176 });
2177 let NotifAction::Defer(d) = action else { panic!("expected Defer") };
2178 assert!(matches!(d.run().await, NotifAction::ReturnValue(42)));
2179 }
2180
2181 #[tokio::test]
2182 async fn deferred_runs_to_its_terminal_action() {
2183 let action = NotifAction::defer(async { NotifAction::ReturnValue(7) });
2185 let NotifAction::Defer(deferred) = action else {
2186 panic!("defer() must construct a NotifAction::Defer");
2187 };
2188 assert!(matches!(deferred.run().await, NotifAction::ReturnValue(7)));
2189 }
2190
2191 #[tokio::test(start_paused = true)]
2192 async fn deferred_times_out_to_eio() {
2193 let slow = Deferred::new(async {
2197 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
2198 NotifAction::ReturnValue(7)
2199 });
2200 let action = run_deferred_within(slow, std::time::Duration::from_secs(1)).await;
2201 assert!(matches!(action, NotifAction::Errno(e) if e == libc::EIO));
2202 }
2203
2204 #[tokio::test(start_paused = true)]
2205 async fn deferred_within_limit_passes_through() {
2206 let fast = Deferred::new(async { NotifAction::ReturnValue(7) });
2208 let action = run_deferred_within(fast, std::time::Duration::from_secs(1)).await;
2209 assert!(matches!(action, NotifAction::ReturnValue(7)));
2210 }
2211
2212 #[test]
2213 fn finalize_deferred_collapses_nested_defer_to_eio() {
2214 let nested = NotifAction::defer(async { NotifAction::Continue });
2217 assert!(matches!(finalize_deferred(nested), NotifAction::Errno(e) if e == libc::EIO));
2218 assert!(matches!(finalize_deferred(NotifAction::Continue), NotifAction::Continue));
2220 assert!(matches!(
2221 finalize_deferred(NotifAction::ReturnValue(3)),
2222 NotifAction::ReturnValue(3)
2223 ));
2224 }
2225
2226 #[test]
2227 fn content_memfd_roundtrips_content() {
2228 use std::io::Read;
2229 let fd = content_memfd(b"hello world", true).expect("content_memfd");
2230 let mut f = std::fs::File::from(fd);
2232 let mut buf = String::new();
2233 f.read_to_string(&mut buf).unwrap();
2234 assert_eq!(buf, "hello world");
2235 }
2236
2237 #[test]
2238 fn content_memfd_sealed_applies_write_seal() {
2239 let fd = content_memfd(b"data", true).expect("content_memfd");
2240 let seals = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GET_SEALS) };
2241 assert!(seals >= 0, "F_GET_SEALS failed");
2242 assert!(
2243 seals & libc::F_SEAL_WRITE != 0,
2244 "expected F_SEAL_WRITE on a sealed memfd, got {seals:#x}"
2245 );
2246 }
2247
2248 #[test]
2249 fn content_memfd_unsealed_has_no_write_seal() {
2250 let fd = content_memfd(b"data", false).expect("content_memfd");
2251 let seals = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GET_SEALS) };
2252 assert!(seals >= 0, "F_GET_SEALS failed");
2253 assert_eq!(
2254 seals & libc::F_SEAL_WRITE,
2255 0,
2256 "unsealed memfd must not carry a write seal, got {seals:#x}"
2257 );
2258 }
2259
2260 #[test]
2261 fn inject_bytes_produces_sealed_cloexec_injectfdsend() {
2262 use std::io::Read;
2263 match NotifAction::inject_bytes(b"payload") {
2264 NotifAction::InjectFdSend { srcfd, newfd_flags } => {
2265 assert_eq!(newfd_flags, libc::O_CLOEXEC as u32);
2266 let seals = unsafe { libc::fcntl(srcfd.as_raw_fd(), libc::F_GET_SEALS) };
2267 assert!(seals & libc::F_SEAL_WRITE != 0, "inject_bytes must seal");
2268 let mut f = std::fs::File::from(srcfd);
2269 let mut buf = String::new();
2270 f.read_to_string(&mut buf).unwrap();
2271 assert_eq!(buf, "payload");
2272 }
2273 other => panic!("expected InjectFdSend, got {other:?}"),
2274 }
2275 }
2276
2277 #[test]
2278 fn test_network_state_new() {
2279 let ns = super::super::state::NetworkState::new();
2280 assert!(matches!(ns.tcp_policy, NetworkPolicy::Unrestricted));
2281 assert!(matches!(ns.udp_policy, NetworkPolicy::Unrestricted));
2282 assert!(matches!(ns.icmp_policy, NetworkPolicy::Unrestricted));
2283 assert!(ns.port_map.bound_ports.is_empty());
2284 }
2285
2286 #[test]
2287 fn test_time_random_state_new() {
2288 let tr = super::super::state::TimeRandomState::new(None, None);
2289 assert!(tr.time_offset.is_none());
2290 assert!(tr.random_state.is_none());
2291 }
2292
2293 #[test]
2294 fn test_resource_state_new() {
2295 let rs = super::super::state::ResourceState::new(1024 * 1024, 10);
2296 assert_eq!(rs.mem_used, 0);
2297 assert_eq!(rs.max_memory_bytes, 1024 * 1024);
2298 assert_eq!(rs.max_processes, 10);
2299 assert!(!rs.hold_forks);
2300 assert!(rs.held_notif_ids.is_empty());
2301 }
2302
2303 #[test]
2304 fn test_process_vm_readv_self() {
2305 let data: u64 = 0xDEADBEEF_CAFEBABE;
2306 let addr = &data as *const u64 as u64;
2307 let pid = std::process::id();
2308 let result = read_child_mem_vm(pid, addr, 8);
2309 assert!(result.is_ok());
2310 let bytes = result.unwrap();
2311 let read_val = u64::from_ne_bytes(bytes[..8].try_into().unwrap());
2312 assert_eq!(read_val, 0xDEADBEEF_CAFEBABE);
2313 }
2314
2315 #[test]
2316 fn test_process_vm_writev_self() {
2317 let mut data: u64 = 0;
2318 let addr = &mut data as *mut u64 as u64;
2319 let pid = std::process::id();
2320 let payload = 0x1234567890ABCDEFu64.to_ne_bytes();
2321 let result = write_child_mem_vm(pid, addr, &payload);
2322 assert!(result.is_ok());
2323 assert_eq!(data, 0x1234567890ABCDEF);
2324 }
2325
2326 #[test]
2331 fn write_child_mem_proc_forces_past_readonly_page() {
2332 const PAGE: usize = 4096;
2333 let orig = b"/some/rodata/path\0";
2334 let newb = b"/proc/self/fd/7\0\0"; let addr = unsafe {
2337 libc::mmap(
2338 std::ptr::null_mut(),
2339 PAGE,
2340 libc::PROT_READ | libc::PROT_WRITE,
2341 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
2342 -1,
2343 0,
2344 )
2345 };
2346 assert_ne!(addr, libc::MAP_FAILED, "mmap failed");
2347 unsafe { std::ptr::copy_nonoverlapping(orig.as_ptr(), addr as *mut u8, orig.len()) };
2348
2349 assert_eq!(
2352 unsafe { libc::mprotect(addr, PAGE, libc::PROT_READ) },
2353 0,
2354 "mprotect PROT_READ failed"
2355 );
2356
2357 let pid = std::process::id();
2358 let uaddr = addr as u64;
2359
2360 assert!(
2361 write_child_mem_vm(pid, uaddr, newb).is_err(),
2362 "process_vm_writev must fail on a read-only page"
2363 );
2364
2365 write_child_mem_proc(pid, uaddr, newb)
2367 .expect("force-write through /proc/pid/mem must succeed on a read-only page");
2368
2369 let got = unsafe { std::slice::from_raw_parts(addr as *const u8, newb.len()) };
2372 assert_eq!(got, newb, "forced write must be visible at the target address");
2373
2374 unsafe { libc::munmap(addr, PAGE) };
2375 }
2376
2377 #[test]
2378 fn denylist_blocks_matching_cidr_allows_rest() {
2379 use crate::network::IpCidr;
2380 let policy = NetworkPolicy::DenyList {
2381 cidrs: vec![(IpCidr::parse("10.0.0.0/8").unwrap(), PortAllow::Any)],
2382 any_ip_ports: HashSet::new(),
2383 deny_all: false,
2384 };
2385 assert!(!policy.allows("10.1.2.3".parse().unwrap(), 443)); assert!(policy.allows("8.8.8.8".parse().unwrap(), 443)); }
2388
2389 #[test]
2390 fn denylist_blocks_any_ip_port() {
2391 let mut ports = HashSet::new();
2392 ports.insert(25u16);
2393 let policy = NetworkPolicy::DenyList {
2394 cidrs: Vec::new(),
2395 any_ip_ports: ports,
2396 deny_all: false,
2397 };
2398 assert!(!policy.allows("8.8.8.8".parse().unwrap(), 25)); assert!(policy.allows("8.8.8.8".parse().unwrap(), 80)); }
2401
2402 #[test]
2403 fn denylist_specific_ports_on_cidr() {
2404 use crate::network::IpCidr;
2405 let mut ports = HashSet::new();
2406 ports.insert(443u16);
2407 let policy = NetworkPolicy::DenyList {
2408 cidrs: vec![(IpCidr::parse("1.2.3.4/32").unwrap(), PortAllow::Specific(ports))],
2409 any_ip_ports: HashSet::new(),
2410 deny_all: false,
2411 };
2412 assert!(!policy.allows("1.2.3.4".parse().unwrap(), 443)); assert!(policy.allows("1.2.3.4".parse().unwrap(), 80)); }
2415
2416 #[test]
2417 fn allowlist_permits_matching_cidr_only() {
2418 use crate::network::IpCidr;
2419 let mut ports = HashSet::new();
2420 ports.insert(80u16);
2421 let policy = NetworkPolicy::AllowList {
2422 per_ip: HashMap::new(),
2423 cidrs: vec![(IpCidr::parse("10.0.0.0/8").unwrap(), PortAllow::Specific(ports))],
2424 any_ip_ports: HashSet::new(),
2425 };
2426 assert!(policy.allows("10.1.2.3".parse().unwrap(), 80)); assert!(!policy.allows("10.1.2.3".parse().unwrap(), 443)); assert!(!policy.allows("8.8.8.8".parse().unwrap(), 80)); }
2430
2431 #[test]
2432 fn allowlist_cidr_all_ports() {
2433 use crate::network::IpCidr;
2434 let policy = NetworkPolicy::AllowList {
2435 per_ip: HashMap::new(),
2436 cidrs: vec![(IpCidr::parse("192.168.0.0/16").unwrap(), PortAllow::Any)],
2437 any_ip_ports: HashSet::new(),
2438 };
2439 assert!(policy.allows("192.168.5.5".parse().unwrap(), 9999)); assert!(!policy.allows("10.0.0.1".parse().unwrap(), 9999)); }
2442}