1use std::collections::HashMap;
20use std::future::Future;
21use std::os::unix::io::RawFd;
22use std::pin::Pin;
23use std::sync::Arc;
24
25use super::ctx::SupervisorCtx;
26use super::notif::{NotifAction, NotifPolicy};
27use super::state::ResourceState;
28use crate::arch;
29use crate::sys::structs::SeccompNotif;
30
31use tokio::sync::Mutex;
32
33pub type HandlerFn = Box<
40 dyn Fn(SeccompNotif, Arc<SupervisorCtx>, RawFd) -> Pin<Box<dyn Future<Output = NotifAction> + Send>>
41 + Send
42 + Sync,
43>;
44
45struct HandlerChain {
47 handlers: Vec<HandlerFn>,
48}
49
50pub struct DispatchTable {
52 chains: HashMap<i64, HandlerChain>,
53}
54
55impl DispatchTable {
56 pub fn new() -> Self {
58 Self {
59 chains: HashMap::new(),
60 }
61 }
62
63 pub fn register(&mut self, syscall_nr: i64, handler: HandlerFn) {
66 self.chains
67 .entry(syscall_nr)
68 .or_insert_with(|| HandlerChain {
69 handlers: Vec::new(),
70 })
71 .handlers
72 .push(handler);
73 }
74
75 pub async fn dispatch(
77 &self,
78 notif: SeccompNotif,
79 ctx: &Arc<SupervisorCtx>,
80 notif_fd: RawFd,
81 ) -> NotifAction {
82 let nr = notif.data.nr as i64;
83 if let Some(chain) = self.chains.get(&nr) {
84 for handler in &chain.handlers {
85 let action = handler(notif, Arc::clone(ctx), notif_fd).await;
86 if !matches!(action, NotifAction::Continue) {
87 return action;
88 }
89 }
90 }
91 NotifAction::Continue
92 }
93}
94
95pub fn build_dispatch_table(
103 policy: &Arc<NotifPolicy>,
104 resource: &Arc<Mutex<ResourceState>>,
105) -> DispatchTable {
106 let mut table = DispatchTable::new();
107
108 let mut fork_nrs = vec![libc::SYS_clone, libc::SYS_clone3];
112 if let Some(vfork) = arch::SYS_VFORK {
113 fork_nrs.push(vfork);
114 }
115 for nr in fork_nrs {
116 let policy = Arc::clone(policy);
117 let resource = Arc::clone(resource);
118 table.register(nr, Box::new(move |notif, _ctx, _notif_fd| {
119 let policy = Arc::clone(&policy);
120 let resource = Arc::clone(&resource);
121 Box::pin(async move {
122 crate::resource::handle_fork(¬if, &resource, &policy).await
123 })
124 }));
125 }
126
127 for &nr in &[libc::SYS_wait4, libc::SYS_waitid] {
131 let resource = Arc::clone(resource);
132 table.register(nr, Box::new(move |notif, _ctx, _notif_fd| {
133 let resource = Arc::clone(&resource);
134 Box::pin(async move {
135 crate::resource::handle_wait(¬if, &resource).await
136 })
137 }));
138 }
139
140 if policy.has_memory_limit {
144 for &nr in &[
145 libc::SYS_mmap, libc::SYS_munmap, libc::SYS_brk,
146 libc::SYS_mremap, libc::SYS_shmget,
147 ] {
148 let policy = Arc::clone(policy);
149 table.register(nr, Box::new(move |notif, ctx, _notif_fd| {
150 let policy = Arc::clone(&policy);
151 Box::pin(async move {
152 crate::resource::handle_memory(¬if, &ctx, &policy).await
153 })
154 }));
155 }
156 }
157
158 if policy.has_net_allowlist || policy.has_http_acl {
162 for &nr in &[libc::SYS_connect, libc::SYS_sendto, libc::SYS_sendmsg] {
163 table.register(nr, Box::new(|notif, ctx, notif_fd| {
164 Box::pin(async move {
165 crate::network::handle_net(¬if, &ctx, notif_fd).await
166 })
167 }));
168 }
169 }
170
171 if policy.has_random_seed {
175 table.register(libc::SYS_getrandom, Box::new(|notif, ctx, notif_fd| {
176 Box::pin(async move {
177 let mut tr = ctx.time_random.lock().await;
178 if let Some(ref mut rng) = tr.random_state {
179 crate::random::handle_getrandom(¬if, rng, notif_fd)
180 } else {
181 NotifAction::Continue
182 }
183 })
184 }));
185 }
186
187 if policy.has_random_seed {
191 table.register(libc::SYS_openat, Box::new(|notif, ctx, notif_fd| {
192 Box::pin(async move {
193 let mut tr = ctx.time_random.lock().await;
194 if let Some(ref mut rng) = tr.random_state {
195 if let Some(action) = crate::random::handle_random_open(¬if, rng, notif_fd) {
196 return action;
197 }
198 }
199 NotifAction::Continue
200 })
201 }));
202 }
203
204 if policy.has_time_start {
208 let time_offset = policy.time_offset;
209 for &nr in &[
210 libc::SYS_clock_nanosleep as i64,
211 libc::SYS_timerfd_settime as i64,
212 libc::SYS_timer_settime as i64,
213 ] {
214 table.register(nr, Box::new(move |notif, _ctx, notif_fd| {
215 Box::pin(async move {
216 crate::time::handle_timer(¬if, time_offset, notif_fd)
217 })
218 }));
219 }
220 }
221
222 if policy.chroot_root.is_some() {
226 register_chroot_handlers(&mut table, policy);
227 }
228
229 if policy.cow_enabled {
233 register_cow_handlers(&mut table);
234 }
235
236 {
240 let policy = Arc::clone(policy);
241 let resource = Arc::clone(resource);
242 table.register(libc::SYS_openat, Box::new(move |notif, ctx, notif_fd| {
243 let policy = Arc::clone(&policy);
244 let resource = Arc::clone(&resource);
245 let processes = Arc::clone(&ctx.processes);
246 let network = Arc::clone(&ctx.network);
247 Box::pin(async move {
248 crate::procfs::handle_proc_open(¬if, &processes, &resource, &network, &policy, notif_fd).await
249 })
250 }));
251 }
252 let mut getdents_nrs = vec![libc::SYS_getdents64];
253 if let Some(getdents) = arch::SYS_GETDENTS {
254 getdents_nrs.push(getdents);
255 }
256 for nr in getdents_nrs {
257 let policy = Arc::clone(policy);
258 table.register(nr, Box::new(move |notif, ctx, notif_fd| {
259 let policy = Arc::clone(&policy);
260 let processes = Arc::clone(&ctx.processes);
261 Box::pin(async move {
262 crate::procfs::handle_getdents(¬if, &processes, &policy, notif_fd).await
263 })
264 }));
265 }
266
267 if let Some(n) = policy.num_cpus {
271 table.register(libc::SYS_sched_getaffinity, Box::new(move |notif, _ctx, notif_fd| {
272 Box::pin(async move {
273 crate::procfs::handle_sched_getaffinity(¬if, n, notif_fd)
274 })
275 }));
276 }
277
278 if let Some(ref hostname) = policy.hostname {
282 let hostname = hostname.clone();
283 let hostname2 = hostname.clone();
284 table.register(libc::SYS_uname, Box::new(move |notif, _ctx, notif_fd| {
285 let hostname = hostname.clone();
286 Box::pin(async move {
287 crate::procfs::handle_uname(¬if, &hostname, notif_fd)
288 })
289 }));
290 table.register(libc::SYS_openat, Box::new(move |notif, _ctx, notif_fd| {
291 let hostname = hostname2.clone();
292 Box::pin(async move {
293 if let Some(action) = crate::procfs::handle_hostname_open(¬if, &hostname, notif_fd) {
294 action
295 } else {
296 NotifAction::Continue
297 }
298 })
299 }));
300 }
301
302 if let Some(ref etc_hosts) = policy.virtual_etc_hosts {
306 let etc_hosts = etc_hosts.clone();
307 table.register(libc::SYS_openat, Box::new(move |notif, _ctx, notif_fd| {
308 let etc_hosts = etc_hosts.clone();
309 Box::pin(async move {
310 if let Some(action) = crate::procfs::handle_etc_hosts_open(¬if, &etc_hosts, notif_fd) {
311 action
312 } else {
313 NotifAction::Continue
314 }
315 })
316 }));
317 }
318
319 if policy.deterministic_dirs {
323 let mut getdents_nrs = vec![libc::SYS_getdents64];
324 if let Some(getdents) = arch::SYS_GETDENTS {
325 getdents_nrs.push(getdents);
326 }
327 for nr in getdents_nrs {
328 table.register(nr, Box::new(|notif, ctx, notif_fd| {
329 let processes = Arc::clone(&ctx.processes);
330 Box::pin(async move {
331 crate::procfs::handle_sorted_getdents(¬if, &processes, notif_fd).await
332 })
333 }));
334 }
335 }
336
337 {
350 table.register(libc::SYS_socket, Box::new(|notif, ctx, _fd| {
351 let state = Arc::clone(&ctx.netlink);
352 Box::pin(async move {
353 crate::netlink::handlers::handle_socket(¬if, &state).await
354 })
355 }));
356 table.register(libc::SYS_bind, Box::new(|notif, ctx, _fd| {
357 let state = Arc::clone(&ctx.netlink);
358 Box::pin(async move {
359 crate::netlink::handlers::handle_bind(¬if, &state).await
360 })
361 }));
362 table.register(libc::SYS_getsockname, Box::new(|notif, ctx, notif_fd| {
363 let state = Arc::clone(&ctx.netlink);
364 Box::pin(async move {
365 crate::netlink::handlers::handle_getsockname(¬if, &state, notif_fd).await
366 })
367 }));
368 for &nr in &[libc::SYS_recvfrom, libc::SYS_recvmsg] {
372 table.register(nr, Box::new(|notif, ctx, notif_fd| {
373 let state = Arc::clone(&ctx.netlink);
374 Box::pin(async move {
375 crate::netlink::handlers::handle_netlink_recvmsg(¬if, &state, notif_fd).await
376 })
377 }));
378 }
379 table.register(libc::SYS_close, Box::new(|notif, ctx, _fd| {
382 let state = Arc::clone(&ctx.netlink);
383 Box::pin(async move {
384 crate::netlink::handlers::handle_close(¬if, &state).await
385 })
386 }));
387 }
388
389 if policy.port_remap || policy.has_net_allowlist {
393 table.register(libc::SYS_bind, Box::new(|notif, ctx, notif_fd| {
394 Box::pin(async move {
395 crate::port_remap::handle_bind(¬if, &ctx.network, notif_fd).await
396 })
397 }));
398 }
399
400 if policy.port_remap {
404 table.register(libc::SYS_getsockname, Box::new(|notif, ctx, notif_fd| {
405 Box::pin(async move {
406 crate::port_remap::handle_getsockname(¬if, &ctx.network, notif_fd).await
407 })
408 }));
409 }
410
411 table
412}
413
414fn register_chroot_handlers(table: &mut DispatchTable, policy: &Arc<NotifPolicy>) {
419 use crate::chroot::dispatch::ChrootCtx;
420
421 macro_rules! chroot_handler {
424 ($policy:expr, $handler:expr) => {{
425 let policy = Arc::clone($policy);
426 let handler_fn: HandlerFn = Box::new(move |notif, ctx, notif_fd| {
427 let policy = Arc::clone(&policy);
428 Box::pin(async move {
429 let chroot_ctx = ChrootCtx {
430 root: policy.chroot_root.as_ref().unwrap(),
431 readable: &policy.chroot_readable,
432 writable: &policy.chroot_writable,
433 denied: &policy.chroot_denied,
434 mounts: &policy.chroot_mounts,
435 };
436 $handler(¬if, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx).await
437 })
438 });
439 handler_fn
440 }};
441 }
442
443 macro_rules! chroot_handler_fallthrough {
445 ($policy:expr, $handler:expr) => {{
446 let policy = Arc::clone($policy);
447 let handler_fn: HandlerFn = Box::new(move |notif, ctx, notif_fd| {
448 let policy = Arc::clone(&policy);
449 Box::pin(async move {
450 let chroot_ctx = ChrootCtx {
451 root: policy.chroot_root.as_ref().unwrap(),
452 readable: &policy.chroot_readable,
453 writable: &policy.chroot_writable,
454 denied: &policy.chroot_denied,
455 mounts: &policy.chroot_mounts,
456 };
457 $handler(¬if, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx).await
458 })
459 });
460 handler_fn
461 }};
462 }
463
464 table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
466 crate::chroot::dispatch::handle_chroot_open));
467
468 if let Some(open) = arch::SYS_OPEN {
470 table.register(open, chroot_handler_fallthrough!(policy,
471 crate::chroot::dispatch::handle_chroot_legacy_open));
472 }
473
474 for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
476 table.register(nr, chroot_handler!(policy,
477 crate::chroot::dispatch::handle_chroot_exec));
478 }
479
480 for &nr in &[
482 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
483 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
484 libc::SYS_fchownat, libc::SYS_truncate,
485 ] {
486 table.register(nr, chroot_handler!(policy,
487 crate::chroot::dispatch::handle_chroot_write));
488 }
489
490 if let Some(nr) = arch::SYS_UNLINK {
492 table.register(nr, chroot_handler!(policy,
493 crate::chroot::dispatch::handle_chroot_legacy_unlink));
494 }
495 if let Some(nr) = arch::SYS_RMDIR {
496 table.register(nr, chroot_handler!(policy,
497 crate::chroot::dispatch::handle_chroot_legacy_rmdir));
498 }
499 if let Some(nr) = arch::SYS_MKDIR {
500 table.register(nr, chroot_handler!(policy,
501 crate::chroot::dispatch::handle_chroot_legacy_mkdir));
502 }
503 if let Some(nr) = arch::SYS_RENAME {
504 table.register(nr, chroot_handler!(policy,
505 crate::chroot::dispatch::handle_chroot_legacy_rename));
506 }
507 if let Some(nr) = arch::SYS_SYMLINK {
508 table.register(nr, chroot_handler!(policy,
509 crate::chroot::dispatch::handle_chroot_legacy_symlink));
510 }
511 if let Some(nr) = arch::SYS_LINK {
512 table.register(nr, chroot_handler!(policy,
513 crate::chroot::dispatch::handle_chroot_legacy_link));
514 }
515 if let Some(nr) = arch::SYS_CHMOD {
516 table.register(nr, chroot_handler!(policy,
517 crate::chroot::dispatch::handle_chroot_legacy_chmod));
518 }
519
520 if let Some(chown) = arch::SYS_CHOWN {
522 let policy = Arc::clone(policy);
523 table.register(chown, Box::new(move |notif, ctx, notif_fd| {
524 let policy = Arc::clone(&policy);
525 Box::pin(async move {
526 let chroot_ctx = ChrootCtx {
527 root: policy.chroot_root.as_ref().unwrap(),
528 readable: &policy.chroot_readable,
529 writable: &policy.chroot_writable,
530 denied: &policy.chroot_denied,
531 mounts: &policy.chroot_mounts,
532 };
533 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx, false).await
534 })
535 }));
536 }
537
538 if let Some(lchown) = arch::SYS_LCHOWN {
540 let policy = Arc::clone(policy);
541 table.register(lchown, Box::new(move |notif, ctx, notif_fd| {
542 let policy = Arc::clone(&policy);
543 Box::pin(async move {
544 let chroot_ctx = ChrootCtx {
545 root: policy.chroot_root.as_ref().unwrap(),
546 readable: &policy.chroot_readable,
547 writable: &policy.chroot_writable,
548 denied: &policy.chroot_denied,
549 mounts: &policy.chroot_mounts,
550 };
551 crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx, true).await
552 })
553 }));
554 }
555
556 for &nr in &[
558 libc::SYS_newfstatat,
559 libc::SYS_faccessat,
560 crate::chroot::dispatch::SYS_FACCESSAT2,
561 ] {
562 table.register(nr, chroot_handler!(policy,
563 crate::chroot::dispatch::handle_chroot_stat));
564 }
565
566 if let Some(nr) = arch::SYS_STAT {
568 table.register(nr, chroot_handler!(policy,
569 crate::chroot::dispatch::handle_chroot_legacy_stat));
570 }
571 if let Some(nr) = arch::SYS_LSTAT {
572 table.register(nr, chroot_handler!(policy,
573 crate::chroot::dispatch::handle_chroot_legacy_lstat));
574 }
575 if let Some(nr) = arch::SYS_ACCESS {
576 table.register(nr, chroot_handler!(policy,
577 crate::chroot::dispatch::handle_chroot_legacy_access));
578 }
579
580 table.register(libc::SYS_statx, chroot_handler!(policy,
582 crate::chroot::dispatch::handle_chroot_statx));
583
584 table.register(libc::SYS_readlinkat, chroot_handler!(policy,
586 crate::chroot::dispatch::handle_chroot_readlink));
587 if let Some(nr) = arch::SYS_READLINK {
588 table.register(nr, chroot_handler!(policy,
589 crate::chroot::dispatch::handle_chroot_legacy_readlink));
590 }
591
592 let mut getdents_nrs = vec![libc::SYS_getdents64];
594 if let Some(getdents) = arch::SYS_GETDENTS {
595 getdents_nrs.push(getdents);
596 }
597 for nr in getdents_nrs {
598 table.register(nr, chroot_handler!(policy,
599 crate::chroot::dispatch::handle_chroot_getdents));
600 }
601
602 table.register(libc::SYS_chdir as i64, chroot_handler!(policy,
604 crate::chroot::dispatch::handle_chroot_chdir));
605 table.register(libc::SYS_getcwd as i64, chroot_handler!(policy,
606 crate::chroot::dispatch::handle_chroot_getcwd));
607 table.register(libc::SYS_statfs as i64, chroot_handler!(policy,
608 crate::chroot::dispatch::handle_chroot_statfs));
609 table.register(libc::SYS_utimensat as i64, chroot_handler!(policy,
610 crate::chroot::dispatch::handle_chroot_utimensat));
611}
612
613fn register_cow_handlers(table: &mut DispatchTable) {
618 macro_rules! cow_call {
620 ($handler:expr) => {
621 Box::new(|notif, ctx, notif_fd| {
622 let cow = Arc::clone(&ctx.cow);
623 let processes = Arc::clone(&ctx.processes);
624 Box::pin(async move { $handler(¬if, &cow, &processes, notif_fd).await })
625 })
626 };
627 }
628
629 let mut write_nrs = vec![
631 libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
632 libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
633 libc::SYS_fchownat, libc::SYS_truncate,
634 ];
635 write_nrs.extend([
636 arch::SYS_UNLINK, arch::SYS_RMDIR, arch::SYS_MKDIR, arch::SYS_RENAME,
637 arch::SYS_SYMLINK, arch::SYS_LINK, arch::SYS_CHMOD, arch::SYS_CHOWN,
638 arch::SYS_LCHOWN,
639 ].into_iter().flatten());
640 for nr in write_nrs {
641 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_write));
642 }
643
644 table.register(libc::SYS_utimensat, cow_call!(crate::cow::dispatch::handle_cow_utimensat));
645
646 let mut access_nrs = vec![libc::SYS_faccessat, crate::cow::dispatch::SYS_FACCESSAT2];
647 access_nrs.extend(arch::SYS_ACCESS);
648 for nr in access_nrs {
649 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_access));
650 }
651
652 let mut open_nrs = vec![libc::SYS_openat];
653 open_nrs.extend(arch::SYS_OPEN);
654 for nr in open_nrs {
655 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_open));
656 }
657
658 let mut stat_nrs = vec![libc::SYS_newfstatat, libc::SYS_faccessat];
659 stat_nrs.extend([arch::SYS_STAT, arch::SYS_LSTAT, arch::SYS_ACCESS].into_iter().flatten());
660 for nr in stat_nrs {
661 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_stat));
662 }
663
664 table.register(libc::SYS_statx, cow_call!(crate::cow::dispatch::handle_cow_statx));
665
666 let mut readlink_nrs = vec![libc::SYS_readlinkat];
667 readlink_nrs.extend(arch::SYS_READLINK);
668 for nr in readlink_nrs {
669 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_readlink));
670 }
671
672 let mut getdents_nrs = vec![libc::SYS_getdents64];
673 getdents_nrs.extend(arch::SYS_GETDENTS);
674 for nr in getdents_nrs {
675 table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_getdents));
676 }
677
678 table.register(libc::SYS_chdir, cow_call!(crate::cow::dispatch::handle_cow_chdir));
679 table.register(libc::SYS_getcwd, cow_call!(crate::cow::dispatch::handle_cow_getcwd));
680}