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
142pub(crate) fn validate_handler_syscalls_against_policy(
162 syscall_nrs: &[i64],
163 policy: &crate::sandbox::Sandbox,
164) -> Result<(), i64> {
165 let blocklist: std::collections::HashSet<u32> =
166 crate::context::blocklist_syscall_numbers(policy).into_iter().collect();
167 for &nr in syscall_nrs {
168 if blocklist.contains(&(nr as u32)) {
169 return Err(nr);
170 }
171 }
172 Ok(())
173}
174
175
176struct HandlerChain {
178 handlers: Vec<std::sync::Arc<dyn Handler>>,
179}
180
181pub struct DispatchTable {
183 chains: HashMap<i64, HandlerChain>,
184}
185
186impl DispatchTable {
187 pub fn new() -> Self {
189 Self {
190 chains: HashMap::new(),
191 }
192 }
193
194 pub fn register<H: Handler>(&mut self, syscall_nr: i64, handler: H) {
200 self.register_arc(syscall_nr, std::sync::Arc::new(handler));
201 }
202
203 pub(crate) fn register_arc(
209 &mut self,
210 syscall_nr: i64,
211 handler: std::sync::Arc<dyn Handler>,
212 ) {
213 self.chains
214 .entry(syscall_nr)
215 .or_insert_with(|| HandlerChain { handlers: Vec::new() })
216 .handlers
217 .push(handler);
218 }
219
220 pub(crate) async fn dispatch(
222 &self,
223 notif: SeccompNotif,
224 notif_fd: RawFd,
225 ) -> NotifAction {
226 let nr = notif.data.nr as i64;
227 if let Some(chain) = self.chains.get(&nr) {
228 let handler_ctx = HandlerCtx { notif, notif_fd };
229 for handler in &chain.handlers {
230 let action = handler.handle(&handler_ctx).await;
231 if !matches!(action, NotifAction::Continue) {
232 return action;
233 }
234 }
235 }
236 NotifAction::Continue
237 }
238}
239
240pub(crate) fn build_dispatch_table(
253 policy: &Arc<NotifPolicy>,
254 resource: &Arc<Mutex<ResourceState>>,
255 ctx: &Arc<SupervisorCtx>,
256 pending_handlers: Vec<(i64, std::sync::Arc<dyn Handler>)>,
257) -> DispatchTable {
258 let mut table = DispatchTable::new();
259
260 for &nr in arch::FORK_LIKE_SYSCALLS {
264 let policy_for_fork = Arc::clone(policy);
265 let resource_for_fork = Arc::clone(resource);
266 table.register(nr, move |cx: &HandlerCtx| {
267 let notif = cx.notif;
268 let notif_fd = cx.notif_fd;
269 let policy = Arc::clone(&policy_for_fork);
270 let resource = Arc::clone(&resource_for_fork);
271 async move {
272 crate::resource::handle_fork(¬if, notif_fd, &resource, &policy).await
273 }
274 });
275 }
276
277 for &nr in &[libc::SYS_wait4, libc::SYS_waitid] {
281 let resource_for_wait = Arc::clone(resource);
282 table.register(nr, move |cx: &HandlerCtx| {
283 let notif = cx.notif;
284 let resource = Arc::clone(&resource_for_wait);
285 async move {
286 crate::resource::handle_wait(¬if, &resource).await
287 }
288 });
289 }
290
291 if policy.has_memory_limit {
295 for &nr in &[
296 libc::SYS_mmap, libc::SYS_munmap, libc::SYS_brk,
297 libc::SYS_mremap, libc::SYS_shmget,
298 ] {
299 let policy_for_mem = Arc::clone(policy);
300 let __sup = Arc::clone(ctx);
301 table.register(nr, move |cx: &HandlerCtx| {
302 let notif = cx.notif;
303 let sup = Arc::clone(&__sup);
304 let policy = Arc::clone(&policy_for_mem);
305 async move {
306 crate::resource::handle_memory(¬if, &sup, &policy).await
307 }
308 });
309 }
310 }
311
312 if policy.has_net_allowlist || policy.has_http_acl {
316 for &nr in &[
317 libc::SYS_connect,
318 libc::SYS_sendto,
319 libc::SYS_sendmsg,
320 libc::SYS_sendmmsg,
321 ] {
322 let __sup = Arc::clone(ctx);
323 table.register(nr, move |cx: &HandlerCtx| {
324 let notif = cx.notif;
325 let sup = Arc::clone(&__sup);
326 let notif_fd = cx.notif_fd;
327 async move {
328 crate::network::handle_net(¬if, &sup, notif_fd).await
329 }
330 });
331 }
332 }
333
334 if policy.has_random_seed {
338 let __sup = Arc::clone(ctx);
339 table.register(libc::SYS_getrandom, move |cx: &HandlerCtx| {
340 let notif = cx.notif;
341 let sup = Arc::clone(&__sup);
342 let notif_fd = cx.notif_fd;
343 async move {
344 let mut tr = sup.time_random.lock().await;
345 if let Some(ref mut rng) = tr.random_state {
346 crate::random::handle_getrandom(¬if, rng, notif_fd)
347 } else {
348 NotifAction::Continue
349 }
350 }
351 });
352 }
353
354 if policy.has_random_seed {
358 let __sup = Arc::clone(ctx);
359 table.register(libc::SYS_openat, move |cx: &HandlerCtx| {
360 let notif = cx.notif;
361 let sup = Arc::clone(&__sup);
362 let notif_fd = cx.notif_fd;
363 async move {
364 let mut tr = sup.time_random.lock().await;
365 if let Some(ref mut rng) = tr.random_state {
366 if let Some(action) = crate::random::handle_random_open(¬if, rng, notif_fd) {
367 return action;
368 }
369 }
370 NotifAction::Continue
371 }
372 });
373 }
374
375 if policy.has_time_start {
379 let time_offset = policy.time_offset;
380 for &nr in &[
381 libc::SYS_clock_nanosleep as i64,
382 libc::SYS_timerfd_settime as i64,
383 libc::SYS_timer_settime as i64,
384 ] {
385 table.register(nr, move |cx: &HandlerCtx| {
386 let notif = cx.notif;
387 let notif_fd = cx.notif_fd;
388 async move {
389 crate::time::handle_timer(¬if, time_offset, notif_fd)
390 }
391 });
392 }
393 }
394
395 if policy.chroot_root.is_some() {
399 register_chroot_handlers(&mut table, policy, ctx);
400 }
401
402 if policy.cow_enabled {
406 register_cow_handlers(&mut table, ctx);
407 }
408
409 {
413 let policy_for_proc_open = Arc::clone(policy);
414 let resource_for_proc_open = Arc::clone(resource);
415 let __sup = Arc::clone(ctx);
416 table.register(libc::SYS_openat, move |cx: &HandlerCtx| {
417 let notif = cx.notif;
418 let sup = Arc::clone(&__sup);
419 let notif_fd = cx.notif_fd;
420 let policy = Arc::clone(&policy_for_proc_open);
421 let resource = Arc::clone(&resource_for_proc_open);
422 async move {
423 let processes = Arc::clone(&sup.processes);
424 let network = Arc::clone(&sup.network);
425 crate::procfs::handle_proc_open(¬if, &processes, &resource, &network, &policy, notif_fd).await
426 }
427 });
428 }
429 let mut getdents_nrs = vec![libc::SYS_getdents64];
430 if let Some(getdents) = arch::SYS_GETDENTS {
431 getdents_nrs.push(getdents);
432 }
433 for nr in getdents_nrs {
434 let policy_for_getdents = Arc::clone(policy);
435 let __sup = Arc::clone(ctx);
436 table.register(nr, move |cx: &HandlerCtx| {
437 let notif = cx.notif;
438 let sup = Arc::clone(&__sup);
439 let notif_fd = cx.notif_fd;
440 let policy = Arc::clone(&policy_for_getdents);
441 async move {
442 let processes = Arc::clone(&sup.processes);
443 crate::procfs::handle_getdents(¬if, &processes, &policy, notif_fd).await
444 }
445 });
446 }
447
448 if let Some(n) = policy.num_cpus {
452 table.register(libc::SYS_sched_getaffinity, move |cx: &HandlerCtx| {
453 let notif = cx.notif;
454 let notif_fd = cx.notif_fd;
455 async move {
456 crate::procfs::handle_sched_getaffinity(¬if, n, notif_fd)
457 }
458 });
459 }
460
461 if let Some(ref hostname) = policy.virtual_hostname {
465 let hostname_for_uname = hostname.clone();
466 let hostname_for_open = hostname.clone();
467 table.register(libc::SYS_uname, move |cx: &HandlerCtx| {
468 let notif = cx.notif;
469 let notif_fd = cx.notif_fd;
470 let hostname = hostname_for_uname.clone();
471 async move {
472 crate::procfs::handle_uname(¬if, &hostname, notif_fd)
473 }
474 });
475 table.register(libc::SYS_openat, move |cx: &HandlerCtx| {
476 let notif = cx.notif;
477 let notif_fd = cx.notif_fd;
478 let hostname = hostname_for_open.clone();
479 async move {
480 if let Some(action) = crate::procfs::handle_hostname_open(¬if, &hostname, notif_fd) {
481 action
482 } else {
483 NotifAction::Continue
484 }
485 }
486 });
487 }
488
489 if let Some(ref etc_hosts) = policy.virtual_etc_hosts {
493 let etc_hosts_for_open = etc_hosts.clone();
494 table.register(libc::SYS_openat, move |cx: &HandlerCtx| {
495 let notif = cx.notif;
496 let notif_fd = cx.notif_fd;
497 let etc_hosts = etc_hosts_for_open.clone();
498 async move {
499 if let Some(action) = crate::procfs::handle_etc_hosts_open(¬if, &etc_hosts, notif_fd) {
500 action
501 } else {
502 NotifAction::Continue
503 }
504 }
505 });
506 }
507
508 if policy.deterministic_dirs {
512 let mut getdents_nrs = vec![libc::SYS_getdents64];
513 if let Some(getdents) = arch::SYS_GETDENTS {
514 getdents_nrs.push(getdents);
515 }
516 for nr in getdents_nrs {
517 let __sup = Arc::clone(ctx);
518 table.register(nr, move |cx: &HandlerCtx| {
519 let notif = cx.notif;
520 let sup = Arc::clone(&__sup);
521 let notif_fd = cx.notif_fd;
522 async move {
523 let processes = Arc::clone(&sup.processes);
524 crate::procfs::handle_sorted_getdents(¬if, &processes, notif_fd).await
525 }
526 });
527 }
528 }
529
530 {
543 let __sup = Arc::clone(ctx);
544 table.register(libc::SYS_socket, move |cx: &HandlerCtx| {
545 let notif = cx.notif;
546 let sup = Arc::clone(&__sup);
547 async move {
548 let state = Arc::clone(&sup.netlink);
549 crate::netlink::handlers::handle_socket(¬if, &state).await
550 }
551 });
552 let __sup = Arc::clone(ctx);
553 table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
554 let notif = cx.notif;
555 let sup = Arc::clone(&__sup);
556 async move {
557 let state = Arc::clone(&sup.netlink);
558 crate::netlink::handlers::handle_bind(¬if, &state).await
559 }
560 });
561 let __sup = Arc::clone(ctx);
562 table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
563 let notif = cx.notif;
564 let sup = Arc::clone(&__sup);
565 let notif_fd = cx.notif_fd;
566 async move {
567 let state = Arc::clone(&sup.netlink);
568 crate::netlink::handlers::handle_getsockname(¬if, &state, notif_fd).await
569 }
570 });
571 for &nr in &[libc::SYS_recvfrom, libc::SYS_recvmsg] {
575 let __sup = Arc::clone(ctx);
576 table.register(nr, move |cx: &HandlerCtx| {
577 let notif = cx.notif;
578 let sup = Arc::clone(&__sup);
579 let notif_fd = cx.notif_fd;
580 async move {
581 let state = Arc::clone(&sup.netlink);
582 crate::netlink::handlers::handle_netlink_recvmsg(¬if, &state, notif_fd).await
583 }
584 });
585 }
586 let __sup = Arc::clone(ctx);
589 table.register(libc::SYS_close, move |cx: &HandlerCtx| {
590 let notif = cx.notif;
591 let sup = Arc::clone(&__sup);
592 async move {
593 let state = Arc::clone(&sup.netlink);
594 crate::netlink::handlers::handle_close(¬if, &state).await
595 }
596 });
597 }
598
599 if policy.port_remap || policy.has_net_allowlist {
603 let __sup = Arc::clone(ctx);
604 table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
605 let notif = cx.notif;
606 let sup = Arc::clone(&__sup);
607 let notif_fd = cx.notif_fd;
608 async move {
609 crate::port_remap::handle_bind(¬if, &sup.network, notif_fd).await
610 }
611 });
612 }
613
614 if policy.port_remap {
618 let __sup = Arc::clone(ctx);
619 table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
620 let notif = cx.notif;
621 let sup = Arc::clone(&__sup);
622 let notif_fd = cx.notif_fd;
623 async move {
624 crate::port_remap::handle_getsockname(¬if, &sup.network, notif_fd).await
625 }
626 });
627 }
628
629 for (nr, h) in pending_handlers {
635 table.register_arc(nr, h);
636 }
637
638 table
639}
640
641fn register_chroot_handlers(
646 table: &mut DispatchTable,
647 policy: &Arc<NotifPolicy>,
648 ctx: &Arc<SupervisorCtx>,
649) {
650 use crate::chroot::dispatch::ChrootCtx;
651
652 macro_rules! chroot_handler {
656 ($policy:expr, $handler:expr) => {{
657 let policy = Arc::clone($policy);
658 let chroot_state = Arc::clone(&ctx.chroot);
659 let cow_state = Arc::clone(&ctx.cow);
660 move |cx: &HandlerCtx| {
661 let notif = cx.notif;
662 let chroot_state = Arc::clone(&chroot_state);
663 let cow_state = Arc::clone(&cow_state);
664 let notif_fd = cx.notif_fd;
665 let policy = Arc::clone(&policy);
666 async move {
667 let chroot_ctx = ChrootCtx {
668 root: policy.chroot_root.as_ref().unwrap(),
669 readable: &policy.chroot_readable,
670 writable: &policy.chroot_writable,
671 denied: &policy.chroot_denied,
672 mounts: &policy.chroot_mounts,
673 };
674 $handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
675 }
676 }
677 }};
678 }
679
680 macro_rules! chroot_handler_fallthrough {
683 ($policy:expr, $handler:expr) => {{
684 let policy = Arc::clone($policy);
685 let chroot_state = Arc::clone(&ctx.chroot);
686 let cow_state = Arc::clone(&ctx.cow);
687 move |cx: &HandlerCtx| {
688 let notif = cx.notif;
689 let chroot_state = Arc::clone(&chroot_state);
690 let cow_state = Arc::clone(&cow_state);
691 let notif_fd = cx.notif_fd;
692 let policy = Arc::clone(&policy);
693 async move {
694 let chroot_ctx = ChrootCtx {
695 root: policy.chroot_root.as_ref().unwrap(),
696 readable: &policy.chroot_readable,
697 writable: &policy.chroot_writable,
698 denied: &policy.chroot_denied,
699 mounts: &policy.chroot_mounts,
700 };
701 $handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
702 }
703 }
704 }};
705 }
706
707 table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
709 crate::chroot::dispatch::handle_chroot_open));
710
711 if let Some(open) = arch::SYS_OPEN {
713 table.register(open, chroot_handler_fallthrough!(policy,
714 crate::chroot::dispatch::handle_chroot_legacy_open));
715 }
716
717 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
719 table.register(nr, chroot_handler!(policy,
720 crate::chroot::dispatch::handle_chroot_exec));
721 }
722
723 for &nr in &[
725 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
726 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
727 libc::SYS_fchownat, libc::SYS_truncate,
728 ] {
729 table.register(nr, chroot_handler!(policy,
730 crate::chroot::dispatch::handle_chroot_write));
731 }
732
733 if let Some(nr) = arch::SYS_UNLINK {
735 table.register(nr, chroot_handler!(policy,
736 crate::chroot::dispatch::handle_chroot_legacy_unlink));
737 }
738 if let Some(nr) = arch::SYS_RMDIR {
739 table.register(nr, chroot_handler!(policy,
740 crate::chroot::dispatch::handle_chroot_legacy_rmdir));
741 }
742 if let Some(nr) = arch::SYS_MKDIR {
743 table.register(nr, chroot_handler!(policy,
744 crate::chroot::dispatch::handle_chroot_legacy_mkdir));
745 }
746 if let Some(nr) = arch::SYS_RENAME {
747 table.register(nr, chroot_handler!(policy,
748 crate::chroot::dispatch::handle_chroot_legacy_rename));
749 }
750 if let Some(nr) = arch::SYS_SYMLINK {
751 table.register(nr, chroot_handler!(policy,
752 crate::chroot::dispatch::handle_chroot_legacy_symlink));
753 }
754 if let Some(nr) = arch::SYS_LINK {
755 table.register(nr, chroot_handler!(policy,
756 crate::chroot::dispatch::handle_chroot_legacy_link));
757 }
758 if let Some(nr) = arch::SYS_CHMOD {
759 table.register(nr, chroot_handler!(policy,
760 crate::chroot::dispatch::handle_chroot_legacy_chmod));
761 }
762
763 if let Some(chown) = arch::SYS_CHOWN {
765 let policy_for_chown = Arc::clone(policy);
766 let __sup = Arc::clone(ctx);
767 table.register(chown, move |cx: &HandlerCtx| {
768 let notif = cx.notif;
769 let sup = Arc::clone(&__sup);
770 let notif_fd = cx.notif_fd;
771 let policy = Arc::clone(&policy_for_chown);
772 async move {
773 let chroot_ctx = ChrootCtx {
774 root: policy.chroot_root.as_ref().unwrap(),
775 readable: &policy.chroot_readable,
776 writable: &policy.chroot_writable,
777 denied: &policy.chroot_denied,
778 mounts: &policy.chroot_mounts,
779 };
780 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, false).await
781 }
782 });
783 }
784
785 if let Some(lchown) = arch::SYS_LCHOWN {
787 let policy_for_lchown = Arc::clone(policy);
788 let __sup = Arc::clone(ctx);
789 table.register(lchown, move |cx: &HandlerCtx| {
790 let notif = cx.notif;
791 let sup = Arc::clone(&__sup);
792 let notif_fd = cx.notif_fd;
793 let policy = Arc::clone(&policy_for_lchown);
794 async move {
795 let chroot_ctx = ChrootCtx {
796 root: policy.chroot_root.as_ref().unwrap(),
797 readable: &policy.chroot_readable,
798 writable: &policy.chroot_writable,
799 denied: &policy.chroot_denied,
800 mounts: &policy.chroot_mounts,
801 };
802 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, true).await
803 }
804 });
805 }
806
807 for &nr in &[
809 libc::SYS_newfstatat,
810 libc::SYS_faccessat,
811 crate::chroot::dispatch::SYS_FACCESSAT2,
812 ] {
813 table.register(nr, chroot_handler!(policy,
814 crate::chroot::dispatch::handle_chroot_stat));
815 }
816
817 if let Some(nr) = arch::SYS_STAT {
819 table.register(nr, chroot_handler!(policy,
820 crate::chroot::dispatch::handle_chroot_legacy_stat));
821 }
822 if let Some(nr) = arch::SYS_LSTAT {
823 table.register(nr, chroot_handler!(policy,
824 crate::chroot::dispatch::handle_chroot_legacy_lstat));
825 }
826 if let Some(nr) = arch::SYS_ACCESS {
827 table.register(nr, chroot_handler!(policy,
828 crate::chroot::dispatch::handle_chroot_legacy_access));
829 }
830
831 table.register(libc::SYS_statx, chroot_handler!(policy,
833 crate::chroot::dispatch::handle_chroot_statx));
834
835 table.register(libc::SYS_readlinkat, chroot_handler!(policy,
837 crate::chroot::dispatch::handle_chroot_readlink));
838 if let Some(nr) = arch::SYS_READLINK {
839 table.register(nr, chroot_handler!(policy,
840 crate::chroot::dispatch::handle_chroot_legacy_readlink));
841 }
842
843 let mut getdents_nrs = vec![libc::SYS_getdents64];
845 if let Some(getdents) = arch::SYS_GETDENTS {
846 getdents_nrs.push(getdents);
847 }
848 for nr in getdents_nrs {
849 table.register(nr, chroot_handler!(policy,
850 crate::chroot::dispatch::handle_chroot_getdents));
851 }
852
853 table.register(libc::SYS_chdir as i64, chroot_handler!(policy,
855 crate::chroot::dispatch::handle_chroot_chdir));
856 table.register(libc::SYS_getcwd as i64, chroot_handler!(policy,
857 crate::chroot::dispatch::handle_chroot_getcwd));
858 table.register(libc::SYS_statfs as i64, chroot_handler!(policy,
859 crate::chroot::dispatch::handle_chroot_statfs));
860 table.register(libc::SYS_utimensat as i64, chroot_handler!(policy,
861 crate::chroot::dispatch::handle_chroot_utimensat));
862}
863
864fn register_cow_handlers(table: &mut DispatchTable, ctx: &Arc<SupervisorCtx>) {
869 macro_rules! cow_call {
872 ($handler:expr) => {{
873 let cow_state = Arc::clone(&ctx.cow);
874 let processes_state = Arc::clone(&ctx.processes);
875 move |cx: &HandlerCtx| {
876 let notif = cx.notif;
877 let cow_state = Arc::clone(&cow_state);
878 let processes_state = Arc::clone(&processes_state);
879 let notif_fd = cx.notif_fd;
880 async move {
881 $handler(¬if, &cow_state, &processes_state, notif_fd).await
882 }
883 }
884 }};
885 }
886
887 let mut write_nrs = vec![
889 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
890 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
891 libc::SYS_fchownat, libc::SYS_truncate,
892 ];
893 write_nrs.extend([
894 arch::SYS_UNLINK, arch::SYS_RMDIR, arch::SYS_MKDIR, arch::SYS_RENAME,
895 arch::SYS_SYMLINK, arch::SYS_LINK, arch::SYS_CHMOD, arch::SYS_CHOWN,
896 arch::SYS_LCHOWN,
897 ].into_iter().flatten());
898 for nr in write_nrs {
899 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_write));
900 }
901
902 table.register(libc::SYS_utimensat, cow_call!(crate::cow::dispatch::handle_cow_utimensat));
903
904 let mut access_nrs = vec![libc::SYS_faccessat, crate::cow::dispatch::SYS_FACCESSAT2];
905 access_nrs.extend(arch::SYS_ACCESS);
906 for nr in access_nrs {
907 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_access));
908 }
909
910 let mut open_nrs = vec![libc::SYS_openat];
911 open_nrs.extend(arch::SYS_OPEN);
912 for nr in open_nrs {
913 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_open));
914 }
915
916 let mut stat_nrs = vec![libc::SYS_newfstatat, libc::SYS_faccessat];
917 stat_nrs.extend([arch::SYS_STAT, arch::SYS_LSTAT, arch::SYS_ACCESS].into_iter().flatten());
918 for nr in stat_nrs {
919 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_stat));
920 }
921
922 table.register(libc::SYS_statx, cow_call!(crate::cow::dispatch::handle_cow_statx));
923
924 let mut readlink_nrs = vec![libc::SYS_readlinkat];
925 readlink_nrs.extend(arch::SYS_READLINK);
926 for nr in readlink_nrs {
927 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_readlink));
928 }
929
930 let mut getdents_nrs = vec![libc::SYS_getdents64];
931 getdents_nrs.extend(arch::SYS_GETDENTS);
932 for nr in getdents_nrs {
933 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_getdents));
934 }
935
936 table.register(libc::SYS_chdir, cow_call!(crate::cow::dispatch::handle_cow_chdir));
937 table.register(libc::SYS_getcwd, cow_call!(crate::cow::dispatch::handle_cow_getcwd));
938
939 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
940 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_exec));
941 }
942}
943
944#[cfg(test)]
949mod handler_tests {
950 use super::*;
961 use crate::netlink::NetlinkState;
962 use crate::seccomp::ctx::SupervisorCtx;
963 use crate::seccomp::notif::NotifPolicy;
964 use crate::seccomp::state::{
965 ChrootState, CowState, NetworkState, PolicyFnState, ProcessIndex, ProcfsState,
966 ResourceState, TimeRandomState,
967 };
968 use crate::sys::structs::{SeccompData, SeccompNotif};
969 use std::sync::atomic::{AtomicUsize, Ordering};
970
971 fn fake_notif(nr: i32) -> SeccompNotif {
972 SeccompNotif {
973 id: 0,
974 pid: 1,
975 flags: 0,
976 data: SeccompData {
977 nr,
978 arch: 0,
979 instruction_pointer: 0,
980 args: [0; 6],
981 },
982 }
983 }
984
985 fn fake_supervisor_ctx() -> Arc<SupervisorCtx> {
992 Arc::new(SupervisorCtx {
993 resource: Arc::new(Mutex::new(ResourceState::new(0, 0))),
994 cow: Arc::new(Mutex::new(CowState::new())),
995 procfs: Arc::new(Mutex::new(ProcfsState::new())),
996 network: Arc::new(Mutex::new(NetworkState::new())),
997 time_random: Arc::new(Mutex::new(TimeRandomState::new(None, None))),
998 policy_fn: Arc::new(Mutex::new(PolicyFnState::new())),
999 chroot: Arc::new(Mutex::new(ChrootState::new())),
1000 netlink: Arc::new(NetlinkState::new()),
1001 processes: Arc::new(ProcessIndex::new()),
1002 policy: Arc::new(NotifPolicy {
1003 max_memory_bytes: 0,
1004 max_processes: 0,
1005 has_memory_limit: false,
1006 has_net_allowlist: false,
1007 has_random_seed: false,
1008 has_time_start: false,
1009 time_offset: 0,
1010 num_cpus: None,
1011 argv_safety_required: false,
1012 port_remap: false,
1013 cow_enabled: false,
1014 chroot_root: None,
1015 chroot_readable: Vec::new(),
1016 chroot_writable: Vec::new(),
1017 chroot_denied: Vec::new(),
1018 chroot_mounts: Vec::new(),
1019 deterministic_dirs: false,
1020 virtual_hostname: None,
1021 has_http_acl: false,
1022 virtual_etc_hosts: None,
1023 }),
1024 child_pidfd: None,
1025 notif_fd: -1,
1026 })
1027 }
1028
1029 #[tokio::test]
1033 async fn dispatch_walks_chain_in_registration_order() {
1034 let mut table = DispatchTable::new();
1035 let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
1036
1037 for tag in [1u8, 2u8, 3u8] {
1038 let order_clone = Arc::clone(&order);
1039 table.register(
1040 libc::SYS_openat,
1041 move |_cx: &HandlerCtx| {
1042 let order = Arc::clone(&order_clone);
1043 async move {
1044 order.lock().unwrap().push(tag);
1045 NotifAction::Continue
1046 }
1047 },
1048 );
1049 }
1050
1051 let _ctx = fake_supervisor_ctx();
1052 let action = table
1053 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1054 .await;
1055
1056 assert!(matches!(action, NotifAction::Continue));
1057 let recorded = order.lock().unwrap();
1058 assert_eq!(
1059 *recorded,
1060 [1u8, 2u8, 3u8],
1061 "every handler must run, in the order it was registered"
1062 );
1063 }
1064
1065 #[tokio::test]
1073 async fn dispatch_runs_builtin_before_extra() {
1074 let mut table = DispatchTable::new();
1075 let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
1076
1077 let order_builtin = Arc::clone(&order);
1079 table.register(
1080 libc::SYS_openat,
1081 move |_cx: &HandlerCtx| {
1082 let order = Arc::clone(&order_builtin);
1083 async move {
1084 order.lock().unwrap().push(b'B');
1085 NotifAction::Continue
1086 }
1087 },
1088 );
1089
1090 let order_extra = Arc::clone(&order);
1093 table.register(
1094 libc::SYS_openat,
1095 move |_cx: &HandlerCtx| {
1096 let order = Arc::clone(&order_extra);
1097 async move {
1098 order.lock().unwrap().push(b'E');
1099 NotifAction::Continue
1100 }
1101 },
1102 );
1103
1104 let _ctx = fake_supervisor_ctx();
1105 let action = table
1106 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1107 .await;
1108
1109 assert!(matches!(action, NotifAction::Continue));
1110 let recorded = order.lock().unwrap();
1111 assert_eq!(
1112 *recorded,
1113 [b'B', b'E'],
1114 "builtin must run before extra (insertion order preserved)"
1115 );
1116 }
1117
1118 #[tokio::test]
1125 async fn dispatch_stops_at_first_non_continue() {
1126 let mut table = DispatchTable::new();
1127 let calls = Arc::new(AtomicUsize::new(0));
1128
1129 let calls_first = Arc::clone(&calls);
1131 table.register(
1132 libc::SYS_openat,
1133 move |_cx: &HandlerCtx| {
1134 let calls = Arc::clone(&calls_first);
1135 async move {
1136 calls.fetch_add(1, Ordering::SeqCst);
1137 NotifAction::Errno(libc::EACCES)
1138 }
1139 },
1140 );
1141
1142 let calls_second = Arc::clone(&calls);
1144 table.register(
1145 libc::SYS_openat,
1146 move |_cx: &HandlerCtx| {
1147 let calls = Arc::clone(&calls_second);
1148 async move {
1149 calls.fetch_add(1, Ordering::SeqCst);
1150 NotifAction::Continue
1151 }
1152 },
1153 );
1154
1155 let _ctx = fake_supervisor_ctx();
1156 let action = table
1157 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1158 .await;
1159
1160 match action {
1161 NotifAction::Errno(e) => assert_eq!(e, libc::EACCES),
1162 other => panic!("expected Errno(EACCES), got {:?}", other),
1163 }
1164 assert_eq!(
1165 calls.load(Ordering::SeqCst),
1166 1,
1167 "second handler must not run after first returned non-Continue"
1168 );
1169 }
1170
1171 #[test]
1188 fn validate_extras_rejects_user_specified_blocklist() {
1189 let policy = crate::sandbox::Sandbox::builder()
1190 .extra_deny_syscalls(vec!["mremap".into()])
1191 .build()
1192 .expect("policy builds");
1193
1194 let result = validate_handler_syscalls_against_policy(&[libc::SYS_mremap], &policy);
1195 assert_eq!(
1196 result,
1197 Err(libc::SYS_mremap),
1198 "handler on user-specified blocklist must be rejected, naming the offending syscall"
1199 );
1200 }
1201
1202 #[tokio::test]
1205 async fn handler_via_blanket_impl_dispatches_closures() {
1206 use std::sync::atomic::{AtomicU64, Ordering};
1207 let counter = Arc::new(AtomicU64::new(0));
1208 let counter_clone = Arc::clone(&counter);
1209
1210 let h = move |cx: &HandlerCtx| {
1211 let counter = Arc::clone(&counter_clone);
1212 async move {
1213 counter.fetch_add(1, Ordering::SeqCst);
1214 let _ = cx.notif.pid; NotifAction::Continue
1216 }
1217 };
1218
1219 let _sup = fake_supervisor_ctx();
1220 let notif = fake_notif(libc::SYS_openat as i32);
1221 let cx = HandlerCtx { notif, notif_fd: -1 };
1222
1223 let action = h.handle(&cx).await;
1224 assert!(matches!(action, NotifAction::Continue));
1225 assert_eq!(counter.load(Ordering::SeqCst), 1);
1226 }
1227
1228 #[tokio::test]
1237 async fn dispatch_invokes_struct_handler_with_persistent_self_state() {
1238 use std::sync::atomic::{AtomicU64, Ordering};
1239
1240 struct StructHandler {
1241 calls: AtomicU64,
1242 }
1243
1244 impl Handler for StructHandler {
1245 fn handle<'a>(
1246 &'a self,
1247 _cx: &'a HandlerCtx,
1248 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
1249 Box::pin(async move {
1250 self.calls.fetch_add(1, Ordering::SeqCst);
1251 NotifAction::Continue
1252 })
1253 }
1254 }
1255
1256 let mut table = DispatchTable::new();
1257 let handler = std::sync::Arc::new(StructHandler {
1258 calls: AtomicU64::new(0),
1259 });
1260 table.register_arc(libc::SYS_openat, handler.clone() as std::sync::Arc<dyn Handler>);
1261
1262 let _sup = fake_supervisor_ctx();
1263 let notif = fake_notif(libc::SYS_openat as i32);
1264
1265 for _ in 0..3 {
1269 let action = table.dispatch(notif, -1).await;
1270 assert!(matches!(action, NotifAction::Continue));
1271 }
1272
1273 assert_eq!(
1274 handler.calls.load(Ordering::SeqCst),
1275 3,
1276 "dispatch must invoke the struct-based handler on every walk"
1277 );
1278 }
1279}