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 ctx_for_fork = Arc::clone(ctx);
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 ctx = Arc::clone(&ctx_for_fork);
285 async move {
286 crate::resource::handle_fork(¬if, notif_fd, &ctx, &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_destination_policy || policy.has_unix_fs_gate {
332 for &nr in &[
333 libc::SYS_connect,
334 libc::SYS_sendto,
335 libc::SYS_sendmsg,
336 libc::SYS_sendmmsg,
337 ] {
338 let __sup = Arc::clone(ctx);
339 table.register(nr, 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 crate::network::handle_net(¬if, &sup, notif_fd).await
345 }
346 });
347 }
348 }
349
350 if policy.has_random_seed {
354 let __sup = Arc::clone(ctx);
355 table.register(libc::SYS_getrandom, move |cx: &HandlerCtx| {
356 let notif = cx.notif;
357 let sup = Arc::clone(&__sup);
358 let notif_fd = cx.notif_fd;
359 async move {
360 let mut tr = sup.time_random.lock().await;
361 if let Some(ref mut rng) = tr.random_state {
362 crate::random::handle_getrandom(¬if, rng, notif_fd)
363 } else {
364 NotifAction::Continue
365 }
366 }
367 });
368 }
369
370 if policy.has_random_seed {
377 for nr in open_family_syscalls() {
378 let __sup = Arc::clone(ctx);
379 let policy_rand = Arc::clone(policy);
380 table.register(nr, move |cx: &HandlerCtx| {
381 let notif = cx.notif;
382 let sup = Arc::clone(&__sup);
383 let policy = Arc::clone(&policy_rand);
384 let notif_fd = cx.notif_fd;
385 async move {
386 let mut tr = sup.time_random.lock().await;
387 if let Some(ref mut rng) = tr.random_state {
388 if let Some(action) = crate::random::handle_random_open(
389 ¬if, rng, notif_fd,
390 policy.chroot_root.as_deref(), &policy.chroot_mounts,
391 ) {
392 return action;
393 }
394 }
395 NotifAction::Continue
396 }
397 });
398 }
399 }
400
401 if policy.has_time_start {
405 let time_offset = policy.time_offset;
406 for &nr in &[
407 libc::SYS_clock_nanosleep as i64,
408 libc::SYS_timerfd_settime as i64,
409 libc::SYS_timer_settime as i64,
410 ] {
411 table.register(nr, move |cx: &HandlerCtx| {
412 let notif = cx.notif;
413 let notif_fd = cx.notif_fd;
414 async move {
415 crate::time::handle_timer(¬if, time_offset, notif_fd)
416 }
417 });
418 }
419 }
420
421 {
435 let etc_hosts = policy.virtual_etc_hosts.clone();
436 for nr in open_family_syscalls() {
437 let etc_hosts = etc_hosts.clone();
438 let policy_hosts = Arc::clone(policy);
439 table.register(nr, move |cx: &HandlerCtx| {
440 let notif = cx.notif;
441 let notif_fd = cx.notif_fd;
442 let etc_hosts = etc_hosts.clone();
443 let policy = Arc::clone(&policy_hosts);
444 async move {
445 if let Some(action) = crate::procfs::handle_etc_hosts_open(
446 ¬if, &etc_hosts, notif_fd,
447 policy.chroot_root.as_deref(), &policy.chroot_mounts,
448 ) {
449 action
450 } else {
451 NotifAction::Continue
452 }
453 }
454 });
455 }
456 }
457
458 if let Some(ca_pem) = policy.ca_inject_pem.clone() {
465 if !policy.ca_inject_paths.is_empty() {
466 let inject_paths = std::sync::Arc::new(policy.ca_inject_paths.clone());
467 for nr in open_family_syscalls() {
468 let ca_pem = std::sync::Arc::clone(&ca_pem);
469 let inject_paths = std::sync::Arc::clone(&inject_paths);
470 let policy_ca = Arc::clone(policy);
471 table.register(nr, move |cx: &HandlerCtx| {
472 let notif = cx.notif;
473 let notif_fd = cx.notif_fd;
474 let ca_pem = std::sync::Arc::clone(&ca_pem);
475 let inject_paths = std::sync::Arc::clone(&inject_paths);
476 let policy = Arc::clone(&policy_ca);
477 async move {
478 crate::ca_inject::handle_ca_inject_open(
479 ¬if, &inject_paths, &ca_pem, notif_fd,
480 policy.chroot_root.as_deref(), &policy.chroot_mounts,
481 )
482 .unwrap_or(NotifAction::Continue)
483 }
484 });
485 }
486 }
487 }
488
489 for nr in open_family_syscalls() {
500 let policy_for_proc_open = Arc::clone(policy);
501 let resource_for_proc_open = Arc::clone(resource);
502 let __sup = Arc::clone(ctx);
503 table.register(nr, move |cx: &HandlerCtx| {
504 let notif = cx.notif;
505 let sup = Arc::clone(&__sup);
506 let notif_fd = cx.notif_fd;
507 let policy = Arc::clone(&policy_for_proc_open);
508 let resource = Arc::clone(&resource_for_proc_open);
509 async move {
510 let processes = Arc::clone(&sup.processes);
511 let network = Arc::clone(&sup.network);
512 crate::procfs::handle_proc_open(¬if, &processes, &resource, &network, &policy, notif_fd).await
513 }
514 });
515 }
516
517 if policy.chroot_root.is_some() {
521 register_chroot_handlers(&mut table, policy, ctx);
522 }
523
524 if policy.cow_enabled {
528 register_cow_handlers(&mut table, ctx);
529 }
530
531 let mut getdents_nrs = vec![libc::SYS_getdents64];
538 if let Some(getdents) = arch::sys_getdents() {
539 getdents_nrs.push(getdents);
540 }
541 for nr in getdents_nrs {
542 let policy_for_getdents = Arc::clone(policy);
543 let __sup = Arc::clone(ctx);
544 table.register(nr, move |cx: &HandlerCtx| {
545 let notif = cx.notif;
546 let sup = Arc::clone(&__sup);
547 let notif_fd = cx.notif_fd;
548 let policy = Arc::clone(&policy_for_getdents);
549 async move {
550 let processes = Arc::clone(&sup.processes);
551 crate::procfs::handle_getdents(¬if, &processes, &policy, notif_fd).await
552 }
553 });
554 }
555
556 if let Some(n) = policy.num_cpus {
560 table.register(libc::SYS_sched_getaffinity, move |cx: &HandlerCtx| {
561 let notif = cx.notif;
562 let notif_fd = cx.notif_fd;
563 async move {
564 crate::procfs::handle_sched_getaffinity(¬if, n, notif_fd)
565 }
566 });
567 }
568
569 if let Some(ref hostname) = policy.virtual_hostname {
575 let hostname_for_uname = hostname.clone();
576 let hostname_for_open = hostname.clone();
577 table.register(libc::SYS_uname, move |cx: &HandlerCtx| {
578 let notif = cx.notif;
579 let notif_fd = cx.notif_fd;
580 let hostname = hostname_for_uname.clone();
581 async move {
582 crate::procfs::handle_uname(¬if, &hostname, notif_fd)
583 }
584 });
585 for nr in open_family_syscalls() {
586 let hostname = hostname_for_open.clone();
587 let policy_hostname = Arc::clone(policy);
588 table.register(nr, move |cx: &HandlerCtx| {
589 let notif = cx.notif;
590 let notif_fd = cx.notif_fd;
591 let hostname = hostname.clone();
592 let policy = Arc::clone(&policy_hostname);
593 async move {
594 if let Some(action) = crate::procfs::handle_hostname_open(
595 ¬if, &hostname, notif_fd,
596 policy.chroot_root.as_deref(), &policy.chroot_mounts,
597 ) {
598 action
599 } else {
600 NotifAction::Continue
601 }
602 }
603 });
604 }
605 }
606
607 if policy.deterministic_dirs {
613 let mut getdents_nrs = vec![libc::SYS_getdents64];
614 if let Some(getdents) = arch::sys_getdents() {
615 getdents_nrs.push(getdents);
616 }
617 for nr in getdents_nrs {
618 let __sup = Arc::clone(ctx);
619 table.register(nr, 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 let processes = Arc::clone(&sup.processes);
625 crate::procfs::handle_sorted_getdents(¬if, &processes, notif_fd).await
626 }
627 });
628 }
629 }
630
631 {
644 let __sup = Arc::clone(ctx);
645 table.register(libc::SYS_socket, move |cx: &HandlerCtx| {
646 let notif = cx.notif;
647 let sup = Arc::clone(&__sup);
648 async move {
649 let state = Arc::clone(&sup.netlink);
650 crate::netlink::handlers::handle_socket(¬if, &state).await
651 }
652 });
653 let __sup = Arc::clone(ctx);
654 table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
655 let notif = cx.notif;
656 let sup = Arc::clone(&__sup);
657 async move {
658 let state = Arc::clone(&sup.netlink);
659 crate::netlink::handlers::handle_bind(¬if, &state).await
660 }
661 });
662 let __sup = Arc::clone(ctx);
663 table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
664 let notif = cx.notif;
665 let sup = Arc::clone(&__sup);
666 let notif_fd = cx.notif_fd;
667 async move {
668 let state = Arc::clone(&sup.netlink);
669 crate::netlink::handlers::handle_getsockname(¬if, &state, notif_fd).await
670 }
671 });
672 for &nr in &[libc::SYS_recvfrom, libc::SYS_recvmsg] {
676 let __sup = Arc::clone(ctx);
677 table.register(nr, move |cx: &HandlerCtx| {
678 let notif = cx.notif;
679 let sup = Arc::clone(&__sup);
680 let notif_fd = cx.notif_fd;
681 async move {
682 let state = Arc::clone(&sup.netlink);
683 crate::netlink::handlers::handle_netlink_recvmsg(¬if, &state, notif_fd).await
684 }
685 });
686 }
687 let __sup = Arc::clone(ctx);
690 table.register(libc::SYS_close, move |cx: &HandlerCtx| {
691 let notif = cx.notif;
692 let sup = Arc::clone(&__sup);
693 async move {
694 let state = Arc::clone(&sup.netlink);
695 crate::netlink::handlers::handle_close(¬if, &state).await
696 }
697 });
698 }
699
700 if policy.port_remap || policy.has_net_destination_policy || policy.has_bind_denylist {
704 let __sup = Arc::clone(ctx);
705 table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
706 let notif = cx.notif;
707 let sup = Arc::clone(&__sup);
708 let notif_fd = cx.notif_fd;
709 async move {
710 crate::port_remap::handle_bind(¬if, &sup.network, notif_fd).await
711 }
712 });
713 }
714
715 if policy.port_remap {
719 let __sup = Arc::clone(ctx);
720 table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
721 let notif = cx.notif;
722 let sup = Arc::clone(&__sup);
723 let notif_fd = cx.notif_fd;
724 async move {
725 crate::port_remap::handle_getsockname(¬if, &sup.network, notif_fd).await
726 }
727 });
728 }
729
730 for (nr, h) in pending_handlers {
736 table.register_arc(nr, h);
737 }
738
739 table
740}
741
742fn register_chroot_handlers(
747 table: &mut DispatchTable,
748 policy: &Arc<NotifPolicy>,
749 ctx: &Arc<SupervisorCtx>,
750) {
751 use crate::chroot::dispatch::ChrootCtx;
752
753 macro_rules! chroot_handler {
757 ($policy:expr, $handler:expr) => {{
758 let policy = Arc::clone($policy);
759 let chroot_state = Arc::clone(&ctx.chroot);
760 let cow_state = Arc::clone(&ctx.cow);
761 move |cx: &HandlerCtx| {
762 let notif = cx.notif;
763 let chroot_state = Arc::clone(&chroot_state);
764 let cow_state = Arc::clone(&cow_state);
765 let notif_fd = cx.notif_fd;
766 let policy = Arc::clone(&policy);
767 async move {
768 let chroot_ctx = ChrootCtx {
769 root: policy.chroot_root.as_ref().unwrap(),
770 readable: &policy.chroot_readable,
771 writable: &policy.chroot_writable,
772 denied: &policy.chroot_denied,
773 mounts: &policy.chroot_mounts,
774 mount_ro: &policy.chroot_mount_ro,
775 };
776 $handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
777 }
778 }
779 }};
780 }
781
782 macro_rules! chroot_handler_fallthrough {
785 ($policy:expr, $handler:expr) => {{
786 let policy = Arc::clone($policy);
787 let chroot_state = Arc::clone(&ctx.chroot);
788 let cow_state = Arc::clone(&ctx.cow);
789 move |cx: &HandlerCtx| {
790 let notif = cx.notif;
791 let chroot_state = Arc::clone(&chroot_state);
792 let cow_state = Arc::clone(&cow_state);
793 let notif_fd = cx.notif_fd;
794 let policy = Arc::clone(&policy);
795 async move {
796 let chroot_ctx = ChrootCtx {
797 root: policy.chroot_root.as_ref().unwrap(),
798 readable: &policy.chroot_readable,
799 writable: &policy.chroot_writable,
800 denied: &policy.chroot_denied,
801 mounts: &policy.chroot_mounts,
802 mount_ro: &policy.chroot_mount_ro,
803 };
804 $handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
805 }
806 }
807 }};
808 }
809
810 table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
812 crate::chroot::dispatch::handle_chroot_open));
813
814 if let Some(open) = arch::sys_open() {
816 table.register(open, chroot_handler_fallthrough!(policy,
817 crate::chroot::dispatch::handle_chroot_legacy_open));
818 }
819
820 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
822 table.register(nr, chroot_handler!(policy,
823 crate::chroot::dispatch::handle_chroot_exec));
824 }
825
826 for &nr in &[
828 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
829 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
830 libc::SYS_fchownat, libc::SYS_truncate,
831 ] {
832 table.register(nr, chroot_handler!(policy,
833 crate::chroot::dispatch::handle_chroot_write));
834 }
835
836 if let Some(nr) = arch::sys_unlink() {
838 table.register(nr, chroot_handler!(policy,
839 crate::chroot::dispatch::handle_chroot_legacy_unlink));
840 }
841 if let Some(nr) = arch::sys_rmdir() {
842 table.register(nr, chroot_handler!(policy,
843 crate::chroot::dispatch::handle_chroot_legacy_rmdir));
844 }
845 if let Some(nr) = arch::sys_mkdir() {
846 table.register(nr, chroot_handler!(policy,
847 crate::chroot::dispatch::handle_chroot_legacy_mkdir));
848 }
849 if let Some(nr) = arch::sys_rename() {
850 table.register(nr, chroot_handler!(policy,
851 crate::chroot::dispatch::handle_chroot_legacy_rename));
852 }
853 if let Some(nr) = arch::sys_symlink() {
854 table.register(nr, chroot_handler!(policy,
855 crate::chroot::dispatch::handle_chroot_legacy_symlink));
856 }
857 if let Some(nr) = arch::sys_link() {
858 table.register(nr, chroot_handler!(policy,
859 crate::chroot::dispatch::handle_chroot_legacy_link));
860 }
861 if let Some(nr) = arch::sys_chmod() {
862 table.register(nr, chroot_handler!(policy,
863 crate::chroot::dispatch::handle_chroot_legacy_chmod));
864 }
865
866 if let Some(chown) = arch::sys_chown() {
868 let policy_for_chown = Arc::clone(policy);
869 let __sup = Arc::clone(ctx);
870 table.register(chown, move |cx: &HandlerCtx| {
871 let notif = cx.notif;
872 let sup = Arc::clone(&__sup);
873 let notif_fd = cx.notif_fd;
874 let policy = Arc::clone(&policy_for_chown);
875 async move {
876 let chroot_ctx = ChrootCtx {
877 root: policy.chroot_root.as_ref().unwrap(),
878 readable: &policy.chroot_readable,
879 writable: &policy.chroot_writable,
880 denied: &policy.chroot_denied,
881 mounts: &policy.chroot_mounts,
882 mount_ro: &policy.chroot_mount_ro,
883 };
884 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, false).await
885 }
886 });
887 }
888
889 if let Some(lchown) = arch::sys_lchown() {
891 let policy_for_lchown = Arc::clone(policy);
892 let __sup = Arc::clone(ctx);
893 table.register(lchown, move |cx: &HandlerCtx| {
894 let notif = cx.notif;
895 let sup = Arc::clone(&__sup);
896 let notif_fd = cx.notif_fd;
897 let policy = Arc::clone(&policy_for_lchown);
898 async move {
899 let chroot_ctx = ChrootCtx {
900 root: policy.chroot_root.as_ref().unwrap(),
901 readable: &policy.chroot_readable,
902 writable: &policy.chroot_writable,
903 denied: &policy.chroot_denied,
904 mounts: &policy.chroot_mounts,
905 mount_ro: &policy.chroot_mount_ro,
906 };
907 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, true).await
908 }
909 });
910 }
911
912 for &nr in &[
914 libc::SYS_newfstatat,
915 libc::SYS_faccessat,
916 arch::SYS_FACCESSAT2,
917 ] {
918 table.register(nr, chroot_handler!(policy,
919 crate::chroot::dispatch::handle_chroot_stat));
920 }
921
922 if let Some(nr) = arch::sys_stat() {
924 table.register(nr, chroot_handler!(policy,
925 crate::chroot::dispatch::handle_chroot_legacy_stat));
926 }
927 if let Some(nr) = arch::sys_lstat() {
928 table.register(nr, chroot_handler!(policy,
929 crate::chroot::dispatch::handle_chroot_legacy_lstat));
930 }
931 if let Some(nr) = arch::sys_access() {
932 table.register(nr, chroot_handler!(policy,
933 crate::chroot::dispatch::handle_chroot_legacy_access));
934 }
935
936 table.register(libc::SYS_statx, chroot_handler!(policy,
938 crate::chroot::dispatch::handle_chroot_statx));
939
940 table.register(libc::SYS_readlinkat, chroot_handler!(policy,
942 crate::chroot::dispatch::handle_chroot_readlink));
943 if let Some(nr) = arch::sys_readlink() {
944 table.register(nr, chroot_handler!(policy,
945 crate::chroot::dispatch::handle_chroot_legacy_readlink));
946 }
947
948 let mut getdents_nrs = vec![libc::SYS_getdents64];
950 if let Some(getdents) = arch::sys_getdents() {
951 getdents_nrs.push(getdents);
952 }
953 for nr in getdents_nrs {
954 table.register(nr, chroot_handler!(policy,
955 crate::chroot::dispatch::handle_chroot_getdents));
956 }
957
958 table.register(libc::SYS_chdir as i64, chroot_handler!(policy,
960 crate::chroot::dispatch::handle_chroot_chdir));
961 table.register(libc::SYS_getcwd as i64, chroot_handler!(policy,
962 crate::chroot::dispatch::handle_chroot_getcwd));
963 table.register(libc::SYS_statfs as i64, chroot_handler!(policy,
964 crate::chroot::dispatch::handle_chroot_statfs));
965 table.register(libc::SYS_utimensat as i64, chroot_handler!(policy,
966 crate::chroot::dispatch::handle_chroot_utimensat));
967
968 for &nr in &[
970 libc::SYS_getxattr, libc::SYS_lgetxattr,
971 libc::SYS_setxattr, libc::SYS_lsetxattr,
972 libc::SYS_listxattr, libc::SYS_llistxattr,
973 libc::SYS_removexattr, libc::SYS_lremovexattr,
974 ] {
975 table.register(nr, chroot_handler!(policy,
976 crate::chroot::dispatch::handle_chroot_xattr));
977 }
978}
979
980fn register_cow_handlers(table: &mut DispatchTable, ctx: &Arc<SupervisorCtx>) {
985 macro_rules! cow_call {
988 ($handler:expr) => {{
989 let cow_state = Arc::clone(&ctx.cow);
990 let processes_state = Arc::clone(&ctx.processes);
991 move |cx: &HandlerCtx| {
992 let notif = cx.notif;
993 let cow_state = Arc::clone(&cow_state);
994 let processes_state = Arc::clone(&processes_state);
995 let notif_fd = cx.notif_fd;
996 async move {
997 $handler(¬if, &cow_state, &processes_state, notif_fd).await
998 }
999 }
1000 }};
1001 }
1002
1003 let mut write_nrs = vec![
1005 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
1006 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
1007 libc::SYS_fchownat, libc::SYS_truncate,
1008 ];
1009 write_nrs.extend([
1010 arch::sys_unlink(), arch::sys_rmdir(), arch::sys_mkdir(), arch::sys_rename(),
1011 arch::sys_symlink(), arch::sys_link(), arch::sys_chmod(), arch::sys_chown(),
1012 arch::sys_lchown(),
1013 ].into_iter().flatten());
1014 for nr in write_nrs {
1015 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_write));
1016 }
1017
1018 table.register(libc::SYS_utimensat, cow_call!(crate::cow::dispatch::handle_cow_utimensat));
1019
1020 let mut access_nrs = vec![libc::SYS_faccessat, arch::SYS_FACCESSAT2];
1021 access_nrs.extend(arch::sys_access());
1022 for nr in access_nrs {
1023 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_access));
1024 }
1025
1026 let mut open_nrs = vec![libc::SYS_openat];
1027 open_nrs.extend(arch::sys_open());
1028 for nr in open_nrs {
1029 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_open));
1030 }
1031
1032 let mut stat_nrs = vec![libc::SYS_newfstatat, libc::SYS_faccessat];
1033 stat_nrs.extend([arch::sys_stat(), arch::sys_lstat(), arch::sys_access()].into_iter().flatten());
1034 for nr in stat_nrs {
1035 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_stat));
1036 }
1037
1038 table.register(libc::SYS_statx, cow_call!(crate::cow::dispatch::handle_cow_statx));
1039
1040 let mut readlink_nrs = vec![libc::SYS_readlinkat];
1041 readlink_nrs.extend(arch::sys_readlink());
1042 for nr in readlink_nrs {
1043 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_readlink));
1044 }
1045
1046 let mut getdents_nrs = vec![libc::SYS_getdents64];
1047 getdents_nrs.extend(arch::sys_getdents());
1048 for nr in getdents_nrs {
1049 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_getdents));
1050 }
1051
1052 table.register(libc::SYS_chdir, cow_call!(crate::cow::dispatch::handle_cow_chdir));
1053 table.register(libc::SYS_getcwd, cow_call!(crate::cow::dispatch::handle_cow_getcwd));
1054
1055 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
1056 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_exec));
1057 }
1058}
1059
1060#[cfg(test)]
1065mod handler_tests {
1066 use super::*;
1077 use crate::netlink::NetlinkState;
1078 use crate::seccomp::ctx::SupervisorCtx;
1079 use crate::seccomp::notif::NotifPolicy;
1080 use crate::seccomp::state::{
1081 ChrootState, CowState, NetworkState, PolicyFnState, ProcessIndex, ProcfsState,
1082 ResourceState, TimeRandomState,
1083 };
1084 use crate::sys::structs::{SeccompData, SeccompNotif};
1085 use std::sync::atomic::{AtomicUsize, Ordering};
1086
1087 fn fake_notif(nr: i32) -> SeccompNotif {
1088 SeccompNotif {
1089 id: 0,
1090 pid: 1,
1091 flags: 0,
1092 data: SeccompData {
1093 nr,
1094 arch: 0,
1095 instruction_pointer: 0,
1096 args: [0; 6],
1097 },
1098 }
1099 }
1100
1101 fn fake_supervisor_ctx() -> Arc<SupervisorCtx> {
1108 Arc::new(SupervisorCtx {
1109 resource: Arc::new(Mutex::new(ResourceState::new(0, 0))),
1110 cow: Arc::new(Mutex::new(CowState::new())),
1111 procfs: Arc::new(Mutex::new(ProcfsState::new())),
1112 network: Arc::new(Mutex::new(NetworkState::new())),
1113 time_random: Arc::new(Mutex::new(TimeRandomState::new(None, None))),
1114 policy_fn: Arc::new(Mutex::new(PolicyFnState::new())),
1115 chroot: Arc::new(Mutex::new(ChrootState::new())),
1116 netlink: Arc::new(NetlinkState::new()),
1117 processes: Arc::new(ProcessIndex::new()),
1118 policy: Arc::new(NotifPolicy {
1119 max_memory_bytes: 0,
1120 max_processes: 0,
1121 has_memory_limit: false,
1122 has_net_destination_policy: false,
1123 has_bind_denylist: false,
1124 has_unix_fs_gate: false,
1125 has_random_seed: false,
1126 has_time_start: false,
1127 time_offset: 0,
1128 num_cpus: None,
1129 argv_safety_required: false,
1130 port_remap: false,
1131 cow_enabled: false,
1132 chroot_root: None,
1133 chroot_readable: Vec::new(),
1134 chroot_writable: Vec::new(),
1135 chroot_denied: Vec::new(),
1136 chroot_mounts: Vec::new(),
1137 chroot_mount_ro: Vec::new(),
1138 deterministic_dirs: false,
1139 virtual_hostname: None,
1140 has_http_acl: false,
1141 virtual_etc_hosts: String::new(),
1142 ca_inject_paths: Vec::new(),
1143 ca_inject_pem: None,
1144 }),
1145 child_pidfd: None,
1146 notif_fd: -1,
1147 })
1148 }
1149
1150 #[tokio::test]
1154 async fn dispatch_walks_chain_in_registration_order() {
1155 let mut table = DispatchTable::new();
1156 let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
1157
1158 for tag in [1u8, 2u8, 3u8] {
1159 let order_clone = Arc::clone(&order);
1160 table.register(
1161 libc::SYS_openat,
1162 move |_cx: &HandlerCtx| {
1163 let order = Arc::clone(&order_clone);
1164 async move {
1165 order.lock().unwrap().push(tag);
1166 NotifAction::Continue
1167 }
1168 },
1169 );
1170 }
1171
1172 let _ctx = fake_supervisor_ctx();
1173 let action = table
1174 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1175 .await;
1176
1177 assert!(matches!(action, NotifAction::Continue));
1178 let recorded = order.lock().unwrap();
1179 assert_eq!(
1180 *recorded,
1181 [1u8, 2u8, 3u8],
1182 "every handler must run, in the order it was registered"
1183 );
1184 }
1185
1186 #[tokio::test]
1194 async fn dispatch_runs_builtin_before_extra() {
1195 let mut table = DispatchTable::new();
1196 let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
1197
1198 let order_builtin = Arc::clone(&order);
1200 table.register(
1201 libc::SYS_openat,
1202 move |_cx: &HandlerCtx| {
1203 let order = Arc::clone(&order_builtin);
1204 async move {
1205 order.lock().unwrap().push(b'B');
1206 NotifAction::Continue
1207 }
1208 },
1209 );
1210
1211 let order_extra = Arc::clone(&order);
1214 table.register(
1215 libc::SYS_openat,
1216 move |_cx: &HandlerCtx| {
1217 let order = Arc::clone(&order_extra);
1218 async move {
1219 order.lock().unwrap().push(b'E');
1220 NotifAction::Continue
1221 }
1222 },
1223 );
1224
1225 let _ctx = fake_supervisor_ctx();
1226 let action = table
1227 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1228 .await;
1229
1230 assert!(matches!(action, NotifAction::Continue));
1231 let recorded = order.lock().unwrap();
1232 assert_eq!(
1233 *recorded,
1234 [b'B', b'E'],
1235 "builtin must run before extra (insertion order preserved)"
1236 );
1237 }
1238
1239 #[tokio::test]
1246 async fn dispatch_stops_at_first_non_continue() {
1247 let mut table = DispatchTable::new();
1248 let calls = Arc::new(AtomicUsize::new(0));
1249
1250 let calls_first = Arc::clone(&calls);
1252 table.register(
1253 libc::SYS_openat,
1254 move |_cx: &HandlerCtx| {
1255 let calls = Arc::clone(&calls_first);
1256 async move {
1257 calls.fetch_add(1, Ordering::SeqCst);
1258 NotifAction::Errno(libc::EACCES)
1259 }
1260 },
1261 );
1262
1263 let calls_second = Arc::clone(&calls);
1265 table.register(
1266 libc::SYS_openat,
1267 move |_cx: &HandlerCtx| {
1268 let calls = Arc::clone(&calls_second);
1269 async move {
1270 calls.fetch_add(1, Ordering::SeqCst);
1271 NotifAction::Continue
1272 }
1273 },
1274 );
1275
1276 let _ctx = fake_supervisor_ctx();
1277 let action = table
1278 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1279 .await;
1280
1281 match action {
1282 NotifAction::Errno(e) => assert_eq!(e, libc::EACCES),
1283 other => panic!("expected Errno(EACCES), got {:?}", other),
1284 }
1285 assert_eq!(
1286 calls.load(Ordering::SeqCst),
1287 1,
1288 "second handler must not run after first returned non-Continue"
1289 );
1290 }
1291
1292 #[tokio::test]
1296 async fn dispatch_short_circuits_on_defer() {
1297 let mut table = DispatchTable::new();
1298 let later_ran = Arc::new(AtomicUsize::new(0));
1299
1300 table.register(
1301 libc::SYS_openat,
1302 |_cx: &HandlerCtx| async { NotifAction::defer(async { NotifAction::ReturnValue(1) }) },
1303 );
1304
1305 let later = Arc::clone(&later_ran);
1306 table.register(
1307 libc::SYS_openat,
1308 move |_cx: &HandlerCtx| {
1309 let later = Arc::clone(&later);
1310 async move {
1311 later.fetch_add(1, Ordering::SeqCst);
1312 NotifAction::Continue
1313 }
1314 },
1315 );
1316
1317 let _ctx = fake_supervisor_ctx();
1318 let action = table
1319 .dispatch(fake_notif(libc::SYS_openat as i32), -1)
1320 .await;
1321
1322 assert!(
1323 matches!(action, NotifAction::Defer(_)),
1324 "dispatch must return the Defer produced by the first handler"
1325 );
1326 assert_eq!(
1327 later_ran.load(Ordering::SeqCst),
1328 0,
1329 "Defer must short-circuit the chain like any non-Continue action"
1330 );
1331 }
1332
1333 #[test]
1350 fn validate_extras_rejects_user_specified_blocklist() {
1351 let policy = crate::sandbox::Sandbox::builder()
1352 .extra_deny_syscalls(vec!["mremap".into()])
1353 .build()
1354 .expect("policy builds");
1355
1356 let result = validate_handler_syscalls_against_policy(&[libc::SYS_mremap], &policy);
1357 assert_eq!(
1358 result,
1359 Err(libc::SYS_mremap),
1360 "handler on user-specified blocklist must be rejected, naming the offending syscall"
1361 );
1362 }
1363
1364 #[tokio::test]
1367 async fn handler_via_blanket_impl_dispatches_closures() {
1368 use std::sync::atomic::{AtomicU64, Ordering};
1369 let counter = Arc::new(AtomicU64::new(0));
1370 let counter_clone = Arc::clone(&counter);
1371
1372 let h = move |cx: &HandlerCtx| {
1373 let counter = Arc::clone(&counter_clone);
1374 async move {
1375 counter.fetch_add(1, Ordering::SeqCst);
1376 let _ = cx.notif.pid; NotifAction::Continue
1378 }
1379 };
1380
1381 let _sup = fake_supervisor_ctx();
1382 let notif = fake_notif(libc::SYS_openat as i32);
1383 let cx = HandlerCtx { notif, notif_fd: -1 };
1384
1385 let action = h.handle(&cx).await;
1386 assert!(matches!(action, NotifAction::Continue));
1387 assert_eq!(counter.load(Ordering::SeqCst), 1);
1388 }
1389
1390 #[tokio::test]
1399 async fn dispatch_invokes_struct_handler_with_persistent_self_state() {
1400 use std::sync::atomic::{AtomicU64, Ordering};
1401
1402 struct StructHandler {
1403 calls: AtomicU64,
1404 }
1405
1406 impl Handler for StructHandler {
1407 fn handle<'a>(
1408 &'a self,
1409 _cx: &'a HandlerCtx,
1410 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
1411 Box::pin(async move {
1412 self.calls.fetch_add(1, Ordering::SeqCst);
1413 NotifAction::Continue
1414 })
1415 }
1416 }
1417
1418 let mut table = DispatchTable::new();
1419 let handler = std::sync::Arc::new(StructHandler {
1420 calls: AtomicU64::new(0),
1421 });
1422 table.register_arc(libc::SYS_openat, handler.clone() as std::sync::Arc<dyn Handler>);
1423
1424 let _sup = fake_supervisor_ctx();
1425 let notif = fake_notif(libc::SYS_openat as i32);
1426
1427 for _ in 0..3 {
1431 let action = table.dispatch(notif, -1).await;
1432 assert!(matches!(action, NotifAction::Continue));
1433 }
1434
1435 assert_eq!(
1436 handler.calls.load(Ordering::SeqCst),
1437 3,
1438 "dispatch must invoke the struct-based handler on every walk"
1439 );
1440 }
1441}