1use std::collections::HashMap;
20use std::os::unix::io::RawFd;
21use std::sync::Arc;
22
23use super::ctx::SupervisorCtx;
24use super::notif::{NotifAction, NotifPolicy};
25use super::state::ResourceState;
26use super::syscall::SyscallError;
27use crate::arch;
28use crate::sys::structs::SeccompNotif;
29
30use thiserror::Error;
31use tokio::sync::Mutex;
32
33pub trait Handler: Send + Sync + 'static {
59 fn handle<'a>(
60 &'a self,
61 cx: &'a HandlerCtx,
62 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>>;
63}
64
65pub struct HandlerCtx {
77 pub notif: SeccompNotif,
78 pub notif_fd: RawFd,
79}
80
81impl<F, Fut> Handler for F
87where
88 F: Fn(&HandlerCtx) -> Fut + Send + Sync + 'static,
89 Fut: std::future::Future<Output = NotifAction> + Send + 'static,
90{
91 fn handle<'a>(
92 &'a self,
93 cx: &'a HandlerCtx,
94 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
95 Box::pin((self)(cx))
96 }
97}
98
99impl Handler for Box<dyn Handler> {
111 fn handle<'a>(
112 &'a self,
113 cx: &'a HandlerCtx,
114 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
115 (**self).handle(cx)
116 }
117}
118
119impl Handler for std::sync::Arc<dyn Handler> {
120 fn handle<'a>(
121 &'a self,
122 cx: &'a HandlerCtx,
123 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
124 (**self).handle(cx)
125 }
126}
127
128#[derive(Debug, Error, PartialEq, Eq)]
131pub enum HandlerError {
132 #[error("invalid syscall in handler registration: {0}")]
133 InvalidSyscall(#[from] SyscallError),
134
135 #[error(
136 "handler on syscall {syscall_nr} conflicts with the policy syscall blocklist \
137 and would let user code bypass it via SECCOMP_USER_NOTIF_FLAG_CONTINUE"
138 )]
139 OnDenySyscall { syscall_nr: i64 },
140}
141
142fn open_family_syscalls() -> Vec<i64> {
162 let mut v = vec![libc::SYS_openat, arch::SYS_OPENAT2];
163 if let Some(legacy_open) = arch::SYS_OPEN {
164 v.push(legacy_open);
165 }
166 v
167}
168
169pub(crate) fn validate_handler_syscalls_against_policy(
176 syscall_nrs: &[i64],
177 policy: &crate::sandbox::Sandbox,
178) -> Result<(), i64> {
179 let blocklist: std::collections::HashSet<u32> =
180 crate::context::blocklist_syscall_numbers(policy).into_iter().collect();
181 for &nr in syscall_nrs {
182 if blocklist.contains(&(nr as u32)) {
183 return Err(nr);
184 }
185 }
186 Ok(())
187}
188
189
190struct HandlerChain {
192 handlers: Vec<std::sync::Arc<dyn Handler>>,
193}
194
195pub struct DispatchTable {
197 chains: HashMap<i64, HandlerChain>,
198}
199
200impl DispatchTable {
201 pub fn new() -> Self {
203 Self {
204 chains: HashMap::new(),
205 }
206 }
207
208 pub fn register<H: Handler>(&mut self, syscall_nr: i64, handler: H) {
214 self.register_arc(syscall_nr, std::sync::Arc::new(handler));
215 }
216
217 pub(crate) fn register_arc(
223 &mut self,
224 syscall_nr: i64,
225 handler: std::sync::Arc<dyn Handler>,
226 ) {
227 self.chains
228 .entry(syscall_nr)
229 .or_insert_with(|| HandlerChain { handlers: Vec::new() })
230 .handlers
231 .push(handler);
232 }
233
234 pub(crate) async fn dispatch(
236 &self,
237 notif: SeccompNotif,
238 notif_fd: RawFd,
239 ) -> NotifAction {
240 let nr = notif.data.nr as i64;
241 if let Some(chain) = self.chains.get(&nr) {
242 let handler_ctx = HandlerCtx { notif, notif_fd };
243 for handler in &chain.handlers {
244 let action = handler.handle(&handler_ctx).await;
245 if !matches!(action, NotifAction::Continue) {
246 return action;
247 }
248 }
249 }
250 NotifAction::Continue
251 }
252}
253
254pub(crate) fn build_dispatch_table(
267 policy: &Arc<NotifPolicy>,
268 resource: &Arc<Mutex<ResourceState>>,
269 ctx: &Arc<SupervisorCtx>,
270 pending_handlers: Vec<(i64, std::sync::Arc<dyn Handler>)>,
271) -> DispatchTable {
272 let mut table = DispatchTable::new();
273
274 for &nr in arch::FORK_LIKE_SYSCALLS {
278 let policy_for_fork = Arc::clone(policy);
279 let resource_for_fork = Arc::clone(resource);
280 table.register(nr, move |cx: &HandlerCtx| {
281 let notif = cx.notif;
282 let notif_fd = cx.notif_fd;
283 let policy = Arc::clone(&policy_for_fork);
284 let resource = Arc::clone(&resource_for_fork);
285 async move {
286 crate::resource::handle_fork(¬if, notif_fd, &resource, &policy).await
287 }
288 });
289 }
290
291 for &nr in &[libc::SYS_wait4, libc::SYS_waitid] {
295 let resource_for_wait = Arc::clone(resource);
296 table.register(nr, move |cx: &HandlerCtx| {
297 let notif = cx.notif;
298 let resource = Arc::clone(&resource_for_wait);
299 async move {
300 crate::resource::handle_wait(¬if, &resource).await
301 }
302 });
303 }
304
305 if policy.has_memory_limit {
309 for &nr in &[
310 libc::SYS_mmap, libc::SYS_munmap, libc::SYS_brk,
311 libc::SYS_mremap, libc::SYS_shmget,
312 ] {
313 let policy_for_mem = Arc::clone(policy);
314 let __sup = Arc::clone(ctx);
315 table.register(nr, move |cx: &HandlerCtx| {
316 let notif = cx.notif;
317 let sup = Arc::clone(&__sup);
318 let policy = Arc::clone(&policy_for_mem);
319 async move {
320 crate::resource::handle_memory(¬if, &sup, &policy).await
321 }
322 });
323 }
324 }
325
326 if policy.has_net_allowlist || policy.has_http_acl {
330 for &nr in &[
331 libc::SYS_connect,
332 libc::SYS_sendto,
333 libc::SYS_sendmsg,
334 libc::SYS_sendmmsg,
335 ] {
336 let __sup = Arc::clone(ctx);
337 table.register(nr, move |cx: &HandlerCtx| {
338 let notif = cx.notif;
339 let sup = Arc::clone(&__sup);
340 let notif_fd = cx.notif_fd;
341 async move {
342 crate::network::handle_net(¬if, &sup, notif_fd).await
343 }
344 });
345 }
346 }
347
348 if policy.has_random_seed {
352 let __sup = Arc::clone(ctx);
353 table.register(libc::SYS_getrandom, move |cx: &HandlerCtx| {
354 let notif = cx.notif;
355 let sup = Arc::clone(&__sup);
356 let notif_fd = cx.notif_fd;
357 async move {
358 let mut tr = sup.time_random.lock().await;
359 if let Some(ref mut rng) = tr.random_state {
360 crate::random::handle_getrandom(¬if, rng, notif_fd)
361 } else {
362 NotifAction::Continue
363 }
364 }
365 });
366 }
367
368 if policy.has_random_seed {
375 for nr in open_family_syscalls() {
376 let __sup = Arc::clone(ctx);
377 table.register(nr, move |cx: &HandlerCtx| {
378 let notif = cx.notif;
379 let sup = Arc::clone(&__sup);
380 let notif_fd = cx.notif_fd;
381 async move {
382 let mut tr = sup.time_random.lock().await;
383 if let Some(ref mut rng) = tr.random_state {
384 if let Some(action) = crate::random::handle_random_open(¬if, rng, notif_fd) {
385 return action;
386 }
387 }
388 NotifAction::Continue
389 }
390 });
391 }
392 }
393
394 if policy.has_time_start {
398 let time_offset = policy.time_offset;
399 for &nr in &[
400 libc::SYS_clock_nanosleep as i64,
401 libc::SYS_timerfd_settime as i64,
402 libc::SYS_timer_settime as i64,
403 ] {
404 table.register(nr, move |cx: &HandlerCtx| {
405 let notif = cx.notif;
406 let notif_fd = cx.notif_fd;
407 async move {
408 crate::time::handle_timer(¬if, time_offset, notif_fd)
409 }
410 });
411 }
412 }
413
414 {
428 let etc_hosts = policy.virtual_etc_hosts.clone();
429 for nr in open_family_syscalls() {
430 let etc_hosts = etc_hosts.clone();
431 table.register(nr, move |cx: &HandlerCtx| {
432 let notif = cx.notif;
433 let notif_fd = cx.notif_fd;
434 let etc_hosts = etc_hosts.clone();
435 async move {
436 if let Some(action) = crate::procfs::handle_etc_hosts_open(¬if, &etc_hosts, notif_fd) {
437 action
438 } else {
439 NotifAction::Continue
440 }
441 }
442 });
443 }
444 }
445
446 if policy.chroot_root.is_some() {
450 register_chroot_handlers(&mut table, policy, ctx);
451 }
452
453 if policy.cow_enabled {
457 register_cow_handlers(&mut table, ctx);
458 }
459
460 for nr in open_family_syscalls() {
466 let policy_for_proc_open = Arc::clone(policy);
467 let resource_for_proc_open = Arc::clone(resource);
468 let __sup = Arc::clone(ctx);
469 table.register(nr, move |cx: &HandlerCtx| {
470 let notif = cx.notif;
471 let sup = Arc::clone(&__sup);
472 let notif_fd = cx.notif_fd;
473 let policy = Arc::clone(&policy_for_proc_open);
474 let resource = Arc::clone(&resource_for_proc_open);
475 async move {
476 let processes = Arc::clone(&sup.processes);
477 let network = Arc::clone(&sup.network);
478 crate::procfs::handle_proc_open(¬if, &processes, &resource, &network, &policy, notif_fd).await
479 }
480 });
481 }
482 let mut getdents_nrs = vec![libc::SYS_getdents64];
483 if let Some(getdents) = arch::SYS_GETDENTS {
484 getdents_nrs.push(getdents);
485 }
486 for nr in getdents_nrs {
487 let policy_for_getdents = Arc::clone(policy);
488 let __sup = Arc::clone(ctx);
489 table.register(nr, move |cx: &HandlerCtx| {
490 let notif = cx.notif;
491 let sup = Arc::clone(&__sup);
492 let notif_fd = cx.notif_fd;
493 let policy = Arc::clone(&policy_for_getdents);
494 async move {
495 let processes = Arc::clone(&sup.processes);
496 crate::procfs::handle_getdents(¬if, &processes, &policy, notif_fd).await
497 }
498 });
499 }
500
501 if let Some(n) = policy.num_cpus {
505 table.register(libc::SYS_sched_getaffinity, move |cx: &HandlerCtx| {
506 let notif = cx.notif;
507 let notif_fd = cx.notif_fd;
508 async move {
509 crate::procfs::handle_sched_getaffinity(¬if, n, notif_fd)
510 }
511 });
512 }
513
514 if let Some(ref hostname) = policy.virtual_hostname {
520 let hostname_for_uname = hostname.clone();
521 let hostname_for_open = hostname.clone();
522 table.register(libc::SYS_uname, move |cx: &HandlerCtx| {
523 let notif = cx.notif;
524 let notif_fd = cx.notif_fd;
525 let hostname = hostname_for_uname.clone();
526 async move {
527 crate::procfs::handle_uname(¬if, &hostname, notif_fd)
528 }
529 });
530 for nr in open_family_syscalls() {
531 let hostname = hostname_for_open.clone();
532 table.register(nr, move |cx: &HandlerCtx| {
533 let notif = cx.notif;
534 let notif_fd = cx.notif_fd;
535 let hostname = hostname.clone();
536 async move {
537 if let Some(action) = crate::procfs::handle_hostname_open(¬if, &hostname, notif_fd) {
538 action
539 } else {
540 NotifAction::Continue
541 }
542 }
543 });
544 }
545 }
546
547 if policy.deterministic_dirs {
553 let mut getdents_nrs = vec![libc::SYS_getdents64];
554 if let Some(getdents) = arch::SYS_GETDENTS {
555 getdents_nrs.push(getdents);
556 }
557 for nr in getdents_nrs {
558 let __sup = Arc::clone(ctx);
559 table.register(nr, move |cx: &HandlerCtx| {
560 let notif = cx.notif;
561 let sup = Arc::clone(&__sup);
562 let notif_fd = cx.notif_fd;
563 async move {
564 let processes = Arc::clone(&sup.processes);
565 crate::procfs::handle_sorted_getdents(¬if, &processes, notif_fd).await
566 }
567 });
568 }
569 }
570
571 {
584 let __sup = Arc::clone(ctx);
585 table.register(libc::SYS_socket, move |cx: &HandlerCtx| {
586 let notif = cx.notif;
587 let sup = Arc::clone(&__sup);
588 async move {
589 let state = Arc::clone(&sup.netlink);
590 crate::netlink::handlers::handle_socket(¬if, &state).await
591 }
592 });
593 let __sup = Arc::clone(ctx);
594 table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
595 let notif = cx.notif;
596 let sup = Arc::clone(&__sup);
597 async move {
598 let state = Arc::clone(&sup.netlink);
599 crate::netlink::handlers::handle_bind(¬if, &state).await
600 }
601 });
602 let __sup = Arc::clone(ctx);
603 table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
604 let notif = cx.notif;
605 let sup = Arc::clone(&__sup);
606 let notif_fd = cx.notif_fd;
607 async move {
608 let state = Arc::clone(&sup.netlink);
609 crate::netlink::handlers::handle_getsockname(¬if, &state, notif_fd).await
610 }
611 });
612 for &nr in &[libc::SYS_recvfrom, libc::SYS_recvmsg] {
616 let __sup = Arc::clone(ctx);
617 table.register(nr, move |cx: &HandlerCtx| {
618 let notif = cx.notif;
619 let sup = Arc::clone(&__sup);
620 let notif_fd = cx.notif_fd;
621 async move {
622 let state = Arc::clone(&sup.netlink);
623 crate::netlink::handlers::handle_netlink_recvmsg(¬if, &state, notif_fd).await
624 }
625 });
626 }
627 let __sup = Arc::clone(ctx);
630 table.register(libc::SYS_close, move |cx: &HandlerCtx| {
631 let notif = cx.notif;
632 let sup = Arc::clone(&__sup);
633 async move {
634 let state = Arc::clone(&sup.netlink);
635 crate::netlink::handlers::handle_close(¬if, &state).await
636 }
637 });
638 }
639
640 if policy.port_remap || policy.has_net_allowlist {
644 let __sup = Arc::clone(ctx);
645 table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
646 let notif = cx.notif;
647 let sup = Arc::clone(&__sup);
648 let notif_fd = cx.notif_fd;
649 async move {
650 crate::port_remap::handle_bind(¬if, &sup.network, notif_fd).await
651 }
652 });
653 }
654
655 if policy.port_remap {
659 let __sup = Arc::clone(ctx);
660 table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
661 let notif = cx.notif;
662 let sup = Arc::clone(&__sup);
663 let notif_fd = cx.notif_fd;
664 async move {
665 crate::port_remap::handle_getsockname(¬if, &sup.network, notif_fd).await
666 }
667 });
668 }
669
670 for (nr, h) in pending_handlers {
676 table.register_arc(nr, h);
677 }
678
679 table
680}
681
682fn register_chroot_handlers(
687 table: &mut DispatchTable,
688 policy: &Arc<NotifPolicy>,
689 ctx: &Arc<SupervisorCtx>,
690) {
691 use crate::chroot::dispatch::ChrootCtx;
692
693 macro_rules! chroot_handler {
697 ($policy:expr, $handler:expr) => {{
698 let policy = Arc::clone($policy);
699 let chroot_state = Arc::clone(&ctx.chroot);
700 let cow_state = Arc::clone(&ctx.cow);
701 move |cx: &HandlerCtx| {
702 let notif = cx.notif;
703 let chroot_state = Arc::clone(&chroot_state);
704 let cow_state = Arc::clone(&cow_state);
705 let notif_fd = cx.notif_fd;
706 let policy = Arc::clone(&policy);
707 async move {
708 let chroot_ctx = ChrootCtx {
709 root: policy.chroot_root.as_ref().unwrap(),
710 readable: &policy.chroot_readable,
711 writable: &policy.chroot_writable,
712 denied: &policy.chroot_denied,
713 mounts: &policy.chroot_mounts,
714 };
715 $handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
716 }
717 }
718 }};
719 }
720
721 macro_rules! chroot_handler_fallthrough {
724 ($policy:expr, $handler:expr) => {{
725 let policy = Arc::clone($policy);
726 let chroot_state = Arc::clone(&ctx.chroot);
727 let cow_state = Arc::clone(&ctx.cow);
728 move |cx: &HandlerCtx| {
729 let notif = cx.notif;
730 let chroot_state = Arc::clone(&chroot_state);
731 let cow_state = Arc::clone(&cow_state);
732 let notif_fd = cx.notif_fd;
733 let policy = Arc::clone(&policy);
734 async move {
735 let chroot_ctx = ChrootCtx {
736 root: policy.chroot_root.as_ref().unwrap(),
737 readable: &policy.chroot_readable,
738 writable: &policy.chroot_writable,
739 denied: &policy.chroot_denied,
740 mounts: &policy.chroot_mounts,
741 };
742 $handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
743 }
744 }
745 }};
746 }
747
748 table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
750 crate::chroot::dispatch::handle_chroot_open));
751
752 if let Some(open) = arch::SYS_OPEN {
754 table.register(open, chroot_handler_fallthrough!(policy,
755 crate::chroot::dispatch::handle_chroot_legacy_open));
756 }
757
758 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
760 table.register(nr, chroot_handler!(policy,
761 crate::chroot::dispatch::handle_chroot_exec));
762 }
763
764 for &nr in &[
766 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
767 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
768 libc::SYS_fchownat, libc::SYS_truncate,
769 ] {
770 table.register(nr, chroot_handler!(policy,
771 crate::chroot::dispatch::handle_chroot_write));
772 }
773
774 if let Some(nr) = arch::SYS_UNLINK {
776 table.register(nr, chroot_handler!(policy,
777 crate::chroot::dispatch::handle_chroot_legacy_unlink));
778 }
779 if let Some(nr) = arch::SYS_RMDIR {
780 table.register(nr, chroot_handler!(policy,
781 crate::chroot::dispatch::handle_chroot_legacy_rmdir));
782 }
783 if let Some(nr) = arch::SYS_MKDIR {
784 table.register(nr, chroot_handler!(policy,
785 crate::chroot::dispatch::handle_chroot_legacy_mkdir));
786 }
787 if let Some(nr) = arch::SYS_RENAME {
788 table.register(nr, chroot_handler!(policy,
789 crate::chroot::dispatch::handle_chroot_legacy_rename));
790 }
791 if let Some(nr) = arch::SYS_SYMLINK {
792 table.register(nr, chroot_handler!(policy,
793 crate::chroot::dispatch::handle_chroot_legacy_symlink));
794 }
795 if let Some(nr) = arch::SYS_LINK {
796 table.register(nr, chroot_handler!(policy,
797 crate::chroot::dispatch::handle_chroot_legacy_link));
798 }
799 if let Some(nr) = arch::SYS_CHMOD {
800 table.register(nr, chroot_handler!(policy,
801 crate::chroot::dispatch::handle_chroot_legacy_chmod));
802 }
803
804 if let Some(chown) = arch::SYS_CHOWN {
806 let policy_for_chown = Arc::clone(policy);
807 let __sup = Arc::clone(ctx);
808 table.register(chown, move |cx: &HandlerCtx| {
809 let notif = cx.notif;
810 let sup = Arc::clone(&__sup);
811 let notif_fd = cx.notif_fd;
812 let policy = Arc::clone(&policy_for_chown);
813 async move {
814 let chroot_ctx = ChrootCtx {
815 root: policy.chroot_root.as_ref().unwrap(),
816 readable: &policy.chroot_readable,
817 writable: &policy.chroot_writable,
818 denied: &policy.chroot_denied,
819 mounts: &policy.chroot_mounts,
820 };
821 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, false).await
822 }
823 });
824 }
825
826 if let Some(lchown) = arch::SYS_LCHOWN {
828 let policy_for_lchown = Arc::clone(policy);
829 let __sup = Arc::clone(ctx);
830 table.register(lchown, move |cx: &HandlerCtx| {
831 let notif = cx.notif;
832 let sup = Arc::clone(&__sup);
833 let notif_fd = cx.notif_fd;
834 let policy = Arc::clone(&policy_for_lchown);
835 async move {
836 let chroot_ctx = ChrootCtx {
837 root: policy.chroot_root.as_ref().unwrap(),
838 readable: &policy.chroot_readable,
839 writable: &policy.chroot_writable,
840 denied: &policy.chroot_denied,
841 mounts: &policy.chroot_mounts,
842 };
843 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, true).await
844 }
845 });
846 }
847
848 for &nr in &[
850 libc::SYS_newfstatat,
851 libc::SYS_faccessat,
852 crate::chroot::dispatch::SYS_FACCESSAT2,
853 ] {
854 table.register(nr, chroot_handler!(policy,
855 crate::chroot::dispatch::handle_chroot_stat));
856 }
857
858 if let Some(nr) = arch::SYS_STAT {
860 table.register(nr, chroot_handler!(policy,
861 crate::chroot::dispatch::handle_chroot_legacy_stat));
862 }
863 if let Some(nr) = arch::SYS_LSTAT {
864 table.register(nr, chroot_handler!(policy,
865 crate::chroot::dispatch::handle_chroot_legacy_lstat));
866 }
867 if let Some(nr) = arch::SYS_ACCESS {
868 table.register(nr, chroot_handler!(policy,
869 crate::chroot::dispatch::handle_chroot_legacy_access));
870 }
871
872 table.register(libc::SYS_statx, chroot_handler!(policy,
874 crate::chroot::dispatch::handle_chroot_statx));
875
876 table.register(libc::SYS_readlinkat, chroot_handler!(policy,
878 crate::chroot::dispatch::handle_chroot_readlink));
879 if let Some(nr) = arch::SYS_READLINK {
880 table.register(nr, chroot_handler!(policy,
881 crate::chroot::dispatch::handle_chroot_legacy_readlink));
882 }
883
884 let mut getdents_nrs = vec![libc::SYS_getdents64];
886 if let Some(getdents) = arch::SYS_GETDENTS {
887 getdents_nrs.push(getdents);
888 }
889 for nr in getdents_nrs {
890 table.register(nr, chroot_handler!(policy,
891 crate::chroot::dispatch::handle_chroot_getdents));
892 }
893
894 table.register(libc::SYS_chdir as i64, chroot_handler!(policy,
896 crate::chroot::dispatch::handle_chroot_chdir));
897 table.register(libc::SYS_getcwd as i64, chroot_handler!(policy,
898 crate::chroot::dispatch::handle_chroot_getcwd));
899 table.register(libc::SYS_statfs as i64, chroot_handler!(policy,
900 crate::chroot::dispatch::handle_chroot_statfs));
901 table.register(libc::SYS_utimensat as i64, chroot_handler!(policy,
902 crate::chroot::dispatch::handle_chroot_utimensat));
903}
904
905fn register_cow_handlers(table: &mut DispatchTable, ctx: &Arc<SupervisorCtx>) {
910 macro_rules! cow_call {
913 ($handler:expr) => {{
914 let cow_state = Arc::clone(&ctx.cow);
915 let processes_state = Arc::clone(&ctx.processes);
916 move |cx: &HandlerCtx| {
917 let notif = cx.notif;
918 let cow_state = Arc::clone(&cow_state);
919 let processes_state = Arc::clone(&processes_state);
920 let notif_fd = cx.notif_fd;
921 async move {
922 $handler(¬if, &cow_state, &processes_state, notif_fd).await
923 }
924 }
925 }};
926 }
927
928 let mut write_nrs = vec![
930 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
931 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
932 libc::SYS_fchownat, libc::SYS_truncate,
933 ];
934 write_nrs.extend([
935 arch::SYS_UNLINK, arch::SYS_RMDIR, arch::SYS_MKDIR, arch::SYS_RENAME,
936 arch::SYS_SYMLINK, arch::SYS_LINK, arch::SYS_CHMOD, arch::SYS_CHOWN,
937 arch::SYS_LCHOWN,
938 ].into_iter().flatten());
939 for nr in write_nrs {
940 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_write));
941 }
942
943 table.register(libc::SYS_utimensat, cow_call!(crate::cow::dispatch::handle_cow_utimensat));
944
945 let mut access_nrs = vec![libc::SYS_faccessat, crate::cow::dispatch::SYS_FACCESSAT2];
946 access_nrs.extend(arch::SYS_ACCESS);
947 for nr in access_nrs {
948 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_access));
949 }
950
951 let mut open_nrs = vec![libc::SYS_openat];
952 open_nrs.extend(arch::SYS_OPEN);
953 for nr in open_nrs {
954 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_open));
955 }
956
957 let mut stat_nrs = vec![libc::SYS_newfstatat, libc::SYS_faccessat];
958 stat_nrs.extend([arch::SYS_STAT, arch::SYS_LSTAT, arch::SYS_ACCESS].into_iter().flatten());
959 for nr in stat_nrs {
960 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_stat));
961 }
962
963 table.register(libc::SYS_statx, cow_call!(crate::cow::dispatch::handle_cow_statx));
964
965 let mut readlink_nrs = vec![libc::SYS_readlinkat];
966 readlink_nrs.extend(arch::SYS_READLINK);
967 for nr in readlink_nrs {
968 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_readlink));
969 }
970
971 let mut getdents_nrs = vec![libc::SYS_getdents64];
972 getdents_nrs.extend(arch::SYS_GETDENTS);
973 for nr in getdents_nrs {
974 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_getdents));
975 }
976
977 table.register(libc::SYS_chdir, cow_call!(crate::cow::dispatch::handle_cow_chdir));
978 table.register(libc::SYS_getcwd, cow_call!(crate::cow::dispatch::handle_cow_getcwd));
979
980 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
981 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_exec));
982 }
983}
984
985#[cfg(test)]
990mod handler_tests {
991 use super::*;
1002 use crate::netlink::NetlinkState;
1003 use crate::seccomp::ctx::SupervisorCtx;
1004 use crate::seccomp::notif::NotifPolicy;
1005 use crate::seccomp::state::{
1006 ChrootState, CowState, NetworkState, PolicyFnState, ProcessIndex, ProcfsState,
1007 ResourceState, TimeRandomState,
1008 };
1009 use crate::sys::structs::{SeccompData, SeccompNotif};
1010 use std::sync::atomic::{AtomicUsize, Ordering};
1011
1012 fn fake_notif(nr: i32) -> SeccompNotif {
1013 SeccompNotif {
1014 id: 0,
1015 pid: 1,
1016 flags: 0,
1017 data: SeccompData {
1018 nr,
1019 arch: 0,
1020 instruction_pointer: 0,
1021 args: [0; 6],
1022 },
1023 }
1024 }
1025
1026 fn fake_supervisor_ctx() -> Arc<SupervisorCtx> {
1033 Arc::new(SupervisorCtx {
1034 resource: Arc::new(Mutex::new(ResourceState::new(0, 0))),
1035 cow: Arc::new(Mutex::new(CowState::new())),
1036 procfs: Arc::new(Mutex::new(ProcfsState::new())),
1037 network: Arc::new(Mutex::new(NetworkState::new())),
1038 time_random: Arc::new(Mutex::new(TimeRandomState::new(None, None))),
1039 policy_fn: Arc::new(Mutex::new(PolicyFnState::new())),
1040 chroot: Arc::new(Mutex::new(ChrootState::new())),
1041 netlink: Arc::new(NetlinkState::new()),
1042 processes: Arc::new(ProcessIndex::new()),
1043 policy: Arc::new(NotifPolicy {
1044 max_memory_bytes: 0,
1045 max_processes: 0,
1046 has_memory_limit: false,
1047 has_net_allowlist: false,
1048 has_random_seed: false,
1049 has_time_start: false,
1050 time_offset: 0,
1051 num_cpus: None,
1052 argv_safety_required: false,
1053 port_remap: false,
1054 cow_enabled: false,
1055 chroot_root: None,
1056 chroot_readable: Vec::new(),
1057 chroot_writable: Vec::new(),
1058 chroot_denied: Vec::new(),
1059 chroot_mounts: Vec::new(),
1060 deterministic_dirs: false,
1061 virtual_hostname: None,
1062 has_http_acl: false,
1063 virtual_etc_hosts: String::new(),
1064 }),
1065 child_pidfd: None,
1066 notif_fd: -1,
1067 })
1068 }
1069
1070 #[tokio::test]
1074 async fn dispatch_walks_chain_in_registration_order() {
1075 let mut table = DispatchTable::new();
1076 let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
1077
1078 for tag in [1u8, 2u8, 3u8] {
1079 let order_clone = Arc::clone(&order);
1080 table.register(
1081 libc::SYS_openat,
1082 move |_cx: &HandlerCtx| {
1083 let order = Arc::clone(&order_clone);
1084 async move {
1085 order.lock().unwrap().push(tag);
1086 NotifAction::Continue
1087 }
1088 },
1089 );
1090 }
1091
1092 let _ctx = fake_supervisor_ctx();
1093 let action = table
1094 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1095 .await;
1096
1097 assert!(matches!(action, NotifAction::Continue));
1098 let recorded = order.lock().unwrap();
1099 assert_eq!(
1100 *recorded,
1101 [1u8, 2u8, 3u8],
1102 "every handler must run, in the order it was registered"
1103 );
1104 }
1105
1106 #[tokio::test]
1114 async fn dispatch_runs_builtin_before_extra() {
1115 let mut table = DispatchTable::new();
1116 let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
1117
1118 let order_builtin = Arc::clone(&order);
1120 table.register(
1121 libc::SYS_openat,
1122 move |_cx: &HandlerCtx| {
1123 let order = Arc::clone(&order_builtin);
1124 async move {
1125 order.lock().unwrap().push(b'B');
1126 NotifAction::Continue
1127 }
1128 },
1129 );
1130
1131 let order_extra = Arc::clone(&order);
1134 table.register(
1135 libc::SYS_openat,
1136 move |_cx: &HandlerCtx| {
1137 let order = Arc::clone(&order_extra);
1138 async move {
1139 order.lock().unwrap().push(b'E');
1140 NotifAction::Continue
1141 }
1142 },
1143 );
1144
1145 let _ctx = fake_supervisor_ctx();
1146 let action = table
1147 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1148 .await;
1149
1150 assert!(matches!(action, NotifAction::Continue));
1151 let recorded = order.lock().unwrap();
1152 assert_eq!(
1153 *recorded,
1154 [b'B', b'E'],
1155 "builtin must run before extra (insertion order preserved)"
1156 );
1157 }
1158
1159 #[tokio::test]
1166 async fn dispatch_stops_at_first_non_continue() {
1167 let mut table = DispatchTable::new();
1168 let calls = Arc::new(AtomicUsize::new(0));
1169
1170 let calls_first = Arc::clone(&calls);
1172 table.register(
1173 libc::SYS_openat,
1174 move |_cx: &HandlerCtx| {
1175 let calls = Arc::clone(&calls_first);
1176 async move {
1177 calls.fetch_add(1, Ordering::SeqCst);
1178 NotifAction::Errno(libc::EACCES)
1179 }
1180 },
1181 );
1182
1183 let calls_second = Arc::clone(&calls);
1185 table.register(
1186 libc::SYS_openat,
1187 move |_cx: &HandlerCtx| {
1188 let calls = Arc::clone(&calls_second);
1189 async move {
1190 calls.fetch_add(1, Ordering::SeqCst);
1191 NotifAction::Continue
1192 }
1193 },
1194 );
1195
1196 let _ctx = fake_supervisor_ctx();
1197 let action = table
1198 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1199 .await;
1200
1201 match action {
1202 NotifAction::Errno(e) => assert_eq!(e, libc::EACCES),
1203 other => panic!("expected Errno(EACCES), got {:?}", other),
1204 }
1205 assert_eq!(
1206 calls.load(Ordering::SeqCst),
1207 1,
1208 "second handler must not run after first returned non-Continue"
1209 );
1210 }
1211
1212 #[test]
1229 fn validate_extras_rejects_user_specified_blocklist() {
1230 let policy = crate::sandbox::Sandbox::builder()
1231 .extra_deny_syscalls(vec!["mremap".into()])
1232 .build()
1233 .expect("policy builds");
1234
1235 let result = validate_handler_syscalls_against_policy(&[libc::SYS_mremap], &policy);
1236 assert_eq!(
1237 result,
1238 Err(libc::SYS_mremap),
1239 "handler on user-specified blocklist must be rejected, naming the offending syscall"
1240 );
1241 }
1242
1243 #[tokio::test]
1246 async fn handler_via_blanket_impl_dispatches_closures() {
1247 use std::sync::atomic::{AtomicU64, Ordering};
1248 let counter = Arc::new(AtomicU64::new(0));
1249 let counter_clone = Arc::clone(&counter);
1250
1251 let h = move |cx: &HandlerCtx| {
1252 let counter = Arc::clone(&counter_clone);
1253 async move {
1254 counter.fetch_add(1, Ordering::SeqCst);
1255 let _ = cx.notif.pid; NotifAction::Continue
1257 }
1258 };
1259
1260 let _sup = fake_supervisor_ctx();
1261 let notif = fake_notif(libc::SYS_openat as i32);
1262 let cx = HandlerCtx { notif, notif_fd: -1 };
1263
1264 let action = h.handle(&cx).await;
1265 assert!(matches!(action, NotifAction::Continue));
1266 assert_eq!(counter.load(Ordering::SeqCst), 1);
1267 }
1268
1269 #[tokio::test]
1278 async fn dispatch_invokes_struct_handler_with_persistent_self_state() {
1279 use std::sync::atomic::{AtomicU64, Ordering};
1280
1281 struct StructHandler {
1282 calls: AtomicU64,
1283 }
1284
1285 impl Handler for StructHandler {
1286 fn handle<'a>(
1287 &'a self,
1288 _cx: &'a HandlerCtx,
1289 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
1290 Box::pin(async move {
1291 self.calls.fetch_add(1, Ordering::SeqCst);
1292 NotifAction::Continue
1293 })
1294 }
1295 }
1296
1297 let mut table = DispatchTable::new();
1298 let handler = std::sync::Arc::new(StructHandler {
1299 calls: AtomicU64::new(0),
1300 });
1301 table.register_arc(libc::SYS_openat, handler.clone() as std::sync::Arc<dyn Handler>);
1302
1303 let _sup = fake_supervisor_ctx();
1304 let notif = fake_notif(libc::SYS_openat as i32);
1305
1306 for _ in 0..3 {
1310 let action = table.dispatch(notif, -1).await;
1311 assert!(matches!(action, NotifAction::Continue));
1312 }
1313
1314 assert_eq!(
1315 handler.calls.load(Ordering::SeqCst),
1316 3,
1317 "dispatch must invoke the struct-based handler on every walk"
1318 );
1319 }
1320}