Skip to main content

sandlock_core/seccomp/
dispatch.rs

1// Table-driven syscall dispatch — routes seccomp notifications to handler chains.
2//
3// Each syscall number maps to an ordered chain of handlers.  The chain is walked
4// until a handler returns a non-Continue action (or the chain is exhausted, in
5// which case Continue is returned).
6//
7// Continue safety (issue #27):
8//   - The chain walker treats Continue as "this handler did not intervene,
9//     try the next one." A final Continue (no handler intervened, or chain
10//     exhausted) means the syscall passes through to the kernel as-issued.
11//     The kernel still enforces Landlock and the BPF filter on the
12//     untouched syscall, so dispatch-level Continue is not a security
13//     decision — it's the absence of one.
14//   - The conditional shim closures (random/hostname/etc_hosts opens) that
15//     wrap an Option-returning helper translate `None` into Continue,
16//     which is the same "not my path, next handler" semantics. None of
17//     them approve a syscall based on user-memory contents.
18
19use 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
33// ============================================================
34// Types
35// ============================================================
36
37/// An async handler function.  Receives the notification, the supervisor
38/// context, and the notif fd.  Returns a `NotifAction`.
39pub type HandlerFn = Box<
40    dyn Fn(SeccompNotif, Arc<SupervisorCtx>, RawFd) -> Pin<Box<dyn Future<Output = NotifAction> + Send>>
41        + Send
42        + Sync,
43>;
44
45/// Ordered chain of handlers for a single syscall number.
46struct HandlerChain {
47    handlers: Vec<HandlerFn>,
48}
49
50/// Maps syscall numbers to handler chains.
51pub struct DispatchTable {
52    chains: HashMap<i64, HandlerChain>,
53}
54
55impl DispatchTable {
56    /// Create an empty dispatch table.
57    pub fn new() -> Self {
58        Self {
59            chains: HashMap::new(),
60        }
61    }
62
63    /// Register a handler for the given syscall number.  Handlers are called in
64    /// registration order; the first non-Continue result wins.
65    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    /// Dispatch a notification through the handler chain for its syscall number.
76    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
95// ============================================================
96// Table builder — mechanical translation of old dispatch()
97// ============================================================
98
99/// Build the dispatch table from a `NotifPolicy`.  Every branch from the old
100/// monolithic `dispatch()` function is translated into a `table.register()` call.
101/// Priority is preserved by registration order.
102pub fn build_dispatch_table(
103    policy: &Arc<NotifPolicy>,
104    resource: &Arc<Mutex<ResourceState>>,
105) -> DispatchTable {
106    let mut table = DispatchTable::new();
107
108    // ------------------------------------------------------------------
109    // Fork/clone family (always on)
110    // ------------------------------------------------------------------
111    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(&notif, &resource, &policy).await
123            })
124        }));
125    }
126
127    // ------------------------------------------------------------------
128    // Wait family (always on)
129    // ------------------------------------------------------------------
130    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(&notif, &resource).await
136            })
137        }));
138    }
139
140    // ------------------------------------------------------------------
141    // Memory management (conditional on has_memory_limit)
142    // ------------------------------------------------------------------
143    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(&notif, &ctx, &policy).await
153                })
154            }));
155        }
156    }
157
158    // ------------------------------------------------------------------
159    // Network (conditional on has_net_allowlist || has_http_acl)
160    // ------------------------------------------------------------------
161    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(&notif, &ctx, notif_fd).await
166                })
167            }));
168        }
169    }
170
171    // ------------------------------------------------------------------
172    // Deterministic random — getrandom()
173    // ------------------------------------------------------------------
174    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(&notif, rng, notif_fd)
180                } else {
181                    NotifAction::Continue
182                }
183            })
184        }));
185    }
186
187    // ------------------------------------------------------------------
188    // Deterministic random — /dev/urandom opens (openat)
189    // ------------------------------------------------------------------
190    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(&notif, rng, notif_fd) {
196                        return action;
197                    }
198                }
199                NotifAction::Continue
200            })
201        }));
202    }
203
204    // ------------------------------------------------------------------
205    // Timer adjustment (conditional on has_time_start)
206    // ------------------------------------------------------------------
207    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(&notif, time_offset, notif_fd)
217                })
218            }));
219        }
220    }
221
222    // ------------------------------------------------------------------
223    // Chroot path interception (before COW)
224    // ------------------------------------------------------------------
225    if policy.chroot_root.is_some() {
226        register_chroot_handlers(&mut table, policy);
227    }
228
229    // ------------------------------------------------------------------
230    // COW filesystem interception
231    // ------------------------------------------------------------------
232    if policy.cow_enabled {
233        register_cow_handlers(&mut table);
234    }
235
236    // ------------------------------------------------------------------
237    // /proc virtualization (always on)
238    // ------------------------------------------------------------------
239    {
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(&notif, &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(&notif, &processes, &policy, notif_fd).await
263            })
264        }));
265    }
266
267    // ------------------------------------------------------------------
268    // Virtual CPU count
269    // ------------------------------------------------------------------
270    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(&notif, n, notif_fd)
274            })
275        }));
276    }
277
278    // ------------------------------------------------------------------
279    // Hostname virtualization
280    // ------------------------------------------------------------------
281    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(&notif, &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(&notif, &hostname, notif_fd) {
294                    action
295                } else {
296                    NotifAction::Continue
297                }
298            })
299        }));
300    }
301
302    // ------------------------------------------------------------------
303    // /etc/hosts virtualization (for net_allow_hosts)
304    // ------------------------------------------------------------------
305    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(&notif, &etc_hosts, notif_fd) {
311                    action
312                } else {
313                    NotifAction::Continue
314                }
315            })
316        }));
317    }
318
319    // ------------------------------------------------------------------
320    // Deterministic directory listing
321    // ------------------------------------------------------------------
322    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(&notif, &processes, notif_fd).await
332                })
333            }));
334        }
335    }
336
337    // ------------------------------------------------------------------
338    // NETLINK_ROUTE virtualization (always on).
339    //
340    // Send/recv traffic flows through a `socketpair(AF_UNIX,
341    // SOCK_SEQPACKET)` whose supervisor-side end is driven by a tokio
342    // task spawned in `handle_socket`.  Only `socket`, `bind`,
343    // `getsockname`, `recvmsg`/`recvfrom`, and `close` need supervisor
344    // intercepts; send uses the kernel directly.
345    //
346    // Must register before `port_remap` so the netlink `bind` handler
347    // runs first and returns `Continue` for non-cookie fds.
348    // ------------------------------------------------------------------
349    {
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(&notif, &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(&notif, &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(&notif, &state, notif_fd).await
366            })
367        }));
368        // Zero the msg_name region on recv so glibc sees nl_pid=0
369        // (the kernel only writes sun_family on unix socketpair recvmsg,
370        //  leaving the rest of the buffer as stack garbage otherwise).
371        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(&notif, &state, notif_fd).await
376                })
377            }));
378        }
379        // Unregister on close so the (pid, fd) slot isn't left in the
380        // cookie set once the child reuses the fd for something else.
381        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(&notif, &state).await
385            })
386        }));
387    }
388
389    // ------------------------------------------------------------------
390    // Bind — on-behalf
391    // ------------------------------------------------------------------
392    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(&notif, &ctx.network, notif_fd).await
396            })
397        }));
398    }
399
400    // ------------------------------------------------------------------
401    // getsockname — port remap
402    // ------------------------------------------------------------------
403    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(&notif, &ctx.network, notif_fd).await
407            })
408        }));
409    }
410
411    table
412}
413
414// ============================================================
415// Chroot handler registration
416// ============================================================
417
418fn register_chroot_handlers(table: &mut DispatchTable, policy: &Arc<NotifPolicy>) {
419    use crate::chroot::dispatch::ChrootCtx;
420
421    // Helper macro to reduce boilerplate for chroot handlers that unconditionally
422    // return (non-fallthrough).
423    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(&notif, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx).await
437                })
438            });
439            handler_fn
440        }};
441    }
442
443    // Helper for chroot handlers that may fall through (return Continue).
444    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(&notif, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx).await
458                })
459            });
460            handler_fn
461        }};
462    }
463
464    // openat — fallthrough if Continue
465    table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
466        crate::chroot::dispatch::handle_chroot_open));
467
468    // open (legacy) — fallthrough if Continue
469    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    // execve, execveat — unconditional return
475    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    // Modern write syscalls
481    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    // Legacy write syscalls
491    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    // chown — non-follow
521    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(&notif, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx, false).await
534            })
535        }));
536    }
537
538    // lchown — follow
539    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(&notif, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx, true).await
552            })
553        }));
554    }
555
556    // stat family
557    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    // Legacy stat
567    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    // statx
581    table.register(libc::SYS_statx, chroot_handler!(policy,
582        crate::chroot::dispatch::handle_chroot_statx));
583
584    // readlink
585    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    // getdents
593    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    // chdir, getcwd, statfs, utimensat
603    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
613// ============================================================
614// COW handler registration
615// ============================================================
616
617fn register_cow_handlers(table: &mut DispatchTable) {
618    // Helper to grab cow + processes from ctx in one place.
619    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(&notif, &cow, &processes, notif_fd).await })
625            })
626        };
627    }
628
629    // Write syscalls (*at variants + legacy)
630    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}