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,
241 AllowList {
244 per_ip: HashMap<IpAddr, PortAllow>,
247 cidrs: Vec<(crate::network::IpCidr, PortAllow)>,
251 any_ip_ports: HashSet<u16>,
254 },
255 DenyList {
258 cidrs: Vec<(crate::network::IpCidr, PortAllow)>,
261 any_ip_ports: HashSet<u16>,
263 deny_all: bool,
266 },
267}
268
269impl NetworkPolicy {
270 pub fn allows(&self, ip: IpAddr, port: u16) -> bool {
272 match self {
273 NetworkPolicy::Unrestricted => true,
274 NetworkPolicy::AllowList { per_ip, cidrs, any_ip_ports } => {
275 if any_ip_ports.contains(&port) {
276 return true;
277 }
278 match per_ip.get(&ip) {
279 Some(PortAllow::Any) => return true,
280 Some(PortAllow::Specific(s)) if s.contains(&port) => return true,
281 _ => {}
282 }
283 for (net, allowed) in cidrs {
284 if net.contains(ip) {
285 match allowed {
286 PortAllow::Any => return true,
287 PortAllow::Specific(s) => {
288 if s.contains(&port) {
289 return true;
290 }
291 }
292 }
293 }
294 }
295 false
296 }
297 NetworkPolicy::DenyList { cidrs, any_ip_ports, deny_all } => {
298 if *deny_all {
299 return false;
300 }
301 if any_ip_ports.contains(&port) {
302 return false;
303 }
304 for (net, denied) in cidrs {
305 if net.contains(ip) {
306 match denied {
307 PortAllow::Any => return false,
308 PortAllow::Specific(s) => {
309 if s.contains(&port) {
310 return false;
311 }
312 }
313 }
314 }
315 }
316 true
317 }
318 }
319 }
320}
321
322pub(crate) fn is_path_denied_for_notif(
333 policy_fn_state: &super::state::PolicyFnState,
334 notif: &SeccompNotif,
335 notif_fd: RawFd,
336) -> bool {
337 if let Some(path) = resolve_path_for_notif(notif, notif_fd) {
338 if is_denied_with_symlink_resolve(policy_fn_state, &path) {
339 return true;
340 }
341 }
342 if let Some(path) = resolve_second_path_for_notif(notif, notif_fd) {
344 if is_denied_with_symlink_resolve(policy_fn_state, &path) {
345 return true;
346 }
347 }
348 false
349}
350
351fn is_denied_with_symlink_resolve(
357 policy_fn_state: &super::state::PolicyFnState,
358 path: &str,
359) -> bool {
360 if policy_fn_state.is_path_denied(path) {
362 return true;
363 }
364 if let Ok(real) = std::fs::canonicalize(path) {
366 if policy_fn_state.is_path_denied(&real.to_string_lossy()) {
367 return true;
368 }
369 }
370 false
371}
372
373fn tgid_of(tid: u32) -> Option<u32> {
375 let status = std::fs::read_to_string(format!("/proc/{}/status", tid)).ok()?;
376 status
377 .lines()
378 .find_map(|l| l.strip_prefix("Tgid:").and_then(|r| r.trim().parse().ok()))
379}
380
381pub(crate) fn dup_fd_from_pid(pid: u32, target_fd: i32) -> io::Result<OwnedFd> {
390 use crate::sys::syscall::{pidfd_getfd, pidfd_open};
391 let pidfd = pidfd_open(pid, 0).or_else(|e| match tgid_of(pid) {
392 Some(tgid) if tgid != pid => pidfd_open(tgid, 0),
393 _ => Err(e),
394 })?;
395 pidfd_getfd(&pidfd, target_fd, 0)
396}
397
398pub struct NotifPolicy {
404 pub max_memory_bytes: u64,
405 pub max_processes: u32,
406 pub has_memory_limit: bool,
407 pub has_net_allowlist: bool,
408 pub has_bind_denylist: bool,
412 pub has_random_seed: bool,
413 pub has_time_start: bool,
414 pub argv_safety_required: bool,
421 pub time_offset: i64,
422 pub num_cpus: Option<u32>,
423 pub port_remap: bool,
424 pub cow_enabled: bool,
425 pub chroot_root: Option<std::path::PathBuf>,
426 pub chroot_readable: Vec<std::path::PathBuf>,
428 pub chroot_writable: Vec<std::path::PathBuf>,
430 pub chroot_denied: Vec<std::path::PathBuf>,
432 pub chroot_mounts: Vec<(std::path::PathBuf, std::path::PathBuf)>,
434 pub deterministic_dirs: bool,
435 pub virtual_hostname: Option<String>,
436 pub has_http_acl: bool,
437 pub virtual_etc_hosts: String,
442 pub ca_inject_paths: Vec<std::path::PathBuf>,
444 pub ca_inject_pem: Option<std::sync::Arc<Vec<u8>>>,
447}
448
449fn recv_notif(fd: RawFd) -> io::Result<SeccompNotif> {
456 let mut notif: SeccompNotif = unsafe { std::mem::zeroed() };
457 let ret = unsafe {
458 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_RECV as libc::c_ulong, &mut notif as *mut _)
459 };
460 if ret < 0 {
461 Err(io::Error::last_os_error())
462 } else {
463 Ok(notif)
464 }
465}
466
467enum NotifFdState {
469 Pending,
472 Empty,
475 Terminal,
480}
481
482fn probe_notif_fd(fd: RawFd) -> NotifFdState {
493 let mut pfd = libc::pollfd {
494 fd,
495 events: libc::POLLIN,
496 revents: 0,
497 };
498 let r = unsafe { libc::poll(&mut pfd, 1, 0) };
499 if r > 0 && (pfd.revents & libc::POLLIN) != 0 {
500 return NotifFdState::Pending;
501 }
502 if r < 0 || (pfd.revents & (libc::POLLHUP | libc::POLLERR | libc::POLLNVAL)) != 0 {
503 return NotifFdState::Terminal;
504 }
505 NotifFdState::Empty
506}
507
508fn respond_continue(fd: RawFd, id: u64) -> io::Result<()> {
510 let resp = SeccompNotifResp {
511 id,
512 val: 0,
513 error: 0,
514 flags: SECCOMP_USER_NOTIF_FLAG_CONTINUE,
515 };
516 send_resp_raw(fd, &resp)
517}
518
519fn respond_errno(fd: RawFd, id: u64, errno: i32) -> io::Result<()> {
521 let resp = SeccompNotifResp {
522 id,
523 val: 0,
524 error: -errno,
525 flags: 0,
526 };
527 send_resp_raw(fd, &resp)
528}
529
530fn respond_value(fd: RawFd, id: u64, val: i64) -> io::Result<()> {
532 let resp = SeccompNotifResp {
533 id,
534 val,
535 error: 0,
536 flags: 0,
537 };
538 send_resp_raw(fd, &resp)
539}
540
541fn inject_failure_resp(id: u64) -> SeccompNotifResp {
549 SeccompNotifResp {
550 id,
551 val: 0,
552 error: -libc::EACCES,
553 flags: 0,
554 }
555}
556
557fn inject_fd_and_send(fd: RawFd, id: u64, srcfd: RawFd, newfd_flags: u32) -> io::Result<i32> {
563 let addfd = SeccompNotifAddfd {
564 id,
565 flags: SECCOMP_ADDFD_FLAG_SEND,
566 srcfd: srcfd as u32,
567 newfd: 0, newfd_flags,
569 };
570 let ret = unsafe {
571 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong, &addfd as *const _)
572 };
573 if ret < 0 {
574 Err(io::Error::last_os_error())
575 } else {
576 Ok(ret as i32)
577 }
578}
579
580fn inject_fd(fd: RawFd, id: u64, srcfd: RawFd, targetfd: i32) -> io::Result<()> {
583 let addfd = SeccompNotifAddfd {
584 id,
585 flags: 0,
586 srcfd: srcfd as u32,
587 newfd: targetfd as u32,
588 newfd_flags: 0,
589 };
590 let ret = unsafe {
591 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_ADDFD as libc::c_ulong, &addfd as *const _)
592 };
593 if ret < 0 {
594 Err(io::Error::last_os_error())
595 } else {
596 Ok(())
597 }
598}
599
600fn send_resp_raw(fd: RawFd, resp: &SeccompNotifResp) -> io::Result<()> {
602 let ret = unsafe {
603 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND as libc::c_ulong, resp as *const _)
604 };
605 if ret < 0 {
606 Err(io::Error::last_os_error())
607 } else {
608 Ok(())
609 }
610}
611
612pub(crate) fn id_valid(fd: RawFd, id: u64) -> io::Result<()> {
615 let ret = unsafe {
616 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_ID_VALID as libc::c_ulong, &id as *const _)
617 };
618 if ret < 0 {
619 Err(io::Error::last_os_error())
620 } else {
621 Ok(())
622 }
623}
624
625fn try_set_sync_wakeup(fd: RawFd) {
627 let flags: u64 = SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP as u64;
628 unsafe {
629 libc::ioctl(fd, SECCOMP_IOCTL_NOTIF_SET_FLAGS as libc::c_ulong, &flags as *const _);
630 }
631}
632
633fn read_child_mem_vm(pid: u32, addr: u64, len: usize) -> Result<Vec<u8>, NotifError> {
639 let mut buf = vec![0u8; len];
640 let local_iov = libc::iovec {
641 iov_base: buf.as_mut_ptr() as *mut libc::c_void,
642 iov_len: len,
643 };
644 let remote_iov = libc::iovec {
645 iov_base: addr as *mut libc::c_void,
646 iov_len: len,
647 };
648 let ret = unsafe {
649 libc::process_vm_readv(pid as i32, &local_iov, 1, &remote_iov, 1, 0)
650 };
651 if ret < 0 {
652 Err(NotifError::ChildMemoryRead(io::Error::last_os_error()))
653 } else {
654 buf.truncate(ret as usize);
655 Ok(buf)
656 }
657}
658
659fn write_child_mem_vm(pid: u32, addr: u64, data: &[u8]) -> Result<(), NotifError> {
661 let local_iov = libc::iovec {
662 iov_base: data.as_ptr() as *mut libc::c_void,
663 iov_len: data.len(),
664 };
665 let remote_iov = libc::iovec {
666 iov_base: addr as *mut libc::c_void,
667 iov_len: data.len(),
668 };
669 let ret = unsafe {
670 libc::process_vm_writev(pid as i32, &local_iov, 1, &remote_iov, 1, 0)
671 };
672 if ret < 0 {
673 Err(NotifError::ChildMemoryRead(io::Error::last_os_error()))
674 } else if (ret as usize) < data.len() {
675 Err(NotifError::ChildMemoryRead(io::Error::new(
676 io::ErrorKind::WriteZero,
677 format!("short write: {} of {} bytes", ret, data.len()),
678 )))
679 } else {
680 Ok(())
681 }
682}
683
684pub fn read_child_mem(
694 notif_fd: RawFd,
695 id: u64,
696 pid: u32,
697 addr: u64,
698 len: usize,
699) -> Result<Vec<u8>, NotifError> {
700 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
701 let result = read_child_mem_vm(pid, addr, len)?;
702 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
703 Ok(result)
704}
705
706pub fn read_child_cstr(
721 notif_fd: RawFd,
722 id: u64,
723 pid: u32,
724 addr: u64,
725 max_len: usize,
726) -> Option<String> {
727 if addr == 0 || max_len == 0 {
728 return None;
729 }
730
731 const PAGE_SIZE: u64 = 4096;
732 let mut result = Vec::with_capacity(max_len.min(256));
733 let mut cur = addr;
734 while result.len() < max_len {
735 let page_remaining = PAGE_SIZE - (cur % PAGE_SIZE);
736 let remaining = max_len - result.len();
737 let to_read = page_remaining.min(remaining as u64) as usize;
738 let bytes = read_child_mem(notif_fd, id, pid, cur, to_read).ok()?;
739 if let Some(nul) = bytes.iter().position(|&b| b == 0) {
740 result.extend_from_slice(&bytes[..nul]);
741 return String::from_utf8(result).ok();
742 }
743 result.extend_from_slice(&bytes);
744 cur += to_read as u64;
745 }
746
747 String::from_utf8(result).ok()
748}
749
750pub fn write_child_mem(
757 notif_fd: RawFd,
758 id: u64,
759 pid: u32,
760 addr: u64,
761 data: &[u8],
762) -> Result<(), NotifError> {
763 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
764 write_child_mem_vm(pid, addr, data)?;
765 id_valid(notif_fd, id).map_err(NotifError::Ioctl)?;
766 Ok(())
767}
768
769fn send_response(fd: RawFd, id: u64, action: NotifAction) -> io::Result<()> {
775 match action {
776 NotifAction::Continue => respond_continue(fd, id),
777 NotifAction::Errno(errno) => respond_errno(fd, id, errno),
778 NotifAction::InjectFd { srcfd, targetfd } => {
779 inject_fd(fd, id, srcfd, targetfd)?;
780 respond_continue(fd, id)
781 }
782 NotifAction::InjectFdSend { srcfd, newfd_flags } => {
783 match inject_fd_and_send(fd, id, srcfd.as_raw_fd(), newfd_flags) {
789 Ok(_new_fd) => Ok(()),
790 Err(_) => send_resp_raw(fd, &inject_failure_resp(id)),
791 }
792 }
793 NotifAction::InjectFdSendTracked { srcfd, newfd_flags, on_success } => {
794 match inject_fd_and_send(fd, id, srcfd.as_raw_fd(), newfd_flags) {
795 Ok(new_fd) => {
796 (on_success.0)(new_fd);
797 Ok(())
798 }
799 Err(_) => send_resp_raw(fd, &inject_failure_resp(id)),
800 }
801 }
802 NotifAction::ReturnValue(val) => respond_value(fd, id, val),
803 NotifAction::Hold => Ok(()), NotifAction::Defer(_) => {
805 debug_assert!(false, "Defer reached send_response; should be intercepted earlier");
809 respond_errno(fd, id, libc::EIO)
810 }
811 NotifAction::Kill { sig, pgid } => {
812 unsafe { libc::killpg(pgid, sig) };
815 respond_errno(fd, id, ENOMEM)
816 }
817 }
818}
819
820fn maybe_patch_vdso(pid: i32, procfs: &mut super::state::ProcfsState, policy: &NotifPolicy) {
826 let base = match crate::vdso::find_vdso_base(pid) {
827 Ok(addr) => addr,
828 Err(_) => return,
829 };
830 if base == procfs.vdso_patched_addr {
831 return; }
833 let time_offset = if policy.has_time_start { Some(policy.time_offset) } else { None };
834 if crate::vdso::patch(pid, time_offset, policy.has_random_seed).is_ok() {
835 procfs.vdso_patched_addr = base;
836 }
837}
838
839fn syscall_name(nr: i64) -> &'static str {
845 match nr {
846 n if n == libc::SYS_openat => "openat",
847 n if n == libc::SYS_connect => "connect",
848 n if n == libc::SYS_sendto => "sendto",
849 n if n == libc::SYS_sendmsg => "sendmsg",
850 n if n == libc::SYS_sendmmsg => "sendmmsg",
851 n if n == libc::SYS_bind => "bind",
852 n if n == libc::SYS_clone => "clone",
853 n if n == libc::SYS_clone3 => "clone3",
854 n if Some(n) == arch::sys_vfork() => "vfork",
855 n if Some(n) == arch::sys_fork() => "fork",
856 n if n == libc::SYS_execve => "execve",
857 n if n == libc::SYS_execveat => "execveat",
858 n if n == libc::SYS_mmap => "mmap",
859 n if n == libc::SYS_munmap => "munmap",
860 n if n == libc::SYS_brk => "brk",
861 n if n == libc::SYS_getrandom => "getrandom",
862 n if n == libc::SYS_unlinkat => "unlinkat",
863 n if n == libc::SYS_mkdirat => "mkdirat",
864 _ => "unknown",
865 }
866}
867
868fn syscall_category(nr: i64) -> crate::policy_fn::SyscallCategory {
870 use crate::policy_fn::SyscallCategory;
871 match nr {
872 n if n == libc::SYS_openat || n == libc::SYS_unlinkat
873 || n == libc::SYS_mkdirat || n == libc::SYS_renameat2
874 || n == libc::SYS_symlinkat || n == libc::SYS_linkat
875 || n == libc::SYS_fchmodat || n == libc::SYS_fchownat
876 || n == libc::SYS_truncate || n == libc::SYS_readlinkat
877 || n == libc::SYS_newfstatat || n == libc::SYS_statx
878 || n == libc::SYS_faccessat || n == libc::SYS_getdents64
879 || Some(n) == arch::sys_getdents() => SyscallCategory::File,
880 n if n == libc::SYS_connect || n == libc::SYS_sendto
881 || n == libc::SYS_sendmsg || n == libc::SYS_sendmmsg
882 || n == libc::SYS_bind
883 || n == libc::SYS_getsockname => SyscallCategory::Network,
884 n if n == libc::SYS_clone || n == libc::SYS_clone3
885 || Some(n) == arch::sys_vfork() || Some(n) == arch::sys_fork()
886 || n == libc::SYS_execve || n == libc::SYS_execveat => SyscallCategory::Process,
887 n if n == libc::SYS_mmap || n == libc::SYS_munmap
888 || n == libc::SYS_brk || n == libc::SYS_mremap
889 => SyscallCategory::Memory,
890 _ => SyscallCategory::File, }
892}
893
894fn read_ppid(pid: u32) -> Option<u32> {
896 let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
897 let close_paren = stat.rfind(')')?;
900 let rest = &stat[close_paren + 2..]; let fields: Vec<&str> = rest.split_whitespace().collect();
902 fields.get(1)?.parse().ok()
904}
905
906fn read_path_for_event(notif: &SeccompNotif, addr: u64, notif_fd: RawFd) -> Option<String> {
908 if addr == 0 { return None; }
909 let bytes = read_child_mem(notif_fd, notif.id, notif.pid, addr, 256).ok()?;
910 let nul = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
911 String::from_utf8(bytes[..nul].to_vec()).ok()
912}
913
914fn normalize_path(path: &std::path::Path) -> String {
915 use std::path::{Component, PathBuf};
916
917 let mut normalized = PathBuf::new();
918 let absolute = path.is_absolute();
919 if absolute {
920 normalized.push("/");
921 }
922
923 for component in path.components() {
924 match component {
925 Component::RootDir | Component::CurDir => {}
926 Component::ParentDir => {
927 normalized.pop();
928 }
929 Component::Normal(part) => normalized.push(part),
930 Component::Prefix(_) => {}
931 }
932 }
933
934 if normalized.as_os_str().is_empty() {
935 if absolute { "/".into() } else { ".".into() }
936 } else {
937 normalized.to_string_lossy().into_owned()
938 }
939}
940
941fn resolve_at_path_for_event(notif: &SeccompNotif, dirfd: i64, path: &str) -> Option<String> {
942 use std::path::Path;
943
944 if Path::new(path).is_absolute() {
945 return Some(normalize_path(Path::new(path)));
946 }
947
948 let dirfd32 = dirfd as i32;
949 let base = if dirfd32 == libc::AT_FDCWD {
950 std::fs::read_link(format!("/proc/{}/cwd", notif.pid)).ok()?
951 } else {
952 std::fs::read_link(format!("/proc/{}/fd/{}", notif.pid, dirfd32)).ok()?
953 };
954
955 Some(normalize_path(&base.join(path)))
956}
957
958fn resolve_path_for_notif(notif: &SeccompNotif, notif_fd: RawFd) -> Option<String> {
959 let nr = notif.data.nr as i64;
960 match nr {
961 n if n == libc::SYS_openat => {
962 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
964 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
965 }
966 n if Some(n) == arch::sys_open() || n == libc::SYS_execve => {
967 let path = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
968 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
969 }
970 n if n == libc::SYS_execveat => {
971 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
972 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
973 }
974 n if n == libc::SYS_linkat => {
977 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
978 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
979 }
980 n if n == libc::SYS_renameat2 => {
983 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
984 resolve_at_path_for_event(notif, notif.data.args[0] as i64, &path)
985 }
986 n if n == libc::SYS_symlinkat => {
989 let target = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
990 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &target)
992 }
993 n if Some(n) == arch::sys_link() => {
995 let path = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
996 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
997 }
998 n if Some(n) == arch::sys_rename() => {
1000 let path = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
1001 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1002 }
1003 n if Some(n) == arch::sys_symlink() => {
1005 let target = read_path_for_event(notif, notif.data.args[0], notif_fd)?;
1006 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &target)
1007 }
1008 _ => None,
1009 }
1010}
1011
1012fn resolve_second_path_for_notif(notif: &SeccompNotif, notif_fd: RawFd) -> Option<String> {
1016 let nr = notif.data.nr as i64;
1017 match nr {
1018 n if n == libc::SYS_renameat2 => {
1020 let path = read_path_for_event(notif, notif.data.args[3], notif_fd)?;
1021 resolve_at_path_for_event(notif, notif.data.args[2] as i64, &path)
1022 }
1023 n if n == libc::SYS_linkat => {
1027 let path = read_path_for_event(notif, notif.data.args[3], notif_fd)?;
1028 resolve_at_path_for_event(notif, notif.data.args[2] as i64, &path)
1029 }
1030 n if Some(n) == arch::sys_rename() => {
1032 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1033 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1034 }
1035 n if Some(n) == arch::sys_link() => {
1037 let path = read_path_for_event(notif, notif.data.args[1], notif_fd)?;
1038 resolve_at_path_for_event(notif, libc::AT_FDCWD as i64, &path)
1039 }
1040 _ => None,
1041 }
1042}
1043
1044fn read_sockaddr_for_event(notif: &SeccompNotif, addr: u64, len: usize, notif_fd: RawFd)
1046 -> (Option<std::net::IpAddr>, Option<u16>)
1047{
1048 if addr == 0 || len < 4 { return (None, None); }
1049 let bytes = match read_child_mem(notif_fd, notif.id, notif.pid, addr, len.min(128)) {
1050 Ok(b) => b,
1051 Err(_) => return (None, None),
1052 };
1053 if bytes.len() < 4 { return (None, None); }
1054 let family = u16::from_ne_bytes([bytes[0], bytes[1]]);
1055 let port = u16::from_be_bytes([bytes[2], bytes[3]]);
1056 let ip = match family as u32 {
1057 f if f == crate::sys::structs::AF_INET && bytes.len() >= 8 => {
1058 Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
1059 bytes[4], bytes[5], bytes[6], bytes[7],
1060 )))
1061 }
1062 f if f == crate::sys::structs::AF_INET6 && bytes.len() >= 24 => {
1063 let mut addr = [0u8; 16];
1064 addr.copy_from_slice(&bytes[8..24]);
1065 Some(std::net::IpAddr::V6(std::net::Ipv6Addr::from(addr)))
1066 }
1067 _ => None,
1068 };
1069 (ip, if port > 0 { Some(port) } else { None })
1070}
1071
1072fn read_argv_for_event(notif: &SeccompNotif, argv_ptr: u64, notif_fd: RawFd) -> Option<Vec<String>> {
1075 if argv_ptr == 0 { return None; }
1076 let mut args = Vec::new();
1077 let ptr_size = std::mem::size_of::<u64>();
1078
1079 for i in 0..64u64 {
1080 let ptr_addr = argv_ptr + i * ptr_size as u64;
1081 let ptr_bytes = read_child_mem(notif_fd, notif.id, notif.pid, ptr_addr, ptr_size).ok()?;
1082 let str_ptr = u64::from_ne_bytes(ptr_bytes[..8].try_into().ok()?);
1083 if str_ptr == 0 { break; } if let Some(s) = read_path_for_event(notif, str_ptr, notif_fd) {
1086 args.push(s);
1087 } else {
1088 break;
1089 }
1090 }
1091
1092 if args.is_empty() { None } else { Some(args) }
1093}
1094
1095fn resolve_held_gate(
1102 received: Option<crate::policy_fn::Verdict>,
1103) -> Option<crate::policy_fn::Verdict> {
1104 match received {
1105 Some(v) => Some(v),
1106 None => Some(crate::policy_fn::Verdict::Deny),
1107 }
1108}
1109
1110async fn emit_policy_event(
1113 notif: &SeccompNotif,
1114 action: &NotifAction,
1115 policy_fn_state: &Arc<tokio::sync::Mutex<super::state::PolicyFnState>>,
1116 notif_fd: RawFd,
1117) -> Option<crate::policy_fn::Verdict> {
1118 let pfs = policy_fn_state.lock().await;
1119 let tx = match pfs.event_tx.as_ref() {
1120 Some(tx) => tx.clone(),
1121 None => return None,
1122 };
1123 drop(pfs);
1124
1125 let nr = notif.data.nr as i64;
1126 let denied = matches!(action, NotifAction::Errno(_));
1127 let name = syscall_name(nr);
1128 let category = syscall_category(nr);
1129 let parent_pid = read_ppid(notif.pid);
1130
1131 let mut host = None;
1148 let mut port = None;
1149 let mut size = None;
1150 let mut argv = None;
1151
1152 if !denied && (nr == libc::SYS_execve || nr == libc::SYS_execveat) {
1153 let argv_ptr = if nr == libc::SYS_execveat {
1156 notif.data.args[2]
1157 } else {
1158 notif.data.args[1]
1159 };
1160 argv = read_argv_for_event(notif, argv_ptr, notif_fd);
1161 }
1162
1163 if nr == libc::SYS_connect || nr == libc::SYS_sendto || nr == libc::SYS_bind {
1164 let addr_ptr = notif.data.args[1];
1166 let addr_len = notif.data.args[2] as usize;
1167 let (h, p) = read_sockaddr_for_event(notif, addr_ptr, addr_len, notif_fd);
1168 host = h;
1169 port = p;
1170 }
1171
1172 if nr == libc::SYS_mmap {
1173 size = Some(notif.data.args[1]);
1175 }
1176
1177 let event = crate::policy_fn::SyscallEvent {
1178 syscall: name.to_string(),
1179 category,
1180 pid: notif.pid,
1181 parent_pid,
1182 host,
1183 port,
1184 size,
1185 argv,
1186 denied,
1187 };
1188
1189 let is_held = nr == libc::SYS_execve || nr == libc::SYS_execveat
1192 || nr == libc::SYS_connect || nr == libc::SYS_sendto
1193 || nr == libc::SYS_bind || nr == libc::SYS_openat;
1194
1195 if is_held {
1196 let (gate_tx, gate_rx) = tokio::sync::oneshot::channel();
1197 let _ = tx.send(crate::policy_fn::PolicyEvent {
1198 event,
1199 gate: Some(gate_tx),
1200 });
1201 let received = match tokio::time::timeout(std::time::Duration::from_secs(5), gate_rx).await {
1202 Ok(Ok(verdict)) => Some(verdict),
1203 _ => None, };
1205 resolve_held_gate(received)
1206 } else {
1207 let _ = tx.send(crate::policy_fn::PolicyEvent {
1208 event,
1209 gate: None,
1210 });
1211 None
1212 }
1213}
1214
1215const DEFER_MAX_INFLIGHT: usize = 64;
1225
1226const DEFER_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
1231
1232async fn run_deferred_within(deferred: Deferred, limit: std::time::Duration) -> NotifAction {
1238 match tokio::time::timeout(limit, deferred.run()).await {
1239 Ok(action) => finalize_deferred(action),
1240 Err(_) => {
1241 eprintln!(
1242 "sandlock: deferred handler exceeded {:?}; failing syscall with EIO",
1243 limit
1244 );
1245 NotifAction::Errno(libc::EIO)
1246 }
1247 }
1248}
1249
1250fn spawn_deferred(
1256 fd: RawFd,
1257 id: u64,
1258 deferred: Deferred,
1259 permit: tokio::sync::OwnedSemaphorePermit,
1260) {
1261 tokio::spawn(async move {
1262 let _permit = permit; let action = run_deferred_within(deferred, DEFER_TIMEOUT).await;
1264 let _ = send_response(fd, id, action);
1265 });
1266}
1267
1268async fn handle_notification(
1269 notif: SeccompNotif,
1270 ctx: &Arc<super::ctx::SupervisorCtx>,
1271 dispatch_table: &super::dispatch::DispatchTable,
1272 fd: RawFd,
1273 defer_sem: &Arc<tokio::sync::Semaphore>,
1274) {
1275 let policy = &ctx.policy;
1276
1277 crate::resource::register_child_if_new(ctx, notif.pid as i32).await;
1283
1284 if policy.has_time_start || policy.has_random_seed {
1286 let mut pfs = ctx.procfs.lock().await;
1287 maybe_patch_vdso(notif.pid as i32, &mut pfs, policy);
1288 }
1289
1290 let mut action = {
1292 let nr = notif.data.nr as i64;
1293 let mut path_check_nrs = vec![
1294 libc::SYS_openat, libc::SYS_execve, libc::SYS_execveat,
1295 libc::SYS_linkat, libc::SYS_renameat2, libc::SYS_symlinkat,
1296 ];
1297 path_check_nrs.extend([
1298 arch::sys_open(), arch::sys_link(), arch::sys_rename(), arch::sys_symlink(),
1299 ].into_iter().flatten());
1300 let should_precheck_denied = policy.chroot_root.is_none()
1301 && path_check_nrs.contains(&nr);
1302 if should_precheck_denied {
1303 let pfs = ctx.policy_fn.lock().await;
1304 if is_path_denied_for_notif(&pfs, ¬if, fd) {
1305 NotifAction::Errno(libc::EACCES)
1306 } else {
1307 drop(pfs);
1308 dispatch_table.dispatch(notif, fd).await
1309 }
1310 } else {
1311 dispatch_table.dispatch(notif, fd).await
1312 }
1313 };
1314
1315 let nr = notif.data.nr as i64;
1316 let fork_counted = matches!(action, NotifAction::Continue)
1317 && crate::resource::fork_counted_on_continue(¬if, fd);
1318
1319 let mut exec_freeze = None;
1333 if matches!(action, NotifAction::Continue)
1334 && policy.argv_safety_required
1335 && crate::freeze::requires_freeze_on_continue(nr)
1336 {
1337 match crate::freeze::freeze_sandbox_for_execve(
1338 &ctx.processes,
1339 notif.pid as i32,
1340 ) {
1341 Ok(outcome) => {
1342 exec_freeze = Some(outcome);
1343 }
1344 Err(e) => {
1345 eprintln!(
1346 "sandlock: argv-safety freeze failed for pid {}: {} \
1347 — denying execve to preserve TOCTOU invariant",
1348 notif.pid, e
1349 );
1350 action = NotifAction::Errno(libc::EPERM);
1351 }
1352 }
1353 }
1354
1355 if let Some(verdict) = emit_policy_event(¬if, &action, &ctx.policy_fn, fd).await {
1359 use crate::policy_fn::Verdict;
1360 match verdict {
1361 Verdict::Deny => { action = NotifAction::Errno(libc::EPERM); }
1362 Verdict::DenyWith(errno) => { action = NotifAction::Errno(errno); }
1363 Verdict::Audit => { }
1364 Verdict::Allow => {}
1365 }
1366 }
1367
1368 if fork_counted && !matches!(action, NotifAction::Continue) {
1369 crate::resource::rollback_fork_count(&ctx.resource).await;
1370 }
1371
1372 let mut creation_trace = None;
1377 if matches!(action, NotifAction::Continue)
1378 && crate::resource::requires_process_creation_tracking(¬if, fd, policy)
1379 {
1380 match crate::resource::prepare_process_creation_tracking(notif.pid as i32).await {
1381 Ok(trace) => {
1382 creation_trace = Some(trace);
1383 }
1384 Err(e) => {
1385 eprintln!(
1386 "sandlock: process-creation tracking failed for pid {}: {} \
1387 — denying fork-like syscall to preserve argv TOCTOU invariant",
1388 notif.pid, e
1389 );
1390 if fork_counted {
1391 crate::resource::rollback_fork_count(&ctx.resource).await;
1392 }
1393 action = NotifAction::Errno(libc::EPERM);
1394 }
1395 }
1396 }
1397
1398 if let NotifAction::Defer(deferred) = action {
1409 if crate::freeze::requires_freeze_on_continue(nr)
1410 || crate::resource::requires_process_creation_tracking(¬if, fd, policy)
1411 {
1412 let _ = send_response(fd, notif.id, NotifAction::Errno(libc::EPERM));
1413 return;
1414 }
1415 match Arc::clone(defer_sem).try_acquire_owned() {
1416 Ok(permit) => spawn_deferred(fd, notif.id, deferred, permit),
1417 Err(_) => {
1420 let _ = send_response(fd, notif.id, NotifAction::Errno(libc::EAGAIN));
1421 }
1422 }
1423 return;
1424 }
1425
1426 let exec_continued = exec_freeze.is_some() && matches!(action, NotifAction::Continue);
1428 let send_result = send_response(fd, notif.id, action);
1429
1430 if let Some(trace) = creation_trace {
1431 if send_result.is_ok() {
1432 match crate::resource::finish_process_creation_tracking(ctx, trace).await {
1433 Ok(true) => {}
1434 Ok(false) => {
1435 crate::resource::rollback_fork_count(&ctx.resource).await;
1436 }
1437 Err(e) => {
1438 crate::resource::rollback_fork_count(&ctx.resource).await;
1439 eprintln!(
1440 "sandlock: process-creation tracking completion failed for pid {}: {}",
1441 notif.pid, e
1442 );
1443 }
1444 }
1445 } else {
1446 crate::resource::rollback_fork_count(&ctx.resource).await;
1447 crate::resource::abort_process_creation_tracking(trace).await;
1448 }
1449 }
1450
1451 if let Some(freeze) = exec_freeze {
1452 if exec_continued && send_result.is_ok() {
1453 crate::freeze::detach_peers(&freeze.peer_tids);
1454 } else {
1455 crate::freeze::detach_all(&freeze);
1456 }
1457 }
1458}
1459
1460pub async fn supervisor(
1472 notif_fd: OwnedFd,
1473 ctx: Arc<super::ctx::SupervisorCtx>,
1474 pending_handlers: Vec<(i64, std::sync::Arc<dyn super::dispatch::Handler>)>,
1475 startup: tokio::sync::oneshot::Sender<io::Result<()>>,
1476) {
1477 let async_fd = match tokio::io::unix::AsyncFd::with_interest(
1480 notif_fd,
1481 tokio::io::Interest::READABLE,
1482 ) {
1483 Ok(fd) => fd,
1484 Err(err) => {
1485 let _ = startup.send(Err(err));
1486 return;
1487 }
1488 };
1489 let fd = async_fd.get_ref().as_raw_fd();
1490
1491 let dispatch_table = Arc::new(super::dispatch::build_dispatch_table(
1493 &ctx.policy,
1494 &ctx.resource,
1495 &ctx,
1496 pending_handlers,
1497 ));
1498
1499 try_set_sync_wakeup(fd);
1501
1502 let _ = startup.send(Ok(()));
1506
1507 let gc = tokio::spawn(process_index_gc(Arc::clone(&ctx.processes)));
1513
1514 let defer_sem = Arc::new(tokio::sync::Semaphore::new(DEFER_MAX_INFLIGHT));
1518
1519 'outer: loop {
1529 let mut ready = match async_fd.readable().await {
1530 Ok(r) => r,
1531 Err(_) => break 'outer,
1532 };
1533 ready.clear_ready();
1534 drop(ready);
1535
1536 loop {
1537 match probe_notif_fd(fd) {
1538 NotifFdState::Pending => {
1539 let notif = match recv_notif(fd) {
1540 Ok(n) => n,
1541 Err(e) if e.raw_os_error() == Some(libc::EINTR) => continue,
1542 Err(_) => break 'outer,
1543 };
1544 handle_notification(notif, &ctx, &dispatch_table, fd, &defer_sem).await;
1545 }
1546 NotifFdState::Empty => break,
1547 NotifFdState::Terminal => break 'outer,
1548 }
1549 }
1550 }
1551
1552 gc.abort();
1553}
1554
1555async fn process_index_gc(processes: Arc<super::state::ProcessIndex>) {
1559 let interval = std::time::Duration::from_secs(300);
1560 loop {
1561 tokio::time::sleep(interval).await;
1562 if processes.len() == 0 {
1563 continue;
1564 }
1565 processes.prune_dead();
1566 }
1567}
1568
1569pub(crate) fn spawn_pid_watcher(
1579 ctx: Arc<super::ctx::SupervisorCtx>,
1580 key: super::state::PidKey,
1581 pidfd: std::os::unix::io::OwnedFd,
1582) {
1583 tokio::spawn(async move {
1584 let async_fd = match tokio::io::unix::AsyncFd::with_interest(
1585 pidfd,
1586 tokio::io::Interest::READABLE,
1587 ) {
1588 Ok(f) => f,
1589 Err(_) => {
1590 cleanup_pid(&ctx, key).await;
1596 return;
1597 }
1598 };
1599 let _ = async_fd.readable().await;
1602 cleanup_pid(&ctx, key).await;
1603 });
1605}
1606
1607pub(crate) async fn cleanup_pid(ctx: &super::ctx::SupervisorCtx, key: super::state::PidKey) {
1613 ctx.processes.unregister(key);
1614}
1615
1616#[cfg(test)]
1621mod tests {
1622 use super::*;
1623 use std::os::unix::io::FromRawFd;
1624
1625 fn gettid() -> u32 {
1626 (unsafe { libc::syscall(libc::SYS_gettid) }) as u32
1627 }
1628
1629 #[test]
1630 fn inject_failure_response_denies_not_continues() {
1631 let resp = inject_failure_resp(123);
1635 assert_eq!(resp.id, 123);
1636 assert_eq!(
1637 resp.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE,
1638 0,
1639 "fd-injection failure must not respond with CONTINUE"
1640 );
1641 assert_ne!(resp.error, 0, "fd-injection failure must be a denial");
1642 assert_eq!(resp.error, -libc::EACCES);
1643 }
1644
1645 #[test]
1646 fn held_gate_no_decision_denies() {
1647 use crate::policy_fn::Verdict;
1648 assert!(matches!(resolve_held_gate(None), Some(Verdict::Deny)));
1651 }
1652
1653 #[test]
1654 fn held_gate_passes_through_callback_verdict() {
1655 use crate::policy_fn::Verdict;
1656 assert!(matches!(
1658 resolve_held_gate(Some(Verdict::Allow)),
1659 Some(Verdict::Allow)
1660 ));
1661 assert!(matches!(
1662 resolve_held_gate(Some(Verdict::Deny)),
1663 Some(Verdict::Deny)
1664 ));
1665 assert!(matches!(
1666 resolve_held_gate(Some(Verdict::DenyWith(13))),
1667 Some(Verdict::DenyWith(13))
1668 ));
1669 }
1670
1671 #[test]
1672 fn tgid_of_main_thread_is_own_pid() {
1673 assert_eq!(tgid_of(gettid()), Some(std::process::id()));
1675 }
1676
1677 #[test]
1678 fn tgid_of_worker_thread_resolves_to_process() {
1679 let (tid_tx, tid_rx) = std::sync::mpsc::channel();
1681 let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
1682 let h = std::thread::spawn(move || {
1683 tid_tx.send(gettid()).unwrap();
1684 done_rx.recv().ok(); });
1686 let worker_tid = tid_rx.recv().unwrap();
1687 let pid = std::process::id();
1688 assert_ne!(worker_tid, pid, "worker tid must differ from pid");
1689 assert_eq!(tgid_of(worker_tid), Some(pid));
1690 done_tx.send(()).ok();
1691 h.join().unwrap();
1692 }
1693
1694 #[test]
1695 fn dup_fd_from_pid_handles_worker_thread_fd() {
1696 use std::os::unix::io::AsRawFd;
1697 let (info_tx, info_rx) = std::sync::mpsc::channel();
1701 let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
1702 let h = std::thread::spawn(move || {
1703 let f = std::fs::File::open("/dev/null").unwrap();
1704 info_tx.send((gettid(), f.as_raw_fd())).unwrap();
1705 done_rx.recv().ok();
1706 drop(f);
1707 });
1708 let (worker_tid, fd) = info_rx.recv().unwrap();
1709 let dup = dup_fd_from_pid(worker_tid, fd);
1710 done_tx.send(()).ok();
1711 h.join().unwrap();
1712 assert!(dup.is_ok(), "dup_fd_from_pid for a worker-thread fd failed: {:?}", dup.err());
1713 }
1714
1715 #[test]
1716 fn read_child_cstr_returns_none_for_null_addr_or_zero_max_len() {
1717 assert!(read_child_cstr(-1, 0, 0, 0, 4096).is_none());
1719 assert!(read_child_cstr(-1, 0, 0, 0xdeadbeef, 0).is_none());
1721 }
1722
1723 #[test]
1724 fn test_notif_action_debug() {
1725 let _ = format!("{:?}", NotifAction::Continue);
1727 let _ = format!("{:?}", NotifAction::Errno(1));
1728 let _ = format!("{:?}", NotifAction::InjectFd { srcfd: 3, targetfd: 4 });
1729 let test_fd = unsafe { OwnedFd::from_raw_fd(libc::dup(2)) };
1731 let _ = format!("{:?}", NotifAction::InjectFdSend { srcfd: test_fd, newfd_flags: 0 });
1732 let _ = format!("{:?}", NotifAction::ReturnValue(42));
1733 let _ = format!("{:?}", NotifAction::Hold);
1734 let _ = format!("{:?}", NotifAction::Kill { sig: 9, pgid: 1 });
1735 let _ = format!("{:?}", NotifAction::defer(async { NotifAction::Continue }));
1736 }
1737
1738 #[tokio::test]
1739 async fn deferred_future_need_not_be_sync() {
1740 use std::cell::Cell;
1745 let action = NotifAction::defer(async move {
1746 let counter = Cell::new(0);
1747 counter.set(counter.get() + 41);
1748 tokio::task::yield_now().await; NotifAction::ReturnValue(counter.get() + 1)
1750 });
1751 let NotifAction::Defer(d) = action else { panic!("expected Defer") };
1752 assert!(matches!(d.run().await, NotifAction::ReturnValue(42)));
1753 }
1754
1755 #[tokio::test]
1756 async fn deferred_runs_to_its_terminal_action() {
1757 let action = NotifAction::defer(async { NotifAction::ReturnValue(7) });
1759 let NotifAction::Defer(deferred) = action else {
1760 panic!("defer() must construct a NotifAction::Defer");
1761 };
1762 assert!(matches!(deferred.run().await, NotifAction::ReturnValue(7)));
1763 }
1764
1765 #[tokio::test(start_paused = true)]
1766 async fn deferred_times_out_to_eio() {
1767 let slow = Deferred::new(async {
1771 tokio::time::sleep(std::time::Duration::from_secs(60)).await;
1772 NotifAction::ReturnValue(7)
1773 });
1774 let action = run_deferred_within(slow, std::time::Duration::from_secs(1)).await;
1775 assert!(matches!(action, NotifAction::Errno(e) if e == libc::EIO));
1776 }
1777
1778 #[tokio::test(start_paused = true)]
1779 async fn deferred_within_limit_passes_through() {
1780 let fast = Deferred::new(async { NotifAction::ReturnValue(7) });
1782 let action = run_deferred_within(fast, std::time::Duration::from_secs(1)).await;
1783 assert!(matches!(action, NotifAction::ReturnValue(7)));
1784 }
1785
1786 #[test]
1787 fn finalize_deferred_collapses_nested_defer_to_eio() {
1788 let nested = NotifAction::defer(async { NotifAction::Continue });
1791 assert!(matches!(finalize_deferred(nested), NotifAction::Errno(e) if e == libc::EIO));
1792 assert!(matches!(finalize_deferred(NotifAction::Continue), NotifAction::Continue));
1794 assert!(matches!(
1795 finalize_deferred(NotifAction::ReturnValue(3)),
1796 NotifAction::ReturnValue(3)
1797 ));
1798 }
1799
1800 #[test]
1801 fn content_memfd_roundtrips_content() {
1802 use std::io::Read;
1803 let fd = content_memfd(b"hello world", true).expect("content_memfd");
1804 let mut f = std::fs::File::from(fd);
1806 let mut buf = String::new();
1807 f.read_to_string(&mut buf).unwrap();
1808 assert_eq!(buf, "hello world");
1809 }
1810
1811 #[test]
1812 fn content_memfd_sealed_applies_write_seal() {
1813 let fd = content_memfd(b"data", true).expect("content_memfd");
1814 let seals = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GET_SEALS) };
1815 assert!(seals >= 0, "F_GET_SEALS failed");
1816 assert!(
1817 seals & libc::F_SEAL_WRITE != 0,
1818 "expected F_SEAL_WRITE on a sealed memfd, got {seals:#x}"
1819 );
1820 }
1821
1822 #[test]
1823 fn content_memfd_unsealed_has_no_write_seal() {
1824 let fd = content_memfd(b"data", false).expect("content_memfd");
1825 let seals = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GET_SEALS) };
1826 assert!(seals >= 0, "F_GET_SEALS failed");
1827 assert_eq!(
1828 seals & libc::F_SEAL_WRITE,
1829 0,
1830 "unsealed memfd must not carry a write seal, got {seals:#x}"
1831 );
1832 }
1833
1834 #[test]
1835 fn inject_bytes_produces_sealed_cloexec_injectfdsend() {
1836 use std::io::Read;
1837 match NotifAction::inject_bytes(b"payload") {
1838 NotifAction::InjectFdSend { srcfd, newfd_flags } => {
1839 assert_eq!(newfd_flags, libc::O_CLOEXEC as u32);
1840 let seals = unsafe { libc::fcntl(srcfd.as_raw_fd(), libc::F_GET_SEALS) };
1841 assert!(seals & libc::F_SEAL_WRITE != 0, "inject_bytes must seal");
1842 let mut f = std::fs::File::from(srcfd);
1843 let mut buf = String::new();
1844 f.read_to_string(&mut buf).unwrap();
1845 assert_eq!(buf, "payload");
1846 }
1847 other => panic!("expected InjectFdSend, got {other:?}"),
1848 }
1849 }
1850
1851 #[test]
1852 fn test_network_state_new() {
1853 let ns = super::super::state::NetworkState::new();
1854 assert!(matches!(ns.tcp_policy, NetworkPolicy::Unrestricted));
1855 assert!(matches!(ns.udp_policy, NetworkPolicy::Unrestricted));
1856 assert!(matches!(ns.icmp_policy, NetworkPolicy::Unrestricted));
1857 assert!(ns.port_map.bound_ports.is_empty());
1858 }
1859
1860 #[test]
1861 fn test_time_random_state_new() {
1862 let tr = super::super::state::TimeRandomState::new(None, None);
1863 assert!(tr.time_offset.is_none());
1864 assert!(tr.random_state.is_none());
1865 }
1866
1867 #[test]
1868 fn test_resource_state_new() {
1869 let rs = super::super::state::ResourceState::new(1024 * 1024, 10);
1870 assert_eq!(rs.mem_used, 0);
1871 assert_eq!(rs.max_memory_bytes, 1024 * 1024);
1872 assert_eq!(rs.max_processes, 10);
1873 assert!(!rs.hold_forks);
1874 assert!(rs.held_notif_ids.is_empty());
1875 }
1876
1877 #[test]
1878 fn test_process_vm_readv_self() {
1879 let data: u64 = 0xDEADBEEF_CAFEBABE;
1880 let addr = &data as *const u64 as u64;
1881 let pid = std::process::id();
1882 let result = read_child_mem_vm(pid, addr, 8);
1883 assert!(result.is_ok());
1884 let bytes = result.unwrap();
1885 let read_val = u64::from_ne_bytes(bytes[..8].try_into().unwrap());
1886 assert_eq!(read_val, 0xDEADBEEF_CAFEBABE);
1887 }
1888
1889 #[test]
1890 fn test_process_vm_writev_self() {
1891 let mut data: u64 = 0;
1892 let addr = &mut data as *mut u64 as u64;
1893 let pid = std::process::id();
1894 let payload = 0x1234567890ABCDEFu64.to_ne_bytes();
1895 let result = write_child_mem_vm(pid, addr, &payload);
1896 assert!(result.is_ok());
1897 assert_eq!(data, 0x1234567890ABCDEF);
1898 }
1899
1900 #[test]
1901 fn denylist_blocks_matching_cidr_allows_rest() {
1902 use crate::network::IpCidr;
1903 let policy = NetworkPolicy::DenyList {
1904 cidrs: vec![(IpCidr::parse("10.0.0.0/8").unwrap(), PortAllow::Any)],
1905 any_ip_ports: HashSet::new(),
1906 deny_all: false,
1907 };
1908 assert!(!policy.allows("10.1.2.3".parse().unwrap(), 443)); assert!(policy.allows("8.8.8.8".parse().unwrap(), 443)); }
1911
1912 #[test]
1913 fn denylist_blocks_any_ip_port() {
1914 let mut ports = HashSet::new();
1915 ports.insert(25u16);
1916 let policy = NetworkPolicy::DenyList {
1917 cidrs: Vec::new(),
1918 any_ip_ports: ports,
1919 deny_all: false,
1920 };
1921 assert!(!policy.allows("8.8.8.8".parse().unwrap(), 25)); assert!(policy.allows("8.8.8.8".parse().unwrap(), 80)); }
1924
1925 #[test]
1926 fn denylist_specific_ports_on_cidr() {
1927 use crate::network::IpCidr;
1928 let mut ports = HashSet::new();
1929 ports.insert(443u16);
1930 let policy = NetworkPolicy::DenyList {
1931 cidrs: vec![(IpCidr::parse("1.2.3.4/32").unwrap(), PortAllow::Specific(ports))],
1932 any_ip_ports: HashSet::new(),
1933 deny_all: false,
1934 };
1935 assert!(!policy.allows("1.2.3.4".parse().unwrap(), 443)); assert!(policy.allows("1.2.3.4".parse().unwrap(), 80)); }
1938
1939 #[test]
1940 fn allowlist_permits_matching_cidr_only() {
1941 use crate::network::IpCidr;
1942 let mut ports = HashSet::new();
1943 ports.insert(80u16);
1944 let policy = NetworkPolicy::AllowList {
1945 per_ip: HashMap::new(),
1946 cidrs: vec![(IpCidr::parse("10.0.0.0/8").unwrap(), PortAllow::Specific(ports))],
1947 any_ip_ports: HashSet::new(),
1948 };
1949 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)); }
1953
1954 #[test]
1955 fn allowlist_cidr_all_ports() {
1956 use crate::network::IpCidr;
1957 let policy = NetworkPolicy::AllowList {
1958 per_ip: HashMap::new(),
1959 cidrs: vec![(IpCidr::parse("192.168.0.0/16").unwrap(), PortAllow::Any)],
1960 any_ip_ports: HashSet::new(),
1961 };
1962 assert!(policy.allows("192.168.5.5".parse().unwrap(), 9999)); assert!(!policy.allows("10.0.0.1".parse().unwrap(), 9999)); }
1965}