//
// Syd: rock-solid unikernel
// src/hook.rs: Secure computing hooks
//
// Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
// Based in part upon greenhook which is under public domain.
// MDWE code is based in part upon systemd which is LGPL-2.1-or-later.
// Personality code is based on pacwrap which is GPL-3.0-only.
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{
    env,
    ffi::{CStr, CString, OsStr, OsString},
    fs::{File, OpenOptions},
    io::{self, BufReader, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write},
    mem::MaybeUninit,
    net::IpAddr,
    os::{
        fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd},
        unix::ffi::{OsStrExt, OsStringExt},
    },
    path::Path,
    str::FromStr,
    sync::Arc,
};

use libseccomp::{
    scmp_cmp, ScmpAction, ScmpArch, ScmpArgCompare, ScmpCompareOp, ScmpFilterAttr,
    ScmpFilterContext, ScmpNotifResp, ScmpNotifRespFlags, ScmpSyscall, ScmpVersion,
};
use libseccomp_sys::{seccomp_notif_addfd, __NR_SCMP_ERROR};
use nix::{
    errno::Errno,
    fcntl::{
        fallocate, open, openat2, renameat, AtFlags, FallocateFlags, OFlag, OpenHow, ResolveFlag,
    },
    libc::{ioctl, AT_FDCWD},
    mount::{mount, MsFlags},
    sys::{
        inotify::AddWatchFlags,
        prctl::{set_child_subreaper, set_dumpable},
        ptrace::{cont, getevent},
        resource::{getrlimit, setrlimit, Resource},
        signal::{kill, killpg, Signal},
        socket::{
            bind, connect, getsockname, send, sendmsg, sendto, AddressFamily, ControlMessage,
            MsgFlags, SockFlag, SockaddrLike, SockaddrStorage, UnixCredentials,
        },
        stat::{
            fchmod, fchmodat, futimens, lstat, mkdirat, mknodat, umask, utimensat, FchmodatFlags,
            Mode, SFlag, UtimensatFlags,
        },
        time::TimeSpec,
        uio::{process_vm_readv, process_vm_writev, RemoteIoVec},
        utsname::uname,
        wait::{Id, WaitPidFlag},
    },
    unistd::{
        chdir, chroot, close, dup3, faccessat, fchdir, fchown, fchownat, ftruncate, getpgid,
        getpgrp, getresgid, getresuid, linkat, lseek64, mkstemp, setgid, setuid, symlinkat, unlink,
        unlinkat, write, AccessFlags, Gid, Pid, Uid, UnlinkatFlags, Whence,
    },
    NixPath,
};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use procfs::process::Process;
use quick_cache::sync::GuardResult;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{ser::SerializeMap, Serialize};
use smallvec::{smallvec, SmallVec};

use crate::{
    cache::{addr_cache_new, path_cache_new, AddrCache, PathCache, PathCap},
    caps,
    compat::{
        addr_family, cmsg_len_32, cmsg_space_32, fstatat64, fstatfs64, fstatx, ftruncate64,
        getdents, getsockdomain, msghdr, msghdr32, pipe2_raw, statx, waitid, TimeSpec32,
        TimeSpec64, WaitStatus, PF_ALG, PF_INET, PF_INET6, PF_NETLINK, PF_UNIX, STATX_BASIC_STATS,
        STATX_INO, STATX_MODE, STATX_TYPE,
    },
    config::*,
    elf::{ElfError, ElfFileType, ElfType, ExecutableFile, LinkingType},
    err::{err2no, SydResult},
    error,
    fs::{
        canonicalize, create_memfd, denyxattr, fanotify_mark, fhasposixacl, inotify_add_watch,
        is_executable, is_sidechannel_device, mask_path, parse_fd, path_ends_with_dot,
        path_ends_with_slash, path_is_dot, path_split, path_unsafe, read_link, retry_on_eintr,
        safe_clone, safe_open, safe_open_file, safe_open_how, safe_open_path, seal_memfd,
        MissingHandling, MissingHandling::*, SmallPathBuf, TinyPathBuf, MFD_ALLOW_SEALING,
        MFD_CLOEXEC, MFD_EXEC, MFD_NOEXEC_SEAL,
    },
    info, is_coredump,
    landlock::RulesetStatus,
    log::NO_SYSLOG,
    nsflag_name, op2errno, op2name, pool,
    proc::{
        proc_executables, proc_fd, proc_mem_limit, proc_mmap, proc_mmap_min_addr, proc_pgrp,
        proc_task_limit, proc_tgid, proc_tty, proc_umask,
    },
    safe_drop_cap,
    sandbox::{
        Action, Capability, Flag, IntegrityError, NetlinkFamily, Sandbox, SandboxGuard, SandboxLock,
    },
    scmp_arch_bits, scmp_big_endian, seccomp_add_architectures, seccomp_native_has_socketcall,
    warn,
    wildmatch::litmatch,
    IoctlRequest, ScmpNotifReq, SydArch, Sydcall, NAMESPACE_FLAGS, NAMESPACE_FLAGS_ALL,
    NAMESPACE_NAMES, SCMP_ARCH,
};

const UNIX_PATH_MAX: usize = 108;

const NONE: Option<&SmallPathBuf> = None::<SmallPathBuf>.as_ref();

const PIDFD_THREAD: i32 = OFlag::O_EXCL.bits();

const PROT_EXEC: u64 = nix::libc::PROT_EXEC as u64;
const MAP_ANONYMOUS: u64 = nix::libc::MAP_ANONYMOUS as u64;

const FD_MAX: u64 = i32::MAX as u64;

// TODO: Our version of nix does not have TimeSpec::UTIME_NOW.
const UTIME_NOW: TimeSpec = TimeSpec::new(0, nix::libc::UTIME_NOW as nix::libc::c_long);

/*
 * Personality values obtained from personality.h in the Linux kernel
 *
 * https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/personality.h
 */
const PERSONALITY: u64 = if cfg!(target_pointer_width = "64") {
    0x0000
} else {
    0x0800000
};

/*
 * Seccomp constants
 */

const SECCOMP_IOCTL_NOTIF_ADDFD: IoctlRequest = 0x40182103;
/*
const SECCOMP_IOCTL_NOTIF_ID_VALID: IoctlRequest = 0x40082102;
const SECCOMP_IOCTL_NOTIF_RECV: IoctlRequest = 0xc0502100;
const SECCOMP_IOCTL_NOTIF_SEND: IoctlRequest = 0xc0182101;
*/

const SECCOMP_IOCTL_NOTIF_SET_FLAGS: IoctlRequest = 0x40082104;

/// Flag to set synchronous mode for the seccomp notify fd.
pub const SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP: u32 = 1;

/*
 * Lazy statics
 */

// Define a static (global) Lazy value for the kernel version.
#[allow(clippy::disallowed_methods)]
static KERNEL_VERSION: Lazy<(u32, u32)> = Lazy::new(|| {
    let version = uname().unwrap();
    let version = version.release();
    let mut iter = version.to_str().unwrap().split('.');
    let major = iter.next().unwrap().parse::<u32>().unwrap();
    let minor = iter.next().unwrap().parse::<u32>().unwrap();
    (major, minor)
});

// Check for PIDFD_THREAD support (Linux-6.9 or newer).
static HAS_PIDFD_THREAD: Lazy<bool> = Lazy::new(|| {
    let (major, minor) = *KERNEL_VERSION;
    major > 6 || (major == 6 && minor >= 9)
});

// Check for SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP support (Linux-6.6 or newer).
static HAS_SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP: Lazy<bool> = Lazy::new(|| {
    let (major, minor) = *KERNEL_VERSION;
    major > 6 || (major == 6 && minor >= 6)
});

/// Set seccomp notify fd flags, useful to set synchronous mode.
pub fn seccomp_notify_set_flags(fd: RawFd, flags: u32) -> Result<(), Errno> {
    if *HAS_SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP {
        // SAFETY: We need more control than what nix offers.
        retry_on_eintr(|| Errno::result(unsafe { ioctl(fd, SECCOMP_IOCTL_NOTIF_SET_FLAGS, flags) }))
            .map(drop)
    } else {
        Err(Errno::ENOSYS)
    }
}

/*
 * Macros
 */
macro_rules! syscall_handler {
    ($request:expr, $body:expr) => {{
        let inner = |request: &UNotifyEventRequest| -> Result<ScmpNotifResp, Errno> {
            let (req, proc) = request.prepare();
            $body(req, &proc)
        };

        match inner($request) {
            Ok(result) => result,
            Err(Errno::UnknownErrno) => $request.return_syscall(0),
            Err(error) => $request.fail_syscall(error as i32),
        }
    }};
}

macro_rules! syscall_handler_lock {
    ($request:expr, $body:expr) => {{
        let inner = |request: &UNotifyEventRequest| -> Result<ScmpNotifResp, Errno> {
            let (req, proc, sandbox) = request.prepare_with_sandbox();
            $body(req, &proc, sandbox)
        };

        match inner($request) {
            Ok(result) => result,
            Err(Errno::UnknownErrno) => $request.return_syscall(0),
            Err(error) => $request.fail_syscall(error as i32),
        }
    }};
}

macro_rules! remote_path_n {
    ($remote_process:expr, $request:expr, $n:expr, $event_request:expr) => {
        $remote_process.remote_path($request.data.args[$n], &$event_request)
    };
}

/// Seccomp sandbox profile export modes.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ExportMode {
    /// Berkeley Packet Filter (binary, machine readable)
    BerkeleyPacketFilter,
    /// Pseudo Filter Code (text, human readable)
    PseudoFiltercode,
}

impl FromStr for ExportMode {
    type Err = Errno;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "bpf" => Ok(Self::BerkeleyPacketFilter),
            "pfc" => Ok(Self::PseudoFiltercode),
            _ => Err(Errno::EINVAL),
        }
    }
}

/// `SyscallPathArgument` represents a system call path argument,
/// coupled with a directory file descriptor as necessary.
#[derive(Debug, Default)]
pub(crate) struct SyscallPathArgument {
    /// DirFd index in syscall args, if applicable.
    pub dirfd: Option<usize>,
    /// Path index in syscall args, if applicable.
    pub path: Option<usize>,
    /// Whether if it's ok for the path to be a NULL pointer.
    pub null: bool,
    /// Whether if it's ok for the path to be empty.
    pub empty: bool,
    /// Whether symbolic links should be resolved.
    pub resolve: bool,
    /// Whether dot as final component must return the given `Errno`.
    pub dotlast: Option<Errno>,
    /// Missing mode parameter for canonicalize.
    pub miss: MissingHandling,
    /// True if the syscall cannot act on the FD, e.g. rmdir, unlink.
    pub fs: bool,
    /// True if the syscall cannot act on a O_PATH FD and needs O_RDONLY.
    pub rd: bool,
    /// True if the syscall needs a O_RDONLY dirfd to check POSIX ACLs.
    pub rd_dir: bool,
    /// True if the syscall cannot act on a O_PATH FD and needs O_WRONLY.
    pub wr: bool,
    /// The system call is not going to be emulated (unsafe!).
    pub cont: bool,
}

impl Serialize for SyscallPathArgument {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut map = serializer.serialize_map(Some(7))?;
        map.serialize_entry("dirfd", &self.dirfd)?;
        map.serialize_entry("path", &self.path)?;
        map.serialize_entry("null", &self.null)?;
        map.serialize_entry("empty", &self.empty)?;
        map.serialize_entry("resolve", &self.resolve)?;
        map.serialize_entry("dotlast", &self.dotlast.map(|e| e as i32))?;
        map.serialize_entry("miss", &self.miss)?;
        map.end()
    }
}

impl SyscallPathArgument {
    fn open(flags: OFlag, atfunc: bool) -> Self {
        let (dirfd, path) = if atfunc {
            (Some(0), Some(1))
        } else {
            (None, Some(0))
        };

        // SAFETY: We do not resolve symbolic links if O_CREAT|O_EXCL
        // is specified to support creating files through dangling
        // symbolic links, see the creat_thru_dangling test for more
        // information.
        Self {
            dirfd,
            path,
            empty: flags.contains(OFlag::O_TMPFILE),
            miss: if flags.contains(OFlag::O_CREAT) || flags.contains(OFlag::O_TMPFILE) {
                Normal
            } else {
                Existing
            },
            resolve: !(flags.contains(OFlag::O_NOFOLLOW)
                || flags.contains(OFlag::O_CREAT | OFlag::O_EXCL)),
            ..Default::default()
        }
    }
}

// Represents a path argument.
#[derive(Debug)]
struct PathArg(OwnedFd, SmallPathBuf);

// Represents the root of a UNIX socket address.
// For connect, sendto who follow symlinks this is File.
// For bind, who does not follow symlinks this is Path.
enum AddrRoot {
    File((SmallPathBuf, OwnedFd)),
    Path(SmallPathBuf),
}

/// `UNotifyEventRequest` is the type of parameter that user's function
/// would get.
#[derive(Debug)]
pub struct UNotifyEventRequest {
    request: ScmpNotifReq,
    notify_fd: RawFd,
    cache: Arc<(PathCache, AddrCache)>,
    sandbox: Arc<RwLock<Sandbox>>,
}

impl UNotifyEventRequest {
    pub(crate) fn new(
        request: ScmpNotifReq,
        notify_fd: RawFd,
        cache: Arc<(PathCache, AddrCache)>,
        sandbox: Arc<RwLock<Sandbox>>,
    ) -> Self {
        UNotifyEventRequest {
            request,
            notify_fd,
            cache,
            sandbox,
        }
    }

    /// Check if the given path is append-only (cached).
    fn is_append(&self, sandbox: &SandboxGuard, path: &Path) -> bool {
        match self
            .cache
            .0
            .get_value_or_guard(&PathCap(Capability::CAP_APPEND, path), None)
        {
            GuardResult::Value(result) => result.1,
            GuardResult::Guard(guard) => {
                let result = (Action::Allow, sandbox.is_append(path));
                let _ = guard.insert(result);
                result.1
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unsafe { std::hint::unreachable_unchecked() };
            }
        }
    }

    /// Check if the given path is masked (cached).
    fn is_masked(&self, sandbox: &SandboxGuard, path: &Path) -> bool {
        match self
            .cache
            .0
            .get_value_or_guard(&PathCap(Capability::CAP_MASK, path), None)
        {
            GuardResult::Value(result) => result.1,
            GuardResult::Guard(guard) => {
                let result = (Action::Allow, sandbox.is_masked(path));
                let _ = guard.insert(result);
                result.1
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unsafe { std::hint::unreachable_unchecked() };
            }
        }
    }

    /// Check if the given path is hidden (ie denylisted for stat sandboxing, cached)
    fn is_hidden(&self, sandbox: &SandboxGuard, path: &Path) -> bool {
        if sandbox.enabled(Capability::CAP_STAT) {
            let (action, _) = self.check_path(sandbox, Capability::CAP_STAT, path);
            action.is_denying()
        } else {
            false
        }
    }

    /// Check IPv{4,6} address for access (cached).
    fn check_ip(
        &self,
        sandbox: &SandboxGuard,
        cap: Capability,
        addr: IpAddr,
        port: u16,
    ) -> (Action, bool) {
        match self.cache.1.get_value_or_guard(&(cap, addr, port), None) {
            GuardResult::Value(result) => result,
            GuardResult::Guard(guard) => {
                let result = sandbox.check_ip(cap, addr, port);
                let _ = guard.insert(result);
                result
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unsafe { std::hint::unreachable_unchecked() };
            }
        }
    }

    /// Check UNIX socket for access (cached).
    fn check_unix(&self, sandbox: &SandboxGuard, cap: Capability, path: &Path) -> (Action, bool) {
        match self.cache.0.get_value_or_guard(&PathCap(cap, path), None) {
            GuardResult::Value(result) => result,
            GuardResult::Guard(guard) => {
                let result = sandbox.check_unix(cap, path);
                let _ = guard.insert(result);
                result
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unsafe { std::hint::unreachable_unchecked() };
            }
        }
    }

    /// Check path for access (cached).
    fn check_path(&self, sandbox: &SandboxGuard, cap: Capability, path: &Path) -> (Action, bool) {
        match self.cache.0.get_value_or_guard(&PathCap(cap, path), None) {
            GuardResult::Value(result) => result,
            GuardResult::Guard(guard) => {
                let result = sandbox.check_path(cap, path);
                let _ = guard.insert(result);
                result
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unsafe { std::hint::unreachable_unchecked() };
            }
        }
    }

    /// Send the request pid a signal based on the given action.
    /// If `group_sig` is true, send the signal to the process group as well.
    pub fn kill(&self, action: Action, group_sig: bool) -> Result<(), Errno> {
        let sig = match action {
            Action::Stop => Signal::SIGSTOP,
            Action::Kill => Signal::SIGKILL,
            _ => unreachable!(),
        };
        let req = self.get_request();
        let pid = req.pid();
        if self.is_valid() {
            if group_sig {
                if let Ok(pgid) = getpgid(Some(pid)) {
                    if pgid != getpgrp() {
                        let _ = killpg(pgid, Some(sig));
                    }
                }
            }
            kill(pid, Some(sig))
        } else {
            Err(Errno::ESRCH)
        }
    }

    /// Given a `UNotifyEventRequest` return:
    /// 1. The seccomp request
    /// 2. Corresponding `RemoteProcess` instance.
    pub(crate) fn prepare(&self) -> (&ScmpNotifReq, RemoteProcess) {
        let req = self.get_request();
        let pid = req.pid();
        let proc = RemoteProcess::new(pid);
        (req, proc)
    }

    /// Given a `UNotifyEventRequest` return:
    /// 1. The seccomp request
    /// 2. Corresponding `RemoteProcess` instance.
    /// 3. Corresponding `Sandbox` locked as Read.
    pub(crate) fn prepare_with_sandbox(&self) -> (&ScmpNotifReq, RemoteProcess, SandboxGuard) {
        let req = self.get_request();
        let pid = req.pid();
        let proc = RemoteProcess::new(pid);
        let sandbox = self.get_sandbox();
        (req, proc, sandbox)
    }

    /// Returns the unotify request (`ScmpNotifReq`) of
    /// this event.
    pub(crate) fn get_request(&self) -> &ScmpNotifReq {
        &self.request
    }

    // Returns the internal `Sandbox` object locking it as necessary,
    // and wrapped in a `SandboxGuard`.
    #[inline]
    fn sandbox(&self, slock: SandboxLock) -> SandboxGuard {
        match slock {
            SandboxLock::Read => SandboxGuard::Read(self.sandbox.read()),
            SandboxLock::UpgradableRead => {
                SandboxGuard::UpgradableRead(self.sandbox.upgradable_read())
            }
            SandboxLock::Write => SandboxGuard::Write(self.sandbox.write()),
        }
    }

    /// Returns a clone of the sandbox.
    pub fn clone_sandbox(&self) -> Arc<RwLock<Sandbox>> {
        Arc::clone(&self.sandbox)
    }

    /// Returns a read locked sandbox.
    #[inline]
    pub fn get_sandbox(&self) -> SandboxGuard {
        self.sandbox(SandboxLock::Read)
    }

    /// Returns a read locked sandbox.
    #[inline]
    pub fn get_sandbox_upgradable(&self) -> SandboxGuard {
        self.sandbox(SandboxLock::UpgradableRead)
    }

    /// Returns a write locked sandbox.
    #[inline]
    pub fn get_sandbox_mut(&self) -> SandboxGuard {
        self.sandbox(SandboxLock::Write)
    }

    /// Let the kernel continue the syscall.
    ///
    /// # Safety
    /// CAUTION! This method is unsafe because it may suffer TOCTOU attack.
    /// Please read `seccomp_unotify(2)` "NOTES/Design goals; use of `SECCOMP_USER_NOTIF_FLAG_CONTINUE`"
    /// before using this method.
    pub unsafe fn continue_syscall(&self) -> ScmpNotifResp {
        ScmpNotifResp::new(self.request.id, 0, 0, ScmpNotifRespFlags::CONTINUE.bits())
    }

    /// Returns error to supervised process.
    /// `err` parameter should be a number larger than 0.
    pub fn fail_syscall(&self, err: i32) -> ScmpNotifResp {
        debug_assert!(err > 0);
        #[allow(clippy::arithmetic_side_effects)]
        ScmpNotifResp::new(self.request.id, 0, -err, 0)
    }

    /// Returns value to supervised process.
    pub fn return_syscall(&self, val: i64) -> ScmpNotifResp {
        ScmpNotifResp::new(self.request.id, val, 0, 0)
    }

    /// Check if this event is still valid.
    /// In some cases this is necessary, please check `seccomp_unotify(2)` for more information.
    pub fn is_valid(&self) -> bool {
        // SAFETY: This function is a hot path where we don't want to run
        // notify_supported() on each call.
        // libseccomp::notify_id_valid(self.notify_fd, self.request.id).is_ok()
        unsafe { libseccomp_sys::seccomp_notify_id_valid(self.notify_fd, self.request.id) == 0 }
    }

    /// Add a file descriptor to the supervised process,
    /// and reply to the seccomp request at the same time.
    /// This could help avoid TOCTOU attack in some cases.
    pub fn send_fd(&self, src_fd: RawFd, close_on_exec: bool) -> Result<ScmpNotifResp, Errno> {
        let src_fd = u32::try_from(src_fd).or(Err(Errno::EBADF))?;

        let newfd_flags = if close_on_exec {
            nix::libc::O_CLOEXEC as u32
        } else {
            0
        };
        #[allow(clippy::cast_possible_truncation)]
        let addfd: seccomp_notif_addfd = seccomp_notif_addfd {
            id: self.request.id,
            flags: nix::libc::SECCOMP_ADDFD_FLAG_SEND as u32,
            srcfd: src_fd,
            newfd: 0,
            newfd_flags,
        };

        // SAFETY: The 'ioctl' function is a low-level interface to the
        // kernel, and its safety depends on the correctness of its
        // arguments. Here, we ensure that 'self.notify_fd' is a valid
        // file descriptor and 'addr_of!(addfd)' provides a valid
        // pointer to 'addfd'. The usage of ioctl is considered safe
        // under these conditions, as it does not lead to undefined
        // behavior.
        if unsafe {
            ioctl(
                self.notify_fd,
                SECCOMP_IOCTL_NOTIF_ADDFD,
                std::ptr::addr_of!(addfd),
            )
        } >= 0
        {
            // We do not need to send a response,
            // return a dummy response which will be
            // skipped by the handler.
            Ok(ScmpNotifResp::new(0, 0, 0, 0))
        } else {
            Err(Errno::last())
        }
    }
}

/// By using `RemoteProcess`, you can get some information about the supervised process.
#[derive(Debug)]
pub struct RemoteProcess {
    pid: Pid,
}

impl RemoteProcess {
    /// Create a `RemoteProcess` object from a `Pid`.
    pub fn new(pid: Pid) -> Self {
        Self { pid }
    }

    /// Get file descriptor from remote process with `pidfd_getfd()`.
    /// This function requires Linux 5.6+.
    pub fn get_fd(&self, remote_fd: RawFd, req: &UNotifyEventRequest) -> Result<OwnedFd, Errno> {
        // SAFETY: Check if the RawFd is valid.
        if remote_fd < 0 {
            return Err(Errno::EBADF);
        }

        // Get the Thread Group ID of the given Thread ID.
        // Use PIDFD_THREAD is available.
        let (tgid, flags) = if *HAS_PIDFD_THREAD {
            (self.pid, PIDFD_THREAD)
        } else {
            (proc_tgid(self.pid)?, 0)
        };

        // SAFETY: The call to `libc::syscall` for `SYS_pidfd_getfd` is
        // safe provided the arguments are correct.  Here, `self.fd` is
        // assumed to be a valid file descriptor, and `remote_fd` is a
        // valid RawFd. The third argument, `0`, is a valid flag for
        // the syscall. Assuming these conditions, the syscall does not
        // lead to undefined behavior.
        let pid_fd = match unsafe { libc::syscall(libc::SYS_pidfd_open, tgid, flags) } {
            e if e < 0 => return Err(Errno::last()),
            fd => {
                // SAFETY: pidfd_open returnd success, fd is valid.
                let fd = unsafe { OwnedFd::from_raw_fd(fd as RawFd) };
                if !req.is_valid() {
                    return Err(Errno::ESRCH);
                }
                fd
            }
        };
        // SAFETY: nix does not have a pidfd_getfd wrapper yet.
        let local_fd =
            unsafe { libc::syscall(libc::SYS_pidfd_getfd, pid_fd.as_raw_fd(), remote_fd, 0) };
        if local_fd < 0 {
            Err(Errno::last())
        } else {
            // SAFETY: pidfd_getfd returns a valid FD.
            Ok(unsafe { OwnedFd::from_raw_fd(local_fd as RawFd) })
        }
    }

    /// Read data from remote process's memory with `process_vm_readv()`.
    #[inline(always)]
    pub fn read_mem(
        &self,
        local_buffer: &mut [u8],
        remote_addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<usize, Errno> {
        match self.read_mem_unchecked(local_buffer, remote_addr) {
            Ok(n) => {
                if request.is_valid() {
                    Ok(n)
                } else {
                    Err(Errno::EACCES)
                }
            }
            Err(e) => Err(e),
        }
    }

    /// Read data from remote process's memory with `process_vm_readv()`.
    #[inline(always)]
    pub fn read_mem_unchecked(
        &self,
        local_buffer: &mut [u8],
        remote_addr: u64,
    ) -> Result<usize, Errno> {
        static FORCE_PROC: Lazy<bool> =
            Lazy::new(|| std::env::var(ENV_NO_CROSS_MEMORY_ATTACH).is_ok());
        if *FORCE_PROC {
            return self.read_mem_proc_unchecked(local_buffer, remote_addr);
        }

        if remote_addr == 0 {
            // mmap.min_addr?
            return Err(Errno::EFAULT);
        }
        let len = local_buffer.len();
        match process_vm_readv(
            self.pid,
            &mut [IoSliceMut::new(local_buffer)],
            &[RemoteIoVec {
                len,
                base: usize::try_from(remote_addr).or(Err(Errno::EFAULT))?,
            }],
        ) {
            Ok(n) => Ok(n),
            Err(Errno::ENOSYS) => self.read_mem_proc_unchecked(local_buffer, remote_addr),
            Err(e) => Err(e),
        }
    }

    /// Write data to remote process's memory with `process_vm_writev()`.
    #[inline(always)]
    pub fn write_mem(
        &self,
        local_buffer: &[u8],
        remote_addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<usize, Errno> {
        match self.write_mem_unchecked(local_buffer, remote_addr) {
            Ok(n) => {
                if request.is_valid() {
                    Ok(n)
                } else {
                    Err(Errno::EACCES)
                }
            }
            Err(e) => Err(e),
        }
    }

    /// Write data to remote process's memory with `process_vm_writev()`.
    #[inline(always)]
    pub fn write_mem_unchecked(
        &self,
        local_buffer: &[u8],
        remote_addr: u64,
    ) -> Result<usize, Errno> {
        static FORCE_PROC: Lazy<bool> =
            Lazy::new(|| std::env::var(ENV_NO_CROSS_MEMORY_ATTACH).is_ok());
        if *FORCE_PROC {
            return self.write_mem_proc_unchecked(local_buffer, remote_addr);
        }

        if remote_addr == 0 {
            // mmap.min_addr?
            return Err(Errno::EFAULT);
        }
        let len = local_buffer.len();
        match process_vm_writev(
            self.pid,
            &[IoSlice::new(local_buffer)],
            &[RemoteIoVec {
                len,
                base: usize::try_from(remote_addr).or(Err(Errno::EFAULT))?,
            }],
        ) {
            Ok(n) => Ok(n),
            Err(Errno::ENOSYS) => self.write_mem_proc_unchecked(local_buffer, remote_addr),
            Err(e) => Err(e),
        }
    }

    /// Fallback method to read data from `/proc/$pid/mem` when `process_vm_readv()` is unavailable.
    #[inline(always)]
    pub fn read_mem_proc(
        &self,
        local_buffer: &mut [u8],
        remote_addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<usize, Errno> {
        match self.read_mem_proc_unchecked(local_buffer, remote_addr) {
            Ok(n) => {
                if request.is_valid() {
                    Ok(n)
                } else {
                    Err(Errno::EACCES)
                }
            }
            Err(e) => Err(e),
        }
    }

    /// Fallback method to read data from `/proc/$pid/mem` when `process_vm_readv()` is unavailable.
    #[inline(always)]
    pub fn read_mem_proc_unchecked(
        &self,
        local_buffer: &mut [u8],
        remote_addr: u64,
    ) -> Result<usize, Errno> {
        if remote_addr == 0 {
            return Err(Errno::EFAULT);
        }

        let mut buf = itoa::Buffer::new();
        let mut path = TinyPathBuf::new();
        path.push(Path::new("/proc"));
        path.push(Path::new(buf.format(self.pid.as_raw())));
        path.push(Path::new("mem"));

        let mut file = loop {
            match File::open(&path) {
                Ok(file) => break file,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(_) => return Err(Errno::EACCES),
            }
        };
        file.seek(SeekFrom::Start(remote_addr))
            .or(Err(Errno::EACCES))?;

        let mut nread = 0;
        #[allow(clippy::arithmetic_side_effects)]
        while nread < local_buffer.len() {
            match file.read(&mut local_buffer[nread..]) {
                Ok(0) => return Err(Errno::EACCES),
                Ok(n) => nread += n,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(_) => return Err(Errno::EACCES),
            }
        }

        Ok(nread)
    }

    /// Fallback method to write data to `/proc/$pid/mem` when `process_vm_writev()` is unavailable.
    #[inline(always)]
    pub fn write_mem_proc(
        &self,
        local_buffer: &[u8],
        remote_addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<usize, Errno> {
        match self.write_mem_proc_unchecked(local_buffer, remote_addr) {
            Ok(n) => {
                if request.is_valid() {
                    Ok(n)
                } else {
                    Err(Errno::EACCES)
                }
            }
            Err(e) => Err(e),
        }
    }

    /// Fallback method to write data to `/proc/$pid/mem` when `process_vm_writev()` is unavailable.
    #[inline(always)]
    pub fn write_mem_proc_unchecked(
        &self,
        local_buffer: &[u8],
        remote_addr: u64,
    ) -> Result<usize, Errno> {
        if remote_addr == 0 {
            return Err(Errno::EFAULT);
        }

        let mut buf = itoa::Buffer::new();
        let mut path = TinyPathBuf::new();
        path.push(Path::new("/proc"));
        path.push(Path::new(buf.format(self.pid.as_raw())));
        path.push(Path::new("mem"));

        let mut file = loop {
            match OpenOptions::new().write(true).open(&path) {
                Ok(file) => break file,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(_) => return Err(Errno::EACCES),
            }
        };
        file.seek(SeekFrom::Start(remote_addr))
            .or(Err(Errno::EACCES))?;

        let mut nwritten = 0;
        #[allow(clippy::arithmetic_side_effects)]
        while nwritten < local_buffer.len() {
            match file.write(&local_buffer[nwritten..]) {
                Ok(0) => return Err(Errno::EACCES),
                Ok(n) => nwritten += n,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(_) => return Err(Errno::EACCES),
            }
        }

        Ok(nwritten)
    }

    /// Read path from the given system call argument with the given request.
    /// Check for magic prefix is magic is true.
    #[allow(clippy::cognitive_complexity)]
    #[allow(clippy::too_many_arguments)]
    fn read_path(
        &self,
        request: &UNotifyEventRequest,
        sandbox: &SandboxGuard,
        arg: &SyscallPathArgument,
        mut magic: bool,
        nodotdot: bool,
        kallsyms: bool,
    ) -> Result<(SmallPathBuf, bool), Errno> {
        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let orig = match arg.path {
            Some(idx) => {
                if req.data.args[idx] == 0 {
                    if arg.null {
                        // NULL is ok, use fd
                        None
                    } else {
                        return Err(Errno::EFAULT);
                    }
                } else {
                    Some(remote_path_n!(self, req, idx, request)?)
                }
            }
            None => None,
        };
        let mut doterr = false;

        // magic is both an in and out variable.
        // in=t: check for magic path.
        // out=t: path is magic path.
        let check_magic = magic;
        magic = false;

        let path = if let Some(mut path) = orig {
            if !arg.empty && path.is_empty() {
                return Err(Errno::ENOENT);
            }
            if let Some(errno) = arg.dotlast {
                if path_ends_with_dot(&path) {
                    if errno == Errno::ENOENT {
                        // This will be handled later, as we may
                        // need to return EEXIST instead of ENOENT
                        // if the path exists.
                        doterr = true;
                    } else {
                        return Err(errno);
                    }
                }
            }
            if path.is_absolute() {
                if check_magic {
                    magic = path.is_magic();
                }
                if !magic {
                    path = canonicalize(
                        req.pid(),
                        &path,
                        None,
                        arg.miss,
                        arg.resolve,
                        nodotdot,
                        kallsyms,
                    )?;
                }
            } else if path.is_empty() {
                // SAFETY: The ends_with_dot check above
                // ensures we return ENOTDIR when e.g. path is
                // a dot and the file descriptor argument is a
                // regular file. This happens because in this
                // case, joining the directory with an empty
                // path on the next branch essentially adds a
                // trailing slash to the path, making the
                // system call emulator fail with ENOTDIR if
                // the argument is not a directory. This way,
                // we avoid stat'ing the path here to
                // determine whether it's a directory or not.
                path = self.read_directory(request, arg)?;
            } else if path.as_os_str().as_bytes() == b"." {
                // SAFETY: Add a trailing slash to the directory
                // to assert it must be a directory.
                path = self.read_directory(request, arg)?;
                path.push(Path::new(""));
            } else {
                let cwd = self.read_directory(request, arg)?;
                path = canonicalize(
                    req.pid(),
                    &path,
                    Some(&cwd),
                    arg.miss,
                    arg.resolve,
                    nodotdot,
                    kallsyms,
                )?;
            };
            path
        } else {
            self.read_directory(request, arg)?
        };
        if !magic {
            Self::read_path_post(request, sandbox, &path, doterr, arg.miss)?;
        }
        Ok((path, magic))
    }

    // Post checker for read_path.
    fn read_path_post(
        request: &UNotifyEventRequest,
        sandbox: &SandboxGuard,
        path: &Path,
        doterr: bool,
        miss: MissingHandling,
    ) -> Result<(), Errno> {
        // SAFETY: Deny magic proc symlinks and sensitive proc files.
        let req = request.get_request();
        #[allow(clippy::arithmetic_side_effects)]
        #[allow(clippy::cast_possible_wrap)]
        let _ = proc_fd(Pid::from_raw(-(req.pid as i32)), path)?;

        if !(doterr || miss == MissingHandling::Missing) {
            return Ok(());
        }

        // (a) Delayed dotlast Errno::ENOENT handler, see above for the rationale.
        // (b) SAFETY: the Missing check is skipped by fs::canonicalize on purpose,
        // so that EEXIST return value cannot be abused to locate hidden paths.
        match safe_open_path(path, OFlag::empty()).map(drop) {
            Ok(()) => {
                // Path exists:
                // Return ENOENT if either one of path or parent is hidden.
                // Return EEXIST if not.
                let hidden = if request.is_hidden(sandbox, path) {
                    true
                } else {
                    let (parent, _) = path_split(path);
                    request.is_hidden(sandbox, &parent)
                };
                if hidden {
                    Err(Errno::ENOENT)
                } else {
                    Err(Errno::EEXIST)
                }
            }
            Err(Errno::ENOENT) => {
                // (a) doterr => return ENOENT.
                // (b) Missing => Path is missing, continue.
                if doterr {
                    Err(Errno::ENOENT)
                } else {
                    Ok(())
                }
            }
            Err(errno) => Err(errno),
        }
    }

    /// Read directory from the given system call argument with the given request.
    fn read_directory(
        &self,
        request: &UNotifyEventRequest,
        arg: &SyscallPathArgument,
    ) -> Result<SmallPathBuf, Errno> {
        let req = request.get_request();
        let sym = match arg.dirfd {
            Some(idx) => Self::remote_dirfd(req.pid, Self::remote_fd(req, idx)?),
            None => Self::remote_dirfd(req.pid, None),
        };
        match read_link(&sym) {
            Ok(dir) => {
                if dir.is_relative() || dir.as_os_str().as_bytes().ends_with(b" (deleted)") {
                    // a. /proc/1/cwd -> pipe:42
                    // b. /proc/1/cwd -> /path/to/foo (deleted)
                    // Both cases must result in ENOENT.
                    Err(Errno::ENOENT)
                } else {
                    // genuine directory.
                    Ok(dir)
                }
            }
            Err(Errno::ENOENT) => Err(Errno::EBADF),
            Err(errno) => Err(errno),
        }
    }

    /// Convert the file descriptor argument into a `RawFd`.
    /// If the argument refers to the current working directory,
    /// this function returns `None`.
    pub(crate) fn remote_fd(req: &ScmpNotifReq, idx: usize) -> Result<Option<RawFd>, Errno> {
        #[allow(clippy::cast_possible_truncation)]
        let fd = req.data.args[idx] as RawFd;
        if fd == AT_FDCWD {
            Ok(None)
        } else if fd < 0 {
            Err(Errno::EBADF)
        } else {
            Ok(Some(fd))
        }
    }

    /// Returns the file descriptor or current working directory path for the given `Pid`.
    pub fn remote_dirfd(pid: u32, fd: Option<RawFd>) -> TinyPathBuf {
        let mut buf0 = itoa::Buffer::new();
        let mut path = TinyPathBuf::new();
        path.push(Path::new("/proc"));
        path.push(Path::new(buf0.format(pid)));

        if let Some(fd) = fd {
            let mut buf1 = itoa::Buffer::new();
            path.push(Path::new("fd"));
            path.push(Path::new(buf1.format(fd)));
        } else {
            path.push(Path::new("cwd"));
        }

        path
    }

    /// Read the `nix::libc::open_how` struct from process memory
    /// at the given address and size.
    pub fn remote_ohow(
        &self,
        addr: u64,
        size: u64,
        request: &UNotifyEventRequest,
    ) -> Result<nix::libc::open_how, Errno> {
        if usize::try_from(size).or(Err(Errno::EINVAL))?
            != std::mem::size_of::<nix::libc::open_how>()
        {
            return Err(Errno::EINVAL);
        }

        let mut buf = [0u8; std::mem::size_of::<nix::libc::open_how>()];
        self.read_mem(&mut buf, addr, request)?;

        // SAFETY: The following unsafe block assumes that:
        // 1. The memory layout of open_how in our Rust environment
        //    matches that of the target process.
        // 2. The proc.read_mem call has populated buf with valid data
        //    of the appropriate size (ensured by the size check above).
        // 3. The buffer is appropriately aligned for reading an
        //    open_how struct. If the remote process's representation of
        //    open_how was correctly aligned, our local buffer should be
        //    too, since it's an array on the stack.
        Ok(unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const _) })
    }

    /// Read the `nix::libc::utimbuf` struct from process memory at the given address.
    /// Convert it to a `nix::libc::timespec[2]` for easy interoperability.
    fn remote_utimbuf(
        &self,
        addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<(TimeSpec, TimeSpec), Errno> {
        if addr == 0 {
            // utimbuf pointer is NULL: Set to current time.
            return Ok((UTIME_NOW, UTIME_NOW));
        }

        let mut buf = [0u8; std::mem::size_of::<nix::libc::utimbuf>()];
        self.read_mem(&mut buf, addr, request)?;

        // SAFETY: The following unsafe block assumes that:
        // 1. The memory layout of utimbuf in our Rust environment
        //    matches that of the target process.
        // 2. The proc.read_mem call has populated buf with valid data
        //    of the appropriate size (ensured by the size check above).
        // 3. The buffer is appropriately aligned for reading a utimbuf
        //    struct. If the remote process's representation of utimbuf
        //    was correctly aligned, our local buffer should be too,
        //    since it's an array on the stack.
        let utimbuf: nix::libc::utimbuf =
            unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const _) };

        Ok((
            TimeSpec::new(utimbuf.actime, 0),
            TimeSpec::new(utimbuf.modtime, 0),
        ))
    }

    /// Read the `nix::libc::timeval[2]` struct from process memory at the given address.
    /// Convert it to a `nix::libc::timespec[2]` for easy interoperability.
    fn remote_timeval(
        &self,
        addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<(TimeSpec, TimeSpec), Errno> {
        if addr == 0 {
            // timeval pointer is NULL: Set to current time.
            return Ok((UTIME_NOW, UTIME_NOW));
        }

        let mut buf = [0u8; std::mem::size_of::<nix::libc::timeval>() * 2];
        self.read_mem(&mut buf, addr, request)?;

        // SAFETY: The following unsafe block assumes that:
        // 1. The memory layout of timeval in our Rust environment
        //    matches that of the target process.
        // 2. The proc.read_mem call has populated buf with valid data
        //    of the appropriate size (ensured by the size check above).
        // 3. The buffer is appropriately aligned for reading a timeval
        //    struct. If the remote process's representation of timeval
        //    was correctly aligned, our local buffer should be too,
        //    since it's an array on the stack.
        #[allow(clippy::cast_ptr_alignment)]
        let timevals = unsafe {
            // Create a raw pointer to the buffer.
            let ptr = buf.as_ptr() as *const nix::libc::timeval;

            // Read the timeval values from the buffer.
            [
                std::ptr::read_unaligned(ptr),
                std::ptr::read_unaligned(ptr.add(1)),
            ]
        };

        Ok((
            TimeSpec::new(
                timevals[0].tv_sec,
                (timevals[0].tv_usec as nix::libc::c_long).saturating_mul(1_000), /* ms->ns */
            ),
            TimeSpec::new(
                timevals[1].tv_sec,
                (timevals[1].tv_usec as nix::libc::c_long).saturating_mul(1_000), /* ms->ns */
            ),
        ))
    }

    /// Read the `TimeSpec32[2]` struct from process memory at the given address.
    fn remote_timespec32(
        &self,
        addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<(TimeSpec, TimeSpec), Errno> {
        if addr == 0 {
            // timespec pointer is NULL: Set to current time.
            return Ok((UTIME_NOW, UTIME_NOW));
        }

        let mut buf = [0u8; std::mem::size_of::<TimeSpec32>() * 2];
        self.read_mem(&mut buf, addr, request)?;

        // SAFETY: The following unsafe block assumes that:
        // 1. The memory layout of timespec in our Rust environment
        //    matches that of the target process.
        // 2. The proc.read_mem call has populated buf with valid data
        //    of the appropriate size (ensured by the size check above).
        // 3. The buffer is appropriately aligned for reading a timespec
        //    struct. If the remote process's representation of timespec
        //    was correctly aligned, our local buffer should be too,
        //    since it's an array on the stack.
        #[allow(clippy::cast_ptr_alignment)]
        let timespecs = unsafe {
            // Create a raw pointer to the buffer.
            let ptr = buf.as_ptr() as *const TimeSpec32;

            // Read the timespec values from the buffer.
            [
                std::ptr::read_unaligned(ptr),
                std::ptr::read_unaligned(ptr.add(1)),
            ]
        };

        Ok((
            TimeSpec::new(timespecs[0].tv_sec.into(), timespecs[0].tv_nsec.into()),
            TimeSpec::new(timespecs[1].tv_sec.into(), timespecs[1].tv_nsec.into()),
        ))
    }

    /// Read the `TimeSpec64[2]` struct from process memory at the given address.
    fn remote_timespec64(
        &self,
        addr: u64,
        request: &UNotifyEventRequest,
    ) -> Result<(TimeSpec, TimeSpec), Errno> {
        if addr == 0 {
            // timespec pointer is NULL: Set to current time.
            return Ok((UTIME_NOW, UTIME_NOW));
        }

        let mut buf = [0u8; std::mem::size_of::<TimeSpec64>() * 2];
        self.read_mem(&mut buf, addr, request)?;

        // SAFETY: The following unsafe block assumes that:
        // 1. The memory layout of timespec in our Rust environment
        //    matches that of the target process.
        // 2. The proc.read_mem call has populated buf with valid data
        //    of the appropriate size (ensured by the size check above).
        // 3. The buffer is appropriately aligned for reading a timespec
        //    struct. If the remote process's representation of timespec
        //    was correctly aligned, our local buffer should be too,
        //    since it's an array on the stack.
        #[allow(clippy::cast_ptr_alignment)]
        let timespecs = unsafe {
            // Create a raw pointer to the buffer.
            let ptr = buf.as_ptr() as *const TimeSpec64;

            // Read the timespec values from the buffer.
            [
                std::ptr::read_unaligned(ptr),
                std::ptr::read_unaligned(ptr.add(1)),
            ]
        };

        #[cfg(target_pointer_width = "32")]
        {
            Ok((
                TimeSpec::new(timespecs[0].tv_sec as i32, timespecs[0].tv_nsec as i32),
                TimeSpec::new(timespecs[1].tv_sec as i32, timespecs[1].tv_nsec as i32),
            ))
        }
        #[cfg(target_pointer_width = "64")]
        {
            Ok((
                TimeSpec::new(timespecs[0].tv_sec, timespecs[0].tv_nsec),
                TimeSpec::new(timespecs[1].tv_sec, timespecs[1].tv_nsec),
            ))
        }
    }

    /// Read the path from memory of the process with the given `Pid` with the given address.
    fn remote_path(&self, addr: u64, request: &UNotifyEventRequest) -> Result<SmallPathBuf, Errno> {
        // Most paths are short, read PATH_MIN bytes at a time.
        let chk_size = crate::fs::PATH_MIN;

        let mut off = 0;
        let mut buf = SmallPathBuf::new();
        #[allow(clippy::arithmetic_side_effects)]
        loop {
            // Determine batch size.
            let cap = crate::fs::PATH_MAX - off;
            let siz = cap.min(chk_size);
            let all = off + siz;

            // Ensure buffer has enough space for new data.
            if all > buf.capacity() {
                buf.try_reserve(siz).or(Err(Errno::ENOMEM))?;
            }

            // Temporarily set the length to accommodate reading new data.
            // SAFETY: try_reserve() succeeded which ensures enough space.
            unsafe { buf.set_len(all) };

            // Read from process memory.
            let chk = &mut buf.as_mut_slice()[off..off + siz];
            self.read_mem_unchecked(chk, addr + off as u64)?;

            // Check for NUL-byte.
            if let Some(pos) = memchr::memchr(0, chk) {
                // SAFETY: Check for the validity of the request.
                if !request.is_valid() {
                    return Err(Errno::EACCES);
                }

                // Adjust to actual size up to NUL-byte.
                buf.truncate(off + pos);
                return Ok(buf);
            }

            // Do bounds checking.
            off += siz;
            if off >= crate::fs::PATH_MAX {
                // SAFETY: If buffer has no null byte, return
                // ENAMETOOLONG as the path is too long for us to
                // handle.
                return Err(Errno::ENAMETOOLONG);
            }
        }
    }
}

pub(crate) type UserHookFunc = Box<dyn Fn(&UNotifyEventRequest) -> ScmpNotifResp + Send + Sync>;
pub(crate) type HandlerMap = FxHashMap<Sydcall, Arc<UserHookFunc>>;
type AllowSet = FxHashSet<ScmpSyscall>;

/// supervisor of a syd sandbox.
#[allow(clippy::type_complexity)]
pub struct Supervisor {
    export: Option<ExportMode>,
    handlers: Arc<HandlerMap>,
    sysallow: AllowSet,
    sandbox: Arc<RwLock<Sandbox>>,
}

impl Supervisor {
    /// Create a new `Supervisor` object. You can specify the number of threads in the thread pool.
    /// This function will also check your kernel version and show warning or return error if necessary.
    #[allow(clippy::cognitive_complexity)]
    pub fn new(sandbox: Sandbox, export_mode: Option<ExportMode>) -> SydResult<Self> {
        let (major, minor) = *KERNEL_VERSION;
        if major < 5 {
            error!("ctx": "init", "op": "check_kernel",
                "error": "Your kernel version is too old.");
            return Err(Errno::EINVAL.into());
        } else if major == 5 && minor < 5 {
            error!("ctx": "init", "op": "check_kernel",
                "error": "Your kernel version is too old (Does not support SECCOMP_USER_NOTIF_FLAG_CONTINUE, etc.).");
            return Err(Errno::EINVAL.into());
        } else if major == 5 && minor < 6 {
            error!("ctx": "init", "op": "check_kernel",
                "error": "Your kernel version is too old (Does not support pidfd_getfd() and SECCOMP_IOCTL_NOTIF_ADDFD).");
            return Err(Errno::EINVAL.into());
        } else if major == 5 && minor < 9 {
            error!("ctx": "init", "op": "check_kernel",
                "error": "Your kernel version is too old (Does not support SECCOMP_IOCTL_NOTIF_ADDFD).");
            return Err(Errno::EINVAL.into());
        } else if major == 5 && minor < 19 {
            error!("ctx": "init", "op": "check_kernel",
                "error": "Your kernel version is too old (Does not support SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV).");
            return Err(Errno::EINVAL.into());
        }

        let mut handlers = FxHashMap::default();
        let mut sysallow = FxHashSet::default();
        Self::init(&sandbox, &mut handlers, &mut sysallow)?;

        let sandbox = Arc::new(RwLock::new(sandbox));
        let handlers = Arc::new(handlers);
        let supervisor = Supervisor {
            export: export_mode,
            sysallow,
            handlers: Arc::clone(&handlers),
            sandbox: Arc::clone(&sandbox),
        };

        Ok(supervisor)
    }

    /// Initilizes the supervisor by adding the system call handlers.
    #[allow(clippy::cognitive_complexity)]
    fn init(
        sandbox: &Sandbox,
        handlers: &mut HandlerMap,
        sysallow: &mut AllowSet,
    ) -> SydResult<()> {
        // For performance reasons, we apply pid and memory sandboxing at startup only.
        let has_ioc = sandbox.enabled(Capability::CAP_IOCTL);
        let has_mem = sandbox.enabled(Capability::CAP_MEM);
        let has_pid = sandbox.enabled(Capability::CAP_PID);
        let restrict_bind = !sandbox.allow_safe_bind();
        let restrict_exec = !sandbox.allow_unsafe_exec();
        let restrict_memfd = !sandbox.allow_unsafe_memfd();
        let flags = sandbox.flags;
        let ioctl_denylist = sandbox.get_ioctl_deny();
        let deny_namespaces = sandbox.denied_namespaces();
        let netlink_families = sandbox.netlink_families;

        let mut allow_calls = Vec::with_capacity(8);

        // Insert handler for the dummy system call (sysnum==-1).
        Self::insert_dummy(handlers);

        // Ioctl sandboxing
        if has_ioc {
            Self::insert_handler(handlers, "ioctl", sys_ioctl);
        } else {
            // The denylist will be processed in the parent filter.
            allow_calls.push("ioctl");
        }

        if has_mem {
            // Memory sandboxing
            Self::insert_handler(handlers, "brk", sys_brk);
            Self::insert_handler(handlers, "mremap", sys_mremap);
            Self::insert_handler(handlers, "mmap", sys_mmap);
            Self::insert_handler(handlers, "mmap2", sys_mmap2);
        } else {
            allow_calls.extend(["brk", "mremap"]);

            if restrict_exec {
                // mmap{,2} are checked for Exec too!
                // We handle them specially in setup_seccomp,
                // as we only want to hook into PROT_EXEC and !MAP_ANONYMOUS.
                Self::insert_handler(handlers, "mmap", sys_mmap);
                Self::insert_handler(handlers, "mmap2", sys_mmap2);
            } else {
                allow_calls.extend(["mmap", "mmap2"]);
            }
        }

        if has_pid {
            // PID sandboxing
            // Note, clone3 is hard disallowed with ENOSYS.
            Self::insert_handler(handlers, "fork", sys_fork);
            Self::insert_handler(handlers, "vfork", sys_vfork);
            Self::insert_handler(handlers, "clone", sys_clone);
        } else {
            allow_calls.extend(["fork", "vfork", "clone"]);
        }

        // SafeSetID
        // SAFETY: We do not support diverging FsID from Effective ID.
        // SAFETY: We do not support setgroups (due to pointer deref -> TOCTOU vector)
        // The parent seccomp filter stops setfs*id and setgroups.
        // Parent filter also stops {U,G}ID to privileged user/groups.
        Self::insert_handler(handlers, "setuid", sys_setuid);
        Self::insert_handler(handlers, "setuid32", sys_setuid);
        Self::insert_handler(handlers, "setgid", sys_setgid);
        Self::insert_handler(handlers, "setgid32", sys_setgid);
        Self::insert_handler(handlers, "setreuid", sys_setreuid);
        Self::insert_handler(handlers, "setreuid32", sys_setreuid);
        Self::insert_handler(handlers, "setregid", sys_setregid);
        Self::insert_handler(handlers, "setregid32", sys_setregid);
        Self::insert_handler(handlers, "setresuid", sys_setresuid);
        Self::insert_handler(handlers, "setresuid32", sys_setresuid);
        Self::insert_handler(handlers, "setresgid", sys_setresgid);
        Self::insert_handler(handlers, "setresgid32", sys_setresgid);

        // Sanitize uname(2) to protect against information leaks.
        // This is consistent with denying /proc/version unconditionally.
        Self::insert_handler(handlers, "uname", sys_uname);

        // signal protection
        Self::insert_handler(handlers, "kill", sys_kill);
        Self::insert_handler(handlers, "tkill", sys_tkill);
        Self::insert_handler(handlers, "tgkill", sys_tgkill);
        Self::insert_handler(handlers, "pidfd_open", sys_pidfd_open);

        // network sandboxing
        Self::insert_handler(handlers, "socketcall", sys_socketcall);
        Self::insert_handler(handlers, "socket", sys_socket);
        Self::insert_handler(handlers, "bind", sys_bind);
        Self::insert_handler(handlers, "connect", sys_connect);
        Self::insert_handler(handlers, "sendto", sys_sendto);
        Self::insert_handler(handlers, "sendmsg", sys_sendmsg);
        Self::insert_handler(handlers, "sendmmsg", sys_sendmmsg);

        if restrict_bind {
            Self::insert_handler(handlers, "accept", sys_accept);
            Self::insert_handler(handlers, "accept4", sys_accept4);
        } else {
            allow_calls.extend(["accept", "accept4"]);
        }
        Self::insert_handler(handlers, "getsockname", sys_getsockname);

        // exec sandboxing
        Self::insert_handler(handlers, "execve", sys_execve);
        Self::insert_handler(handlers, "execveat", sys_execveat);

        // stat sandboxing
        // TODO: Hook into chdir, once kernel bug 218501 is fixed:
        // https://bugzilla.kernel.org/show_bug.cgi?id=218501
        Self::insert_handler(handlers, "getdents", sys_getdents);
        Self::insert_handler(handlers, "getdents64", sys_getdents);
        Self::insert_handler(handlers, "stat", sys_stat);
        Self::insert_handler(handlers, "stat64", sys_stat64);
        Self::insert_handler(handlers, "statfs", sys_statfs);
        Self::insert_handler(handlers, "statfs64", sys_statfs64);
        Self::insert_handler(handlers, "statx", sys_statx);
        Self::insert_handler(handlers, "lstat", sys_lstat);
        Self::insert_handler(handlers, "lstat64", sys_lstat64);
        Self::insert_handler(handlers, "fstatat64", sys_newfstatat);
        Self::insert_handler(handlers, "newfstatat", sys_newfstatat);
        Self::insert_handler(handlers, "getxattr", sys_getxattr);
        Self::insert_handler(handlers, "fgetxattr", sys_fgetxattr);
        Self::insert_handler(handlers, "lgetxattr", sys_lgetxattr);
        Self::insert_handler(handlers, "listxattr", sys_listxattr);
        Self::insert_handler(handlers, "flistxattr", sys_flistxattr);
        Self::insert_handler(handlers, "llistxattr", sys_llistxattr);
        Self::insert_handler(handlers, "fanotify_mark", sys_fanotify_mark);
        Self::insert_handler(handlers, "inotify_add_watch", sys_inotify_add_watch);

        // read/write sandboxing
        Self::insert_handler(handlers, "access", sys_access);
        Self::insert_handler(handlers, "faccessat", sys_faccessat);
        Self::insert_handler(handlers, "faccessat2", sys_faccessat2);
        Self::insert_handler(handlers, "chmod", sys_chmod);
        Self::insert_handler(handlers, "fchmod", sys_fchmod);
        Self::insert_handler(handlers, "fchmodat", sys_fchmodat);
        Self::insert_handler(handlers, "fchmodat2", sys_fchmodat2);
        Self::insert_handler(handlers, "chown", sys_chown);
        Self::insert_handler(handlers, "chown32", sys_chown);
        Self::insert_handler(handlers, "fchown", sys_fchown);
        Self::insert_handler(handlers, "fchown32", sys_fchown);
        Self::insert_handler(handlers, "lchown", sys_lchown);
        Self::insert_handler(handlers, "lchown32", sys_lchown);
        Self::insert_handler(handlers, "fchownat", sys_fchownat);
        Self::insert_handler(handlers, "creat", sys_creat);
        Self::insert_handler(handlers, "link", sys_link);
        Self::insert_handler(handlers, "linkat", sys_linkat);
        Self::insert_handler(handlers, "symlink", sys_symlink);
        Self::insert_handler(handlers, "symlinkat", sys_symlinkat);
        Self::insert_handler(handlers, "unlink", sys_unlink);
        Self::insert_handler(handlers, "unlinkat", sys_unlinkat);
        Self::insert_handler(handlers, "mkdir", sys_mkdir);
        Self::insert_handler(handlers, "rmdir", sys_rmdir);
        Self::insert_handler(handlers, "mkdirat", sys_mkdirat);
        Self::insert_handler(handlers, "mknod", sys_mknod);
        Self::insert_handler(handlers, "mknodat", sys_mknodat);
        Self::insert_handler(handlers, "open", sys_open);
        Self::insert_handler(handlers, "openat", sys_openat);
        Self::insert_handler(handlers, "openat2", sys_openat2);
        Self::insert_handler(handlers, "rename", sys_rename);
        Self::insert_handler(handlers, "renameat", sys_renameat);
        Self::insert_handler(handlers, "renameat2", sys_renameat2);
        Self::insert_handler(handlers, "utime", sys_utime);
        Self::insert_handler(handlers, "utimes", sys_utimes);
        Self::insert_handler(handlers, "futimesat", sys_futimesat);
        Self::insert_handler(handlers, "utimensat", sys_utimensat);
        Self::insert_handler(handlers, "utimensat_time64", sys_utimensat64);
        Self::insert_handler(handlers, "truncate", sys_truncate);
        Self::insert_handler(handlers, "truncate64", sys_truncate64);
        Self::insert_handler(handlers, "ftruncate", sys_ftruncate);
        Self::insert_handler(handlers, "ftruncate64", sys_ftruncate64);
        Self::insert_handler(handlers, "fallocate", sys_fallocate);
        Self::insert_handler(handlers, "setxattr", sys_setxattr);
        Self::insert_handler(handlers, "fsetxattr", sys_fsetxattr);
        Self::insert_handler(handlers, "lsetxattr", sys_lsetxattr);
        Self::insert_handler(handlers, "removexattr", sys_removexattr);
        Self::insert_handler(handlers, "fremovexattr", sys_fremovexattr);
        Self::insert_handler(handlers, "lremovexattr", sys_lremovexattr);
        Self::insert_handler(handlers, "getrandom", sys_getrandom);

        // memfds have mode 777 by default,
        // so we check it for all of Read, Write and Exec sandboxing.
        Self::insert_handler(handlers, "memfd_create", sys_memfd_create);
        if !restrict_memfd {
            allow_calls.push("memfd_secret");
        }

        // Allowlist safe system calls.
        for sysname in SAFE_SYSCALLS.iter().chain(&allow_calls) {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => Self::allow_syscall(sysallow, syscall),
                Err(_) => {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // SAFETY: Set Memory-Deny-Write-Execute protection mask.
        // REFUSE_EXEC_GAIN is available since Linux-6.3.
        // NO_INHERIT is available since Linux-6.6.
        // For older systems we also apply a mmap filter in load_seccomp_parent.
        const PR_SET_MDWE: nix::libc::c_int = 65;
        const PR_MDWE_REFUSE_EXEC_GAIN: nix::libc::c_ulong = 1;
        const PR_MDWE_NO_INHERIT: nix::libc::c_ulong = 2;
        // SAFETY: trace/allow_unsafe_debug implies trace/allow_unsafe_{exec,memory,perf,ptrace}
        let restrict_memory =
            !flags.intersects(Flag::FL_ALLOW_UNSAFE_DEBUG | Flag::FL_ALLOW_UNSAFE_MEMORY);
        let mdwe_flags = if restrict_memory {
            // Apply restriction globally.
            PR_MDWE_REFUSE_EXEC_GAIN
        } else {
            // Apply restrictions only for syd process.
            PR_MDWE_REFUSE_EXEC_GAIN | PR_MDWE_NO_INHERIT
        };
        // SAFETY: nix does not have a wrapper for PR_SET_MDWE yet.
        if unsafe { nix::libc::prctl(PR_SET_MDWE, mdwe_flags, 0, 0, 0) } == 0 {
            info!("ctx": "init", "op": "set_mdwe",
                "refuse_exec_gain": true, "inherit": restrict_memory);
        } else {
            // EINVAL: MDWE is not supported (Required Linux 6.3 or newer)
            // EPERM: Permission denied (MDWE already enabled?)
            info!("ctx": "init", "op": "set_mdwe",
                "refuse_exec_gain": false, "inherit": restrict_memory,
                "errno": Errno::last() as i32);
        }

        // SAFETY: Set the no_new_privs attribute.
        if unsafe { nix::libc::prctl(nix::libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } != 0 {
            return Err(Errno::last().into());
        }
        info!("ctx": "init", "op": "set_no_new_privs", "no_new_privs": true);

        // Register as a process subreaper if we're not already pid1.
        // This is important because otherwise processes will be
        // reparented to the actual pid1, after which we can no longer
        // access their /proc/pid/mem without ptrace rights.
        let pid = Pid::this().as_raw();
        if pid != 1 {
            set_child_subreaper(true)?;
            info!("ctx": "init", "op": "set_child_subreaper", "pid1": pid);
        }

        let unsafe_caps = flags.contains(Flag::FL_ALLOW_UNSAFE_CAPS);
        let unsafe_ptrace = flags.contains(Flag::FL_ALLOW_UNSAFE_DEBUG);
        if !unsafe_caps {
            let mut capdrop: Option<SmallVec<[caps::Capability; 32]>> =
                if log::log_enabled!(log::Level::Info) {
                    Some(smallvec![])
                } else {
                    None
                };
            for &cap in CAPS_DROP {
                match cap {
                    caps::Capability::CAP_SYS_PTRACE => {
                        // SAFETY: CAP_SYS_PTRACE is special because
                        // Syd process needs this capability for
                        // pidfd_getfd(2), that's why we drop this
                        // capability late in the child before exec,
                        // see below.
                        continue;
                    }
                    caps::Capability::CAP_SETUID if flags.contains(Flag::FL_ALLOW_SAFE_SETUID) => {
                        continue
                    }
                    caps::Capability::CAP_SETGID if flags.contains(Flag::FL_ALLOW_SAFE_SETGID) => {
                        continue
                    }
                    caps::Capability::CAP_NET_BIND_SERVICE
                        if flags.contains(Flag::FL_ALLOW_UNSAFE_BIND) =>
                    {
                        continue
                    }
                    caps::Capability::CAP_CHOWN if flags.contains(Flag::FL_ALLOW_UNSAFE_CHOWN) => {
                        continue
                    }
                    caps::Capability::CAP_NET_RAW
                        if flags.contains(Flag::FL_ALLOW_UNSAFE_SOCKET) =>
                    {
                        continue
                    }
                    caps::Capability::CAP_SYSLOG
                        if flags.contains(Flag::FL_ALLOW_UNSAFE_SYSLOG) =>
                    {
                        continue
                    }
                    caps::Capability::CAP_SYS_TIME
                        if flags.contains(Flag::FL_ALLOW_UNSAFE_TIME) =>
                    {
                        continue
                    }
                    _ => {}
                }

                // SAFETY: Drop capabilities as early as possible.
                if caps::has_cap(None, caps::CapSet::Effective, cap)? {
                    caps::drop(None, caps::CapSet::Effective, cap)?;
                    if let Some(ref mut capdrop) = capdrop {
                        capdrop.push(cap)
                    }
                }
            }
            let mut capset = caps::read(None, caps::CapSet::Effective).unwrap_or_default();
            // SAFETY: Ensure we cannot gain the capabilities we dropped ever again.
            caps::set(None, caps::CapSet::Permitted, &capset)?;
            // SAFETY: Drop CAP_SYS_PTRACE from Inheritable capabilities.
            if !unsafe_caps && !unsafe_ptrace {
                capset.remove(&caps::Capability::CAP_SYS_PTRACE);
            }
            caps::set(None, caps::CapSet::Inheritable, &capset)?;
            if let Some(capdrop) = capdrop {
                let capdrop = capdrop
                    .into_iter()
                    .map(|cap| cap.to_string())
                    .collect::<Vec<_>>();
                let capset = capset
                    .into_iter()
                    .map(|cap| cap.to_string())
                    .collect::<Vec<_>>();
                info!("ctx": "init", "op": "set_caps", "caps": capset, "drop": capdrop);
            }
        }

        // Apply seccomp hardening for the Syd process itself.
        // This also inherits to the child process, and
        // unshare, mount etc. restrictions happen here.
        Self::load_seccomp_parent(flags, &ioctl_denylist, deny_namespaces, netlink_families)?;

        Ok(())
    }

    /// Insert this system call to the list of allowed system calls.
    /// No filtering is done one these system calls and they're allowed at the kernel level.
    fn allow_syscall(sysallow: &mut AllowSet, syscall: ScmpSyscall) {
        sysallow.insert(syscall);
    }

    /// Insert a system call handler.
    #[allow(clippy::cognitive_complexity)]
    fn insert_handler(
        handlers: &mut HandlerMap,
        syscall_name: &'static str,
        handler: impl Fn(&UNotifyEventRequest) -> ScmpNotifResp + Clone + Send + Sync + 'static,
    ) {
        for arch in SCMP_ARCH {
            if let Ok(sys) = ScmpSyscall::from_name_by_arch(syscall_name, *arch) {
                handlers.insert(Sydcall(sys, *arch), Arc::new(Box::new(handler.clone())));
            } else {
                info!("ctx": "init", "op": "hook_syscall", "error": "scmp_error",
                    "sys": syscall_name, "arch": SydArch(*arch));
            }

            // Support the new non-multiplexed network syscalls on MIPS, PPC, S390 & X86.
            let sys = match *arch {
                ScmpArch::Mips => match syscall_name {
                    "socket" => 183,
                    "bind" => 169,
                    "accept" => 168,
                    "accept4" => 334,
                    "connect" => 170,
                    "getsockname" => 172,
                    "sendto" => 180,
                    "sendmsg" => 179,
                    "sendmmsg" => 343,
                    _ => continue,
                },
                ScmpArch::Ppc | ScmpArch::Ppc64 | ScmpArch::Ppc64Le => match syscall_name {
                    "socket" => 326,
                    "bind" => 327,
                    "accept" => 330,
                    "accept4" => 344,
                    "connect" => 328,
                    "getsockname" => 331,
                    "sendto" => 335,
                    "sendmsg" => 341,
                    "sendmmsg" => 349,
                    _ => continue,
                },
                ScmpArch::S390X | ScmpArch::S390 => match syscall_name {
                    "socket" => 359,
                    "bind" => 361,
                    // no accept on s390x.
                    "accept4" => 364,
                    "connect" => 362,
                    "getsockname" => 367,
                    "sendto" => 369,
                    "sendmsg" => 370,
                    "sendmmsg" => 358,
                    _ => continue,
                },
                ScmpArch::X86 => match syscall_name {
                    "socket" => 359,
                    "bind" => 361,
                    // no accept on x86.
                    "accept4" => 364,
                    "connect" => 362,
                    "getsockname" => 367,
                    "sendto" => 369,
                    "sendmsg" => 370,
                    "sendmmsg" => 345,
                    _ => continue,
                },
                _ => continue,
            };

            handlers.insert(
                Sydcall(ScmpSyscall::from(sys), *arch),
                Arc::new(Box::new(handler.clone())),
            );
        }
    }

    /// Insert a dummy system call handler.
    fn insert_dummy(handlers: &mut HandlerMap) {
        for arch in SCMP_ARCH {
            handlers.insert(
                Sydcall(ScmpSyscall::from(-1), *arch),
                Arc::new(Box::new(sys_unknown)),
            );
        }
    }

    /// Run a command with seccomp filter.
    /// This method will fork a child process, do some preparations and run the command in it.
    #[allow(clippy::cognitive_complexity)]
    #[allow(clippy::type_complexity)]
    fn spawn(
        self,
        mut command: crate::unshare::Command,
    ) -> SydResult<(Arc<(PathCache, AddrCache)>, Arc<RwLock<Sandbox>>)> {
        let seccomp_filter = self.setup_seccomp()?;
        command.seccomp_filter(seccomp_filter);

        // Spawn child under sandbox.
        let child = command.spawn()?;
        let pid = child.id();
        let fd = child.seccomp_fd;

        // Set synchronous mode if supported,
        // so each syscall handler thread wakes up
        // on the same CPU as the respective sandbox process.
        // Note: In our measurements this increases the lock-contention
        // on the kernel-side and hurts performance by roughly 2%.
        /*
        match seccomp_notify_set_flags(fd, SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP) {
            Ok(_) => info!("ctx": "init", "op": "seccomp_set_sync", "fd": fd),
            Err(Errno::ENOSYS) => info!("ctx": "init", "op": "seccomp_set_sync",
                "fd": fd, "errno": Errno::ENOSYS as i32),
            Err(errno) => error!("ctx": "init", "op": "seccomp_set_sync",
                "fd": fd, "errno": errno as i32),
        };
        */

        // SAFETY: Close standard input and output, they're no longer
        // necessary. We keep standard error open for logging.
        close(nix::libc::STDIN_FILENO)?;
        let stdin = safe_open(Path::new("/dev/null"), OFlag::O_RDWR)?;
        std::mem::forget(stdin); // Keep a reference to standard input.
        if env::var("SYD_KEEP_STDOUT").is_err() {
            // SYD_KEEP_STDOUT is used by -Epfc.
            close(nix::libc::STDOUT_FILENO)?;
            dup3(
                nix::libc::STDIN_FILENO,
                nix::libc::STDOUT_FILENO,
                OFlag::O_CLOEXEC,
            )?;
        }

        #[allow(clippy::cast_possible_wrap)]
        let pid = Pid::from_raw(pid as i32);
        let mut sandbox = self.sandbox.write();
        sandbox.set_child(pid, child.pid_fd);
        let locked = sandbox.locked();
        let safe_setid = sandbox.allow_safe_setuid() || sandbox.allow_safe_setgid();
        let cache_path_cap = (sandbox.cache_path_icap, sandbox.cache_path_wcap);
        let cache_addr_cap = (sandbox.cache_addr_icap, sandbox.cache_addr_wcap);
        let restrict_dumps = !sandbox.allow_unsafe_dumpable();
        drop(sandbox);

        // SAFETY: Set Syd process dumpable attribute to false,
        // unless trace/allow_unsafe_dumpable:1 was passed at startup.
        // We do this after spawning the child but before spawning the
        // system call handler threads to ensure the sandbox process
        // does not inherit the attribute but cannot attach to Syd.
        if restrict_dumps {
            set_dumpable(false)?;
        }

        // SAFETY: At this point Syd has successfully forked a new
        // process to execute the sandbox process. As such Syd no longer
        // needs the execve, and execveat system calls. Let's
        // disable these critical system calls here to ensure a
        // compromised Syd process cannot abuse them.
        // EXCEPTION: Sandbox is not locked and we need exec for cmd/exec.
        let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow)?;
        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;
        // Synchronize filter to all threads.
        ctx.set_ctl_tsync(true)?;
        // We kill for bad system call and bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;
        // SAFETY: Do NOT add supported architectures to the filter.
        // This ensures Syd can never run a non-native system call,
        // which we do not need at all.
        // seccomp_add_architectures(&mut ctx).map_err(err2no)?;

        // SAFETY: Mitigate ret2mprotect for a compromised Syd process.
        // Be swift and kill process as this attempt is most certainly
        // malicious and the kill action cannot be misused to DOS the
        // Syd process.
        const X: u64 = nix::libc::PROT_EXEC as u64;
        for sysname in ["mprotect", "pkey_mprotect"] {
            #[allow(clippy::disallowed_methods)]
            let syscall = ScmpSyscall::from_name(sysname).unwrap();
            ctx.add_rule_conditional(
                ScmpAction::KillProcess,
                syscall,
                &[scmp_cmp!($arg2 & X == X)],
            )?;
        }

        // SAFETY: Mitigate kernel heap spraying attacks for a
        // compromised Syd process. Be swift and kill the process as
        // this attempt it most certainly malicious and the kill action
        // cannot be misused to DOS the Syd process.
        match ScmpSyscall::from_name("msgsnd") {
            Ok(syscall) => ctx.add_rule(ScmpAction::KillProcess, syscall)?,
            Err(_) => {
                info!("ctx": "spawn", "op": "filter_syscall",
                    "error": "scmp_error", "sys": "msgsnd");
            }
        }

        if locked {
            // SAFETY: Deny exec(3) calls if locked.
            for sysname in ["execve", "execveat"] {
                // SAFETY: Be swift and kill process as this attempt is most
                // certainly malicious and the kill action cannot be misused
                // to DOS the Syd process.
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule(ScmpAction::KillProcess, syscall)?;
            }

            // SAFETY: Mitigate SROP for a compromised Syd if locked.
            // Since we don't use signal handlers, we have no legit use of _sigreturn_(2).
            // See URL: https://llsoftsec.github.io/llsoftsecbook/#sigreturn-oriented-programming
            // SAFETY: We cannot deny _sigreturn_(2) with SafeSetID!
            // Quoting nptl(7):
            // NPTL makes internal use of the first two real-time
            // signals (signal numbers 32 and 33). One of these signals
            // is used to support thread cancelation and POSIX timers
            // (see timer_create(2)); the other is used as part of a
            // mechanism that ensures all threads in a process always
            // have the same UIDs and GIDs, as required by POSIX.
            // These signals cannot be used in applications.
            if !safe_setid {
                for sysname in ["sigreturn", "rt_sigreturn"] {
                    // SAFETY: Be swift and kill process as this attempt is most
                    // certainly malicious and the kill action cannot be misused
                    // to DOS the Syd process.
                    #[allow(clippy::disallowed_methods)]
                    let syscall = ScmpSyscall::from_name(sysname).unwrap();
                    ctx.add_rule(ScmpAction::KillProcess, syscall)?;
                }
            }
        }

        // SAFETY: After this point we no longer need the PTRACE_SEIZE
        // operation. Since this is a powerful operation, we apply a
        // quick seccomp filter to disable it from ever happening again.
        // This way a compromised Syd process cannot attach to any other
        // process in the system.
        // SAFETY: PTRACE_ATTACH is most certainly malicious,
        // add to kill set.
        // SAFETY: We add these ptrace rules without checking the state
        // of allow_unsafe_exec, because if allow_unsafe_exec is off we
        // have already performed the initial PTRACE_SEIZE, and if
        // allow_unsafe_exec is on we never need PTRACE_SEIZE to begin
        // with.
        #[allow(clippy::disallowed_methods)]
        let syscall = ScmpSyscall::from_name("ptrace").unwrap();
        #[allow(clippy::cast_sign_loss)]
        for op in [nix::libc::PTRACE_ATTACH, nix::libc::PTRACE_SEIZE] {
            ctx.add_rule_conditional(
                ScmpAction::KillProcess,
                syscall,
                &[scmp_cmp!($arg0 == op as u64)],
            )?;
        }

        // Export seccomp rules if requested.
        if env::var("SYD_SECX").is_ok() {
            println!("# Syd process rules");
            ctx.export_pfc(&mut io::stdout())?;
        }

        // Load the seccomp filter.
        ctx.load()?;

        self.supervise(fd, safe_setid, cache_path_cap, cache_addr_cap)
    }

    // Set up seccomp for the sandbox process.
    #[allow(clippy::cognitive_complexity)]
    fn setup_seccomp(&self) -> SydResult<ScmpFilterContext> {
        let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Errno(nix::libc::ENOSYS))?;
        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;
        // Synchronize filter to all threads.
        ctx.set_ctl_tsync(true)?;
        // We deny with ENOSYS for bad/unsupported system call, and kill process for bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;
        // We don't want ECANCELED, we want actual errnos.
        let _ = ctx.set_api_sysrawrc(true);
        // Use a binary tree sorted by syscall number.
        let _ = ctx.set_ctl_optimize(2);

        // Allow -1 syscall in filters.
        let _ = ctx.set_filter_attr(ScmpFilterAttr::ApiTskip, 1);
        // Add a notify handler to the -1 syscall, ignoring errors.
        let _ = ctx.add_rule(ScmpAction::Notify, ScmpSyscall::from(-1));

        seccomp_add_architectures(&mut ctx)?;

        // Acquire the read lock to sandbox configuration.
        let sandbox = self.sandbox.read();
        let deny_tsc = sandbox.deny_tsc();
        let fake_root = sandbox.fake_root();
        let has_mem = sandbox.enabled(Capability::CAP_MEM);
        let restrict_cpu = !sandbox.allow_unsafe_cpu();
        let restrict_exec = !sandbox.allow_unsafe_exec();
        let restrict_keyring = !sandbox.allow_unsafe_keyring();
        let restrict_iouring = !sandbox.allow_unsafe_iouring();
        let restrict_msgsnd = !sandbox.allow_unsafe_msgsnd();
        let restrict_perf = !sandbox.allow_unsafe_perf();
        let restrict_pkey = !sandbox.allow_unsafe_pkey();
        let restrict_prctl = !sandbox.allow_unsafe_prctl();
        let restrict_prlimit = !sandbox.allow_unsafe_prlimit();
        let restrict_ptrace = !sandbox.allow_unsafe_ptrace();
        let restrict_time = !sandbox.allow_unsafe_time();
        drop(sandbox); // release the read lock.

        // Fakeroot
        let id_action = if fake_root {
            ScmpAction::Errno(0)
        } else {
            ScmpAction::Allow
        };
        for sysname in GET_ID_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(id_action, syscall)?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Add notify rules for system calls with handlers.
        // Collect names into a FxHashSet to ensure uniqueness across architectures.
        let mut sydset: FxHashSet<String> = FxHashSet::default();
        for sydcall in self.handlers.keys() {
            if i32::from(sydcall.0) == __NR_SCMP_ERROR {
                // Unsupported system call for the given architecture,
                // move on.
                continue;
            }

            let name = sydcall.to_string();
            if matches!(name.as_str(), "?" | "mmap" | "mmap2" | "sendto") {
                // ? -> unsupported, see Sydcall::Display.
                // We handle mmap{,2}, and sendto specially below.
                continue;
            }

            let syscall = if sydset.insert(name.clone()) {
                if let Ok(syscall) = ScmpSyscall::from_name(&name) {
                    syscall
                } else {
                    info!("ctx": "init", "op": "hook_syscall",
                        "error": "scmp_error", "sys": name);
                    continue;
                }
            } else {
                continue;
            };

            ctx.add_rule(ScmpAction::Notify, syscall)?;
        }

        // Add allow rules for system calls in the default allow list.
        let syscall_allow: Vec<_> = self.sysallow.iter().copied().collect();
        for syscall in &syscall_allow {
            ctx.add_rule(ScmpAction::Allow, *syscall)?;
        }

        // Skip hooking into sendto syscalls which are called on
        // connection-mode sockets. This type of sendto calls have
        // 4th and 5th arguments set to 0.
        if let Ok(syscall) = ScmpSyscall::from_name("sendto") {
            // NULL address with zero length: Connection-mode socket.
            ctx.add_rule_conditional(
                ScmpAction::Allow,
                syscall,
                &[scmp_cmp!($arg4 == 0), scmp_cmp!($arg5 == 0)],
            )?;
            // Non-NULL address with positive length = Notify.
            ctx.add_rule_conditional(
                ScmpAction::Notify,
                syscall,
                &[scmp_cmp!($arg4 != 0), scmp_cmp!($arg5 != 0)],
            )?;
            // NULL address with positive length = EFAULT.
            ctx.add_rule_conditional(
                ScmpAction::Errno(Errno::EFAULT as i32),
                syscall,
                &[scmp_cmp!($arg4 == 0), scmp_cmp!($arg5 != 0)],
            )?;
            // Non-NULL address with zero length = EINVAL.
            ctx.add_rule_conditional(
                ScmpAction::Errno(Errno::EINVAL as i32),
                syscall,
                &[scmp_cmp!($arg4 != 0), scmp_cmp!($arg5 == 0)],
            )?;
        }

        // Only hook into mmap{,2} calls with PROT_EXEC|!MAP_ANONYMOUS,
        // if Memory sandboxing is off and trace/allow_unsafe_exec:0.
        let syscalls = ["mmap", "mmap2"];
        if has_mem {
            for sysname in syscalls {
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule(ScmpAction::Notify, syscall)?;
            }
        } else if restrict_exec {
            for sysname in syscalls {
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule_conditional(
                    ScmpAction::Notify,
                    syscall,
                    &[
                        scmp_cmp!($arg2 & PROT_EXEC == PROT_EXEC),
                        scmp_cmp!($arg3 & MAP_ANONYMOUS == 0),
                        scmp_cmp!($arg4 <= FD_MAX),
                    ],
                )?;
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[scmp_cmp!($arg2 & PROT_EXEC == 0)],
                )?;
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[scmp_cmp!($arg3 & MAP_ANONYMOUS == MAP_ANONYMOUS)],
                )?;
                ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg4 > FD_MAX)])?;
            }
        } else {
            // Allow mmap & mmap2.
            // This was handled already in init,
            // so we don't have to repeat here.
        }

        // Restriction 0: Turn compiled-in list of noop syscalls into no-ops (see config.rs)
        for sysname in NOOP_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Errno(0), syscall)?;
            } else {
                info!("ctx": "init", "op": "noop_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 1: Deny unsafe set-id system calls.
        // Deny with Errno=0 -> Turn the system calls into no-op.
        // This is for compatibility, e.g. postgres invokes
        // setgroups before setuid and aborts on failure.
        for sysname in UNSAFE_ID_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Errno(0), syscall)?;
            } else {
                info!("ctx": "init", "op": "noop_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 2: Allowlist known-safe prctls.
        // Things like PR_SET_MM, PR_SET_PTRACER, and PR_SET_SPECULATION_CTRL are left out.
        // PR_SET_TSC is out if deny-tsc is set and allow_unsafe_prctl is unset.
        #[allow(clippy::disallowed_methods)]
        let sys_prctl = ScmpSyscall::from_name("prctl").unwrap();
        if restrict_prctl {
            for (name, opt) in ALLOWLIST_PRCTL {
                if deny_tsc && *name == "PR_SET_TSC" {
                    continue;
                }
                let cmp = ScmpArgCompare::new(0, ScmpCompareOp::Equal, *opt);
                ctx.add_rule_conditional(ScmpAction::Allow, sys_prctl, &[cmp])?;
            }
        } else {
            ctx.add_rule(ScmpAction::Allow, sys_prctl)?;
        }

        // Restriction 3: Disallow prlimit from setting resources.
        #[allow(clippy::disallowed_methods)]
        let sys_prlimit = ScmpSyscall::from_name("prlimit64").unwrap();
        if restrict_prlimit {
            // prlimit(pid_t pid, int resource,
            //         const struct rlimit *_Nullable new_limit,
            //         struct rlimit *_Nullable old_limit);
            // SAFETY: new_limit==NULL is safe.
            ctx.add_rule_conditional(
                ScmpAction::Allow,
                sys_prlimit,
                &[ScmpArgCompare::new(2, ScmpCompareOp::Equal, 0)],
            )?;
        } else {
            #[allow(clippy::disallowed_methods)]
            let sys_setrlimit = ScmpSyscall::from_name("setrlimit").unwrap();
            ctx.add_rule(ScmpAction::Allow, sys_prlimit)?;
            ctx.add_rule(ScmpAction::Allow, sys_setrlimit)?;
        }

        // Restriction 4: Disallow CPU emulation functionality.
        if !restrict_cpu {
            for sysname in CPU_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                } else {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 5: Disallow Kernel keyring access.
        if !restrict_keyring {
            for sysname in KEYRING_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                } else {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 6: Disallow adjusting system time.
        if !restrict_time {
            for sysname in TIME_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                } else {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 7: Disallow io_uring interface.
        if !restrict_iouring {
            for sysname in IOURING_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                } else {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 8: Disallow msgsnd system call.
        if !restrict_msgsnd {
            if let Ok(syscall) = ScmpSyscall::from_name("msgsnd") {
                ctx.add_rule(ScmpAction::Allow, syscall)?;
            } else {
                info!("ctx": "init", "op": "allow_syscall",
                    "error": "scmp_error", "sys": "msgsnd");
            }
        }

        // Restriction 9: Provide stealth for PTRACE_TRACEME operation.
        // This ptrace operation is the single one that is allowed in
        // the tracee and therefore is quite often used to detect the
        // existence of a ptracer. Here we provide a best-effort
        // mitigation against this and turn PTRACE_TRACEME into a no-op
        // that always succeeds. This way a naive approach is going to
        // fail to detect a ptracer.
        // As of version 3.19.0, we turn all ptrace operations into
        // no-ops so as to provide a best-effort mitigation against
        // using requests such as PTRACE_ATTACH or PTRACE_SEIZE to
        // detect a ptracer.
        // As of version 3.25.2, we log ptrace(2) calls in case we're
        // allowing them to help with malware analysis.
        let action = if restrict_ptrace {
            ScmpAction::Errno(0)
        } else {
            ScmpAction::Allow
        };
        for sysname in PTRACE_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(action, syscall)?;
            } else {
                info!("ctx": "init", "op": "log_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 10: Disallow perf.
        if !restrict_perf {
            for sysname in PERF_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                } else {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 11: Disallow memory protection keys.
        if !restrict_pkey {
            for sysname in PKEY_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                } else {
                    info!("ctx": "init", "op": "allow_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Export seccomp rules if requested.
        if let Some(mode) = self.export {
            self.seccomp_export(&ctx, mode)?;
        }

        Ok(ctx)
    }

    /// Set up seccomp for the Syd process which will be inherited by
    /// the child. this is important to restrict potential attack space
    /// in case Syd process is compromised somehow.
    #[allow(clippy::cognitive_complexity)]
    fn load_seccomp_parent(
        flags: Flag,
        ioctl_denylist: &[u64],
        deny_namespaces: nix::libc::c_int,
        allow_netlink_families: NetlinkFamily,
    ) -> SydResult<()> {
        let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow)?;
        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;
        // Synchronize filter to all threads.
        ctx.set_ctl_tsync(true)?;
        // We deny with ENOSYS for bad/unsupported system call, and kill process for bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;
        // We don't want ECANCELED, we want actual errnos.
        let _ = ctx.set_api_sysrawrc(true);
        // Use a binary tree sorted by syscall number.
        let _ = ctx.set_ctl_optimize(2);
        // Add supported architectures.
        seccomp_add_architectures(&mut ctx)?;

        // Determine restrictions based on sandbox flags.
        let restrict_kcapi = !flags.contains(Flag::FL_ALLOW_SAFE_KCAPI);
        let restrict_personality = flags.contains(Flag::FL_LOCK_PERSONALITY);
        let restrict_socket = !flags.contains(Flag::FL_ALLOW_UNSUPP_SOCKET);

        let restrict_memory =
            !flags.intersects(Flag::FL_ALLOW_UNSAFE_DEBUG | Flag::FL_ALLOW_UNSAFE_MEMORY);
        let restrict_mount = !flags.contains(Flag::FL_ALLOW_UNSAFE_UNSHARE_MOUNT);
        let restrict_perf = !flags.contains(Flag::FL_ALLOW_UNSAFE_DEBUG);
        let restrict_ptrace =
            !flags.intersects(Flag::FL_ALLOW_UNSAFE_DEBUG | Flag::FL_ALLOW_UNSAFE_EXEC);
        let restrict_exec = restrict_ptrace;

        // Restriction -2:
        // (a) Prevent execve where arg0==NULL||arg1==NULL||arg2==NULL
        // (b) Prevent execveat where arg1==NULL||arg2==NULL||arg3==NULL
        // On Linux, argv and envp can be specified as NULL. In
        // both cases, this has the same effect as specifying the
        // argument as a pointer to a list containing a single null
        // pointer. Do not take advantage of this nonstandard and
        // nonportable misfeature! On many other UNIX systems,
        // specifying argv as NULL will result in an error (EFAULT).
        // Some other UNIX systems treat the envp==NULL case the same as
        // Linux.
        // SAFETY: We kill the process rather than deny with EFAULT
        // because this call is most certainly malicious and this gives
        // the system administrator a notification via dmesg(1) about
        // the potentially malicious activity. Should the need arise,
        // we can disable this mitigation as part of
        // trace/allow_unsafe_exec:1.
        #[allow(clippy::disallowed_methods)]
        let sys_execve = ScmpSyscall::from_name("execve").unwrap();
        #[allow(clippy::disallowed_methods)]
        let sys_execveat = ScmpSyscall::from_name("execveat").unwrap();
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            sys_execve,
            &[scmp_cmp!($arg0 == 0)],
        )?;
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            sys_execve,
            &[scmp_cmp!($arg1 == 0)],
        )?;
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            sys_execve,
            &[scmp_cmp!($arg2 == 0)],
        )?;
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            sys_execveat,
            &[scmp_cmp!($arg1 == 0)],
        )?;
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            sys_execveat,
            &[scmp_cmp!($arg2 == 0)],
        )?;
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            sys_execveat,
            &[scmp_cmp!($arg3 == 0)],
        )?;

        // Restriction -1: Prevent mmap(addr<${mmap_min_addr}, MAP_FIXED).
        // Arguably this does not give us much however ensuring mmap_min_addr
        // is constant after the start of the sandbox with zero-cost can't be bad.
        // In addition we kill the process directly rather than denying the call
        // like mmap_min_addr does, thereby giving the system administrator higher
        // chance to notice potentially malicious activity.
        if restrict_memory {
            const MAP_FIXED: u64 = nix::libc::MAP_FIXED as u64;
            const MAP_FIXED_NOREPLACE: u64 = nix::libc::MAP_FIXED_NOREPLACE as u64;
            let mmap_min_addr = proc_mmap_min_addr().unwrap_or(4096);
            for sysname in ["mmap", "mmap2"] {
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule_conditional(
                    ScmpAction::KillProcess,
                    syscall,
                    &[
                        scmp_cmp!($arg0 < mmap_min_addr),
                        scmp_cmp!($arg3 & MAP_FIXED == MAP_FIXED),
                    ],
                )?;
                ctx.add_rule_conditional(
                    ScmpAction::KillProcess,
                    syscall,
                    &[
                        scmp_cmp!($arg0 < mmap_min_addr),
                        scmp_cmp!($arg3 & MAP_FIXED_NOREPLACE == MAP_FIXED_NOREPLACE),
                    ],
                )?;
            }
        }

        // Restriction 0: Prohibit attempts to create memory mappings
        // that are writable and executable at the same time, or to
        // change existing memory mappings to become executable, or
        // mapping shared memory segments as executable.
        if restrict_memory {
            const W: u64 = nix::libc::PROT_WRITE as u64;
            const X: u64 = nix::libc::PROT_EXEC as u64;
            const WX: u64 = W | X;
            const SHM_X: u64 = nix::libc::SHM_EXEC as u64;
            const MAP_A: u64 = nix::libc::MAP_ANONYMOUS as u64;
            const MAP_S: u64 = nix::libc::MAP_SHARED as u64;
            for sysname in ["mmap", "mmap2"] {
                // Prevent writable and executable memory.
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule_conditional(
                    ScmpAction::KillProcess,
                    syscall,
                    &[scmp_cmp!($arg2 & WX == WX)],
                )?;

                // Prevent executable anonymous memory.
                ctx.add_rule_conditional(
                    ScmpAction::KillProcess,
                    syscall,
                    &[scmp_cmp!($arg2 & X == X), scmp_cmp!($arg3 & MAP_A == MAP_A)],
                )?;

                // Prevent executable shared memory.
                ctx.add_rule_conditional(
                    ScmpAction::KillProcess,
                    syscall,
                    &[scmp_cmp!($arg2 & X == X), scmp_cmp!($arg3 & MAP_S == MAP_S)],
                )?;
            }

            for sysname in ["mprotect", "pkey_mprotect"] {
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule_conditional(
                    ScmpAction::KillProcess,
                    syscall,
                    &[scmp_cmp!($arg2 & X == X)],
                )?;
            }

            let sysname = "shmat";
            #[allow(clippy::disallowed_methods)]
            let syscall = ScmpSyscall::from_name(sysname).unwrap();
            ctx.add_rule_conditional(
                ScmpAction::KillProcess,
                syscall,
                &[scmp_cmp!($arg2 & SHM_X == SHM_X)],
            )?;
        }

        // Restriction 1: Disable list of compiled-in dead system calls.
        // These system calls are not used by Syd.
        for sysname in DEAD_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Errno(nix::libc::ENOSYS), syscall)?;
            } else {
                info!("ctx": "init", "op": "deny_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 2: Turn compiled-in list of noop syscalls into no-ops (see config.rs)
        for sysname in NOOP_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Errno(0), syscall)?;
            } else {
                info!("ctx": "init", "op": "noop_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 3: Deny unsafe set-id system calls.
        // Deny with Errno=0 -> Turn the system calls into no-op.
        // This is for compatibility, e.g. postgres invokes
        // setgroups before setuid and aborts on failure.
        for sysname in UNSAFE_ID_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Errno(0), syscall)?;
            } else {
                info!("ctx": "init", "op": "noop_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 4: Deny transition to privileged {U,G}IDs.
        // Step 1: arg0 for UIDs.
        for sysname in &[
            "setuid",
            "setuid32",
            "setreuid",
            "setreuid32",
            "setresuid",
            "setresuid32",
        ] {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg0 <= UID_MIN)],
                )?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Step 2: arg0 for GIDs.
        for sysname in &[
            "setgid",
            "setgid32",
            "setregid",
            "setregid32",
            "setresgid",
            "setresgid32",
        ] {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg0 <= GID_MIN)],
                )?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Step 3: arg1 for UIDs.
        for sysname in &["setreuid", "setreuid32", "setresuid", "setresuid32"] {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg1 <= UID_MIN)],
                )?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Step 4: arg1 for GIDs.
        for sysname in &["setregid", "setregid32", "setresgid", "setresgid32"] {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg1 <= GID_MIN)],
                )?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Step 5: arg2 for UIDS.
        for sysname in &["setresuid", "setresuid32"] {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg2 <= UID_MIN)],
                )?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Step 6: arg2 for GIDs.
        for sysname in &["setresgid", "setresgid32"] {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg2 <= GID_MIN)],
                )?;
            } else {
                info!("ctx": "init", "op": "filter_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 5: Lock changes to personality(2).
        // This restriction is turned on with trace/lock_personality.
        if restrict_personality {
            let sysname = "personality";
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall,
                    &[scmp_cmp!($arg0 != PERSONALITY)],
                )?;
            } else {
                info!("ctx": "init", "op": "deny_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Restriction 6: Disallow perf calls.
        if restrict_perf {
            for sysname in PERF_SYSCALLS {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Errno(nix::libc::EACCES), syscall)?;
                } else {
                    info!("ctx": "init", "op": "deny_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 7: Restrict ptrace(2) operations.
        // The TOCTOU mitigator only needs PTRACE_{CONT,LISTEN,SEIZE}.
        // The SegvGuard additionally needs PTRACE_GETEVENTMSG.
        // If TOCTOU mitigator is disabled, prevent ptrace(2) completely.
        const PTRACE_DENY: &[u64] = &[
            nix::libc::PTRACE_TRACEME as u64,
            nix::libc::PTRACE_PEEKTEXT as u64,
            nix::libc::PTRACE_PEEKDATA as u64,
            nix::libc::PTRACE_PEEKUSER as u64,
            nix::libc::PTRACE_POKETEXT as u64,
            nix::libc::PTRACE_POKEDATA as u64,
            nix::libc::PTRACE_POKEUSER as u64,
            12, // nix::libc::PTRACE_GETREGS as u64,
            13, // nix::libc::PTRACE_SETREGS as u64,
            14, // nix::libc::PTRACE_GETFPREGS as u64,
            15, // nix::libc::PTRACE_SETFPREGS as u64,
            nix::libc::PTRACE_GETREGSET as u64,
            nix::libc::PTRACE_SETREGSET as u64,
            nix::libc::PTRACE_GETSIGINFO as u64,
            nix::libc::PTRACE_SETSIGINFO as u64,
            nix::libc::PTRACE_PEEKSIGINFO as u64,
            0x420a, //nix::libc::PTRACE_GETSIGMASK,
            0x420b, //nix::libc::PTRACE_SETSIGMASK,
            nix::libc::PTRACE_SETOPTIONS as u64,
            // PTRACE_GETEVENTMSG
            // PTRACE_CONT
            nix::libc::PTRACE_SYSCALL as u64,
            nix::libc::PTRACE_SINGLESTEP as u64,
            23, //nix::libc::PTRACE_SET_SYSCALL,
            31,
            0x1d, // nix::libc::PTRACE_SYSEMU as u64,
            32,
            0x1e, // nix::libc::PTRACE_SYSEMU_SINGLESTEP as u64,
            // PTRACE_LISTEN
            nix::libc::PTRACE_KILL as u64,
            nix::libc::PTRACE_INTERRUPT as u64,
            nix::libc::PTRACE_ATTACH as u64,
            // PTRACE_SEIZE
            0x420c, // nix::libc::PTRACE_SECCOMP_GET_FILTER,
            nix::libc::PTRACE_DETACH as u64,
            22,
            25,     // nix::libc::PTRACE_GET_THREAD_AREA,
            26,     // nix::libc::PTRACE_SET_THREAD_AREA,
            0x420e, // nix::libc::PTRACE_GET_SYSCALL_INFO,
        ];
        let sysname = "ptrace";
        #[allow(clippy::disallowed_methods)]
        let syscall = ScmpSyscall::from_name(sysname).unwrap();
        // allow_unsafe_ptrace implies allow_unsafe_exec.
        // STATES:
        // a. restrict_ptrace=1 restrict_exec=1 -> LIMITED PTRACE FOR SYD, NO PTRACE FOR BOX
        // b. restrict_ptrace=1 restrict_exec=0 -> NO PTRACE FOR SYD (unused due to unsafe_exec) OR BOX
        // c. restrict_ptrace=0 restrict_exec=1 -> FULL PTRACE FOR SYD AND BOX
        // d. restrict_ptrace=0 restrict_exec=0 -> DITTO
        // The restrictions for the first two states are handled below.
        // The rest of the logic is handled in the child filter in setup_seccomp.
        if restrict_ptrace {
            if restrict_exec {
                // TOCTOU-mitigation active, limit ptrace(2).
                // SAFETY: Provide stealth with Errno=0.
                for op in PTRACE_DENY {
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(0),
                        syscall,
                        &[scmp_cmp!($arg0 == *op)],
                    )?;
                }
            } else {
                // TOCTOU-mitigation disabled, prevent ptrace(2).
                // SAFETY: Provide stealth with Errno=0.
                ctx.add_rule(ScmpAction::Errno(0), syscall)?;
            }
        }

        // Restriction 8: Deny dangerous ioctl requests.
        let sysname = "ioctl";
        #[allow(clippy::disallowed_methods)]
        let syscall = ScmpSyscall::from_name(sysname).unwrap();
        for request in ioctl_denylist {
            ctx.add_rule_conditional(
                ScmpAction::Errno(nix::libc::EACCES),
                syscall,
                &[scmp_cmp!($arg1 == *request)],
            )?;
        }

        // Restriction 9: Deny mount family unless sub mount-ns is allowed.
        if restrict_mount {
            for sysname in ["mount", "mount_setattr", "umount", "umount2"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Errno(nix::libc::EACCES), syscall)?;
                } else {
                    info!("ctx": "init", "op": "deny_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Restriction 10: Restrict sub namespace creation.
        let (namespace_act, namespace_acl) = if deny_namespaces == 0 {
            (Action::Allow, None)
        } else {
            let sysname_ns = "setns";
            #[allow(clippy::disallowed_methods)]
            let syscall_ns = ScmpSyscall::from_name(sysname_ns).unwrap();
            let deny_all = deny_namespaces & NAMESPACE_FLAGS_ALL == NAMESPACE_FLAGS_ALL;
            let namespace_act = if deny_all {
                // If every single kind of namespace shall be
                // prohibited, then let's block the whole setns()
                // syscall altogether.
                ctx.add_rule(ScmpAction::Errno(nix::libc::EACCES), syscall_ns)?;
                Action::Deny
            } else {
                // Otherwise, block only the invocations with the
                // appropriate flags in the loop below, but also the
                // special invocation with a zero flags argument, right
                // here.
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall_ns,
                    &[scmp_cmp!($arg1 == 0)],
                )?;
                Action::Filter
            };

            let sysname_cl = "clone";
            let sysname_un = "unshare";
            #[allow(clippy::disallowed_methods)]
            let syscall_cl = ScmpSyscall::from_name(sysname_cl).unwrap();
            #[allow(clippy::disallowed_methods)]
            let syscall_un = ScmpSyscall::from_name(sysname_un).unwrap();
            let mut ns_deny = vec![];
            let mut ns_allow = vec![];
            for flag in NAMESPACE_FLAGS {
                if deny_namespaces & flag == 0 {
                    ns_allow.push(nsflag_name(*flag));
                    continue;
                } else {
                    ns_deny.push(nsflag_name(*flag));
                }

                #[allow(clippy::cast_sign_loss)]
                let flag = *flag as u64;
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EACCES),
                    syscall_un,
                    &[scmp_cmp!($arg0 & flag == flag)],
                )?;

                // On s390/s390x the first two parameters to clone are switched.
                if !cfg!(target_arch = "s390x") {
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(nix::libc::EACCES),
                        syscall_cl,
                        &[scmp_cmp!($arg0 & flag == flag)],
                    )?;
                } else {
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(nix::libc::EACCES),
                        syscall_cl,
                        &[scmp_cmp!($arg1 & flag == flag)],
                    )?;
                }

                if !deny_all {
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(nix::libc::EACCES),
                        syscall_ns,
                        &[scmp_cmp!($arg1 & flag == flag)],
                    )?;
                }
            }

            if namespace_act == Action::Deny {
                (namespace_act, None)
            } else {
                (namespace_act, Some((ns_deny, ns_allow)))
            }
        };

        match (namespace_act, namespace_acl) {
            (Action::Allow, _) => info!("ctx": "init", "op": "restrict_namespaces",
                "act": Action::Allow, "deny": None::<u8>, "allow": NAMESPACE_NAMES),
            (Action::Deny, _) => info!("ctx": "init", "op": "restrict_namespaces",
                "act": Action::Deny, "deny": NAMESPACE_NAMES, "allow": None::<u8>),
            (_, Some((acl_deny, acl_allow))) => info!("ctx": "init", "op": "restrict_namespaces",
                "act": Action::Filter, "deny": acl_deny, "allow": acl_allow),
            _ => {} // unreachable.
        };

        // Export seccomp rules if requested.
        if std::env::var("SYD_SECX").is_ok() {
            println!("# Syd parent rules");
            ctx.export_pfc(&mut io::stdout())?;
        }

        // Load the seccomp profile.
        ctx.load()?;

        // Restriction 11: Restrict socket domains based on flags.
        // SAFETY: socket may not exist on every architecture.
        // On some arches such as x86 there's the socketcall
        // system call which involves a pointer indirection
        // for the domain argument therefore on these arches
        // we rely on our socketcall seccomp-notify hook to
        // achieve the same effect.
        if !restrict_socket && !restrict_kcapi {
            return Ok(()); // No need for an additional socket filter.
        } else if seccomp_native_has_socketcall() {
            // Export seccomp rules if requested.
            if std::env::var("SYD_SECX").is_ok() {
                println!("# Syd socket rules");
                println!("# Not applicable on this architecture!");
            }
        } else {
            let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow)?;
            // Enforce the NO_NEW_PRIVS functionality before
            // loading the seccomp filter into the kernel.
            ctx.set_ctl_nnp(true)?;
            // Synchronize filter to all threads.
            ctx.set_ctl_tsync(true)?;
            // SAFETY: We do allow bad architecture, see the comment above.
            ctx.set_act_badarch(ScmpAction::Allow)?;
            // SAFETY: Do not add supported architectures, this filter
            // is for the native architecture only.
            // seccomp_add_architectures(&mut ctx)?;
            // We don't want ECANCELED, we want actual errnos.
            let _ = ctx.set_api_sysrawrc(true);
            // Use a binary tree sorted by syscall number.
            let _ = ctx.set_ctl_optimize(2);

            #[allow(clippy::disallowed_methods)]
            let syscall = ScmpSyscall::from_name("socket").unwrap();
            if restrict_socket {
                // TODO: nix::libc should define this!
                const AF_MAX: nix::libc::c_int = 45;
                // Only allow AF_{UNIX,INET,INET6,NETLINK} by default
                let mut allow_domains = FxHashSet::from_iter([
                    nix::libc::AF_UNIX,
                    nix::libc::AF_INET,
                    nix::libc::AF_INET6,
                    nix::libc::AF_NETLINK,
                ]);
                if !restrict_kcapi {
                    // Allow KCAPI as well.
                    allow_domains.insert(nix::libc::AF_ALG);
                }

                for domain in 0..AF_MAX {
                    if allow_domains.contains(&domain) {
                        continue;
                    }
                    #[allow(clippy::cast_sign_loss)]
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(nix::libc::EAFNOSUPPORT),
                        syscall,
                        &[scmp_cmp!($arg0 == domain as u64)],
                    )?;
                }

                // SAFETY: Guard against new AF_* that may be added in the future.
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EAFNOSUPPORT),
                    syscall,
                    &[scmp_cmp!($arg0 >= AF_MAX as u64)],
                )?;

                // SAFETY: Restrict AF_NETLINK families.
                if allow_netlink_families.is_empty() {
                    // No netlink families were allowed, deny all of AF_NETLINK.
                    // See comment above on the usage of _exact.
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(nix::libc::EAFNOSUPPORT),
                        syscall,
                        &[scmp_cmp!($arg0 == nix::libc::AF_NETLINK as u64)],
                    )?;
                } else {
                    let allow_netlink_families = allow_netlink_families.to_vec();
                    let netlink_family_max = NetlinkFamily::max();
                    for netlink_family in 0..netlink_family_max {
                        if allow_netlink_families.contains(&netlink_family) {
                            continue;
                        }
                        // See comment above on the usage of _exact.
                        #[allow(clippy::cast_sign_loss)]
                        ctx.add_rule_conditional(
                            ScmpAction::Errno(nix::libc::EAFNOSUPPORT),
                            syscall,
                            &[
                                scmp_cmp!($arg0 == nix::libc::AF_NETLINK as u64),
                                scmp_cmp!($arg2 == netlink_family as u64),
                            ],
                        )?;
                    }
                    // SAFETY: Guard against new netlink families that may be added in the future.
                    #[allow(clippy::cast_sign_loss)]
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(nix::libc::EAFNOSUPPORT),
                        syscall,
                        &[
                            scmp_cmp!($arg0 == nix::libc::AF_NETLINK as u64),
                            scmp_cmp!($arg2 >= netlink_family_max as u64),
                        ],
                    )?;
                }
            } else if restrict_kcapi {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(nix::libc::EAFNOSUPPORT),
                    syscall,
                    &[scmp_cmp!($arg0 == nix::libc::AF_ALG as u64)],
                )?;
            }

            // Export seccomp rules if requested.
            if std::env::var("SYD_SECX").is_ok() {
                println!("# Syd socket rules");
                ctx.export_pfc(&mut io::stdout())?;
            }

            ctx.load()?;
        }

        Ok(())
    }

    /// Export a seccomp context as bpf or pfc.
    fn seccomp_export(&self, ctx: &ScmpFilterContext, mode: ExportMode) -> SydResult<()> {
        match mode {
            ExportMode::BerkeleyPacketFilter => Ok(ctx.export_bpf(&mut io::stdout())?),
            ExportMode::PseudoFiltercode => {
                let (fd, path) = mkstemp("syd-tmp-XXXXXX")?;
                unlink(path.as_path())?;
                // SAFETY: mkstemp returns a valid FD.
                let mut file = unsafe { File::from_raw_fd(fd) };

                ctx.export_pfc(&mut file)?;

                file.seek(SeekFrom::Start(0))?;
                let mut buffer = Vec::new();
                file.read_to_end(&mut buffer)?;

                let output = String::from_utf8_lossy(&buffer);
                let output = output.replace("0x7fc00000", "NOTIFY");

                let libver = ScmpVersion::current()?;
                println!(
                    "# Syd v{} seccomp rules generated by libseccomp v{}.{}.{}",
                    env!("CARGO_PKG_VERSION"),
                    libver.major,
                    libver.minor,
                    libver.micro
                );
                println!("# API Version: {API_VERSION}");

                #[allow(clippy::disallowed_methods)]
                let mut syscall_allow: Vec<_> = self
                    .sysallow
                    .iter()
                    .copied()
                    .map(|sys| sys.get_name().unwrap())
                    .collect();
                let syscall_notif: FxHashSet<String> = self.handlers
                    .keys()
                    .map(|key| key.0.to_string()) // Extract the name from keys
                    .collect(); // Collect names into a FxHashSet to ensure uniqueness
                let mut syscall_notif: Vec<String> = syscall_notif.into_iter().collect();
                syscall_allow.sort();
                syscall_notif.sort();

                println!("# System calls with Action=ALLOW: {}", syscall_allow.len());
                println!("# System calls with Action=NOTIF: {}", syscall_notif.len());

                let uidcall = GET_ID_SYSCALLS.to_vec().join(", ");
                let sandbox = self.sandbox.read();
                println!(
                    "# Fake Root: {} ( {uidcall} )",
                    if sandbox.fake_root() { "yes" } else { "no" }
                );
                println!(
                    "{}",
                    sandbox
                        .to_string()
                        .lines()
                        .map(|line| format!("# {}", line))
                        .collect::<Vec<_>>()
                        .join("\n")
                );
                drop(sandbox);

                println!("# Action=NOTIF: {}", syscall_notif.len());
                for name in &syscall_notif {
                    println!("#    - {name}");
                }
                println!("# Action=ALLOW: {}", syscall_allow.len());
                for name in &syscall_allow {
                    println!("#    - {name}");
                }
                print!("{output}");
                Ok(())
            }
        }
    }

    /// Logic for the supervise child thread.
    #[allow(clippy::type_complexity)]
    fn supervise(
        self,
        fd: RawFd,
        safe_setid: bool,
        cache_path_cap: (usize, u64),
        cache_addr_cap: (usize, u64),
    ) -> SydResult<(Arc<(PathCache, AddrCache)>, Arc<RwLock<Sandbox>>)> {
        // Set (process-wide) umask to 0.
        let _ = umask(Mode::empty());

        // Create path and address caches.
        let cache = Arc::new((
            path_cache_new(cache_path_cap.0, cache_path_cap.1),
            addr_cache_new(cache_addr_cap.0, cache_addr_cap.1),
        ));

        // Spawn the thread pool.
        pool::ThreadPool::new(
            safe_setid,
            *EMU_POOL_SIZE,
            EMU_KEEP_ALIVE,
            fd,
            Arc::clone(&cache),
            Arc::clone(&self.sandbox),
            Arc::clone(&self.handlers),
        )?
        .boot()?;

        // We return a clone of the cache and the sandbox to the caller.
        // exec-TOCTOU-mitigator uses this instance in the wait loop.
        Ok((cache, Arc::clone(&self.sandbox)))
    }

    /// Wait for the child process to exit.
    /// It returns the exit code of the process.
    #[allow(clippy::cognitive_complexity)]
    fn wait(cache: Arc<(PathCache, AddrCache)>, sandbox: Arc<RwLock<Sandbox>>) -> SydResult<u8> {
        let my_sandbox = SandboxGuard::Read(sandbox.read());
        let child = my_sandbox.get_child_pid();
        let wait_all = my_sandbox.exit_wait_all();
        let safe_setid = my_sandbox.allow_safe_setuid() || my_sandbox.allow_safe_setgid();
        let transit_uids = my_sandbox.transit_uids.clone();
        let transit_gids = my_sandbox.transit_gids.clone();
        drop(my_sandbox); // release the read lock.

        // SAFETY: Confine the main thread.
        let mut ctx = ScmpFilterContext::new_filter(ScmpAction::KillProcess)?;
        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;
        // DO NOT synchronize filter to all threads.
        // Thread pool confines itself as necessary.
        ctx.set_ctl_tsync(false)?;
        // We kill for bad system call and bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;
        // SAFETY: Do NOT add supported architectures to the filter.
        // This ensures Syd can never run a non-native system call,
        // which we do not need at all.
        // seccomp_add_architectures(&mut ctx)?;

        // Allow safe system calls.
        for sysname in MAIN_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Allow, syscall)?;
            } else {
                info!("ctx": "wait", "op": "allow_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Allow get id system calls.
        for sysname in GET_ID_SYSCALLS {
            if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                ctx.add_rule(ScmpAction::Allow, syscall)?;
            } else {
                info!("ctx": "wait", "op": "allow_syscall",
                    "error": "scmp_error", "sys": sysname);
            }
        }

        // Allow AF_UNIX sockets for syslog.
        if !*NO_SYSLOG {
            const AF_UNIX: u64 = nix::libc::AF_UNIX as u64;
            #[allow(clippy::disallowed_methods)]
            let syscall = ScmpSyscall::from_name("socket").unwrap();
            ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg0 == AF_UNIX)])?;

            // Only allow send to connection-mode sockets.
            #[allow(clippy::disallowed_methods)]
            ctx.add_rule_conditional(
                ScmpAction::Allow,
                ScmpSyscall::from_name("sendto").unwrap(),
                &[scmp_cmp!($arg4 == 0), scmp_cmp!($arg5 == 0)],
            )?;

            for sysname in ["connect", "send"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
            }
        }

        // Set-ID system calls are per-process!
        if safe_setid {
            // SAFETY: We cannot deny _sigreturn_(2) with SafeSetID!
            // NPTL uses reserved-signals during UID/GID change!
            for sysname in ["sigaction", "rt_sigaction", "sigreturn", "rt_sigreturn"] {
                #[allow(clippy::disallowed_methods)]
                let syscall = ScmpSyscall::from_name(sysname).unwrap();
                ctx.add_rule(ScmpAction::Allow, syscall)?;
            }

            // SAFETY: Only allow defined UID/GID transitions.
            let source_uid = Uid::current();
            let source_gid = Gid::current();
            const NULL_ID: u64 = u64::MAX;

            for sysname in &["setuid", "setuid32"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    for (s_uid, t_uid) in &transit_uids {
                        if source_uid == *s_uid {
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[scmp_cmp!($arg0 == u64::from(t_uid.as_raw()))],
                            )?;
                        }
                    }
                } else {
                    info!("ctx": "wait", "op": "filter_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }

            for sysname in &["setgid", "setgid32"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    for (s_gid, t_gid) in &transit_gids {
                        if source_gid == *s_gid {
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[scmp_cmp!($arg0 == u64::from(t_gid.as_raw()))],
                            )?;
                        }
                    }
                } else {
                    info!("ctx": "wait", "op": "filter_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }

            for sysname in &["setreuid", "setreuid32"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    for (s_uid, t_uid) in &transit_uids {
                        if source_uid == *s_uid {
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg1 == NULL_ID),
                                ],
                            )?;
                        }
                    }
                } else {
                    info!("ctx": "wait", "op": "filter_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }

            for sysname in &["setregid", "setregid32"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    for (s_gid, t_gid) in &transit_gids {
                        if source_gid == *s_gid {
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg1 == NULL_ID),
                                ],
                            )?;
                        }
                    }
                } else {
                    info!("ctx": "wait", "op": "filter_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }

            for sysname in &["setresuid", "setresuid32"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    for (s_uid, t_uid) in &transit_uids {
                        if source_uid == *s_uid {
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg1 == NULL_ID),
                                    scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg2 == NULL_ID),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == NULL_ID),
                                    scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg2 == NULL_ID),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
                                    scmp_cmp!($arg1 == NULL_ID),
                                    scmp_cmp!($arg2 == NULL_ID),
                                ],
                            )?;
                        }
                    }
                } else {
                    info!("ctx": "wait", "op": "filter_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }

            for sysname in &["setresgid", "setresgid32"] {
                if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                    for (s_gid, t_gid) in &transit_gids {
                        if source_gid == *s_gid {
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg1 == NULL_ID),
                                    scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg2 == NULL_ID),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == NULL_ID),
                                    scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == NULL_ID),
                                    scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg2 == NULL_ID),
                                ],
                            )?;
                            ctx.add_rule_conditional(
                                ScmpAction::Allow,
                                syscall,
                                &[
                                    scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
                                    scmp_cmp!($arg1 == NULL_ID),
                                    scmp_cmp!($arg2 == NULL_ID),
                                ],
                            )?;
                        }
                    }
                } else {
                    info!("ctx": "wait", "op": "filter_syscall",
                        "error": "scmp_error", "sys": sysname);
                }
            }
        }

        // Export seccomp rules if requested.
        if std::env::var("SYD_SECX").is_ok() {
            println!("# Syd waiter rules");
            let _ = ctx.export_pfc(&mut std::io::stdout());
        }

        // All set, load the filter!
        ctx.load()?;
        info!("ctx": "wait", "op": "confine",
            "nnp": true, "tsync": false,
            "sigreturn": safe_setid);

        let mut exit_code: i32 = 127;
        'waitloop: loop {
            match waitid(Id::All, WaitPidFlag::WEXITED | WaitPidFlag::__WNOTHREAD) {
                Ok(WaitStatus::Exited(pid, code)) => {
                    if pid == child {
                        exit_code = code;
                        if !wait_all {
                            break;
                        }
                    }
                }
                Ok(WaitStatus::Signaled(pid, signal, _core)) => {
                    if pid == child {
                        exit_code = 128_i32.saturating_add(signal);
                        if !wait_all {
                            break;
                        }
                    }
                }
                Ok(WaitStatus::PtraceEvent(
                    pid,
                    nix::libc::SIGTRAP,
                    nix::libc::PTRACE_EVENT_FORK
                    | nix::libc::PTRACE_EVENT_VFORK
                    | nix::libc::PTRACE_EVENT_CLONE,
                )) => {
                    let _ = cont(pid, None);
                }
                Ok(WaitStatus::PtraceEvent(
                    pid,
                    nix::libc::SIGTRAP,
                    nix::libc::PTRACE_EVENT_EXEC,
                )) => {
                    // Step 1. Read executable paths.
                    // This includes the executable, and the loader if
                    // executable is dynamically linked.
                    let bins = match proc_executables(pid) {
                        Ok(bins) => bins,
                        Err(_) => {
                            // This should never happen in an ideal world,
                            // let's handle it as gracefully as we can...
                            let _ = kill(pid, Some(Signal::SIGKILL));
                            continue;
                        }
                    };
                    let path = &bins[0].0; // Path to the executable.

                    // Lock the sandbox for read.
                    let my_sandbox = SandboxGuard::Read(sandbox.read());
                    let mut deny_action: Option<Action> = None;

                    // Step 2: Check SegvGuard.
                    if let Some(action) = my_sandbox.check_segvguard(path) {
                        if action != Action::Filter {
                            warn!("ctx": "access", "cap": 'g',
                                "act": Action::Kill, "path": &path,
                                "sys": "exec", "pid": pid.as_raw());
                        }
                        if action == Action::Exit {
                            std::process::exit(nix::libc::SIGKILL);
                        } else if action.is_denying() {
                            deny_action = Some(Action::Kill);
                        }
                    }

                    // Step 3: Check for Exec sandboxing.
                    if deny_action.is_none() && my_sandbox.enabled(Capability::CAP_EXEC) {
                        for (path, _, _, _) in &bins {
                            let (action, filter) = match cache
                                .0
                                .get_value_or_guard(&PathCap(Capability::CAP_EXEC, path), None)
                            {
                                GuardResult::Value(result) => result,
                                GuardResult::Guard(guard) => {
                                    let result = my_sandbox.check_path(Capability::CAP_EXEC, path);
                                    let _ = guard.insert(result);
                                    result
                                }
                                GuardResult::Timeout => {
                                    // SAFETY: We never pass a timeout, this cannot happen.
                                    unsafe { std::hint::unreachable_unchecked() };
                                }
                            };
                            if !filter {
                                warn!("ctx": "access", "act": action,
                                    "cap": Capability::CAP_EXEC, "path": &path,
                                    "sys": "exec", "pid": pid.as_raw());
                            }
                            match action {
                                Action::Allow | Action::Warn => {}
                                Action::Stop => {
                                    deny_action = Some(Action::Stop);
                                    break;
                                }
                                Action::Exit => std::process::exit(nix::libc::SIGKILL),
                                _ => {
                                    // Deny|Filter|Kill
                                    deny_action = Some(Action::Kill);
                                    break;
                                }
                            }
                        }
                    }

                    // Step 4: Check for Trusted Path Execution (TPE).
                    if deny_action.is_none() && my_sandbox.enabled(Capability::CAP_TPE) {
                        for (path, _, _, _) in &bins {
                            let action = my_sandbox.check_tpe(path);
                            if !matches!(action, Action::Allow | Action::Filter) {
                                warn!("ctx": "access", "act": action,
                                    "cap": Capability::CAP_TPE, "path": &path,
                                    "sys": "exec", "pid": pid.as_raw());
                            }
                            match action {
                                Action::Allow | Action::Warn => {}
                                Action::Stop => deny_action = Some(Action::Stop),
                                Action::Exit => std::process::exit(nix::libc::SIGKILL),
                                _ => {
                                    // Deny|Filter|Kill
                                    deny_action = Some(Action::Kill);
                                }
                            }
                        }
                    }

                    if deny_action.is_none() {
                        // Step 5: Open paths and verify the open FDs
                        // match the device ID and inode information.
                        // The FDs will be used for two things:
                        // 1. Parsing ELF to determine bitness, PIE etc.
                        // 2. Checksumming binary for Force sandboxing.
                        let mut files = Vec::with_capacity(2);
                        for (path, inode, devid_maj, devid_min) in &bins {
                            match safe_open(path, OFlag::O_RDONLY) {
                                Ok(fd) => {
                                    // WORKAROUND: Check if the FS reports sane device ids.
                                    // Check the comment on has_sane_device_id() function
                                    // for more information.
                                    // Assume true on errors for safety.
                                    let dev_check = match retry_on_eintr(|| fstatfs64(&fd)) {
                                        Ok(statfs) => !statfs.has_broken_device_ids(),
                                        Err(Errno::ENOSYS) => {
                                            // Filesystem type does not support this call.
                                            // Assume true for safety.
                                            true
                                        }
                                        Err(errno) => {
                                            warn!("ctx": "access", "cap": Capability::CAP_EXEC,
                                                "act": Action::Kill, "path": &path,
                                                "sys": "exec", "pid": pid.as_raw(),
                                                "reason": "statfs", "errno": errno as i32);
                                            let _ = kill(pid, Some(Signal::SIGKILL));
                                            continue 'waitloop;
                                        }
                                    };
                                    let statx = match retry_on_eintr(|| {
                                        fstatx(fd.as_raw_fd(), STATX_INO)
                                    }) {
                                        Ok(stat) => stat,
                                        Err(errno) => {
                                            warn!("ctx": "access", "cap": Capability::CAP_EXEC,
                                                "act": Action::Kill, "path": &path,
                                                "sys": "exec", "pid": pid.as_raw(),
                                                "reason": "statx", "errno": errno as i32);
                                            let _ = kill(pid, Some(Signal::SIGKILL));
                                            continue 'waitloop;
                                        }
                                    };
                                    // SAFETY: Verify we opened the same file!
                                    #[allow(clippy::cast_sign_loss)]
                                    let devid_maj = *devid_maj as nix::libc::c_uint;
                                    #[allow(clippy::cast_sign_loss)]
                                    let devid_min = *devid_min as nix::libc::c_uint;
                                    if *inode != statx.stx_ino
                                        || (dev_check
                                            && (devid_maj != statx.stx_dev_major
                                                || devid_min != statx.stx_dev_minor))
                                    {
                                        warn!("ctx": "access", "cap": Capability::CAP_EXEC,
                                            "act": Action::Kill, "path": &path,
                                            "sys": "exec", "pid": pid.as_raw(),
                                            "reason": "file-mismatch",
                                            "dev": [
                                                devid_maj,
                                                devid_min,
                                                statx.stx_dev_major,
                                                statx.stx_dev_minor,
                                            ],
                                            "ino": [*inode, statx.stx_ino]);
                                        let _ = kill(pid, Some(Signal::SIGKILL));
                                        continue 'waitloop;
                                    }
                                    files.push(File::from(fd));
                                }
                                Err(errno) => {
                                    warn!("ctx": "access", "cap": Capability::CAP_EXEC,
                                        "act": Action::Kill, "path": &path,
                                        "sys": "exec", "pid": pid.as_raw(),
                                        "reason": "open",
                                        "errno": errno as i32);
                                    let _ = kill(pid, Some(Signal::SIGKILL));
                                    continue 'waitloop;
                                }
                            }
                        }

                        // Step 6: Parse ELF as necessary for restrictions.
                        let restrict_32 = my_sandbox.deny_elf32();
                        let restrict_dyn = my_sandbox.deny_elf_dynamic();
                        let restrict_sta = my_sandbox.deny_elf_static();
                        let restrict_pie = !my_sandbox.allow_unsafe_nopie();
                        let restrict_xs = !my_sandbox.allow_unsafe_stack();

                        // Shared library execution depends on trace/allow_unsafe_exec:1.
                        // unsafe_exec also means no ptrace, hence we can never be here.
                        let restrict_ldd = true /* !my_sandbox.allow_unsafe_exec() */;

                        let check_linking = restrict_ldd
                            || restrict_dyn
                            || restrict_sta
                            || restrict_pie
                            || restrict_xs;
                        let result = (|| -> Result<ExecutableFile, ElfError> {
                            // Parse ELF and reset the file offset.
                            let mut file = &files[0];
                            let result = ExecutableFile::parse(file, check_linking);
                            #[allow(clippy::disallowed_methods)]
                            file.seek(SeekFrom::Start(0)).map_err(ElfError::IoError)?;
                            result
                        })();
                        let exe = match result {
                            Ok(exe) => Some(exe),
                            Err(ElfError::IoError(err)) => {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "sys": "exec", "pid": pid.as_raw(),
                                        "reason": "inaccessible", "errno": err2no(err) as i32);
                                }
                                None
                            }
                            Err(ElfError::BadMagic) => {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "sys": "exec", "pid": pid.as_raw(),
                                        "reason": "unsupported", "errno": Errno::ENOSYS as i32);
                                }
                                None
                            }
                            Err(ElfError::Malformed) => {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "sys": "exec", "pid": pid.as_raw(),
                                        "reason": "malformed", "errno": Errno::ENOEXEC as i32);
                                }
                                None
                            }
                        };

                        if let Some(exe) = exe {
                            if restrict_ldd
                                && !matches!(
                                    exe,
                                    ExecutableFile::Elf {
                                        file_type: ElfFileType::Executable,
                                        ..
                                    }
                                )
                            {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "reason": "lib", "type": exe.to_string(),
                                        "sys": "exec", "pid": pid.as_raw());
                                }
                            }

                            if deny_action.is_none()
                                && restrict_pie
                                && matches!(exe, ExecutableFile::Elf { pie: false, .. })
                            {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "reason": "pie", "type": exe.to_string(),
                                        "sys": "exec", "pid": pid.as_raw());
                                }
                            }

                            if deny_action.is_none()
                                && restrict_xs
                                && matches!(exe, ExecutableFile::Elf { xs: true, .. })
                            {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "reason": "execstack", "type": exe.to_string(),
                                        "sys": "exec", "pid": pid.as_raw());
                                }
                            }

                            if deny_action.is_none()
                                && restrict_32
                                && matches!(
                                    exe,
                                    ExecutableFile::Elf {
                                        elf_type: ElfType::Elf32,
                                        ..
                                    }
                                )
                            {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "reason": "32-bit", "type": exe.to_string(),
                                        "sys": "exec", "pid": pid.as_raw());
                                }
                            }

                            if deny_action.is_none()
                                && restrict_dyn
                                && matches!(
                                    exe,
                                    ExecutableFile::Elf {
                                        linking_type: Some(LinkingType::Dynamic),
                                        ..
                                    }
                                )
                            {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "reason": "dynamic", "type": exe.to_string(),
                                        "sys": "exec", "pid": pid.as_raw());
                                }
                            }

                            if deny_action.is_none()
                                && restrict_sta
                                && matches!(
                                    exe,
                                    ExecutableFile::Elf {
                                        linking_type: Some(LinkingType::Static),
                                        ..
                                    }
                                )
                            {
                                deny_action = Some(Action::Kill);
                                if !my_sandbox.filter_path(Capability::CAP_EXEC, path) {
                                    warn!("ctx": "access", "act": Action::Kill,
                                        "cap": Capability::CAP_EXEC, "path": &path,
                                        "reason": "static", "type": exe.to_string(),
                                        "sys": "exec", "pid": pid.as_raw());
                                }
                            }
                        }

                        // Step 7: Check for Force sandboxing.
                        if deny_action.is_none() && my_sandbox.enabled(Capability::CAP_FORCE) {
                            for (idx, (path, _, _, _)) in bins.iter().enumerate() {
                                let file = &files[idx];
                                match my_sandbox.check_force2(path, BufReader::new(file)) {
                                    Ok(Action::Allow) => {}
                                    Ok(Action::Warn) => {
                                        warn!("ctx": "access", "act": Action::Warn,
                                            "cap": Capability::CAP_FORCE, "path": &path,
                                            "sys": "exec", "pid": pid.as_raw());
                                    }
                                    Ok(Action::Stop) => {
                                        deny_action = Some(Action::Stop);
                                        warn!("ctx": "access", "act": Action::Stop,
                                            "cap": Capability::CAP_FORCE, "path": &path,
                                            "sys": "exec", "pid": pid.as_raw());
                                    }
                                    Ok(Action::Exit) => {
                                        warn!("ctx": "access", "act": Action::Exit,
                                            "cap": Capability::CAP_FORCE, "path": &path,
                                            "sys": "exec", "pid": pid.as_raw());
                                        std::process::exit(nix::libc::SIGKILL);
                                    }
                                    Ok(action) => {
                                        // Deny|Filter|Kill
                                        deny_action = Some(Action::Kill);
                                        if action != Action::Filter {
                                            warn!("ctx": "access", "act": action,
                                                "cap": Capability::CAP_FORCE, "path": &path,
                                                "sys": "exec", "pid": pid.as_raw());
                                        }
                                    }
                                    Err(IntegrityError::Sys(errno)) => {
                                        deny_action = Some(Action::Kill);
                                        warn!("ctx": "access", "act": Action::Kill,
                                            "cap": Capability::CAP_FORCE, "path": &path,
                                            "sys": "exec", "pid": pid.as_raw(),
                                            "errno": errno as i32);
                                    }
                                    Err(IntegrityError::Hash {
                                        action,
                                        expected,
                                        found,
                                    }) => {
                                        if !matches!(action, Action::Allow | Action::Filter) {
                                            warn!("ctx": "access", "act": action,
                                                "cap": Capability::CAP_FORCE, "path": &path,
                                                "sys": "exec", "pid": pid.as_raw(),
                                                "hash": [expected, found],
                                                "errno": Errno::EINVAL as i32);
                                        }
                                        match action {
                                            Action::Allow | Action::Warn => {}
                                            Action::Stop => deny_action = Some(Action::Stop),
                                            Action::Exit => std::process::exit(nix::libc::SIGKILL),
                                            _ =>
                                            /*Deny|Filter|Kill*/
                                            {
                                                deny_action = Some(Action::Kill)
                                            }
                                        };
                                    }
                                }
                            }
                        }
                    }
                    drop(my_sandbox); // release the read lock.

                    if let Some(action) = deny_action {
                        let sig = match action {
                            Action::Stop => Signal::SIGSTOP,
                            Action::Kill => Signal::SIGKILL,
                            _ => unreachable!(),
                        };
                        let _ = kill(pid, Some(sig));
                    } else {
                        let _ = cont(pid, None);
                    }
                }
                Ok(WaitStatus::PtraceEvent(
                    pid,
                    nix::libc::SIGTRAP,
                    nix::libc::PTRACE_EVENT_EXIT,
                )) => {
                    // We stopped before return from exit(2).
                    // Apply SegvGuard.
                    let mut my_sandbox = SandboxGuard::UpgradableRead(sandbox.upgradable_read());

                    // Setting expiry timeout to 0 disables SegvGuard.
                    if !my_sandbox.get_segvguard_expiry().is_zero() {
                        // Step 1:
                        // (a) Check if process produced a core dump.
                        // (b) Check if process received a signal with default action Core.
                        let sig = match getevent(pid) {
                            Ok(status) => {
                                #[allow(clippy::cast_possible_truncation)]
                                match WaitStatus::from_raw(pid, status as i32) {
                                    WaitStatus::Signaled(_, sig, true) => Some(sig),
                                    WaitStatus::Signaled(_, sig, _) if is_coredump(sig) => {
                                        Some(sig)
                                    }
                                    _ => None, // Process did not produce a core dump, move on.
                                }
                            }
                            Err(_) => None, // Process dead? move on.
                        };

                        // Step 2: Record the crash as necessary.
                        if let Some(sig) = sig {
                            // Child received a signal that produces a
                            // coredump and SegvGuard is enabled.
                            // Add the exec path to the segvguard expiry
                            // map.
                            let mut buf = itoa::Buffer::new();
                            let mut proc = TinyPathBuf::new();
                            proc.push(Path::new("/proc"));
                            proc.push(Path::new(buf.format(pid.as_raw())));
                            proc.push(Path::new("exe"));

                            let path = match read_link(&proc) {
                                Ok(path) => path,
                                Err(_) => continue,
                            };

                            // Upgrade the sandbox lock to writable,
                            // and record the crashing program.
                            my_sandbox = my_sandbox.upgrade();
                            let (was_suspended, is_suspended, num_crashes) =
                                my_sandbox.add_segvguard_crash(path.to_path_buf());

                            if is_suspended {
                                warn!("ctx": "segvguard", "op": "suspend",
                                    "pid": pid.as_raw(), "sig": sig,
                                    "path": &path, "num_crashes": num_crashes,
                                    "suspended": true);
                            } else {
                                info!("ctx": "segvguard", "op": "record",
                                    "pid": pid.as_raw(), "sig": sig,
                                    "path": &path, "num_crashes": num_crashes,
                                    "suspended": was_suspended);
                            }
                        }
                    }
                    drop(my_sandbox); // Release the write-lock.

                    // Step 3: Continue the process so it exits.
                    let _ = cont(pid, None);
                }
                Ok(WaitStatus::PtraceEvent(
                    pid,
                    nix::libc::SIGSTOP
                    | nix::libc::SIGTSTP
                    | nix::libc::SIGTTIN
                    | nix::libc::SIGTTOU,
                    nix::libc::PTRACE_EVENT_STOP,
                )) => {
                    // SAFETY: nix does not have a wrapper for PTRACE_LISTEN.
                    let _ =
                        unsafe { nix::libc::ptrace(nix::libc::PTRACE_LISTEN, pid.as_raw(), 0, 0) };
                }
                Ok(WaitStatus::PtraceEvent(
                    pid,
                    _, // Can this ever be !SIGTRAP?
                    nix::libc::PTRACE_EVENT_STOP,
                )) => {
                    // ptrace-stop, do not forward the signal.
                    let _ = cont(pid, None);
                }
                Ok(WaitStatus::PtraceEvent(pid, sig, 0)) => {
                    // SAFETY: nix version does not handle realtime signals.
                    let _ =
                        unsafe { nix::libc::ptrace(nix::libc::PTRACE_CONT, pid.as_raw(), 0, sig) };
                }
                Ok(status) => panic!("Unhandled wait event: {status:?}"),
                Err(Errno::EINTR | Errno::EAGAIN) => {}
                Err(Errno::ECHILD) => break,
                Err(errno) => return Err(errno.into()),
            }
        }

        // SAFETY: Since we do not run epoll on the main thread anymore,
        // seccomp_notify_receive may block forever on seccomp fd even
        // when all processes have exited... Hence we do not join the
        // pools here and this is safe since we can be at this point
        // under two conditions:
        // (a): wait_all == false && exec child has exited.
        // (b): wait_all == true && we received ECHILD on wait().
        // Under both cases it is safe to tear down the sandbox as we
        // ensure we do not interrupt any syscall processing.
        //if wait_all {
        // let _ = thread_handle.join().map_err(|_| Errno::EPIPE)?;
        // pool_handle.join();
        //}

        Ok(u8::try_from(exit_code).unwrap_or(127))
    }

    /// Run the supervisor, main entry point.
    #[allow(clippy::cognitive_complexity)]
    pub fn run(
        mut sandbox: Sandbox,
        argv0: &OsStr,
        argv: Vec<OsString>,
        envp: Option<&FxHashSet<OsString>>,
        arg0: Option<OsString>,
        export: Option<ExportMode>,
    ) -> SydResult<u8> {
        // Adjust process resources.
        // Step 1: Limit core dumps.
        let restrict_prlimit = !sandbox.allow_unsafe_prlimit();
        if restrict_prlimit {
            match setrlimit(Resource::RLIMIT_CORE, 0, 0) {
                Ok(_) => info!("ctx": "init", "op": "set_rlimit_core",
                    "val": 0),
                Err(errno) => error!("ctx": "init", "op": "set_rlimit_core",
                    "val": 0, "errno": errno as i32),
            }
        }
        // Step 2: Attempt to set file-max to hard limit overriding the soft limit.
        // Since this is just an attempt for convenience, we log errors with info.
        match getrlimit(Resource::RLIMIT_NOFILE) {
            Ok((soft_limit, hard_limit)) if soft_limit < hard_limit => {
                match setrlimit(Resource::RLIMIT_NOFILE, hard_limit, hard_limit) {
                    Ok(_) => info!("ctx": "init", "op": "set_rlimit_nofile",
                        "soft": soft_limit, "hard": hard_limit),
                    Err(errno) => info!("ctx": "init", "op": "set_rlimit_nofile",
                        "soft": soft_limit, "hard": hard_limit,
                        "errno": errno as i32),
                }
            }
            Ok((soft_limit, hard_limit)) => info!("ctx": "init", "op": "get_rlimit_nofile",
                "soft": soft_limit, "hard": hard_limit),
            Err(errno) => info!("ctx": "init", "op": "get_rlimit_nofile",
                "errno": errno as i32),
        }

        // Prepare the command to execute.
        // We create it early here so dynamic library loading
        // works even if we mount the owning fs noexec later.
        let mut command = match crate::unshare::Command::new(argv0) {
            Ok(command) => command,
            Err(errno) => return Ok(u8::try_from(errno as i32).unwrap_or(127)),
        };
        command.deny_tsc(sandbox.deny_tsc());
        if sandbox.allow_unsafe_caps() || sandbox.allow_unsafe_ptrace() {
            // Keep CAP_SYS_PTRACE in the sandbox process.
            command.keep(true);
        }
        if !sandbox.allow_unsafe_exec() {
            // Exec TOCTOU mitigation.
            command.stop(true);
        }
        command.args(&argv);
        if let Some(ref arg0) = arg0 {
            command.arg0(arg0);
        }
        info!("ctx": "init", "op": "run",
            "comm": mask_path(Path::new(arg0.as_ref().map(OsStr::new).unwrap_or(argv0))),
            "argv": argv.into_iter().map(|p| mask_path(Path::new(&p))).collect::<Vec<_>>());

        // Set mount propagation on the root filesystem for mount namespace.
        // Note, we can only mount /proc after the initial clone as we're now pid=1.
        if sandbox.unshare_mount() {
            if let Some(flags) = sandbox.propagation {
                mount(Some("none"), "/", NONE, flags, NONE)?;
            }
        }

        // Process bind mounts as necessary.
        if let Some(bind_mounts) = sandbox.collect_bind_mounts() {
            const NONE: Option<&SmallPathBuf> = None::<SmallPathBuf>.as_ref();
            for bind in bind_mounts {
                // Open a FD to the destination path.
                // SAFETY: Do not resolve symbolic links.
                let dfd = match safe_open_path(&bind.dst, OFlag::empty()) {
                    Ok(fd) => fd,
                    Err(Errno::ENOENT) => {
                        info!("ctx": "init", "op": "mount",
                            "mount": bind, "errno": Errno::ENOENT as i32);
                        continue;
                    }
                    Err(errno) => return Err(errno.into()),
                };
                let dst = format!("/proc/self/fd/{}", dfd.as_raw_fd());
                let dst = Path::new(&dst);
                if bind.src.is_relative() {
                    match mount(
                        Some(&bind.src),
                        dst,
                        Some(&bind.src),
                        bind.opt,
                        bind.dat.as_ref(),
                    ) {
                        Ok(_) => info!("ctx": "init", "op": "mount", "mount": bind),
                        Err(errno) => return Err(errno.into()),
                    }
                } else {
                    // Open a FD to the source path.
                    // SAFETY: Do not resolve symbolic links.
                    let sfd = match safe_open_path(&bind.src, OFlag::empty()) {
                        Ok(fd) => fd,
                        Err(Errno::ENOENT) => {
                            // Handle ENOENT gracefully and continue.
                            info!("ctx": "init", "op": "mount",
                                "mount": bind, "errno": Errno::ENOENT as i32);
                            continue;
                        }
                        Err(errno) => return Err(errno.into()),
                    };
                    let src = format!("/proc/self/fd/{}", sfd.as_raw_fd());
                    let src = Path::new(&src);
                    let flags = bind.opt | MsFlags::MS_BIND | MsFlags::MS_REC;
                    mount(Some(src), dst, NONE, flags, NONE)?;
                    info!("ctx": "init", "op": "mount", "mount": bind);
                }
            }
        }

        // Mount private procfs as necessary.
        // The target directory may be under the chroot directory.
        // Use hidepid=2 to hide pid=1.
        // SAFETY: Private procfs is mounted _after_ custom bind mounts
        // to ensure they cannot interfere with this mount.
        if sandbox.unshare_mount() && sandbox.unshare_pid() {
            if let Some(ref proc) = sandbox.proc {
                // Open a FD to the private proc directory.
                // SAFETY: Do not resolve symbolic links.
                let fd = safe_open_path(proc, OFlag::O_DIRECTORY)?;
                let proc = format!("/proc/self/fd/{}", fd.as_raw_fd());
                let proc = Path::new(&proc);
                let flags = MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV;
                mount(Some("proc"), proc, Some("proc"), flags, Some("hidepid=2"))?;
            }
        }

        if let Some(ref root) = sandbox.root {
            // Open a FD to the private proc directory.
            // SAFETY: Do not resolve symbolic links.
            let fd = safe_open_path(root, OFlag::O_DIRECTORY)?;

            // For subsequent actions the current directory must equal root.
            fchdir(fd.as_raw_fd())?;

            // All preparations were done in the parent, let's chroot into cwd.
            chroot(".")?;

            // Almost there, let's reensure our current working directory equals root.
            chdir("/")?;
        }

        // Clean up the environment as necessary.
        if !sandbox.allow_unsafe_env() {
            for &var in UNSAFE_ENV {
                let var = OsStr::from_bytes(var);
                if !envp.map(|envp| envp.contains(var)).unwrap_or(false)
                    && env::var_os(var).is_some()
                {
                    info!("ctx": "init", "op": "unset_environment",
                        "var": mask_path(Path::new(var)));
                    env::remove_var(var);
                }
            }
        }

        // Set up the Landlock sandbox if requested. Note,
        // we set it up here before spawning the child so as to
        // include the syd sandbox threads into the sandbox as
        // well. This is done for added security.
        // Note, Landlock errors are not fatal.
        if let Some((mut path_ro, mut path_rw)) = sandbox.collect_landlock() {
            path_ro.sort();
            path_rw.sort();

            match crate::landlock_operation(&path_ro, &path_rw, LANDLOCK_ABI) {
                Ok(status) => {
                    info!("ctx": "init",
                    "op": "landlock",
                    "abi": LANDLOCK_ABI_STR,
                    "path_ro": path_ro,
                    "path_rw": path_rw,
                    "status": match status.ruleset {
                        // The FullyEnforced case must be tested by the developer.
                        RulesetStatus::FullyEnforced => "fully_enforced",
                        RulesetStatus::PartiallyEnforced => "partially_enforced",
                        // Users should be warned that they are not protected.
                        RulesetStatus::NotEnforced => "not_enforced",
                    });
                }
                Err(_) => {
                    info!("ctx": "init",
                        "op": "landlock",
                        "abi": LANDLOCK_ABI_STR,
                        "path_ro": path_ro,
                        "path_rw": path_rw,
                        "status": "unsupported");
                }
            }
        }

        // Step 6: Initialize sandbox supervisor.
        let supervisor = Supervisor::new(sandbox, export)?;

        // Step 7: Clear the environment variables starting with "SYD_".
        // SYD_TEST_* is a safe exception for integration tests...
        let pre = OsStr::new("SYD_");
        let tst = OsStr::new("SYD_TEST_");
        let env = [
            OsStr::new("SYD_LOG"),
            OsStr::new("SYD_LOG_FD"),
            OsStr::new("SYD_NO_CROSS_MEMORY_ATTACH"),
            OsStr::new("SYD_NO_SYSLOG"),
            OsStr::new("SYD_PROF"),
            OsStr::new("SYD_SH"),
            // TODO: Document the ones below:
            OsStr::new("SYD_SECX"),
            OsStr::new("SYD_KEEP_STDOUT"),
        ];
        for (key, _) in std::env::vars_os() {
            if key.as_bytes().starts_with(pre.as_bytes())
                && !key.as_bytes().starts_with(tst.as_bytes())
            {
                let mut remove = true;
                for var in env {
                    if key.as_bytes() == var.as_bytes() {
                        remove = false;
                        break;
                    }
                }
                if remove {
                    std::env::remove_var(&key);
                }
            }
        }

        // Start profiling if requested.
        #[cfg(feature = "prof")]
        if let Some(val) = env::var_os("SYD_PROF") {
            match val.as_bytes() {
                b"cpu" => crate::start_cpu_profile("main"),
                b"mem" => crate::start_mem_profile("main"),
                _ => {}
            }
        };

        // Spawn the program under sandbox.
        let (cache, sandbox) = match supervisor.spawn(command) {
            Ok(result) => result,
            Err(error) => {
                let errno = Errno::last();
                errno::set_errno(errno::Errno(errno as i32));
                return Err(error);
            }
        };

        // Wait for the process to exit and return the same error code.
        #[allow(clippy::disallowed_methods)]
        let result = Supervisor::wait(cache, sandbox).map_err(|error| {
            errno::set_errno(errno::Errno(
                error.errno().map(|e| e as i32).unwrap_or(nix::libc::ENOSYS),
            ));
            error
        });

        // End profiling if requested.
        #[cfg(feature = "prof")]
        if let Some(val) = env::var_os("SYD_PROF") {
            match val.as_bytes() {
                b"cpu" => crate::stop_cpu_profile(),
                b"mem" => {
                    crate::dump_mem_profile("main");
                    crate::stop_mem_profile();
                }
                _ => {}
            }
        }

        // Finally return the result to the caller.
        result
    }
}

/// Processes the address family of a `SockaddrStorage` object and performs logging or other
/// required operations specific to the syscall being handled.
///
/// This helper function isolates the logic involved in dealing with different address families
/// and reduces code duplication across different syscall handler functions.
///
/// # Parameters
///
/// - `addr`: Reference to a `SockaddrStorage`, representing the socket address involved in the syscall.
/// - `syscall_name`: A string slice holding the name of the syscall being handled, used for logging purposes.
///
/// # Safety
///
/// The function contains unsafe blocks due to potential TOCTOU (Time-of-Check Time-of-Use)
/// vulnerabilities. Each unsafe block within this function has been annotated with a detailed
/// safety comment to ensure that unsafe operations are used correctly and securely.
///
/// # Errors
///
/// The function returns an `io::Error` in cases where:
/// - The conversion from `SockaddrStorage` to a specific address family representation fails.
/// - Any other unexpected error condition occurs during the processing of the address family.
///
/// # Returns
///
/// Returns an `Result<(), Errno>`:
/// - `Ok(())` if the processing is successful.
/// - `Err(Errno)` containing a description of the error, if any error occurs during processing.
fn sandbox_addr(
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    addr: &SockaddrStorage,
    root: &Option<AddrRoot>,
    op: u8,
    caps: Capability,
) -> Result<(), Errno> {
    match addr.family() {
        Some(AddressFamily::Unix) => {
            sandbox_addr_unix(proc, request, sandbox, addr, root, op, caps)
        }
        Some(AddressFamily::Inet) => sandbox_addr_inet(proc, request, sandbox, addr, op, caps),
        Some(AddressFamily::Inet6) => sandbox_addr_inet6(proc, request, sandbox, addr, op, caps),
        Some(_) | None => sandbox_addr_notsup(proc, request, sandbox),
    }
}

/// Process a `AddressFamily::Unix` socket address.
#[allow(clippy::cognitive_complexity)]
fn sandbox_addr_unix(
    _proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    addr: &SockaddrStorage,
    root: &Option<AddrRoot>,
    op: u8,
    caps: Capability,
) -> Result<(), Errno> {
    let addr = addr.as_unix_addr().ok_or(Errno::EINVAL)?;
    let (path, abs) = match (addr.path(), addr.as_abstract()) {
        (Some(path), _) => match root {
            Some(AddrRoot::File((path, _))) => (path.clone(), false),
            Some(AddrRoot::Path(root)) => {
                let mut p = SmallPathBuf::new();
                p.push(root.as_ref());
                let path = path.as_os_str().as_bytes();
                let null = memchr::memchr(0, path).unwrap_or(path.len());
                p.push(Path::new(OsStr::from_bytes(&path[..null])));
                (p, false)
            }
            None => {
                let mut p = SmallPathBuf::new();
                let path = path.as_os_str().as_bytes();
                let null = memchr::memchr(0, path).unwrap_or(path.len());
                p.push(Path::new(OsStr::from_bytes(&path[..null])));
                (p, false)
            }
        },
        (_, Some(path)) => {
            let null = memchr::memchr(0, path).unwrap_or(path.len());
            let path = SmallPathBuf::from(&path[..null]);
            (path, true)
        }
        _ => {
            // Unnamed UNIX socket.
            return Ok(());
        }
    };

    // Check for access.
    let (action, filter) = request.check_unix(sandbox, caps, &path);

    if !filter {
        let req = request.get_request();
        warn!("ctx": "access", "act": action,
            "cap": caps, "pid": req.pid, "sys": op2name(op),
            "abs": abs, "unix": path,
            "args": req.data.args, "arch": SydArch(req.data.arch),
            "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
    }

    match action {
        Action::Allow | Action::Warn => Ok(()),
        Action::Deny | Action::Filter => Err(op2errno(op)),
        Action::Exit => std::process::exit(op2errno(op) as i32),
        action => {
            // Stop|Kill
            let _ = request.kill(action, false);
            Err(op2errno(op))
        }
    }
}

/// Process an `AddressFamily::Inet` socket address.
#[allow(clippy::cognitive_complexity)]
fn sandbox_addr_inet(
    _proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    addr: &SockaddrStorage,
    op: u8,
    caps: Capability,
) -> Result<(), Errno> {
    let addr = addr.as_sockaddr_in().ok_or(Errno::EINVAL)?;
    let port = addr.port();
    let addr = IpAddr::V4(addr.ip());

    // Check for access.
    let (action, filter) = request.check_ip(sandbox, caps, addr, port);

    if !filter {
        let req = request.get_request();
        warn!("ctx": "access", "act": action,
            "pid": req.pid, "sys": op2name(op),
            "arch": SydArch(req.data.arch),
            "args": req.data.args,
            "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
            "cap": caps, "ipv": 4,
            "addr": format!("{addr}!{port}"));
    }

    match action {
        Action::Allow | Action::Warn => Ok(()),
        Action::Deny | Action::Filter => Err(op2errno(op)),
        Action::Exit => std::process::exit(op2errno(op) as i32),
        action => {
            // Stop|Kill
            let _ = request.kill(action, false);
            Err(op2errno(op))
        }
    }
}

/// Process an `AddressFamily::Inet6` socket address.
#[allow(clippy::cognitive_complexity)]
fn sandbox_addr_inet6(
    _proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    addr: &SockaddrStorage,
    op: u8,
    caps: Capability,
) -> Result<(), Errno> {
    let addr = addr.as_sockaddr_in6().ok_or(Errno::EINVAL)?;
    let port = addr.port();
    // Check if the IPv6 address is a mapped IPv4 address
    let (addr, ipv) = if let Some(v4addr) = addr.ip().to_ipv4_mapped() {
        // It's a mapped IPv4 address, convert to IPv4
        (IpAddr::V4(v4addr), 4)
    } else {
        // It's a regular IPv6 address
        (IpAddr::V6(addr.ip()), 6)
    };

    // Check for access.
    let (action, filter) = request.check_ip(sandbox, caps, addr, port);

    if !filter {
        let req = request.get_request();
        warn!("ctx": "access", "act": action,
            "pid": req.pid, "sys": op2name(op),
            "arch": SydArch(req.data.arch),
            "args": req.data.args,
            "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
            "cap": caps, "ipv": ipv,
            "addr": format!("{addr}!{port}"));
    }

    match action {
        Action::Allow | Action::Warn => Ok(()),
        Action::Deny | Action::Filter => Err(op2errno(op)),
        Action::Exit => std::process::exit(op2errno(op) as i32),
        action => {
            // Stop|Kill
            let _ = request.kill(action, false);
            Err(op2errno(op))
        }
    }
}

/// Process a socket address of an unsupported socket family.
fn sandbox_addr_notsup(
    _proc: &RemoteProcess,
    _request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
) -> Result<(), Errno> {
    if sandbox.allow_unsupp_socket() {
        Ok(())
    } else {
        Err(Errno::EAFNOSUPPORT)
    }
}

/// Process the first path argument.
#[allow(clippy::cognitive_complexity)]
fn sandbox_path_1(
    path: &Path,
    caps: Capability,
    syscall_name: &str,
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
) -> Result<(), Errno> {
    if caps.is_empty() {
        return Err(Errno::EINVAL);
    }

    let mut action = Action::Allow;
    let mut filter = false;
    let mut deny_errno = Errno::EACCES;

    // Sandboxing
    if caps.contains(Capability::CAP_READ) {
        (action, filter) = request.check_path(sandbox, Capability::CAP_READ, path);
    }
    if caps.contains(Capability::CAP_STAT) {
        let (new_action, new_filter) = request.check_path(sandbox, Capability::CAP_STAT, path);
        if new_action >= action {
            action = new_action;
            filter = new_filter;
            deny_errno = Errno::ENOENT; // Hiding not denying!
        }
    }
    if caps.contains(Capability::CAP_WRITE) {
        let (new_action, new_filter) = request.check_path(sandbox, Capability::CAP_WRITE, path);
        if new_action >= action {
            action = new_action;
            filter = new_filter;
        }
    }
    if caps.contains(Capability::CAP_EXEC) {
        let (new_action, new_filter) = request.check_path(sandbox, Capability::CAP_EXEC, path);
        if new_action >= action {
            action = new_action;
            filter = new_filter;
        }
    }
    if caps.contains(Capability::CAP_IOCTL) {
        let (new_action, new_filter) = request.check_path(sandbox, Capability::CAP_IOCTL, path);
        if new_action >= action {
            action = new_action;
            filter = new_filter;
        }
    }

    // Check if path is hidden for stat sandbox compat.
    // Do this only when we want to report the access violation.
    // Do not do hidden check for memory fds.
    let hidden = if !filter
        && action == Action::Deny
        && !Capability::mem(syscall_name)
        && !caps.contains(Capability::CAP_STAT)
    {
        request.is_hidden(sandbox, path)
    } else {
        false
    };

    if !filter && !hidden {
        let req = request.get_request();
        warn!("ctx": "access", "act": action,
            "cap": caps, "pid": req.pid, "sys": syscall_name,
            "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
            "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
    }

    match action {
        Action::Allow | Action::Warn => {
            if caps.contains(Capability::CAP_WRITE) && request.is_append(sandbox, path) {
                // SAFETY: Protect append-only paths against writes.
                // We use UnknownErrno which will result in a no-op.
                Err(Errno::UnknownErrno)
            } else {
                Ok(())
            }
        }
        Action::Deny | Action::Filter => Err(deny_errno),
        Action::Exit => std::process::exit(deny_errno as i32),
        action => {
            // Stop|Kill
            let _ = request.kill(action, false);
            Err(deny_errno)
        }
    }
}

/// Process both the first and the second path argument.
fn sandbox_path_2(
    paths: &[SmallPathBuf],
    caps: Capability,
    syscall_name: &str,
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
) -> Result<(), Errno> {
    sandbox_path_1(&paths[0], caps, syscall_name, request, sandbox)?;
    sandbox_path_1(&paths[1], caps, syscall_name, request, sandbox)?;
    Ok(())
}

/*
 * System call handlers
 */
pub(crate) fn sys_unknown(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, _proc: &RemoteProcess, sandbox: SandboxGuard| {
            #[allow(clippy::cast_possible_wrap)]
            if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
                // Lock is on, return ENOSYS.
                Err(Errno::ENOSYS)
            } else {
                // Return syd API version.
                Ok(request.return_syscall(3))
            }
        }
    )
}

fn sys_uname(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, proc: &RemoteProcess| {
        if req.data.args[0] == 0 {
            return Err(Errno::EFAULT);
        }

        let mut name = MaybeUninit::<nix::libc::utsname>::uninit();
        // SAFETY: In libc we trust.
        if unsafe { nix::libc::uname(name.as_mut_ptr()) } != 0 {
            return Err(Errno::last());
        }

        // SAFETY: uname() has initialized `name` if it succeeded.
        let mut name = unsafe { name.assume_init() };

        // Wipe the version fields with zeros.
        // SAFETY: Unsafe is needed because we are directly manipulating
        // C structure fields. Here we trust the return value of
        // uname(2).
        unsafe {
            std::ptr::write_bytes(name.version.as_mut_ptr(), 0, 65);
        }

        // SAFETY: The use of `from_raw_parts` here is safe because
        // `name` is fully initialized at this point by the preceding
        // `uname` system call, ensuring that the memory region from
        // `&name` up to the size of `nix::libc::utsname` is valid. The
        // conversion to a byte slice is done to facilitate copying the
        // structure to another memory location without altering its
        // contents. This operation does not extend the lifetime of
        // `name` beyond this function, nor does it modify the content
        // of `name`, adhering to Rust's safety and borrowing rules.
        let name = unsafe {
            std::slice::from_raw_parts(
                std::ptr::addr_of!(name) as *const u8,
                std::mem::size_of_val(&name),
            )
        };

        // Write the serialized `name` to the memory address specified in `req.data.args[0]`.
        #[allow(clippy::cast_possible_truncation)]
        proc.write_mem(name, req.data.args[0], request)?;

        Ok(request.return_syscall(0))
    })
}

fn sys_brk(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_mem_handler(request, "brk", Capability::CAP_MEM)
}

fn sys_mmap(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_mem_handler(
        request,
        "mmap",
        Capability::CAP_MEM | Capability::CAP_EXEC | Capability::CAP_FORCE | Capability::CAP_TPE,
    )
}

fn sys_mmap2(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_mem_handler(
        request,
        "mmap2",
        Capability::CAP_MEM | Capability::CAP_EXEC | Capability::CAP_FORCE | Capability::CAP_TPE,
    )
}

fn sys_mremap(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let old_size = req.data.args[1];
    let new_size = req.data.args[2];
    if new_size < old_size {
        // SAFETY: System call wants to shrink memory.
        return unsafe { request.continue_syscall() };
    }
    syscall_mem_handler(request, "mremap", Capability::CAP_MEM)
}

#[allow(clippy::cognitive_complexity)]
fn syscall_mem_handler(
    request: &UNotifyEventRequest,
    name: &str,
    caps: Capability,
) -> ScmpNotifResp {
    let req = request.get_request();

    // Get mem & vm max.
    let sandbox = request.get_sandbox();
    let caps = sandbox.getcaps(caps);
    let exec = caps.contains(Capability::CAP_EXEC);
    let force = caps.contains(Capability::CAP_FORCE);
    let tpe = caps.contains(Capability::CAP_TPE);
    let mem = caps.contains(Capability::CAP_MEM);
    let mem_max = sandbox.mem_max;
    let mem_vm_max = sandbox.mem_vm_max;
    let mem_act = sandbox.default_action(Capability::CAP_MEM);
    let restrict_stack = !sandbox.allow_unsafe_stack();

    if !exec && !force && !tpe && !restrict_stack && (!mem || (mem_max == 0 && mem_vm_max == 0)) {
        // SAFETY: No pointer dereference in security check.
        // This is safe to continue.
        return unsafe { request.continue_syscall() };
    }

    if (exec || force || tpe || restrict_stack)
        && matches!(name, "mmap" | "mmap2")
        && req.data.args[2] & PROT_EXEC != 0
        && req.data.args[3] & MAP_ANONYMOUS == 0
    {
        // Check file descriptor for Exec access.
        // Read and Write were already checked at open(2).
        let fd = req.data.args[4] as RawFd;
        if fd < 0 {
            return request.fail_syscall(nix::libc::EBADF);
        }
        // Construct /proc/$pid/fd/$fd and readlink.
        let mut buf0 = itoa::Buffer::new();
        let mut buf1 = itoa::Buffer::new();
        let mut path = TinyPathBuf::new();
        path.push(Path::new("/proc"));
        path.push(Path::new(buf0.format(req.pid)));
        path.push(Path::new("fd"));
        path.push(Path::new(buf1.format(fd)));

        let path = match read_link(&path) {
            Ok(path) if path.is_relative() => {
                // SAFETY: /proc fd symlink does not refer to a
                // filesystem path.
                return request.fail_syscall(nix::libc::EBADF);
            }
            Ok(path) => path,
            Err(_) => return request.fail_syscall(nix::libc::EBADF),
        };

        // Step 1: Check for Exec sandboxing.
        if exec {
            if let Err(errno) = sandbox_path_1(&path, Capability::CAP_EXEC, name, request, &sandbox)
            {
                return request.fail_syscall(errno as i32);
            }
        }

        // Step 2: Check for TPE sandboxing.
        if tpe {
            let action = sandbox.check_tpe(&path);
            if !matches!(action, Action::Allow | Action::Filter) {
                warn!("ctx": "access", "cap": Capability::CAP_TPE,
                    "act": action, "pid": req.pid, "sys": name,
                    "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                    "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
            }
            match action {
                Action::Allow | Action::Warn => {}
                Action::Deny | Action::Filter => return request.fail_syscall(nix::libc::EACCES),
                Action::Exit => std::process::exit(nix::libc::EACCES),
                action => {
                    // Stop|Kill
                    let _ = request.kill(action, false);
                    return request.fail_syscall(nix::libc::EACCES);
                }
            }
        }

        if force || restrict_stack {
            // The following checks require the contents of the file.
            // SAFETY: To avoid TOCTOU, we get the remote FD with
            // pidfd_getfd and read it. Care must be given at this stage
            // to preserve the file offset.
            let proc = RemoteProcess::new(req.pid());
            let result = (|| -> Result<(File, u64), Errno> {
                let fd = proc.get_fd(fd, request).or(Err(Errno::EBADF))?;
                let mut file = File::from(fd);
                let offset = file.stream_position().or(Err(Errno::EBADF))?;
                Ok((file, offset))
            })();

            let (mut file, offset) = match result {
                Ok((file, offset)) => (file, offset),
                Err(errno) => return request.fail_syscall(errno as i32),
            };

            if restrict_stack {
                // Step 3: Check for non-executable stack.
                // An execstack library that is dlopened into an executable
                // that is otherwise mapped no-execstack can change the
                // stack permissions to executable! This has been
                // (ab)used in at least one CVE:
                // https://www.qualys.com/2023/07/19/cve-2023-38408/rce-openssh-forwarded-ssh-agent.txt
                let result = (|file: &mut File| -> Result<(), Errno> {
                    if offset != 0 {
                        file.rewind().or(Err(Errno::EBADF))?;
                    }
                    let exe = ExecutableFile::parse(&mut *file, true).or(Err(Errno::EBADF))?;
                    if matches!(exe, ExecutableFile::Elf { xs: true, .. }) {
                        if !sandbox.filter_path(Capability::CAP_EXEC, &path) {
                            warn!("ctx": "access", "cap": Capability::CAP_EXEC,
                                "act": Action::Kill, "pid": req.pid, "sys": name,
                                "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                                "reason": "execstack", "type": exe.to_string(),
                                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
                        }
                        Err(Errno::EACCES)
                    } else {
                        file.seek(SeekFrom::Start(offset)).or(Err(Errno::EBADF))?;
                        Ok(())
                    }
                })(&mut file);

                if let Err(errno) = result {
                    // SAFETY: On errors we KILL rather than DENY to be
                    // consistent with other memory seccomp filters.
                    if errno == Errno::EACCES {
                        let _ = request.kill(Action::Kill, false);
                    }
                    return request.fail_syscall(errno as i32);
                }
            }

            if force {
                // Step 4: Check for Force sandboxing.
                #[allow(clippy::disallowed_methods)]
                let result = (|| -> Result<Action, IntegrityError> {
                    file.rewind().map_err(err2no).map_err(IntegrityError::Sys)?;
                    let mut reader = BufReader::new(file);
                    let result = sandbox.check_force2(&path, &mut reader);
                    let mut file = reader.into_inner();
                    file.seek(SeekFrom::Start(offset))
                        .map_err(err2no)
                        .map_err(IntegrityError::Sys)?;
                    result
                })();
                let deny = match result {
                    Ok(action) => {
                        if !matches!(action, Action::Allow | Action::Filter) {
                            warn!("ctx": "access", "act": action,
                                "cap": Capability::CAP_FORCE, "sys": name, "pid": req.pid,
                                "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
                        }
                        match action {
                            Action::Allow | Action::Warn => false,
                            Action::Deny | Action::Filter => true,
                            Action::Exit => std::process::exit(nix::libc::EACCES),
                            _ => {
                                // Stop|Kill
                                let _ = request.kill(action, false);
                                true
                            }
                        }
                    }
                    Err(IntegrityError::Sys(errno)) => {
                        warn!("ctx": "access", "act": Action::Deny,
                            "cap": Capability::CAP_FORCE,
                            "sys": name, "pid": req.pid,
                            "arch": SydArch(req.data.arch),
                            "args": req.data.args,
                            "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                            "errno": errno as i32);
                        true
                    }
                    Err(IntegrityError::Hash {
                        action,
                        expected,
                        found,
                    }) => {
                        if action != Action::Filter {
                            warn!("ctx": "access", "act": action,
                                "cap": Capability::CAP_FORCE, "sys": name, "pid": req.pid,
                                "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                                "hash": [expected, found],
                                "errno": Errno::EINVAL as i32);
                        }
                        match action {
                            // Allow cannot happen.
                            Action::Warn => false,
                            Action::Deny | Action::Filter => true,
                            Action::Exit => std::process::exit(nix::libc::EINVAL),
                            _ => {
                                // Stop|Kill
                                let _ = request.kill(action, false);
                                true
                            }
                        }
                    }
                };

                if deny {
                    return request.fail_syscall(nix::libc::EACCES);
                }
            }
        }
        drop(sandbox); // release the read-lock.
    }

    if !mem || (mem_max == 0 && mem_vm_max == 0) {
        // SAFETY:
        // (a) Exec and Memory sandboxing are both disabled.
        // (b) Exec granted access, Memory sandboxing is disabled.
        // The first candidate is safe as sandboxing is disabled,
        // however (b) should theoretically suffer from VFS TOCTOU as
        // the fd can change after the access check. However, our tests
        // show this is not the case, see vfsmod_toctou_mmap integration
        // test.
        return unsafe { request.continue_syscall() };
    }

    // Get process entry.
    #[allow(clippy::cast_possible_wrap)]
    let proc = match Process::new(req.pid as nix::libc::pid_t) {
        Ok(proc) => proc,
        Err(_) => return request.fail_syscall(nix::libc::ENOMEM),
    };

    // Check VmSize
    if mem_vm_max > 0 {
        match proc.statm() {
            Ok(statm) if statm.size.saturating_mul(*PAGE_SIZE) >= mem_vm_max => {
                if mem_act != Action::Filter {
                    warn!("ctx": "access", "cap": Capability::CAP_MEM,
                        "act": mem_act, "pid": req.pid, "sys": name,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "max": mem_max, "max_vm": mem_vm_max);
                }
                match mem_act {
                    // Allow cannot happen.
                    Action::Warn => {}
                    Action::Deny | Action::Filter => {
                        return request.fail_syscall(nix::libc::ENOMEM)
                    }
                    Action::Exit => std::process::exit(nix::libc::ENOMEM),
                    _ => {
                        // Stop|Kill
                        let _ = request.kill(mem_act, false);
                        return request.fail_syscall(nix::libc::ENOMEM);
                    }
                }
            }
            Ok(_) => {}
            Err(_) => return request.fail_syscall(nix::libc::ENOMEM),
        }
    }

    // Check PSS
    match proc_mem_limit(&proc, mem_max) {
        Ok(false) => {
            // SAFETY: No pointer dereference in security check.
            unsafe { request.continue_syscall() }
        }
        Ok(true) => {
            if mem_act != Action::Filter {
                warn!("ctx": "access", "cap": Capability::CAP_MEM,
                    "act": mem_act, "pid": req.pid, "sys": name,
                    "arch": SydArch(req.data.arch),
                    "args": req.data.args,
                    "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                    "max": mem_max, "max_vm": mem_vm_max);
            }
            match mem_act {
                // Allow cannot happen.
                Action::Warn => {
                    // SAFETY: No pointer dereference in security check.
                    unsafe { request.continue_syscall() }
                }
                Action::Deny | Action::Filter => request.fail_syscall(nix::libc::ENOMEM),
                Action::Exit => std::process::exit(nix::libc::ENOMEM),
                _ => {
                    // Stop|Kill
                    let _ = request.kill(mem_act, false);
                    request.fail_syscall(nix::libc::ENOMEM)
                }
            }
        }
        Err(_) => request.fail_syscall(nix::libc::ENOMEM),
    }
}

fn sys_fork(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_pid_handler(request, "fork")
}

fn sys_vfork(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_pid_handler(request, "vfork")
}

fn sys_clone(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_pid_handler(request, "clone")
}

#[allow(clippy::cognitive_complexity)]
fn syscall_pid_handler(request: &UNotifyEventRequest, name: &str) -> ScmpNotifResp {
    // Get pid max.
    let sandbox = request.get_sandbox();
    let enabled = sandbox.enabled(Capability::CAP_PID);
    let pid_act = sandbox.default_action(Capability::CAP_PID);
    let pid_max = sandbox.pid_max;
    drop(sandbox); // release the read lock.

    if !enabled || pid_max == 0 {
        // SAFETY: No pointer dereference in security check.
        // This is safe to continue.
        return unsafe { request.continue_syscall() };
    }

    // Get pid count.
    let req = request.get_request();
    match proc_task_limit(pid_max) {
        Ok(false) => {
            // SAFETY: No pointer dereference in security check.
            unsafe { request.continue_syscall() }
        }
        Ok(true) => {
            if pid_act != Action::Filter {
                warn!("ctx": "access", "cap": Capability::CAP_PID,
                    "act": pid_act, "pid": req.pid, "sys": name,
                    "arch": SydArch(req.data.arch),
                    "args": req.data.args,
                    "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                    "max": pid_max);
            }
            match pid_act {
                // Allow cannot happen.
                Action::Warn => {
                    // SAFETY: No pointer dereference in security check.
                    unsafe { request.continue_syscall() }
                }
                Action::Deny | Action::Filter => request.fail_syscall(nix::libc::EACCES),
                Action::Exit => std::process::exit(nix::libc::EACCES),
                _ => {
                    // Stop|Kill
                    // SAFETY:
                    // 1. For effective mitigation, we send the SIGKILL
                    //    signal both to the process and its process group.
                    //    Note, getpgid() may only ever fail with ESRCH
                    //    which we ignore.
                    // 2. An exception to 1 is when the sandbox process
                    //    shares the PGID with Syd in which case we skip
                    //    killpg to prevent DOS.
                    let _ = request.kill(pid_act, true);
                    request.fail_syscall(nix::libc::EACCES)
                }
            }
        }
        Err(_) => request.fail_syscall(nix::libc::EACCES),
    }
}

#[allow(clippy::cognitive_complexity)]
fn sys_setuid(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, _proc: &RemoteProcess| {
        let target_uid =
            Uid::from_raw(nix::libc::uid_t::try_from(req.data.args[0]).or(Err(Errno::EINVAL))?);
        let source_uid = Uid::current();

        if u64::from(target_uid.as_raw()) <= UID_MIN {
            // SAFETY: This is already asserted with the parent
            // seccomp-bpf filter, this is the second layer.
            return Ok(request.return_syscall(0));
        } else if source_uid == target_uid {
            // SAFETY: There's no pointer dereference in the access check.
            return unsafe { Ok(request.continue_syscall()) };
        }

        let sandbox = request.get_sandbox();
        let allowed = sandbox.chk_uid_transit(source_uid, target_uid);
        drop(sandbox); // release the read lock.

        if !allowed {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setuid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_uid": source_uid.as_raw(),
                "target_uid": target_uid.as_raw(),
                "errno": nix::libc::EACCES);
            return Err(Errno::EACCES);
        }

        if let Err(errno) = setuid(target_uid) {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setuid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_uid": source_uid.as_raw(),
                "target_uid": target_uid.as_raw(),
                "errno": errno as i32);
            return Err(errno);
        } else if safe_drop_cap(caps::Capability::CAP_SETUID).is_err() {
            // SAFETY: We cannot do much on errors,
            // and on panic the thread will be restarted.
            // The best we can do from a security POV is
            // to enter Ghost mode. This is certainly
            // unexpected but it's safe.
            return Err(Errno::EOWNERDEAD);
        }

        // SAFETY: There's no pointer dereference in the access check.
        unsafe { Ok(request.continue_syscall()) }
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_setgid(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, _proc: &RemoteProcess| {
        let target_gid =
            Gid::from_raw(nix::libc::gid_t::try_from(req.data.args[0]).or(Err(Errno::EINVAL))?);
        let source_gid = Gid::current();

        if u64::from(target_gid.as_raw()) <= GID_MIN {
            // SAFETY: This is already asserted with the parent
            // seccomp-bpf filter, this is the second layer.
            return Ok(request.return_syscall(0));
        } else if source_gid == target_gid {
            // SAFETY: There's no pointer dereference in the access check.
            return unsafe { Ok(request.continue_syscall()) };
        }

        let sandbox = request.get_sandbox();
        let allowed = sandbox.chk_gid_transit(source_gid, target_gid);
        drop(sandbox); // release the read lock.

        if !allowed {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setgid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_gid": source_gid.as_raw(),
                "target_gid": target_gid.as_raw(),
                "errno": nix::libc::EACCES);
            return Err(Errno::EACCES);
        }

        if let Err(errno) = setgid(target_gid) {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setgid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_gid": source_gid.as_raw(),
                "target_gid": target_gid.as_raw(),
                "errno": errno as i32);
            return Err(errno);
        } else if safe_drop_cap(caps::Capability::CAP_SETGID).is_err() {
            // SAFETY: We cannot do much on errors,
            // and on panic the thread will be restarted.
            // The best we can do from a security POV is
            // to enter Ghost mode. This is certainly
            // unexpected but it's safe.
            return Err(Errno::EOWNERDEAD);
        }

        // SAFETY: There's no pointer dereference in the access check.
        unsafe { Ok(request.continue_syscall()) }
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_setreuid(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, _proc: &RemoteProcess| {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_ruid = match req.data.args[0] as i32 {
            -1 => None,
            n if n >= 0 => Some(Uid::from_raw(
                nix::libc::uid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_euid = match req.data.args[1] as i32 {
            -1 => None,
            n if n >= 0 => Some(Uid::from_raw(
                nix::libc::uid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };

        if target_ruid.is_none() && target_euid.is_none() {
            return Ok(request.return_syscall(0));
        }

        // getresuid can only fail with EFAULT which should not happen.
        let resuid = getresuid()?;
        let source_ruid = resuid.real;
        let source_euid = resuid.effective;

        let mut change = false;
        if let Some(target_ruid) = target_ruid {
            if u64::from(target_ruid.as_raw()) <= UID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_ruid != target_ruid {
                change = true;
            }
        }
        if let Some(target_euid) = target_euid {
            if u64::from(target_euid.as_raw()) <= UID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_euid != target_euid {
                change = true;
            }
        }

        if !change {
            // SAFETY: There's no pointer dereference in the access check.
            return unsafe { Ok(request.continue_syscall()) };
        }

        // SAFETY: We do not support RUID != EUID
        if let Some(target_ruid) = target_ruid {
            if let Some(target_euid) = target_euid {
                if target_ruid != target_euid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setreuid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_ruid": source_ruid.as_raw(),
                        "target_ruid": target_ruid.as_raw(),
                        "source_euid": source_euid.as_raw(),
                        "target_euid": target_euid.as_raw(),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }

        let sandbox = request.get_sandbox();
        let mut allowed = true;
        if let Some(target_ruid) = target_ruid {
            if !sandbox.chk_uid_transit(source_ruid, target_ruid) {
                allowed = false;
            }
        }
        if allowed {
            if let Some(target_euid) = target_euid {
                if !sandbox.chk_uid_transit(source_euid, target_euid) {
                    allowed = false;
                }
            }
        }
        drop(sandbox); // release the read lock.

        let target_ruid = target_ruid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
        let target_euid = target_euid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
        if !allowed {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setreuid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_ruid": source_ruid.as_raw(),
                "target_ruid": target_ruid,
                "source_euid": source_euid.as_raw(),
                "target_euid": target_euid,
                "errno": nix::libc::EACCES);
            return Err(Errno::EACCES);
        }

        // SAFETY: nix version of setreuid does not allow -1 as argument.
        if unsafe { nix::libc::syscall(nix::libc::SYS_setreuid, target_ruid, target_euid) } == -1 {
            let errno = Errno::last();
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setreuid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_ruid": source_ruid.as_raw(),
                "target_ruid": target_ruid,
                "source_euid": source_euid.as_raw(),
                "target_euid": target_euid,
                "errno": errno as i32);
            return Err(errno);
        } else if safe_drop_cap(caps::Capability::CAP_SETUID).is_err() {
            // SAFETY: We cannot do much on errors,
            // and on panic the thread will be restarted.
            // The best we can do from a security POV is
            // to enter Ghost mode. This is certainly
            // unexpected but it's safe.
            return Err(Errno::EOWNERDEAD);
        }

        // SAFETY: There's no pointer dereference in the access check.
        unsafe { Ok(request.continue_syscall()) }
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_setregid(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, _proc: &RemoteProcess| {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_rgid = match req.data.args[0] as i32 {
            -1 => None,
            n if n >= 0 => Some(Gid::from_raw(
                nix::libc::gid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_egid = match req.data.args[1] as i32 {
            -1 => None,
            n if n >= 0 => Some(Gid::from_raw(
                nix::libc::gid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };

        if target_rgid.is_none() && target_egid.is_none() {
            return Ok(request.return_syscall(0));
        }

        // getresgid can only fail with EFAULT which should not happen.
        let resgid = getresgid()?;
        let source_rgid = resgid.real;
        let source_egid = resgid.effective;

        let mut change = false;
        if let Some(target_rgid) = target_rgid {
            if u64::from(target_rgid.as_raw()) <= GID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_rgid != target_rgid {
                change = true;
            }
        }
        if let Some(target_egid) = target_egid {
            if u64::from(target_egid.as_raw()) <= GID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_egid != target_egid {
                change = true;
            }
        }

        if !change {
            // SAFETY: There's no pointer dereference in the access check.
            return unsafe { Ok(request.continue_syscall()) };
        }

        // SAFETY: We do not support Rgid != Egid
        if let Some(target_rgid) = target_rgid {
            if let Some(target_egid) = target_egid {
                if target_rgid != target_egid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setregid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_rgid": source_rgid.as_raw(),
                        "target_rgid": target_rgid.as_raw(),
                        "source_egid": source_egid.as_raw(),
                        "target_egid": target_egid.as_raw(),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }

        let sandbox = request.get_sandbox();
        let mut allowed = true;
        if let Some(target_rgid) = target_rgid {
            if !sandbox.chk_gid_transit(source_rgid, target_rgid) {
                allowed = false;
            }
        }
        if allowed {
            if let Some(target_egid) = target_egid {
                if !sandbox.chk_gid_transit(source_egid, target_egid) {
                    allowed = false;
                }
            }
        }
        drop(sandbox); // release the read lock.

        let target_rgid = target_rgid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
        let target_egid = target_egid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
        if !allowed {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setregid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_rgid": source_rgid.as_raw(),
                "target_rgid": target_rgid,
                "source_egid": source_egid.as_raw(),
                "target_egid": target_egid,
                "errno": nix::libc::EACCES);
            return Err(Errno::EACCES);
        }

        // SAFETY: nix version of setregid does not allow -1 as argument.
        if unsafe { nix::libc::syscall(nix::libc::SYS_setregid, target_rgid, target_egid) } == -1 {
            let errno = Errno::last();
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setregid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_rgid": source_rgid.as_raw(),
                "target_rgid": target_rgid,
                "source_egid": source_egid.as_raw(),
                "target_egid": target_egid,
                "errno": errno as i32);
            return Err(errno);
        } else if safe_drop_cap(caps::Capability::CAP_SETGID).is_err() {
            // SAFETY: We cannot do much on errors,
            // and on panic the thread will be restarted.
            // The best we can do from a security POV is
            // to enter Ghost mode. This is certainly
            // unexpected but it's safe.
            return Err(Errno::EOWNERDEAD);
        }

        // SAFETY: There's no pointer dereference in the access check.
        unsafe { Ok(request.continue_syscall()) }
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_setresuid(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, _proc: &RemoteProcess| {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_ruid = match req.data.args[0] as i32 {
            -1 => None,
            n if n >= 0 => Some(Uid::from_raw(
                nix::libc::uid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_euid = match req.data.args[1] as i32 {
            -1 => None,
            n if n >= 0 => Some(Uid::from_raw(
                nix::libc::uid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_suid = match req.data.args[2] as i32 {
            -1 => None,
            n if n >= 0 => Some(Uid::from_raw(
                nix::libc::uid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };

        if target_ruid.is_none() && target_euid.is_none() && target_suid.is_none() {
            return Ok(request.return_syscall(0));
        }

        // getresuid can only fail with EFAULT which should not happen.
        let resuid = getresuid()?;
        let source_ruid = resuid.real;
        let source_euid = resuid.effective;
        let source_suid = resuid.saved;

        let mut change = false;
        if let Some(target_ruid) = target_ruid {
            if u64::from(target_ruid.as_raw()) <= UID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_ruid != target_ruid {
                change = true;
            }
        }
        if let Some(target_euid) = target_euid {
            if u64::from(target_euid.as_raw()) <= UID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_euid != target_euid {
                change = true;
            }
        }
        if let Some(target_suid) = target_suid {
            if u64::from(target_suid.as_raw()) <= UID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_suid != target_suid {
                change = true;
            }
        }

        if !change {
            // SAFETY: There's no pointer dereference in the access check.
            return unsafe { Ok(request.continue_syscall()) };
        }

        // SAFETY: We do not support RUID != EUID != SUID
        if let Some(target_ruid) = target_ruid {
            if let Some(target_euid) = target_euid {
                if target_ruid != target_euid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setresuid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_ruid": source_ruid.as_raw(),
                        "target_ruid": target_ruid.as_raw(),
                        "source_euid": source_euid.as_raw(),
                        "target_euid": target_euid.as_raw(),
                        "source_suid": source_suid.as_raw(),
                        "target_suid": target_suid.map(|u| u.as_raw()),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }
        if let Some(target_ruid) = target_ruid {
            if let Some(target_suid) = target_suid {
                if target_ruid != target_suid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setresuid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_ruid": source_ruid.as_raw(),
                        "target_ruid": target_ruid.as_raw(),
                        "source_euid": source_euid.as_raw(),
                        "target_euid": target_euid.map(|u| u.as_raw()),
                        "source_suid": source_suid.as_raw(),
                        "target_suid": target_suid.as_raw(),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }
        if let Some(target_euid) = target_euid {
            if let Some(target_suid) = target_suid {
                if target_euid != target_suid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setresuid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_ruid": source_ruid.as_raw(),
                        "target_ruid": target_ruid.map(|u| u.as_raw()),
                        "source_euid": source_euid.as_raw(),
                        "target_euid": target_euid.as_raw(),
                        "source_suid": source_suid.as_raw(),
                        "target_suid": target_suid.as_raw(),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }

        let sandbox = request.get_sandbox();
        let mut allowed = true;
        if let Some(target_ruid) = target_ruid {
            if !sandbox.chk_uid_transit(source_ruid, target_ruid) {
                allowed = false;
            }
        }
        if allowed {
            if let Some(target_euid) = target_euid {
                if !sandbox.chk_uid_transit(source_euid, target_euid) {
                    allowed = false;
                }
            }
        }
        if allowed {
            if let Some(target_suid) = target_suid {
                if !sandbox.chk_uid_transit(source_suid, target_suid) {
                    allowed = false;
                }
            }
        }
        drop(sandbox); // release the read lock.

        let target_ruid = target_ruid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
        let target_euid = target_euid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
        let target_suid = target_suid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
        if !allowed {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setresuid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_ruid": source_ruid.as_raw(),
                "target_ruid": target_ruid,
                "source_euid": source_euid.as_raw(),
                "target_euid": target_euid,
                "source_suid": source_suid.as_raw(),
                "target_suid": target_suid,
                "errno": nix::libc::EACCES);
            return Err(Errno::EACCES);
        }

        // SAFETY: nix version of setreuid does not allow -1 as argument.
        if unsafe {
            nix::libc::syscall(
                nix::libc::SYS_setresuid,
                target_ruid,
                target_euid,
                target_suid,
            )
        } == -1
        {
            let errno = Errno::last();
            warn!("ctx": "access", "sys": "setresuid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_ruid": source_ruid.as_raw(),
                "target_ruid": target_ruid,
                "source_euid": source_euid.as_raw(),
                "target_euid": target_euid,
                "source_suid": source_suid.as_raw(),
                "target_suid": target_suid,
                "errno": errno as i32);
            return Err(errno);
        } else if safe_drop_cap(caps::Capability::CAP_SETUID).is_err() {
            // SAFETY: We cannot do much on errors,
            // and on panic the thread will be restarted.
            // The best we can do from a security POV is
            // to enter Ghost mode. This is certainly
            // unexpected but it's safe.
            return Err(Errno::EOWNERDEAD);
        }

        // SAFETY: There's no pointer dereference in the access check.
        unsafe { Ok(request.continue_syscall()) }
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_setresgid(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, _proc: &RemoteProcess| {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_rgid = match req.data.args[0] as i32 {
            -1 => None,
            n if n >= 0 => Some(Gid::from_raw(
                nix::libc::gid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_egid = match req.data.args[1] as i32 {
            -1 => None,
            n if n >= 0 => Some(Gid::from_raw(
                nix::libc::gid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        let target_sgid = match req.data.args[2] as i32 {
            -1 => None,
            n if n >= 0 => Some(Gid::from_raw(
                nix::libc::gid_t::try_from(n).or(Err(Errno::EINVAL))?,
            )),
            _ => return Err(Errno::EINVAL),
        };

        if target_rgid.is_none() && target_egid.is_none() && target_sgid.is_none() {
            return Ok(request.return_syscall(0));
        }

        // getresgid can only fail with EFAULT which should not happen.
        let resgid = getresgid()?;
        let source_rgid = resgid.real;
        let source_egid = resgid.effective;
        let source_sgid = resgid.saved;

        let mut change = false;
        if let Some(target_rgid) = target_rgid {
            if u64::from(target_rgid.as_raw()) <= GID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_rgid != target_rgid {
                change = true;
            }
        }
        if let Some(target_egid) = target_egid {
            if u64::from(target_egid.as_raw()) <= GID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_egid != target_egid {
                change = true;
            }
        }
        if let Some(target_sgid) = target_sgid {
            if u64::from(target_sgid.as_raw()) <= GID_MIN {
                // SAFETY: This is already asserted with the parent
                // seccomp-bpf filter, this is the second layer.
                return Ok(request.return_syscall(0));
            } else if source_sgid != target_sgid {
                change = true;
            }
        }

        if !change {
            // SAFETY: There's no pointer dereference in the access check.
            return unsafe { Ok(request.continue_syscall()) };
        }

        // SAFETY: We do not support Rgid != Egid != Sgid
        if let Some(target_rgid) = target_rgid {
            if let Some(target_egid) = target_egid {
                if target_rgid != target_egid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setresgid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_rgid": source_rgid.as_raw(),
                        "target_rgid": target_rgid.as_raw(),
                        "source_egid": source_egid.as_raw(),
                        "target_egid": target_egid.as_raw(),
                        "source_sgid": source_sgid.as_raw(),
                        "target_sgid": target_sgid.map(|u| u.as_raw()),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }
        if let Some(target_rgid) = target_rgid {
            if let Some(target_sgid) = target_sgid {
                if target_rgid != target_sgid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setresgid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_rgid": source_rgid.as_raw(),
                        "target_rgid": target_rgid.as_raw(),
                        "source_egid": source_egid.as_raw(),
                        "target_egid": target_egid.map(|u| u.as_raw()),
                        "source_sgid": source_sgid.as_raw(),
                        "target_sgid": target_sgid.as_raw(),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }
        if let Some(target_egid) = target_egid {
            if let Some(target_sgid) = target_sgid {
                if target_egid != target_sgid {
                    warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                        "sys": "setresgid", "pid": req.pid,
                        "arch": SydArch(req.data.arch),
                        "args": req.data.args,
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                        "source_rgid": source_rgid.as_raw(),
                        "target_rgid": target_rgid.map(|u| u.as_raw()),
                        "source_egid": source_egid.as_raw(),
                        "target_egid": target_egid.as_raw(),
                        "source_sgid": source_sgid.as_raw(),
                        "target_sgid": target_sgid.as_raw(),
                        "errno": nix::libc::EACCES);
                    return Err(Errno::EACCES);
                }
            }
        }

        let sandbox = request.get_sandbox();
        let mut allowed = true;
        if let Some(target_rgid) = target_rgid {
            if !sandbox.chk_gid_transit(source_rgid, target_rgid) {
                allowed = false;
            }
        }
        if allowed {
            if let Some(target_egid) = target_egid {
                if !sandbox.chk_gid_transit(source_egid, target_egid) {
                    allowed = false;
                }
            }
        }
        if allowed {
            if let Some(target_sgid) = target_sgid {
                if !sandbox.chk_gid_transit(source_sgid, target_sgid) {
                    allowed = false;
                }
            }
        }
        drop(sandbox); // release the read lock.

        let target_rgid = target_rgid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
        let target_egid = target_egid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
        let target_sgid = target_sgid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
        if !allowed {
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setresgid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_rgid": source_rgid.as_raw(),
                "target_rgid": target_rgid,
                "source_egid": source_egid.as_raw(),
                "target_egid": target_egid,
                "source_sgid": source_sgid.as_raw(),
                "target_sgid": target_sgid,
                "errno": nix::libc::EACCES);
            return Err(Errno::EACCES);
        }

        // SAFETY: nix version of setregid does not allow -1 as argument.
        if unsafe {
            nix::libc::syscall(
                nix::libc::SYS_setresgid,
                target_rgid,
                target_egid,
                target_sgid,
            )
        } == -1
        {
            let errno = Errno::last();
            warn!("ctx": "access", "cap": 'd', "act": Action::Deny,
                "sys": "setresgid", "pid": req.pid,
                "arch": SydArch(req.data.arch),
                "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                "source_rgid": source_rgid.as_raw(),
                "target_rgid": target_rgid,
                "source_egid": source_egid.as_raw(),
                "target_egid": target_egid,
                "source_sgid": source_sgid.as_raw(),
                "target_sgid": target_sgid,
                "errno": errno as i32);
            return Err(errno);
        } else if safe_drop_cap(caps::Capability::CAP_SETGID).is_err() {
            // SAFETY: We cannot do much on errors,
            // and on panic the thread will be restarted.
            // The best we can do from a security POV is
            // to enter Ghost mode. This is certainly
            // unexpected but it's safe.
            return Err(Errno::EOWNERDEAD);
        }

        // SAFETY: There's no pointer dereference in the access check.
        unsafe { Ok(request.continue_syscall()) }
    })
}

fn sys_kill(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    if req.data.args[1] == 0 {
        // SAFETY: Allow signal 0, ie checking if pid1 is alive.
        return unsafe { request.continue_syscall() };
    } else {
        // SAFETY: Guard against group signals.
        // 1. kill(0, 9) -> Send signal to _current_ process group.
        // 2. kill(-pgrp, 9) -> Send signal to the _given_ process group.
        // -1 and -pid are checked in the signal handler.
        #[allow(clippy::cast_possible_truncation)]
        match req.data.args[0] as nix::libc::pid_t {
            0 => {
                return match proc_pgrp(req.pid()) {
                    Ok(pgrp) if pgrp != getpgrp() => {
                        // SAFETY: This is a version of killpg().
                        // We must stop this signal if Syd is in
                        // the same process group as the process,
                        // otherwise continue is safe.
                        unsafe { request.continue_syscall() }
                    }
                    _ => request.fail_syscall(Errno::EACCES as i32),
                };
            }
            pgrp if pgrp < 0 && pgrp.abs() == getpgrp().as_raw() => {
                // SAFETY: This group signal is directed to Syd's process group, deny.
                return request.fail_syscall(Errno::EACCES as i32);
            }
            _ => {}
        }
    }

    syscall_signal_handler(request, false)
}

fn sys_tkill(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    if req.data.args[1] == 0 {
        // SAFETY: Allow signal 0, ie checking if pid1 is alive.
        return unsafe { request.continue_syscall() };
    }

    syscall_signal_handler(request, false)
}

fn sys_tgkill(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    if req.data.args[2] == 0 {
        // SAFETY: Allow signal 0, ie checking if pid1 is alive.
        return unsafe { request.continue_syscall() };
    }

    syscall_signal_handler(request, true)
}

fn sys_pidfd_open(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_signal_handler(request, false)
}

#[allow(clippy::cognitive_complexity)]
fn sys_socketcall(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let (req, proc) = request.prepare();

    // Determine system call
    // 0x1 => socket
    // 0x2 => bind
    // 0x3 => connect
    // 0x5 => accept
    // 0x6 => getsockname
    // 0xb => sendto
    // 0x10 => sendmsg
    // 0x12 => accept4
    // 0x14 => sendmmsg
    #[allow(clippy::cast_possible_truncation)]
    let op = match req.data.args[0] {
        n @ (0x1 | 0x2 | 0x3 | 0x5 | 0x6 | 0xb | 0x10 | 0x12 | 0x14) => n as u8,
        _ => {
            // SAFETY: Safe network call, continue.
            // No pointer-dereference in access check.
            return unsafe { request.continue_syscall() };
        }
    };

    // Determine system call arguments.
    // On x86 unsigned long is 4 bytes, and on s390x 8 bytes.
    let is32 = scmp_arch_bits(req.data.arch) == 32;
    let sizeof_ulong: usize = if is32 { 4 } else { 8 };
    const ARGLEN: usize = 6;
    let mut args = [0u64; ARGLEN];
    #[allow(clippy::arithmetic_side_effects)]
    let bufsiz = sizeof_ulong * ARGLEN;
    let mut buf = Vec::new();
    if buf.try_reserve(bufsiz).is_err() {
        return request.fail_syscall(nix::libc::ENOMEM);
    }
    buf.resize(bufsiz, 0);
    match proc.read_mem(&mut buf, req.data.args[1], request) {
        Ok(n) if n == bufsiz => {
            for (i, chunk) in buf.chunks_exact(sizeof_ulong).enumerate() {
                match sizeof_ulong {
                    4 => match chunk.try_into() {
                        Ok(bytes) => args[i] = u64::from(u32::from_ne_bytes(bytes)),
                        Err(_) => return request.fail_syscall(nix::libc::EFAULT),
                    },
                    8 => match chunk.try_into() {
                        Ok(bytes) => args[i] = u64::from_ne_bytes(bytes),
                        Err(_) => return request.fail_syscall(nix::libc::EFAULT),
                    },
                    _ => {
                        // SAFETY: The is32 check above
                        // ensures this branch is never reached.
                        unsafe { std::hint::unreachable_unchecked() };
                    }
                }
            }
        }
        _ => {
            // Short read or error.
            return request.fail_syscall(nix::libc::EFAULT);
        }
    }

    syscall_network_handler(request, &args, op)
}

fn sys_socket(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x1)
}

fn sys_bind(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x2)
}

fn sys_accept(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x5)
}

fn sys_accept4(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x12)
}

fn sys_getsockname(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x6)
}

fn sys_connect(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x3)
}

fn sys_sendto(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0xb)
}

fn sys_sendmsg(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x10)
}

fn sys_sendmmsg(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    syscall_network_handler(request, &req.data.args, 0x14)
}

fn sys_execve(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let arg = SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        ..Default::default()
    };
    syscall_exec_handler(request, "execve", &arg)
}

fn sys_execveat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let empty = req.data.args[4] & nix::libc::AT_EMPTY_PATH as u64 != 0;
    let arg = SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        empty,
        resolve: true,
        miss: Existing,
        ..Default::default()
    };
    syscall_exec_handler(request, "execveat", &arg)
}

fn sys_ioctl(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: Deny if the ioctl request is denylisted.
    let req = request.get_request();
    let arg = req.data.args[1];
    let sandbox = request.get_sandbox();
    if let Some(deny) = sandbox.has_ioctl(&arg) {
        return if deny {
            // Request is denylisted.
            request.fail_syscall(nix::libc::EACCES)
        } else {
            // SAFETY: Request is allowlisted.
            unsafe { request.continue_syscall() }
        };
    }
    drop(sandbox); // release the read-lock.

    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        cont: true,
        ..Default::default()
    }];
    syscall_path_handler(request, "ioctl", argv, |_, _, _| {
        // SAFETY: ioctl is fd-only.
        Ok(unsafe { request.continue_syscall() })
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_getdents(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            if req.data.args[1] == 0 {
                // SAFETY: If the second argument which must hold a pointer to a
                // linux_dirent structure is NULL, we must return EFAULT
                // without further processing here.
                return Err(Errno::EFAULT);
            } else if req.data.args[2] == 0 {
                // SAFETY:Result buffer is too small
                return Err(Errno::EINVAL);
            }

            // If sandboxing for List capability is off, return immediately.
            if !sandbox.enabled(Capability::CAP_STAT) {
                // SAFETY: This is unsafe due to vulnerability to TOCTOU.
                // Since stat sandboxing is disabled here, this is safe.
                return unsafe { Ok(request.continue_syscall()) };
            }
            let debug = sandbox.allow_unsafe_perf();

            // Get remote fd and readlink /proc/self/fd/$fd.
            let fd = proc.get_fd(req.data.args[0] as RawFd, request)?;
            let mut buf0 = itoa::Buffer::new();
            let mut buf1 = itoa::Buffer::new();
            let mut sym = TinyPathBuf::new();
            sym.push(Path::new("/proc"));
            sym.push(Path::new(buf0.format(Pid::this().as_raw())));
            sym.push(Path::new("fd"));
            sym.push(Path::new(buf1.format(fd.as_raw_fd())));
            let dir = match read_link(&sym) {
                Ok(dir) => {
                    if dir.is_relative() {
                        // /proc/1/fd/0 -> pipe:42
                        return Err(Errno::EBADF);
                    } else {
                        // genuine dir, check for access.
                        sandbox_path_1(
                            &dir,
                            Capability::CAP_STAT,
                            "getdents64",
                            request,
                            &sandbox,
                        )?;
                        dir
                    }
                }
                Err(_) => return Err(Errno::ENOENT),
            };

            // SAFETY: The count argument to the getdents call
            // must not be fully trusted, it can be overly large,
            // and allocating a Vector of that capacity may overflow.
            // This bug was discovered by trinity in this build:
            // https://builds.sr.ht/~alip/job/1077263
            let count = usize::try_from(req.data.args[2])
                .or(Err(Errno::EINVAL))?
                .min(1000000);
            let mut entries = Vec::new();
            entries.try_reserve(count).or(Err(Errno::EINVAL))?;
            let mut dot: u8 = 0;
            loop {
                match getdents(fd.as_raw_fd(), count)? {
                    None => {
                        // getdents returned None, there are no more entries.
                        if !entries.is_empty() {
                            break;
                        } else {
                            return Ok(request.return_syscall(0));
                        }
                    }
                    Some(e) => {
                        for entry in e.iter() {
                            let name = entry.to_path_buf();
                            #[allow(clippy::arithmetic_side_effects)]
                            if dot < 2 && path_is_dot(&name) {
                                // SAFETY: Allow . and ..
                                entries.push(entry.clone());
                                dot += 1;
                                continue;
                            }
                            let path = dir.join(&name);
                            if path_unsafe(&path, debug) {
                                // SAFETY: Silently skip the entry for stealth.
                            } else if sandbox_path_1(
                                &path,
                                Capability::CAP_STAT,
                                "getdents64",
                                request,
                                &sandbox,
                            )
                            .is_ok()
                            {
                                entries.push(entry.clone());
                            }
                        }
                        if !entries.is_empty() {
                            break; // exit the loop once we have allowed entries
                        }
                    }
                };
            }
            drop(sandbox); // release the read-lock.

            let mut buffer = Vec::with_capacity(count);
            for entry in &entries {
                let bytes = &entry.dirent;

                // Ensure we don't append more bytes than the buffer can hold.
                if buffer.len().saturating_add(bytes.len()) > count {
                    break;
                }

                buffer.extend_from_slice(bytes);
            }

            proc.write_mem(&buffer, req.data.args[1], request)?;

            #[allow(clippy::cast_possible_wrap)]
            Ok(request.return_syscall(buffer.len() as i64))
        }
    )
}

fn sys_access(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        ..Default::default()
    }];
    syscall_path_handler(request, "access", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: Handle base path in a TOCTOU-free way.
        let flags = if base.is_empty() {
            AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH
        } else {
            AtFlags::AT_SYMLINK_NOFOLLOW
        };

        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let mode = AccessFlags::from_bits_truncate(req.data.args[1] as nix::libc::c_int);

        faccessat(Some(root.as_raw_fd()), base, mode, flags).map(|_| request.return_syscall(0))
    })
}

fn sys_faccessat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        resolve: true,
        ..Default::default()
    }];
    syscall_path_handler(request, "faccessat", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: Handle base path in a TOCTOU-free way.
        let flags = if base.is_empty() {
            AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH
        } else {
            AtFlags::AT_SYMLINK_NOFOLLOW
        };

        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let mode = AccessFlags::from_bits_truncate(req.data.args[2] as nix::libc::c_int);

        faccessat(Some(root.as_raw_fd()), base, mode, flags).map(|_| request.return_syscall(0))
    })
}

fn sys_faccessat2(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let flags = AtFlags::from_bits_truncate(req.data.args[3] as nix::libc::c_int);
    #[allow(clippy::cast_possible_truncation)]
    let mode = AccessFlags::from_bits_truncate(req.data.args[2] as nix::libc::c_int);
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        empty: flags.contains(AtFlags::AT_EMPTY_PATH),
        resolve: !flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW),
        ..Default::default()
    }];
    syscall_path_handler(request, "faccessat2", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: Handle base path in a TOCTOU-free way.
        let mut flags = flags;
        if base.is_empty() {
            flags |= AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH;
        } else {
            flags |= AtFlags::AT_SYMLINK_NOFOLLOW;
        };
        faccessat(Some(root.as_raw_fd()), base, mode, flags).map(|_| request.return_syscall(0))
    })
}

fn sys_chmod(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        resolve: true,
        fs: true, // We cannot use fchmod(), we may not have write access yet!
        ..Default::default()
    }];
    syscall_path_handler(request, "chmod", argv, |path_args, _proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: We apply force_umask to chmod modes to ensure consistency.
        let umask = sandbox.umask.unwrap_or(Mode::empty());

        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let mode = Mode::from_bits_truncate(req.data.args[1] as nix::libc::mode_t);

        // SAFETY: NoFollowSymlink avoids filesystem TOCTOU.
        fchmodat(
            Some(root.as_raw_fd()),
            base,
            mode & !umask,
            FchmodatFlags::NoFollowSymlink,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_fchmod(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "fchmod", argv, |path_args, _, sandbox| {
        let PathArg(fd, _) = &path_args[0];

        // SAFETY: We apply force_umask to chmod modes to ensure consistency.
        let umask = sandbox.umask.unwrap_or(Mode::empty());

        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let mode = Mode::from_bits_truncate(req.data.args[1] as nix::libc::mode_t);

        fchmod(fd.as_raw_fd(), mode & !umask).map(|_| request.return_syscall(0))
    })
}

fn sys_fchmodat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        miss: Existing,
        resolve: true, // Unlike fchmodat2, fchmodat always resolves symbolic links.
        fs: true,      // We cannot use fchmod(), we may not have write access yet!
        ..Default::default()
    }];
    syscall_path_handler(request, "fchmodat", argv, |path_args, _proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: We apply force_umask to chmod modes to ensure consistency.
        let umask = sandbox.umask.unwrap_or(Mode::empty());

        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let mode = Mode::from_bits_truncate(req.data.args[2] as nix::libc::mode_t);

        // SAFETY: NoFollowSymlink avoids filesystem TOCTOU.
        fchmodat(
            Some(root.as_raw_fd()),
            base,
            mode & !umask,
            FchmodatFlags::NoFollowSymlink,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_fchmodat2(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(req.data.args[2] as nix::libc::mode_t);
    let resolve = req.data.args[3] & nix::libc::AT_SYMLINK_NOFOLLOW as u64 == 0;
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        miss: Existing,
        resolve,
        fs: true, // We cannot use fchmod(), we may not have write access yet!
        ..Default::default()
    }];
    syscall_path_handler(request, "fchmodat2", argv, |path_args, _proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: We apply force_umask to chmod modes to ensure consistency.
        let umask = sandbox.umask.unwrap_or(Mode::empty());

        // SAFETY: NoFollowSymlink avoids filesystem TOCTOU.
        fchmodat(
            Some(root.as_raw_fd()),
            base,
            mode & !umask,
            FchmodatFlags::NoFollowSymlink,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_chown(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        fs: true, // We cannot use fchown(), we may not have write access yet!
        ..Default::default()
    }];
    syscall_path_handler(request, "chown", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        let req = request.get_request();
        #[allow(clippy::cast_possible_wrap)]
        let owner = if req.data.args[1] as i64 == -1 {
            None
        } else {
            Some(Uid::from_raw(req.data.args[1] as nix::libc::uid_t))
        };
        #[allow(clippy::cast_possible_wrap)]
        let group = if req.data.args[2] as i64 == -1 {
            None
        } else {
            Some(Gid::from_raw(req.data.args[2] as nix::libc::gid_t))
        };

        // SAFETY: NoFollowSymlink avoids filesystem TOCTOU.
        fchownat(
            Some(root.as_raw_fd()),
            base,
            owner,
            group,
            AtFlags::AT_SYMLINK_NOFOLLOW,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_fchown(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "fchown", argv, |path_args, _, _| {
        let PathArg(fd, _) = &path_args[0];

        let req = request.get_request();
        #[allow(clippy::cast_possible_wrap)]
        let owner = if req.data.args[1] as i64 == -1 {
            None
        } else {
            Some(Uid::from_raw(req.data.args[1] as nix::libc::uid_t))
        };
        #[allow(clippy::cast_possible_wrap)]
        let group = if req.data.args[2] as i64 == -1 {
            None
        } else {
            Some(Gid::from_raw(req.data.args[2] as nix::libc::gid_t))
        };

        fchown(fd.as_raw_fd(), owner, group).map(|_| request.return_syscall(0))
    })
}

fn sys_lchown(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "lchown", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        let req = request.get_request();
        #[allow(clippy::cast_possible_wrap)]
        let owner = if req.data.args[1] as i64 == -1 {
            None
        } else {
            Some(Uid::from_raw(req.data.args[1] as nix::libc::uid_t))
        };
        #[allow(clippy::cast_possible_wrap)]
        let group = if req.data.args[2] as i64 == -1 {
            None
        } else {
            Some(Gid::from_raw(req.data.args[2] as nix::libc::gid_t))
        };

        // base cannot be empty here because resolve is false.
        fchownat(
            Some(root.as_raw_fd()),
            base,
            owner,
            group,
            AtFlags::AT_SYMLINK_NOFOLLOW,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_fchownat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let empty = req.data.args[4] & nix::libc::AT_EMPTY_PATH as u64 != 0;
    let resolve = req.data.args[4] & nix::libc::AT_SYMLINK_NOFOLLOW as u64 == 0;
    // The combination AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH is invalid.
    if empty && !resolve {
        return request.fail_syscall(nix::libc::EINVAL);
    }
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        miss: Existing,
        empty,
        resolve,
        fs: true, // We cannot use fchown(), we may not have write access yet!
        ..Default::default()
    }];
    syscall_path_handler(request, "fchownat", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        #[allow(clippy::cast_possible_wrap)]
        let owner = if req.data.args[2] as i64 == -1 {
            None
        } else {
            Some(Uid::from_raw(req.data.args[2] as nix::libc::uid_t))
        };
        #[allow(clippy::cast_possible_wrap)]
        let group = if req.data.args[3] as i64 == -1 {
            None
        } else {
            Some(Gid::from_raw(req.data.args[3] as nix::libc::gid_t))
        };

        // SAFETY: NoFollowSymlink avoids filesystem TOCTOU.
        fchownat(
            Some(root.as_raw_fd()),
            base,
            owner,
            group,
            AtFlags::AT_SYMLINK_NOFOLLOW,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_link(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SyscallPathArgument {
            path: Some(0),
            miss: Existing,
            ..Default::default()
        },
        SyscallPathArgument {
            path: Some(1),
            dotlast: Some(Errno::ENOENT),
            ..Default::default()
        },
    ];
    syscall_path_handler(request, "link", argv, |path_args, _proc, _sandbox| {
        syscall_link_handler(request, path_args)
    })
}

fn sys_linkat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let flags = req.data.args[4] as nix::libc::c_int;
    let empty = flags & nix::libc::AT_EMPTY_PATH != 0;
    let resolve = flags & nix::libc::AT_SYMLINK_FOLLOW != 0;
    // The combination AT_SYMLINK_FOLLOW|AT_EMPTY_PATH is invalid.
    if empty && resolve {
        return request.fail_syscall(nix::libc::EINVAL);
    }
    let argv = &[
        SyscallPathArgument {
            dirfd: Some(0),
            path: Some(1),
            miss: Existing,
            fs: true, // syscall_link_handler does not use AT_EMPTY_PATH.
            empty,
            resolve,
            ..Default::default()
        },
        SyscallPathArgument {
            dirfd: Some(2),
            path: Some(3),
            dotlast: Some(Errno::ENOENT),
            ..Default::default()
        },
    ];
    syscall_path_handler(request, "linkat", argv, |path_args, _proc, _sandbox| {
        syscall_link_handler(request, path_args)
    })
}

fn sys_symlink(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            // SAFETY: No checking of the target is done.
            // This is consistent with the system call.
            let arg: SyscallPathArgument = SyscallPathArgument {
                path: Some(1),
                ..Default::default()
            };
            syscall_symlink_handler(request, proc, req, &sandbox, arg)
        }
    )
}

fn sys_symlinkat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            // SAFETY: No checking of the target is done.
            // This is consistent with the system call.
            let arg: SyscallPathArgument = SyscallPathArgument {
                dirfd: Some(1),
                path: Some(2),
                ..Default::default()
            };
            syscall_symlink_handler(request, proc, req, &sandbox, arg)
        }
    )
}

fn sys_unlink(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        dotlast: Some(Errno::EINVAL),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "unlink", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];
        unlinkat(Some(root.as_raw_fd()), base, UnlinkatFlags::NoRemoveDir)
            .map(|_| request.return_syscall(0))
    })
}

fn sys_unlinkat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        dotlast: Some(Errno::EINVAL),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "unlinkat", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        let req = request.get_request();
        #[allow(clippy::cast_possible_truncation)]
        let flags = if req.data.args[2] as nix::libc::c_int & nix::libc::AT_REMOVEDIR != 0 {
            UnlinkatFlags::RemoveDir
        } else {
            UnlinkatFlags::NoRemoveDir
        };

        unlinkat(Some(root.as_raw_fd()), base, flags).map(|_| request.return_syscall(0))
    })
}

fn sys_mkdir(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(req.data.args[1] as nix::libc::mode_t);
    let argv = &[SyscallPathArgument {
        path: Some(0),
        dotlast: Some(Errno::ENOENT),
        miss: Missing,
        rd_dir: true, // Need to check POSIX ACLs!
        ..Default::default()
    }];
    syscall_path_handler(request, "mkdir", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY:
        // 1. syd's umask is 0 here so we respect process' umask.
        // 2. If the parent directory has POSIX ACLs we skip umask.
        let mode = if fhasposixacl(root) {
            mode
        } else {
            mode & !proc_umask(req.pid())?
        };

        mkdirat(Some(root.as_raw_fd()), base, mode).map(|_| request.return_syscall(0))
    })
}

fn sys_rmdir(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        dotlast: Some(Errno::EINVAL),
        miss: Existing,
        fs: true, // rmdir() does not work on fds!
        ..Default::default()
    }];
    syscall_path_handler(request, "rmdir", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];
        unlinkat(Some(root.as_raw_fd()), base, UnlinkatFlags::RemoveDir)
            .map(|_| request.return_syscall(0))
    })
}

fn sys_mkdirat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(req.data.args[2] as nix::libc::mode_t);
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        dotlast: Some(Errno::ENOENT),
        miss: Missing,
        rd_dir: true, // Need to check POSIX ACLs!
        ..Default::default()
    }];
    syscall_path_handler(request, "mkdirat", argv, |path_args, _proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY:
        // 1. syd's umask is 0 here so we respect process' umask.
        // 2. If the parent directory has POSIX ACLs we skip umask.
        let mode = if fhasposixacl(root) {
            mode
        } else {
            mode & !proc_umask(req.pid())?
        };

        mkdirat(Some(root.as_raw_fd()), base, mode).map(|_| request.return_syscall(0))
    })
}

fn sys_mknod(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    // Careful here, zero file type if equivalent to S_IFREG.
    #[allow(clippy::cast_possible_truncation)]
    let kind = req.data.args[1] as nix::libc::mode_t & SFlag::S_IFMT.bits();
    let kind = if kind != 0 {
        let kind = SFlag::from_bits_truncate(kind);
        if !matches!(kind, SFlag::S_IFIFO | SFlag::S_IFREG | SFlag::S_IFSOCK) {
            // SAFETY: We do not allow:
            // 1. Device special files because this may
            //    circumvent path based access control.
            return request.fail_syscall(nix::libc::EPERM);
        }
        kind
    } else {
        SFlag::S_IFREG
    };
    #[allow(clippy::cast_possible_truncation)]
    let perm =
        Mode::from_bits_truncate(req.data.args[1] as nix::libc::mode_t & !SFlag::S_IFMT.bits());
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Missing,
        rd_dir: true, // Need to check POSIX ACLs!
        ..Default::default()
    }];
    syscall_path_handler(request, "mknod", argv, |path_args, _proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        #[allow(clippy::cast_possible_truncation)]
        let dev = req.data.args[2] as nix::libc::dev_t;
        let force_umask = sandbox.umask;

        // SAFETY:
        // 1. syd's umask is 0 here so we respect process' umask.
        // 2. If the parent directory has POSIX ACLs we skip umask.
        // 3. force_umask overrides ACLs.
        let perm = if let Some(mask) = force_umask {
            perm & !mask
        } else if fhasposixacl(root) {
            perm
        } else {
            perm & !proc_umask(req.pid())?
        };

        mknodat(Some(root.as_raw_fd()), base, kind, perm, dev).map(|_| request.return_syscall(0))
    })
}

fn sys_mknodat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    // Careful here, zero file type if equivalent to S_IFREG.
    #[allow(clippy::cast_possible_truncation)]
    let kind = req.data.args[2] as nix::libc::mode_t & SFlag::S_IFMT.bits();
    let kind = if kind != 0 {
        let kind = SFlag::from_bits_truncate(kind);
        if !matches!(kind, SFlag::S_IFIFO | SFlag::S_IFREG | SFlag::S_IFSOCK) {
            // SAFETY: We do not allow:
            // 1. Device special files because this may
            //    circumvent path based access control.
            return request.fail_syscall(nix::libc::EPERM);
        }
        kind
    } else {
        SFlag::S_IFREG
    };
    #[allow(clippy::cast_possible_truncation)]
    let perm =
        Mode::from_bits_truncate(req.data.args[2] as nix::libc::mode_t & !SFlag::S_IFMT.bits());
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        miss: Missing,
        rd_dir: true, // Need to check POSIX ACLs!
        ..Default::default()
    }];
    syscall_path_handler(request, "mknodat", argv, |path_args, _proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        // SAFETY: syd's umask is 0 here so we respect process' umask.
        #[allow(clippy::cast_possible_truncation)]
        let dev = req.data.args[3] as nix::libc::dev_t;
        let force_umask = sandbox.umask;

        // SAFETY:
        // 1. syd's umask is 0 here so we respect process' umask.
        // 2. If the parent directory has POSIX ACLs we skip umask.
        // 3. force_umask overrides ACLs.
        let perm = if let Some(mask) = force_umask {
            perm & !mask
        } else if fhasposixacl(root) {
            perm
        } else {
            perm & !proc_umask(req.pid())?
        };

        mknodat(Some(root.as_raw_fd()), base, kind, perm, dev).map(|_| request.return_syscall(0))
    })
}

fn sys_creat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();

    // SAFETY:
    // 1. If path is NULL we must return EFAULT here w/o further
    //    processing.
    if req.data.args[0] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }

    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(req.data.args[1] as nix::libc::mode_t);
    let arg = SyscallPathArgument {
        path: Some(0),
        resolve: true,
        ..Default::default()
    };
    let flags = OFlag::O_CREAT | OFlag::O_WRONLY | OFlag::O_TRUNC;

    syscall_open_handler(request, &arg, flags, mode)
}

fn sys_open(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();

    // SAFETY:
    // 1. If path is NULL we must return EFAULT here w/o further
    //    processing.
    if req.data.args[0] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }

    #[allow(clippy::cast_possible_truncation)]
    let flags = OFlag::from_bits_truncate(req.data.args[1] as nix::libc::c_int);
    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(req.data.args[2] as nix::libc::mode_t);
    let arg = SyscallPathArgument::open(flags, false);

    syscall_open_handler(request, &arg, flags, mode)
}

fn sys_openat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();

    // SAFETY:
    // 1. If path is NULL we must return EFAULT here w/o further
    //    processing.
    // Note, using a bad directory is okay for absolute paths,
    // so we cannot validate the dirfd early here.
    if req.data.args[1] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }

    #[allow(clippy::cast_possible_truncation)]
    let flags = OFlag::from_bits_truncate(req.data.args[2] as nix::libc::c_int);
    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(req.data.args[3] as nix::libc::mode_t);
    let arg = SyscallPathArgument::open(flags, true);

    syscall_open_handler(request, &arg, flags, mode)
}

fn sys_openat2(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();

    // SAFETY:
    // 1. If path is NULL we must return EFAULT here w/o further
    //    processing.
    // Note, using a bad directory is okay for absolute paths,
    // so we cannot validate the dirfd early here.
    if req.data.args[1] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }

    let proc = RemoteProcess::new(req.pid());
    let open_how = match proc.remote_ohow(req.data.args[2], req.data.args[3], request) {
        Ok(open_how) => open_how,
        Err(errno) => {
            return request.fail_syscall(errno as i32);
        }
    };

    #[allow(clippy::cast_possible_truncation)]
    let flags = OFlag::from_bits_truncate(open_how.flags as nix::libc::c_int);
    #[allow(clippy::cast_possible_truncation)]
    let mode = Mode::from_bits_truncate(open_how.mode as nix::libc::mode_t);
    let arg = SyscallPathArgument::open(flags, true);

    syscall_open_handler(request, &arg, flags, mode)
}

fn sys_rename(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SyscallPathArgument {
            path: Some(0),
            dotlast: Some(Errno::EINVAL),
            miss: Existing,
            ..Default::default()
        },
        SyscallPathArgument {
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            ..Default::default()
        },
    ];
    syscall_path_handler(request, "rename", argv, |path_args, _proc, _sandbox| {
        let PathArg(old, old_base) = &path_args[0];
        let PathArg(new, new_base) = &path_args[1];

        renameat(
            Some(old.as_raw_fd()),
            old_base,
            Some(new.as_raw_fd()),
            new_base,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_renameat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SyscallPathArgument {
            dirfd: Some(0),
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            miss: Existing,
            ..Default::default()
        },
        SyscallPathArgument {
            dirfd: Some(2),
            path: Some(3),
            dotlast: Some(Errno::EINVAL),
            ..Default::default()
        },
    ];
    syscall_path_handler(request, "renameat", argv, |path_args, _proc, _sandbox| {
        let PathArg(old, old_base) = &path_args[0];
        let PathArg(new, new_base) = &path_args[1];

        renameat(
            Some(old.as_raw_fd()),
            old_base,
            Some(new.as_raw_fd()),
            new_base,
        )
        .map(|_| request.return_syscall(0))
    })
}

fn sys_renameat2(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let flags = req.data.args[4] as u32;
    let noreplace = flags & nix::libc::RENAME_NOREPLACE != 0;

    let argv = &[
        SyscallPathArgument {
            dirfd: Some(0),
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            miss: Existing,
            ..Default::default()
        },
        SyscallPathArgument {
            dirfd: Some(2),
            path: Some(3),
            dotlast: Some(Errno::EINVAL),
            miss: if noreplace { Missing } else { Normal },
            ..Default::default()
        },
    ];
    syscall_path_handler(request, "renameat2", argv, |path_args, _proc, _sandbox| {
        let PathArg(old, old_base) = &path_args[0];
        let PathArg(new, new_base) = &path_args[1];

        let path_old = CString::new(old_base.as_os_str().as_bytes()).or(Err(Errno::EINVAL))?;
        let path_new = CString::new(new_base.as_os_str().as_bytes()).or(Err(Errno::EINVAL))?;

        // Note: musl does not have renameat2 yet.
        // SAFETY: The call to `nix::libc::syscall` for `SYS_renameat2`
        // is safe provided the arguments are correct.
        // `path_old.as_ptr()` and `path_new.as_ptr()` provide valid
        // pointers to null-terminated strings. `flags` is a valid flag
        // for the syscall. Assuming these conditions, the syscall does
        // not lead to undefined behavior.
        if unsafe {
            nix::libc::syscall(
                nix::libc::SYS_renameat2,
                old.as_raw_fd(),
                path_old.as_ptr(),
                new.as_raw_fd(),
                path_new.as_ptr(),
                flags,
            )
        } == 0
        {
            Ok(request.return_syscall(0))
        } else {
            Err(Errno::last())
        }
    })
}

fn sys_stat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;
    let arg = SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        ..Default::default()
    };
    syscall_stat_handler(request, &arg, 1, is32)
}

fn sys_stat64(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let arg = SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        ..Default::default()
    };
    syscall_stat_handler(request, &arg, 1, false)
}

fn sys_lstat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;
    let arg = SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    };
    syscall_stat_handler(request, &arg, 1, is32)
}

fn sys_lstat64(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let arg = SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    };
    syscall_stat_handler(request, &arg, 1, false)
}

fn sys_statx(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let empty = req.data.args[2] & nix::libc::AT_EMPTY_PATH as u64 != 0;
    let resolve = req.data.args[2] & nix::libc::AT_SYMLINK_NOFOLLOW as u64 == 0;
    // The combination AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH is invalid.
    if empty && !resolve {
        return request.fail_syscall(nix::libc::EINVAL);
    }
    let arg = SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        miss: Existing,
        empty,
        resolve,
        ..Default::default()
    };
    syscall_stat_handler(request, &arg, 4, false)
}

fn sys_newfstatat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let empty = req.data.args[3] & nix::libc::AT_EMPTY_PATH as u64 != 0;
    let resolve = req.data.args[3] & nix::libc::AT_SYMLINK_NOFOLLOW as u64 == 0;
    // The combination AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH is invalid.
    if empty && !resolve {
        return request.fail_syscall(nix::libc::EINVAL);
    }
    let arg = SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        miss: Existing,
        empty,
        resolve,
        ..Default::default()
    };
    syscall_stat_handler(request, &arg, 2, false)
}

fn sys_utime(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // Careful here, utimensat() requires write access but
    // we cannot open the fd for write and operate on it,
    // as we may get ENXIO if it's a FIFO opened for read.
    // That's why we set fs==true.
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        fs: true,
        ..Default::default()
    }];
    syscall_path_handler(request, "utime", argv, |path_args, proc, _sandbox| {
        let req = request.get_request();
        let (atime, mtime) = proc.remote_utimbuf(req.data.args[1], request)?;
        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

fn sys_utimes(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // Careful here, utimensat() requires write access but
    // we cannot open the fd for write and operate on it,
    // as we may get ENXIO if it's a FIFO opened for read.
    // That's why we set fs==true.
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        fs: true,
        ..Default::default()
    }];
    syscall_path_handler(request, "utimes", argv, |path_args, proc, _sandbox| {
        let req = request.get_request();
        let (atime, mtime) = proc.remote_timeval(req.data.args[1], request)?;
        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

fn sys_futimesat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: pathname may be NULL here:
    // Careful here, utimensat() requires write access but
    // we cannot open the fd for write and operate on it,
    // as we may get ENXIO if it's a FIFO opened for read.
    // That's why we set fs==true.
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        null: true,
        resolve: true,
        miss: Existing,
        fs: true,
        ..Default::default()
    }];
    syscall_path_handler(request, "futimesat", argv, |path_args, proc, _sandbox| {
        let req = request.get_request();
        let (atime, mtime) = proc.remote_timeval(req.data.args[2], request)?;
        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

fn sys_utimensat(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let flags = req.data.args[3] as nix::libc::c_int;
    let empty = flags & nix::libc::AT_EMPTY_PATH != 0;
    let resolve = flags & nix::libc::AT_SYMLINK_NOFOLLOW == 0;
    // The combination AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH is invalid.
    if empty && !resolve {
        return request.fail_syscall(nix::libc::EINVAL);
    }
    // SAFETY: pathname may be NULL here:
    // Careful here, utimensat() requires write access but
    // we cannot open the fd for write and operate on it,
    // as we may get ENXIO if it's a FIFO opened for read.
    // That's why we set fs==true.
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        null: true,
        miss: Existing,
        fs: true,
        empty,
        resolve,
        ..Default::default()
    }];
    syscall_path_handler(request, "utimensat", argv, |path_args, proc, _sandbox| {
        let addr = req.data.args[2];
        let is32 = scmp_arch_bits(req.data.arch) == 32;

        let (atime, mtime) = if is32 {
            proc.remote_timespec32(addr, request)
        } else {
            proc.remote_timespec64(addr, request)
        }?;

        syscall_utime_handler(request, path_args, &atime, &mtime)
    })
}

fn sys_utimensat64(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let flags = req.data.args[3] as nix::libc::c_int;
    let empty = flags & nix::libc::AT_EMPTY_PATH != 0;
    let resolve = flags & nix::libc::AT_SYMLINK_NOFOLLOW == 0;
    // The combination AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH is invalid.
    if empty && !resolve {
        return request.fail_syscall(nix::libc::EINVAL);
    }
    // SAFETY: pathname may be NULL here:
    // Careful here, utimensat() requires write access but
    // we cannot open the fd for write and operate on it,
    // as we may get ENXIO if it's a FIFO opened for read.
    // That's why we set fs==true.
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        path: Some(1),
        null: true,
        miss: Existing,
        fs: true,
        empty,
        resolve,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "utimensat_time64",
        argv,
        |path_args, proc, _sandbox| {
            let (atime, mtime) = proc.remote_timespec64(req.data.args[2], request)?;
            syscall_utime_handler(request, path_args, &atime, &mtime)
        },
    )
}

fn sys_truncate(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY:
    // 1. If first argument is NULL, return EFAULT.
    // 2. If second argument is negative, return EINVAL.
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    #[allow(clippy::cast_lossless)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_possible_wrap)]
    let len = if is32 {
        nix::libc::off_t::from(req.data.args[1] as i32)
    } else {
        req.data.args[1] as nix::libc::off_t
    };

    if len < 0 {
        return request.fail_syscall(nix::libc::EINVAL);
    } else if req.data.args[0] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }

    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        wr: true, // ftruncate() does not work on O_PATH fds.
        ..Default::default()
    }];
    syscall_path_handler(request, "truncate", argv, |path_args, _proc, _sandbox| {
        let PathArg(fd, _) = &path_args[0];

        retry_on_eintr(|| ftruncate(fd, len)).map(|_| request.return_syscall(0))
    })
}

fn sys_truncate64(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY:
    // 1. If first argument is NULL, return EFAULT.
    // 2. If second argument is negative, return EINVAL.
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    // truncate64 needs argument translation on some architectures.
    #[allow(clippy::arithmetic_side_effects)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_possible_wrap)]
    let len = if is32 {
        // Align arg_no to the next even number for specific 32-bit architectures.
        let arg_no = match req.data.arch {
            ScmpArch::Arm | ScmpArch::Ppc => 2,
            _ => 1,
        };

        let len_low = req.data.args[arg_no] as u32;
        let len_high = req.data.args[arg_no + 1] as u32;

        if scmp_big_endian(req.data.arch) {
            (nix::libc::off64_t::from(len_low) << 32) | nix::libc::off64_t::from(len_high)
        } else {
            (nix::libc::off64_t::from(len_high) << 32) | nix::libc::off64_t::from(len_low)
        }
    } else {
        // Align arg_no to the next even number for specific 64-bit architectures.
        let arg_no = match req.data.arch {
            ScmpArch::Aarch64 | ScmpArch::Ppc64 | ScmpArch::Ppc64Le => 2,
            _ => 1,
        };

        req.data.args[arg_no] as nix::libc::off64_t
    };

    if len < 0 {
        return request.fail_syscall(nix::libc::EINVAL);
    } else if req.data.args[0] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }

    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        wr: true, // ftruncate() does not work on O_PATH fds.
        ..Default::default()
    }];
    syscall_path_handler(request, "truncate64", argv, |path_args, _proc, _sandbox| {
        let PathArg(fd, _) = &path_args[0];

        retry_on_eintr(|| ftruncate64(fd.as_raw_fd(), len)).map(|_| request.return_syscall(0))
    })
}

fn sys_ftruncate(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: If second argument is negative, return EINVAL.
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    #[allow(clippy::cast_lossless)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_possible_wrap)]
    let len = if is32 {
        nix::libc::off_t::from(req.data.args[1] as i32)
    } else {
        req.data.args[1] as nix::libc::off_t
    };

    if len < 0 {
        return request.fail_syscall(nix::libc::EINVAL);
    }

    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "ftruncate", argv, |path_args, _, _| {
        let PathArg(fd, _) = &path_args[0];

        retry_on_eintr(|| ftruncate(fd, len)).map(|_| request.return_syscall(0))
    })
}

fn sys_ftruncate64(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: If second argument is negative, return EINVAL.
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    // truncate64 needs argument translation on some architectures.
    #[allow(clippy::arithmetic_side_effects)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_possible_wrap)]
    let len = if is32 {
        // Align arg_no to the next even number for specific 32-bit architectures.
        let arg_no = match req.data.arch {
            ScmpArch::Arm | ScmpArch::Ppc => 2,
            _ => 1,
        };

        let len_low = req.data.args[arg_no] as u32;
        let len_high = req.data.args[arg_no + 1] as u32;

        if scmp_big_endian(req.data.arch) {
            (nix::libc::off64_t::from(len_low) << 32) | nix::libc::off64_t::from(len_high)
        } else {
            (nix::libc::off64_t::from(len_high) << 32) | nix::libc::off64_t::from(len_low)
        }
    } else {
        // Align arg_no to the next even number for specific 64-bit architectures.
        let arg_no = match req.data.arch {
            ScmpArch::Aarch64 | ScmpArch::Ppc64 | ScmpArch::Ppc64Le => 2,
            _ => 1,
        };

        req.data.args[arg_no] as nix::libc::off64_t
    };

    if len < 0 {
        return request.fail_syscall(nix::libc::EINVAL);
    }

    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "ftruncate64", argv, |path_args, _, _| {
        let PathArg(fd, _) = &path_args[0];

        retry_on_eintr(|| ftruncate64(fd.as_raw_fd(), len)).map(|_| request.return_syscall(0))
    })
}

fn sys_fallocate(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: EINVAL offset was less than 0, or len was less than or equal to 0.
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    #[allow(clippy::cast_lossless)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_possible_wrap)]
    let off = if is32 {
        nix::libc::off_t::from(req.data.args[2] as i32)
    } else {
        req.data.args[2] as nix::libc::off_t
    };

    if off < 0 {
        return request.fail_syscall(nix::libc::EINVAL);
    }

    #[allow(clippy::cast_lossless)]
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_possible_wrap)]
    let len = if is32 {
        nix::libc::off_t::from(req.data.args[3] as i32)
    } else {
        req.data.args[3] as nix::libc::off_t
    };

    if len <= 0 {
        return request.fail_syscall(nix::libc::EINVAL);
    }

    #[allow(clippy::cast_possible_truncation)]
    let mode = FallocateFlags::from_bits_truncate(req.data.args[1] as nix::libc::c_int);
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "fallocate", argv, |path_args, _, _| {
        let PathArg(fd, _) = &path_args[0];

        retry_on_eintr(|| fallocate(fd.as_raw_fd(), mode, off, len))
            .map(|_| request.return_syscall(0))
    })
}

fn sys_getxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: We set fs:true because fgetxattr requires a read-only fd
    // but we may not have access to open the file! Note, getxattr is a
    // Stat access not Read access! Potential TOCTOU-vectors are handled
    // in syscall_getxattr_handler() where we no longer resolve
    // symlinks.
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        fs: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "getxattr", argv, |path_args, proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];
        syscall_getxattr_handler(request, proc, root, base)
    })
}

fn sys_lgetxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "lgetxattr", argv, |path_args, proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];
        syscall_getxattr_handler(request, proc, root, base)
    })
}

fn sys_fgetxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        resolve: true,
        miss: Existing,
        rd: true, // fgetxattr does not work with O_PATH fds!
        ..Default::default()
    }];
    syscall_path_handler(request, "fgetxattr", argv, |path_args, proc, _sandbox| {
        let PathArg(fd, _) = &path_args[0];

        let req = request.get_request();
        let name = if req.data.args[1] != 0 {
            const SIZ: usize = nix::libc::PATH_MAX as usize;
            let mut buf = Vec::new();
            buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
            buf.resize(SIZ, 0);
            proc.read_mem(&mut buf, req.data.args[1], request)?;
            Some(buf)
        } else {
            None
        };
        let name = if let Some(ref name) = name {
            CStr::from_bytes_until_nul(name)
                .or(Err(Errno::E2BIG))?
                .as_ptr()
        } else {
            std::ptr::null()
        };

        /*
         * We allow getting user.syd* but not setting it.
        #[allow(clippy::cast_possible_wrap)]
        if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
            // SAFETY: Deny user.syd* extended attributes. name is either
            // NULL or a valid nul-terminated C-String.
            // SAFETY: Deny with ENODATA for stealth.
            // SAFETY: Deny only if the Sandbox is locked for the process.
            unsafe { denyxattr(name) }?;
        }
        */

        // SAFETY: The size argument to the getxattr call
        // must not be fully trusted, it can be overly large,
        // and allocating a Vector of that capacity may overflow.
        let len = usize::try_from(req.data.args[3]).or(Err(Errno::E2BIG))?;
        let len = len.min(nix::libc::PATH_MAX as usize); // Cap count at PATH_MAX
        let mut buf = if len > 0 {
            let mut buf = Vec::new();
            buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
            buf.resize(len, 0);
            Some(buf)
        } else {
            None
        };
        let ptr = match buf.as_mut() {
            Some(b) => b.as_mut_ptr(),
            None => std::ptr::null_mut(),
        };
        // SAFETY: In libc we trust.
        let n = unsafe {
            nix::libc::fgetxattr(
                fd.as_raw_fd(),
                name,
                ptr as *mut _ as *mut nix::libc::c_void,
                len,
            )
        };
        #[allow(clippy::cast_sign_loss)]
        let n = if n == -1 {
            return Err(Errno::last());
        } else {
            n as usize
        };
        if let Some(buf) = buf {
            proc.write_mem(&buf[..n], req.data.args[2], request)?;
        }
        #[allow(clippy::cast_possible_wrap)]
        Ok(request.return_syscall(n as i64))
    })
}

fn sys_setxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        rd: true, // fsetxattr does not work with O_PATH fds!
        ..Default::default()
    }];
    syscall_path_handler(request, "setxattr", argv, |path_args, proc, sandbox| {
        let PathArg(fd, _) = &path_args[0];
        syscall_setxattr_handler(request, proc, &sandbox, fd)
    })
}

fn sys_fsetxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        resolve: true,
        miss: Existing,
        rd: true, // fsetxattr does not work with O_PATH fds!
        ..Default::default()
    }];
    syscall_path_handler(request, "fsetxattr", argv, |path_args, proc, sandbox| {
        let PathArg(fd, _) = &path_args[0];
        syscall_setxattr_handler(request, proc, &sandbox, fd)
    })
}

fn sys_lsetxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "lsetxattr", argv, |path_args, proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        let req = request.get_request();

        let base = if base.is_empty() {
            // SAFETY: Do not handle magic symlinks.
            return Err(Errno::ENOTSUP);
        } else {
            CString::new(base.as_os_str().as_bytes()).or(Err(Errno::EINVAL))?
        };

        let name = if req.data.args[1] != 0 {
            const SIZ: usize = nix::libc::PATH_MAX as usize;
            let mut buf = Vec::new();
            buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
            buf.resize(SIZ, 0);
            proc.read_mem(&mut buf, req.data.args[1], request)?;
            Some(buf)
        } else {
            None
        };
        let name = if let Some(ref name) = name {
            CStr::from_bytes_until_nul(name)
                .or(Err(Errno::E2BIG))?
                .as_ptr()
        } else {
            std::ptr::null()
        };

        #[allow(clippy::cast_possible_wrap)]
        if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
            // SAFETY: Deny user.syd* extended attributes. name is either
            // NULL or a valid nul-terminated C-String.
            // SAFETY: Deny with ENODATA for stealth.
            // SAFETY: Deny only if the Sandbox is locked for the process.
            unsafe { denyxattr(name) }?;
        }

        // SAFETY: The size argument to the setxattr call
        // must not be fully trusted, it can be overly large,
        // and allocating a Vector of that capacity may overflow.
        let (buf, len) = if req.data.args[3] == 0 {
            (None, 0)
        } else {
            let len = usize::try_from(req.data.args[3]).or(Err(Errno::E2BIG))?;
            let len = len.min(nix::libc::PATH_MAX as usize); // Cap count at PATH_MAX.
            let mut buf = Vec::new();
            buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
            buf.resize(len, 0);
            proc.read_mem(&mut buf, req.data.args[2], request)?;
            (Some(buf), len)
        };
        let buf = buf.as_ref().map_or(std::ptr::null(), |b| b.as_ptr()) as *const nix::libc::c_void;

        #[allow(clippy::cast_possible_truncation)]
        let flags = req.data.args[4] as nix::libc::c_int;

        let mut stack = [0u8; MINI_STACK_SIZE];
        #[allow(clippy::blocks_in_conditions)]
        let pid_fd = match safe_clone(
            Box::new(|| -> isize {
                if let Err(errno) = fchdir(root.as_raw_fd()) {
                    return errno as isize;
                }
                // SAFETY: In libc we trust.
                if unsafe { nix::libc::lsetxattr(base.as_ptr(), name, buf, len, flags) } == 0 {
                    0
                } else {
                    Errno::last() as isize
                }
            }),
            &mut stack[..],
            nix::libc::CLONE_VM | nix::libc::CLONE_VFORK | nix::libc::CLONE_SIGHAND,
            Some(nix::libc::SIGCHLD),
        ) {
            Ok(pid_fd) => pid_fd,
            Err(_) => {
                return Err(Errno::EACCES);
            }
        };

        loop {
            break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
                Ok(WaitStatus::Exited(_, 0)) => Ok(request.return_syscall(0)),
                Ok(WaitStatus::Exited(_, n)) => Err(Errno::from_raw(n)),
                Err(Errno::EINTR) => continue,
                _ => Err(Errno::EACCES),
            };
        }
    })
}

fn sys_flistxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        rd: true, // flistxattr does not work with O_PATH fds!
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "flistxattr", argv, |path_args, proc, _sandbox| {
        let PathArg(fd, _) = &path_args[0];
        let req = request.get_request();

        // SAFETY: The size argument to the flistxattr call
        // must not be fully trusted, it can be overly large,
        // and allocating a Vector of that capacity may overflow.
        let len = usize::try_from(req.data.args[2]).or(Err(Errno::E2BIG))?;
        let len = len.min(10240); // Cap count at 10240.
        let mut buf = if len > 0 {
            let mut buf = Vec::new();
            buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
            buf.resize(len, 0);
            Some(buf)
        } else {
            None
        };
        let ptr = buf
            .as_mut()
            .map_or(std::ptr::null_mut(), |b| b.as_mut_ptr())
            as *mut nix::libc::c_char;

        // SAFETY: In libc we trust.
        let n = unsafe { nix::libc::flistxattr(fd.as_raw_fd(), ptr, len) };
        #[allow(clippy::cast_sign_loss)]
        let n = if n == -1 {
            return Err(Errno::last());
        } else {
            n as usize
        };
        let n = if let Some(buf) = buf {
            /*
             * We allow getting user.syd* but not setting it.
            // SAFETY: Filter out attributes that start with "user.syd".
            // SAFETY: Deny only if the Sandbox is locked for the process.
            #[allow(clippy::cast_possible_wrap)]
            let buf =
                if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
                    filterxattr(&buf[..n], n)?
                } else {
                    buf
                };
            */
            proc.write_mem(&buf, req.data.args[1], request)?;
            buf.len()
        } else {
            n
        };
        #[allow(clippy::cast_possible_wrap)]
        Ok(request.return_syscall(n as i64))
    })
}

fn sys_listxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: We set fs:true because flistxattr requires a read-only fd
    // but we may not have access to open the file! Note, listxattr is a
    // Stat access not Read access! Potential TOCTOU-vectors are handled
    // in syscall_listxattr_handler() where we no longer resolve
    // symlinks.
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        fs: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "listxattr", argv, |path_args, proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];
        syscall_listxattr_handler(request, proc, root, base)
    })
}

fn sys_llistxattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "llistxattr", argv, |path_args, proc, _sandbox| {
        let PathArg(root, base) = &path_args[0];
        syscall_listxattr_handler(request, proc, root, base)
    })
}

fn sys_removexattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        rd: true, // fremovexattr does not work with O_PATH fds!
        ..Default::default()
    }];
    syscall_path_handler(request, "removexattr", argv, |path_args, proc, sandbox| {
        let PathArg(fd, _) = &path_args[0];
        syscall_removexattr_handler(request, proc, &sandbox, fd)
    })
}

fn sys_fremovexattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        dirfd: Some(0),
        empty: true,
        resolve: true,
        miss: Existing,
        rd: true, // fremovexattr does not work with O_PATH fds!
        ..Default::default()
    }];
    syscall_path_handler(request, "fremovexattr", argv, |path_args, proc, sandbox| {
        let PathArg(fd, _) = &path_args[0];
        syscall_removexattr_handler(request, proc, &sandbox, fd)
    })
}

fn sys_lremovexattr(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SyscallPathArgument {
        path: Some(0),
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "lremovexattr", argv, |path_args, proc, sandbox| {
        let PathArg(root, base) = &path_args[0];

        let req = request.get_request();

        let base = if base.is_empty() {
            // SAFETY: Do not handle magic symlinks.
            return Err(Errno::ENOTSUP);
        } else {
            CString::new(base.as_os_str().as_bytes()).or(Err(Errno::EINVAL))?
        };

        let name = if req.data.args[1] != 0 {
            const SIZ: usize = nix::libc::PATH_MAX as usize;
            let mut buf = Vec::new();
            buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
            buf.resize(SIZ, 0);
            proc.read_mem(&mut buf, req.data.args[1], request)?;
            Some(buf)
        } else {
            None
        };
        let name = if let Some(ref name) = name {
            CStr::from_bytes_until_nul(name)
                .or(Err(Errno::E2BIG))?
                .as_ptr()
        } else {
            std::ptr::null()
        };

        #[allow(clippy::cast_possible_wrap)]
        if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
            // SAFETY: Deny user.syd* extended attributes.
            // name is either NULL or a valid nul-terminated C-String.
            // SAFETY: Deny with ENODATA for stealth.
            // SAFETY: Deny only if the Sandbox is locked for the process.
            unsafe { denyxattr(name) }?;
        }

        let mut stack = [0u8; MINI_STACK_SIZE];
        #[allow(clippy::blocks_in_conditions)]
        let pid_fd = match safe_clone(
            Box::new(|| -> isize {
                if let Err(errno) = fchdir(root.as_raw_fd()) {
                    return errno as isize;
                }
                // SAFETY: In libc we trust.
                if unsafe { nix::libc::lremovexattr(base.as_ptr(), name) } == 0 {
                    0
                } else {
                    Errno::last() as isize
                }
            }),
            &mut stack[..],
            nix::libc::CLONE_VM | nix::libc::CLONE_VFORK | nix::libc::CLONE_SIGHAND,
            Some(nix::libc::SIGCHLD),
        ) {
            Ok(pid_fd) => pid_fd,
            Err(_) => {
                return Err(Errno::EACCES);
            }
        };

        loop {
            break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
                Ok(WaitStatus::Exited(_, 0)) => Ok(request.return_syscall(0)),
                Ok(WaitStatus::Exited(_, n)) => Err(Errno::from_raw(n)),
                Err(Errno::EINTR) => continue,
                _ => Err(Errno::EACCES),
            };
        }
    })
}

#[allow(clippy::cognitive_complexity)]
fn sys_getrandom(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, _proc: &RemoteProcess, sandbox: SandboxGuard| {
            let path = SmallPathBuf::from(
                if req.data.args[2] & u64::from(nix::libc::GRND_RANDOM) != 0 {
                    "/dev/random"
                } else {
                    "/dev/urandom"
                },
            );

            // Check for access.
            if !sandbox.enabled(Capability::CAP_READ) {
                // SAFETY: Read sandboxing is not enabled.
                // This is safe to continue.
                return Ok(unsafe { request.continue_syscall() });
            }
            let (action, filter) = request.check_path(&sandbox, Capability::CAP_READ, &path);

            if !filter {
                warn!("ctx": "access", "act": Action::Warn,
                    "cap": Capability::CAP_READ, "pid": req.pid, "sys": "getrandom",
                    "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                    "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
            }

            match action {
                Action::Allow | Action::Warn => {
                    // SAFETY: No pointer deref in access check.
                    Ok(unsafe { request.continue_syscall() })
                }
                Action::Deny | Action::Filter => Err(Errno::EACCES),
                Action::Exit => std::process::exit(nix::libc::EACCES),
                action => {
                    // Stop|Kill
                    let _ = request.kill(action, false);
                    Err(Errno::EACCES)
                }
            }
        }
    )
}

fn sys_statfs(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: If second argument is NULL, return EFAULT.
    let req = request.get_request();
    if req.data.args[1] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "statfs", argv, |path_args, proc, _sandbox| {
        let PathArg(fd, _) = &path_args[0];

        const SIZ: usize = std::mem::size_of::<nix::libc::statfs>();
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        let ptr = buf.as_mut_ptr().cast();

        // SAFETY: In libc we trust.
        retry_on_eintr(|| Errno::result(unsafe { nix::libc::fstatfs(fd.as_raw_fd(), ptr) }))?;

        proc.write_mem(&buf, req.data.args[1], request)?;
        Ok(request.return_syscall(0))
    })
}

fn sys_statfs64(request: &UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: If second argument is NULL, return EFAULT.
    let req = request.get_request();
    if req.data.args[1] == 0 {
        return request.fail_syscall(nix::libc::EFAULT);
    }
    let argv = &[SyscallPathArgument {
        path: Some(0),
        resolve: true,
        miss: Existing,
        ..Default::default()
    }];
    syscall_path_handler(request, "statfs64", argv, |path_args, proc, _sandbox| {
        let PathArg(fd, _) = &path_args[0];

        const SIZ: usize = std::mem::size_of::<nix::libc::statfs64>();
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        let ptr = buf.as_mut_ptr().cast();

        // SAFETY: In libc we trust.
        retry_on_eintr(|| Errno::result(unsafe { nix::libc::fstatfs64(fd.as_raw_fd(), ptr) }))?;

        proc.write_mem(&buf, req.data.args[1], request)?;
        Ok(request.return_syscall(0))
    })
}

fn sys_fanotify_mark(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;
    let resolve = req.data.args[1] & u64::from(nix::libc::FAN_MARK_DONT_FOLLOW) == 0;
    let argv = &[SyscallPathArgument {
        dirfd: Some(if is32 { 4 } else { 3 }),
        path: Some(if is32 { 5 } else { 4 }),
        empty: true,
        null: true,
        rd: true, // fanotify_mark does not work with O_PATH fds!
        miss: Existing,
        resolve,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "fanotify_mark",
        argv,
        |path_args, proc, _sandbox| {
            let PathArg(root, base) = &path_args[0];

            // Get the FANotify FD.
            let fd = proc.get_fd(req.data.args[0] as RawFd, request)?;

            let (base, mask) = if base.is_empty() {
                // SAFETY: Strip FAN_{ACCESS,ACCESS_PERM,MODIFY}
                // if we're marking a sidechannel device.
                let statx = fstatx(root.as_raw_fd(), STATX_TYPE)?;
                let mut mask = req.data.args[2];
                if is_sidechannel_device(statx.stx_mode.into()) {
                    mask &= !(nix::libc::FAN_ACCESS
                        | nix::libc::FAN_ACCESS_PERM
                        | nix::libc::FAN_MODIFY);
                }
                (None, mask)
            } else {
                // Symbolic link.
                (Some(base), req.data.args[2])
            };

            // SAFETY: Handle base path in a TOCTOU-free way.
            let mut flags: nix::libc::c_uint =
                req.data.args[1].try_into().or(Err(Errno::EINVAL))?;
            flags |= nix::libc::FAN_MARK_DONT_FOLLOW;

            fanotify_mark(&fd, flags, mask, Some(root.as_raw_fd()), base)
                .map(|_| request.return_syscall(0))
        },
    )
}

fn sys_inotify_add_watch(request: &UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.get_request();
    #[allow(clippy::cast_possible_truncation)]
    let mask = AddWatchFlags::from_bits_truncate(req.data.args[2] as u32);
    let argv = &[SyscallPathArgument {
        dirfd: None,
        path: Some(1),
        miss: Existing,
        fs: true, // inotify_add_watch has no fd version!
        resolve: !mask.contains(AddWatchFlags::IN_DONT_FOLLOW),
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "inotify_add_watch",
        argv,
        |path_args, proc, _sandbox| {
            let PathArg(root, base) = &path_args[0];

            if base.is_empty() {
                // SAFETY: Do not handle magic symlinks.
                return Err(Errno::ENOENT);
            }

            // Get the INotify FD.
            let fd = proc.get_fd(req.data.args[0] as RawFd, request)?;

            let mut stack = [0u8; MINI_STACK_SIZE];
            let (pipe_ro, pipe_rw) = pipe2_raw(OFlag::O_CLOEXEC | OFlag::O_DIRECT)?;
            #[allow(clippy::blocks_in_conditions)]
            let pid_fd = match safe_clone(
                Box::new(|| -> isize {
                    // Close the read end of the pipe.
                    let _ = close(pipe_ro);

                    if let Err(errno) = fchdir(root.as_raw_fd()) {
                        return errno as isize;
                    }

                    // SAFETY:
                    // 1. Strip IN_{ACCESS,MODIFY} if we're marking a sidechannel device.
                    // 2. Do not follow symbolic links in stat.
                    let stat = match lstat(base) {
                        Ok(stat) => stat,
                        Err(errno) => return errno as isize,
                    };
                    let mut mask = mask | AddWatchFlags::IN_DONT_FOLLOW;
                    if is_sidechannel_device(stat.st_mode) {
                        mask.remove(AddWatchFlags::IN_ACCESS);
                        mask.remove(AddWatchFlags::IN_MODIFY);
                    }

                    // SAFETY: Handle base in a TOCTOU-free way.
                    match inotify_add_watch(&fd, base, mask) {
                        Ok(n) => {
                            let n = n.to_le_bytes();
                            let l = n.len();
                            let n = n.as_ptr().cast();
                            let r = loop {
                                // SAFETY: In libc we trust.
                                let r = unsafe { nix::libc::write(pipe_rw, n, l) };
                                if r != -1 || Errno::last() != Errno::EINTR {
                                    break r;
                                }
                            };
                            let e = Errno::last();
                            let _ = close(pipe_rw);
                            if r >= 0 {
                                0
                            } else {
                                e as isize
                            }
                        }
                        Err(errno) => errno as isize,
                    }
                }),
                &mut stack[..],
                nix::libc::CLONE_VM | nix::libc::CLONE_VFORK | nix::libc::CLONE_SIGHAND,
                Some(nix::libc::SIGCHLD),
            ) {
                Ok(pid_fd) => pid_fd,
                Err(_) => {
                    let _ = close(pipe_ro);
                    let _ = close(pipe_rw);
                    return Err(Errno::EACCES);
                }
            };

            // Close the write end of the pipe.
            let _ = close(pipe_rw);
            loop {
                break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
                    Ok(WaitStatus::Exited(_, 0)) => {
                        let mut n = [0u8; std::mem::size_of::<nix::libc::c_int>()];
                        let l = n.len();
                        let p = n.as_mut_ptr().cast();
                        let r = loop {
                            // SAFETY: In libc we trust.
                            let r = unsafe { nix::libc::read(pipe_ro, p, l) };
                            if r != -1 || Errno::last() != Errno::EINTR {
                                break r;
                            }
                        };
                        let e = Errno::last();
                        let _ = close(pipe_ro);
                        if r > 0 {
                            Ok(request
                                .return_syscall(i64::from(nix::libc::c_int::from_le_bytes(n))))
                        } else {
                            Err(e)
                        }
                    }
                    Ok(WaitStatus::Exited(_, n)) => {
                        let _ = close(pipe_ro);
                        Err(Errno::from_raw(n))
                    }
                    Err(Errno::EINTR) => continue,
                    _ => {
                        let _ = close(pipe_ro);
                        Err(Errno::EACCES)
                    }
                };
            }
        },
    )
}

fn sys_memfd_create(request: &UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            const NAME_MAX: usize = 255;
            const MFD_NAME_PREFIX: &[u8] = b"/memfd:"; // The slash is not included in the limit.
            const MFD_NAME_PREFIX_LEN: usize = MFD_NAME_PREFIX.len() - 1;
            const MFD_NAME_MAX_LEN: usize = NAME_MAX - MFD_NAME_PREFIX_LEN;

            let addr = req.data.args[0];
            if addr == 0 {
                // SAFETY: Return EFAULT for NULL name.
                return Err(Errno::EFAULT);
            }

            // If sandboxing for all the selected capabilities is off, return immediately.
            let mut caps = sandbox
                .getcaps(Capability::CAP_READ | Capability::CAP_WRITE | Capability::CAP_EXEC);
            let restrict_memfd = !sandbox.allow_unsafe_memfd();

            #[allow(clippy::cast_possible_truncation)]
            let mut flags = req.data.args[1] as nix::libc::c_uint;
            if restrict_memfd {
                // SAFETY: Drop the executable flag and seal as nonexecutable.
                flags &= !MFD_EXEC;
                flags |= MFD_NOEXEC_SEAL;
                caps.remove(Capability::CAP_EXEC);
            } else if caps.is_empty() {
                // SAFETY: No restrictions requested, continue.
                return Ok(unsafe { request.continue_syscall() });
            }

            let mut buf = [0u8; MFD_NAME_MAX_LEN];
            proc.read_mem(&mut buf, addr, request)?;

            // SAFETY: If buffer has no null byte, return EINVAL as the path
            // is too long for us to handle.
            let name = CStr::from_bytes_until_nul(&buf).or(Err(Errno::EINVAL))?;

            if !caps.is_empty() {
                // caps may be empty if restrict_memfd=1.
                // Check for access by appending the memfd prefix.
                let mut path = SmallPathBuf::from(MFD_NAME_PREFIX);
                path.append_bytes(name.to_bytes());
                sandbox_path_1(&path, caps, "memfd_create", request, &sandbox)?;
            }

            // Set CLOEXEC for our fd always, and
            // Set CLOEXEC for remote fd as necessary.
            let cloexec = flags & MFD_CLOEXEC != 0;
            flags |= MFD_CLOEXEC;

            // Access granted, emulate call and return the fd to the process.
            // SAFETY: nix' MemFdCreateFlags don't define MFD_EXEC and MFD_NOEXEC_SEAL yet.
            let fd = unsafe { nix::libc::memfd_create(name.as_ptr(), flags) };
            let fd = match Errno::result(fd) {
                Ok(fd) => {
                    // SAFETY: memfd_create returns a valid FD.
                    unsafe { OwnedFd::from_raw_fd(fd) }
                }
                Err(Errno::EINVAL) => {
                    // Return ENOSYS rather than EINVAL if MFD_NOEXEC_SEAL is unsupported.
                    // FIXME: This case includes other invalid flag combinations!
                    return Err(Errno::ENOSYS);
                }
                Err(errno) => return Err(errno),
            };
            request.send_fd(fd.as_raw_fd(), cloexec)
        }
    )
}

#[allow(clippy::cognitive_complexity)]
fn syscall_exec_handler(
    request: &UNotifyEventRequest,
    syscall_name: &str,
    arg: &SyscallPathArgument,
) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            // If sandboxing for all the selected capabilities is off, return immediately.
            let caps = sandbox.getcaps(Capability::CAP_EXEC | Capability::CAP_TPE);
            let kallsyms = sandbox.allow_unsafe_perf();
            let deny_script = sandbox.deny_script();

            if caps.is_empty() && !deny_script {
                // SAFETY: No restrictions requested, continue.
                return Ok(unsafe { request.continue_syscall() });
            }

            // Read remote path.
            let (path, _) = proc.read_path(request, &sandbox, arg, false, false, kallsyms)?;

            // Call sandbox access checker.
            if caps.contains(Capability::CAP_EXEC) {
                sandbox_path_1(&path, Capability::CAP_EXEC, syscall_name, request, &sandbox)?;
            }

            // Trusted Path Execution.
            if caps.contains(Capability::CAP_TPE) {
                let action = sandbox.check_tpe(&path);
                if !matches!(action, Action::Allow | Action::Filter) {
                    warn!("ctx": "access", "cap": Capability::CAP_TPE,
                        "act": action, "pid": req.pid, "sys": syscall_name,
                        "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                        "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer));
                }
                match action {
                    Action::Allow | Action::Warn => {}
                    Action::Deny | Action::Filter => return Err(Errno::EACCES),
                    Action::Exit => std::process::exit(nix::libc::EACCES),
                    _ => {
                        // Stop|Kill
                        let _ = request.kill(action, false);
                        return Err(Errno::EACCES);
                    }
                }
            }

            if deny_script {
                // SAFETY: Use safe open to avoid TOCTOU!
                let (mut file, statx) = safe_open_file(&path).or(Err(Errno::EACCES))?;
                if statx.stx_size >= 2 {
                    let mut marker = [0; 2];
                    let mut bytes_read = 0;
                    while bytes_read < marker.len() {
                        #[allow(clippy::arithmetic_side_effects)]
                        match file.read(&mut marker[bytes_read..]) {
                            Ok(0) => break,
                            Ok(n) => bytes_read += n,
                            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
                            Err(_) => {
                                return Err(if is_executable(&file) {
                                    Errno::ENOEXEC
                                } else {
                                    Errno::EACCES
                                });
                            }
                        }
                    }

                    if bytes_read == 2 && marker[0] == b'#' && marker[1] == b'!' {
                        // SAFETY: Respect executable rights.
                        return Err(if is_executable(&file) {
                            Errno::ENOSYS
                        } else {
                            Errno::EACCES
                        });
                    }
                }
            }

            // SAFETY: This is vulnerable to TOCTOU,
            // unfortunately we cannot emulate exec,
            // Check: https://bugzilla.kernel.org/show_bug.cgi?id=218501
            // The exec-TOCTOU-mitigator gives us a fair protection
            // against this, see the wait() function for context.
            Ok(unsafe { request.continue_syscall() })
        }
    )
}

/// Handles syscalls related to signal handling, protecting the syd
/// process and their threads from signals.
///
/// # Parameters
///
/// - `request`: User notification request from seccomp.
/// - `group`: true if the system call has both progress group id and process id (tgkill), false otherwise.
/// - `syscall_name`: The name of the syscall being handled, used for logging and error reporting.
/// # Returns
///
/// - `ScmpNotifResp`: Response indicating the result of the syscall handling.
fn syscall_signal_handler(request: &UNotifyEventRequest, group: bool) -> ScmpNotifResp {
    let req = request.get_request();

    #[allow(clippy::cast_possible_truncation)]
    let pid = [
        req.data.args[0] as nix::libc::pid_t,
        req.data.args[1] as nix::libc::pid_t,
    ];

    let mut bad = false;
    for (idx, tid) in pid.iter().enumerate() {
        let tid = if *tid == -1 {
            // SAFETY: We do not allow mass signaling with -1.
            bad = true;
            break;
        } else {
            tid.abs()
        };
        if faccessat(
            None,
            format!("/proc/self/task/{tid}").as_str(),
            AccessFlags::F_OK,
            AtFlags::AT_SYMLINK_NOFOLLOW,
        )
        .is_ok()
        {
            bad = true;
            break;
        } else if idx >= 1 || !group {
            // all except tgkill get a single pid argument.
            break;
        }
    }

    if bad {
        return request.fail_syscall(Errno::EACCES as i32);
    }
    // SAFETY: This is safe because we haven't dereferenced
    // any pointers during access check.
    unsafe { request.continue_syscall() }
}

///
/// Handles syscalls related to paths, reducing code redundancy and ensuring a uniform way of dealing with paths.
///
/// # Parameters
///
/// - `request`: User notification request from seccomp.
/// - `syscall_name`: The name of the syscall being handled, used for logging and error reporting.
/// - `arg_mappings`: Non-empty list of argument mappings containing dirfd and path indexes, if applicable.
/// - `handler`: Closure that processes the constructed canonical paths and performs additional syscall-specific operations.
///
/// # Returns
///
/// - `ScmpNotifResp`: Response indicating the result of the syscall handling.
#[allow(clippy::cognitive_complexity)]
fn syscall_path_handler<H>(
    request: &UNotifyEventRequest,
    syscall_name: &str,
    path_argv: &[SyscallPathArgument],
    handler: H,
) -> ScmpNotifResp
where
    H: Fn(&[PathArg], &RemoteProcess, SandboxGuard) -> Result<ScmpNotifResp, Errno>,
{
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            // Determine system call capabilities.
            let caps = Capability::try_from((proc, request, req, syscall_name))?;

            // If sandboxing for all the selected capabilities is off, return immediately.
            let check = sandbox.enabled_any(caps);
            // We only honour nodotdot for chdir and fchdir here.
            // open(2) family calls are handled in syscall_open_handler.
            let nodotdot = sandbox.deny_dotdot() && syscall_name.ends_with("chdir");
            let kallsyms = sandbox.allow_unsafe_perf();

            let mut fds: SmallVec<[Option<OwnedFd>; 2]> = SmallVec::new();
            let mut paths: SmallVec<[SmallPathBuf; 2]> = SmallVec::new();
            for arg in path_argv {
                // Handle system calls that take a FD only,
                // such as fchmod, fchown, falllocate, ftruncate,
                // fgetxattr, fsetxattr safely and efficiently.
                if arg.path.is_some() {
                    let (path, _) =
                        proc.read_path(request, &sandbox, arg, false, nodotdot, kallsyms)?;
                    paths.push(path);
                    fds.push(None);
                } else if let Some(idx) = arg.dirfd {
                    let dirfd = req.data.args[idx] as RawFd;
                    // SAFETY: Get the file descriptor before access check
                    // as it may change after which is a TOCTOU vector.
                    let fd = proc.get_fd(dirfd, request)?;
                    // SAFETY: Get the remote path by readlinking the
                    // local FD which is TOCTOU-free now.
                    let mut buf = itoa::Buffer::new();
                    let mut pfd = TinyPathBuf::new();
                    pfd.push(Path::new("/proc/self/fd"));
                    pfd.push(Path::new(buf.format(fd.as_raw_fd())));
                    let path = match read_link(&pfd) {
                        Ok(target) if target.is_relative() => {
                            // SAFETY: /proc fd symlink does not refer to a
                            // filesystem path.
                            let mut buf = itoa::Buffer::new();
                            let mut pfd = SmallPathBuf::new();
                            pfd.push(Path::new("/proc"));
                            pfd.push(Path::new(buf.format(req.pid)));
                            pfd.push(Path::new("fd"));
                            pfd.push(&target);
                            pfd
                        }
                        Ok(target) => target,
                        Err(_) => {
                            // SAFETY: /proc fd symlink does not refer to a
                            // filesystem path.
                            let mut buf0 = itoa::Buffer::new();
                            let mut buf1 = itoa::Buffer::new();
                            let mut pfd = SmallPathBuf::new();
                            pfd.push(Path::new("/proc"));
                            pfd.push(Path::new(buf0.format(req.pid)));
                            pfd.push(Path::new("fd"));
                            pfd.push(Path::new(buf1.format(dirfd)));
                            pfd
                        }
                    };
                    paths.push(path);
                    if arg.cont {
                        // FD not required.
                        fds.push(None);
                    } else {
                        fds.push(Some(fd));
                    }
                } else {
                    unreachable!("BUG: Both dirfd and path are None in SyscallArgument!");
                }
            }

            if !check {
                // SAFETY: This is unsafe due to vulnerability to TOCTOU,
                // however since sandboxing for the respective Capability
                // is off, this is safe here.
                // SAFETY: We do this late after read_path so that protection
                // against magic proc symlinks still work with sandboxing off.
                return unsafe { Ok(request.continue_syscall()) };
            }

            // Call sandbox access checker.
            match paths.len() {
                1 => {
                    sandbox_path_1(&paths[0], caps, syscall_name, request, &sandbox)?;
                }
                2 => {
                    sandbox_path_2(paths.as_slice(), caps, syscall_name, request, &sandbox)?;
                }
                _ => unreachable!(),
            }

            // SAFETY: After this point we are not permitted to resolve
            // symbolic links any longer or else we risk TOCTOU.
            let mut pargs: SmallVec<[PathArg; 2]> = SmallVec::new();

            // No further handling is necessary if we're continuing.
            // Call the system call handler right away.
            if path_argv[0].cont {
                return handler(&pargs, proc, sandbox);
            }

            for i in 0..paths.len() {
                // Resolve /proc file descriptor links.
                // Use get_fd if fd belongs to the current process.
                // SAFETY: Magic symlinks are disallowed with ELOOP here, see proc_fd.
                let fd = if path_argv[i].path.is_none() {
                    #[allow(clippy::disallowed_methods)]
                    let fd = fds
                        .get_mut(i)
                        .expect("BUG: Both dirfd and path are None in SyscallArgument!");
                    fd.take()
                } else if path_argv[i].resolve {
                    match proc_fd(req.pid(), &paths[i])? {
                        Some(fd) if fd != nix::libc::AT_FDCWD => {
                            Some(match proc.get_fd(fd, request) {
                                Ok(fd) => fd,
                                Err(Errno::EBADF) => return Err(Errno::ENOENT),
                                Err(errno) => return Err(errno),
                            })
                        }
                        _ => None,
                    }
                } else {
                    None
                };

                // If arg.miss != Existing, we open a FD to the parent here
                // rather than to the file itself because the file may not
                // exist. The syscall handler is responsible to handle the
                // base part of the path in a TOCTOU-free way.
                // rmdir and unlink{,at} are exceptions because they cannot
                // directly operate on the file descriptor.
                pargs.push(if let Some(fd) = fd {
                    PathArg(fd, SmallPathBuf::default())
                } else if path_argv[i].miss == Existing && !path_argv[i].fs && path_argv[i].resolve
                {
                    // Use a O_PATH fd by default unless O_{RD,WR}ONLY is requested.
                    // SAFETY: Use NONBLOCK with non-path fds to avoid blocking
                    // on pipes, FIFOs etc.
                    let how = safe_open_how(if path_argv[i].wr {
                        OFlag::O_WRONLY | OFlag::O_NONBLOCK
                    } else if path_argv[i].rd {
                        OFlag::O_RDONLY | OFlag::O_NONBLOCK
                    } else {
                        OFlag::O_PATH
                    });
                    let fd = retry_on_eintr(|| openat2(nix::libc::AT_FDCWD, &paths[i], how))?;
                    // SAFETY: openat2 returns a valid FD.
                    PathArg(unsafe { OwnedFd::from_raw_fd(fd) }, SmallPathBuf::default())
                } else {
                    let how = safe_open_how(
                        OFlag::O_DIRECTORY
                            | if path_argv[i].rd_dir {
                                OFlag::O_RDONLY
                            } else {
                                OFlag::O_PATH
                            },
                    );
                    let (root, base) = path_split(&paths[i]);
                    let fd = retry_on_eintr(|| openat2(nix::libc::AT_FDCWD, root.as_os_str(), how))
                        .map(|fd| {
                            // SAFETY: openat2 returns a valid FD.
                            unsafe { OwnedFd::from_raw_fd(fd) }
                        })?;
                    PathArg(fd, SmallPathBuf::from(base.as_ref()))
                });
            }

            // Call the system call handler.
            handler(&pargs, proc, sandbox)
        }
    )
}

#[allow(clippy::cognitive_complexity)]
fn syscall_open_handler(
    request: &UNotifyEventRequest,
    arg: &SyscallPathArgument,
    flags: OFlag,
    mode: Mode,
) -> ScmpNotifResp {
    syscall_handler_lock!(
        request,
        |req: &ScmpNotifReq, proc: &RemoteProcess, sandbox: SandboxGuard| {
            // SAFETY: When emulating the open call we always open the file
            // descriptor with O_CLOEXEC flag for safety. Here, we mark the
            // state of the O_CLOEXEC flag to use it in seccomp_addfd call.
            // Mutability:
            // 1. o_cloexec is mutable because trace/force_cloexec:1 may change its value
            //    for O_CLOEXEC.
            // 2. flags is mutable because trace/allow_unsafe_open:0 may change its value
            //    for O_PATH. Append-only may also edit flags.
            let mut o_cloexec = flags.contains(OFlag::O_CLOEXEC);
            let mut flags = flags | OFlag::O_CLOEXEC;

            let o_path = flags.contains(OFlag::O_PATH);
            let o_mask = flags.contains(OFlag::O_CREAT) || flags.contains(OFlag::O_TMPFILE);
            // Quoting open(2):
            // Currently, it is not possible to enable signal-driven I/O by specifying O_ASYNC when
            // calling open(); use fcntl(2) to enable this flag.
            // TODO: When this is possible, we must use fcntl to redirect signals
            // to the sandbox thread making the actual open call or else we break
            // async i/o.
            // let o_async = flags.contains(OFlag::O_ASYNC);
            let cap = if o_path {
                Capability::CAP_READ
            } else if o_mask {
                Capability::CAP_WRITE // TODO: CAP_CREAT!
            } else {
                // Note, we set CAP_WRITE only for RDWR for simplicity.
                // This ensures these capabilities are mutually exclusive.
                // We take advantage of this during sandbox access check.
                match flags & OFlag::O_ACCMODE {
                    OFlag::O_RDONLY => Capability::CAP_READ,
                    OFlag::O_WRONLY => Capability::CAP_WRITE,
                    OFlag::O_RDWR => Capability::CAP_WRITE,
                    _ => return Err(Errno::EINVAL), // Invalid access mode.
                }
            };

            #[allow(clippy::cast_possible_wrap)]
            let is_lock =
                Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t);
            let enabled = sandbox.enabled(cap);
            let nodotdot = sandbox.deny_dotdot();
            let kallsyms = sandbox.allow_unsafe_perf();
            let contpath = if o_path {
                sandbox.allow_unsafe_open()
            } else {
                false
            };
            if sandbox.force_cloexec() {
                o_cloexec = true;
            }

            // Read the remote path.
            // If lock is on do not check for magic path.
            let (mut path, magic) =
                proc.read_path(request, &sandbox, arg, !is_lock, nodotdot, kallsyms)?;

            // Handle the special /dev/syd paths.
            if magic {
                if o_path {
                    return Err(Errno::EINVAL);
                }

                let fd = if cap == Capability::CAP_READ {
                    let fd = create_memfd(MFD_ALLOW_SEALING)?;

                    let content = serde_json::to_string(&*sandbox).or(Err(Errno::EINVAL))?;
                    write(fd.as_fd(), content.as_bytes())?;
                    lseek64(fd.as_raw_fd(), 0, Whence::SeekSet)?;
                    seal_memfd(&fd)?; // deny further writes.

                    fd
                } else {
                    retry_on_eintr(|| open(MAGIC_FILE, flags, mode)).map(|fd| {
                        // SAFETY: Open returns a valid FD.
                        unsafe { OwnedFd::from_raw_fd(fd) }
                    })?
                };

                // Send the file descriptor to the process and return the fd no.
                return request.send_fd(fd.as_raw_fd(), true);
            } else if !enabled {
                // SAFETY: This is unsafe due to vulnerability to TOCTOU.
                // Since the sandbox is off, this is fine here.
                // Note, we do this late after read path so that magic
                // symlinks are denied even if sandboxing is off.
                return unsafe { Ok(request.continue_syscall()) };
            }

            // SAFETY: We must provide safe access to
            // sandbox process' controlling terminal.
            let pid = req.pid();
            if litmatch(b"/dev/tty", path.as_os_str().as_bytes()) {
                let dev_tty = proc_tty(pid)?;
                if !request.is_valid() {
                    return Err(Errno::ESRCH);
                }
                path = dev_tty;
            }

            // Check for Read or Write access.
            let (action, filter) = if enabled {
                // Note, cap may only be CAP_READ or CAP_WRITE here!
                request.check_path(&sandbox, cap, &path)
            } else {
                (Action::Allow, true)
            };
            let hidden = if action.is_denying() {
                // No need to check for mask when denying.
                request.is_hidden(&sandbox, &path)
            } else {
                // SAFETY:
                // 1. Check for masked path and change path to /dev/null.
                // 2. Check for append-only path and edit flags argument.
                // We perform these check only if we're allowing.
                let masked = if request.is_masked(&sandbox, &path) {
                    path = SmallPathBuf::from("/dev/null");
                    true
                } else {
                    false
                };

                // Masked files can not be made append-only.
                if !masked && cap == Capability::CAP_WRITE && request.is_append(&sandbox, &path) {
                    flags.insert(OFlag::O_APPEND);
                    flags.remove(OFlag::O_TRUNC);
                }

                // No need for hidden check if we're allowing.
                false
            };
            let force_umask = sandbox.umask;

            // Perform action: allow->emulate, deny->log.
            if action != Action::Allow && !filter && !hidden {
                warn!("ctx": "access", "cap": cap, "act": action,
                    "pid": pid.as_raw(), "sys": "open",
                    "args": req.data.args, "path": &path, "arch": SydArch(req.data.arch),
                    "src": proc_mmap(req.pid(), req.data.arch, req.data.instr_pointer),
                    "flags": flags.bits(), "mode": mode.bits());
            }
            match action {
                Action::Allow | Action::Warn => {
                    // The system call is allowed.
                    // To prevent TOCTOU, we open the file ourselves,
                    // and put the file descriptor to the process'
                    // address space with SECCOMP_IOCTL_NOTIF_ADDFD.

                    if o_path {
                        if !contpath {
                            // SAFETY: Turn O_PATH flag to O_RDONLY for successful emulation.
                            flags.remove(OFlag::O_PATH);
                            flags.insert(OFlag::O_RDONLY);
                        } else {
                            // SAFETY:
                            // seccomp addfd operation returns EBADF for O_PATH file
                            // descriptors so there's no TOCTOU-free way to emulate
                            // this as of yet. However we did our best by
                            // delaying continue up to this point, thereby
                            // including the open request to the sandbox access
                            // check.
                            return unsafe { Ok(request.continue_syscall()) };
                        }
                    }

                    // Resolve /proc file descriptor links.
                    // Use get_fd if fd belongs to the current process.
                    // SAFETY: Magic symlinks are disallowed with ELOOP here, see proc_fd.
                    // TODO: Use a let chain to reduce indentation here, when it's stable.
                    let result = match proc_fd(pid, &path)? {
                        Some(nix::libc::AT_FDCWD) => {
                            // proc/self/{cwd,exe,root}, drop O_NOFOLLOW.
                            flags.remove(OFlag::O_NOFOLLOW);
                            let mode = if o_mask {
                                // SAFETY: syd's umask is 0 here so we respect process' umask.
                                mode & !proc_umask(pid)?
                            } else {
                                // SAFETY: Mode must be 0 if O_CREAT or O_TMPFILE is not in flags.
                                Mode::empty()
                            };
                            // SAFETY: No {magic ,}symlink resolution during path traversal.
                            let how = OpenHow::new().flags(flags).mode(mode).resolve(
                                ResolveFlag::RESOLVE_NO_MAGICLINKS
                                    | ResolveFlag::RESOLVE_NO_SYMLINKS,
                            );
                            retry_on_eintr(|| {
                                openat2(nix::libc::AT_FDCWD, &path, how).map(|fd|
                                    // SAFETY: openat2 returns a valid FD.
                                    unsafe { OwnedFd::from_raw_fd(fd) })
                            })
                        }
                        Some(fd) => match proc.get_fd(fd, request) {
                            Ok(fd) => Ok(fd),
                            Err(Errno::EBADF) => return Err(Errno::ENOENT),
                            Err(errno) => return Err(errno),
                        },
                        None => {
                            // SAFETY: If we're creating the file, we need a FD to
                            // the parent directory to determine whether it has
                            // POSIX ACLs in which case we must skip applying
                            // umask.
                            let (dirfd, path, mode) = if o_mask {
                                // SAFETY:
                                // 1. syd's umask is 0 here so we respect process' umask.
                                // 2. If the parent directory has POSIX ACLs we skip umask.
                                // 3. force_umask overrides ACLs.
                                // Note, fhasposixacl requires a RDONLY fd not PATH!
                                let (root, base) = path_split(&path);
                                let root = safe_open(&root, OFlag::O_RDONLY | OFlag::O_DIRECTORY)?;
                                let mode = if let Some(mask) = force_umask {
                                    mode & !mask
                                } else if fhasposixacl(&root) {
                                    mode
                                } else {
                                    mode & !proc_umask(pid)?
                                };
                                (Some(root), base.as_ref().into(), mode)
                            } else {
                                // SAFETY: Mode must be 0 if O_CREAT or
                                // O_TMPFILE is not in flags.
                                (None, path.clone(), Mode::empty())
                            };

                            // SAFETY: We have already resolved the symbolic
                            // links in the path as necessary, to prevent a
                            // time-of-check to time-of-use vector:
                            // 1. Add O_NOFOLLOW to flags.
                            // 2. Add RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS to flags.
                            // 3. Add RESOLVE_IN_ROOT to flags.
                            flags.insert(OFlag::O_NOFOLLOW);
                            let mut rflags = ResolveFlag::RESOLVE_NO_MAGICLINKS
                                | ResolveFlag::RESOLVE_NO_SYMLINKS;
                            if dirfd.is_some() {
                                rflags |= ResolveFlag::RESOLVE_IN_ROOT;
                            }
                            let how = OpenHow::new().flags(flags).mode(mode).resolve(rflags);

                            // All set, open file.
                            retry_on_eintr(|| {
                                openat2(
                                    dirfd
                                        .as_ref()
                                        .map(|fd| fd.as_raw_fd())
                                        .unwrap_or(nix::libc::AT_FDCWD),
                                    &path,
                                    how,
                                )
                                .map(|fd|
                                    // SAFETY: openat2 returns a valid FD.
                                    unsafe { OwnedFd::from_raw_fd(fd) })
                            })
                        }
                    };
                    request.send_fd(result?.as_raw_fd(), o_cloexec)
                }
                Action::Deny | Action::Filter if hidden => Err(Errno::ENOENT),
                Action::Deny | Action::Filter => Err(Errno::EACCES),
                Action::Exit => std::process::exit(nix::libc::EACCES),
                action => {
                    // Stop|Kill
                    let _ = request.kill(action, false);
                    Err(Errno::EACCES)
                }
            }
        }
    )
}

#[allow(clippy::cognitive_complexity)]
fn syscall_stat_handler(
    request: &UNotifyEventRequest,
    arg: &SyscallPathArgument,
    arg_stat: usize,
    is32: bool,
) -> ScmpNotifResp {
    syscall_handler!(request, |req: &ScmpNotifReq, proc: &RemoteProcess| {
        // Note: This is a virtual call handler,
        // `sandbox` is an upgradable read lock with exclusive access.
        // We'll either upgrade it or downgrade it based on magic lock.
        // Exception: Sandbox lock had been set and there's no turning back.
        let locked_once = Sandbox::locked_once();
        let mut sandbox = if locked_once {
            request.get_sandbox()
        } else {
            request.get_sandbox_upgradable()
        };

        // If sandboxing for CAP_STAT is off, and magic lock is set, return immediately.
        #[allow(clippy::cast_possible_wrap)]
        let is_lock = locked_once || sandbox.locked_for_pid(req.pid as nix::libc::pid_t);
        let is_stat = sandbox.enabled(Capability::CAP_STAT);
        if is_lock && !is_stat {
            // SAFETY: This is unsafe due to vulnerability to TOCTOU.
            // However, since stat sandboxing is disabled here this is
            // safe to call.
            return unsafe { Ok(request.continue_syscall()) };
        }
        let allow_unsafe_perf = sandbox.allow_unsafe_perf();

        let (mut path, magic) = if arg.empty {
            // No need to read the path for AT_EMPTY_PATH,
            // to check for access we're going to pidfd_getfd
            // the file descriptor and only then canonicalize
            // the /proc/pid/fd/$fd symbolic link to workaround
            // potential permission errors.
            // (e.g. hidepid=2 in a user ns)
            (SmallPathBuf::default(), false)
        } else {
            // If lock is on do not check for magic path.
            proc.read_path(request, &sandbox, arg, !is_lock, false, allow_unsafe_perf)?
        };

        let maybe_cmd = if !magic {
            None
        } else {
            let result = path.strip_prefix(Path::new(MAGIC_PREFIX));
            // SAFETY: magic=true asserts path starts with magic prefix.
            Some(unsafe { result.unwrap_unchecked() })
        };
        let (root, base) = if let Some(cmd) = maybe_cmd {
            // Handle magic prefix (ie /dev/syd)
            // Careful here, Path::strip_prefix removes trailing slashes.
            let mut cmd = SmallPathBuf::from(cmd);
            if path_ends_with_slash(&path) {
                cmd.push(Path::new(""));
            }
            let path = cmd;

            // Clear caches to ensure consistency.
            request.cache.0.clear();
            request.cache.1.clear();

            // Execute magic command.
            sandbox = sandbox.upgrade();

            match path.as_os_str().as_bytes() {
                b"ghost" => {
                    // SAFETY: Reset sandbox to ensure no run-away execs.
                    sandbox.reset()?;

                    // Signal the poll process to exit.
                    return Err(Errno::EOWNERDEAD);
                }
                b"panic" => sandbox.panic()?,
                _ => {}
            }

            if path.is_empty() {
                sandbox.config("")?;
            } else if let Ok(path) = path.strip_prefix(Path::new("load")) {
                // We handle load specially here as it involves process access.
                match parse_fd(path) {
                    Ok(remote_fd) => {
                        let fd = proc.get_fd(remote_fd, request)?;
                        let file = BufReader::new(File::from(fd));
                        let mut imap = FxHashSet::default();
                        // SAFETY: parse_config() checks for the file name
                        // /dev/syd/load and disables config file include
                        // feature depending on this check.
                        if sandbox
                            .parse_config(file, "/dev/syd/load", &mut imap)
                            .is_err()
                        {
                            return Ok(request.fail_syscall(nix::libc::EINVAL));
                        }
                        // Fall through to emulate as /dev/null.
                    }
                    Err(errno) => {
                        return Ok(request.fail_syscall(errno as i32));
                    }
                }
            } else {
                // SAFETY: Conversion from PathBuf to String is OK here,
                // since sandbox config function does not work on the
                // filesystem, rather treats the Strings as sandbox
                // commands.
                sandbox.config(path.to_string_lossy().as_ref())?;
            }

            // If the stat buffer is NULL, return immediately.
            if req.data.args[arg_stat] == 0 {
                return Ok(request.return_syscall(0));
            }

            // SAFETY: Do not resolve symbolic links in MAGIC_FILE.
            (
                safe_open_path(Path::new(MAGIC_FILE), OFlag::empty())?,
                SmallPathBuf::default(),
            )
        } else if is_stat {
            // Downgrade lock to Read (not magic command).
            // No-op if lock is already Read (e.g. when locked_once).
            sandbox = sandbox.downgrade();

            // Resolve /proc file descriptor links.
            // Use get_fd if fd belongs to the current process.
            // SAFETY: Magic symlinks are disallowed with ELOOP here, see proc_fd.
            let fd = if arg.empty || arg.path.is_none() {
                Some(req.data.args[0] as RawFd)
            } else if arg.resolve {
                match proc_fd(req.pid(), &path)? {
                    Some(fd) if fd != nix::libc::AT_FDCWD => Some(fd),
                    _ => None,
                }
            } else {
                None
            };

            if let Some(fd) = fd {
                let fd = proc.get_fd(fd, request)?;

                // SAFETY: Access check is made only at this point, to
                // ensure we do have access to the proc fd even with
                // hidepid=2 in a user namespace.
                // SAFETY: We do not resolve symbolic links for AT_EMPTY_PATH, see syd#25.
                #[allow(clippy::cast_sign_loss)]
                let this = Pid::this().as_raw() as u32;
                let path = RemoteProcess::remote_dirfd(this, Some(fd.as_raw_fd()));
                match read_link(&path) {
                    Ok(path) if path.is_absolute() => {
                        // SAFETY: If the path is not absolute, it represents
                        // a special proc file name such as a pipe, or a socket.
                        // In this case, we skip the access check.
                        sandbox_path_1(&path, Capability::CAP_STAT, "stat", request, &sandbox)?;
                    }
                    Err(errno) => return Err(if arg.empty { Errno::EBADF } else { errno }),
                    _ => {} // special proc file, fall through.
                };

                (fd, SmallPathBuf::default())
            } else {
                if !arg.resolve {
                    // SAFETY: Remove trailing slash if it exists,
                    // or else we may get ELOOP on symbolic links.
                    let path_bytes = path.as_os_str().as_bytes();
                    #[allow(clippy::arithmetic_side_effects)]
                    if path_bytes.ends_with(b"/") && path_bytes.len() > 1 {
                        // SAFETY: Since we're operating on valid path bytes, getting a slice is safe.
                        // This excludes the root path "/" to avoid turning it into an empty path.
                        path = SmallPathBuf::from(&path_bytes[..path_bytes.len() - 1]);
                    }
                }

                // Check for access only now that path is in its final form.
                sandbox_path_1(&path, Capability::CAP_STAT, "stat", request, &sandbox)?;

                // SAFETY: We have already resolved symbolic links as
                // necessary, from this point on we do not resolve any
                // longer for safety as the filesystem may change which
                // may result in a TOCTOU.
                if arg.resolve {
                    (
                        safe_open_path(&path, OFlag::empty())?,
                        SmallPathBuf::default(),
                    )
                } else {
                    let (root, base) = path_split(&path);
                    (
                        safe_open_path(&root, OFlag::O_DIRECTORY)?,
                        SmallPathBuf::from(base.as_ref()),
                    )
                }
            }
        } else {
            // Continue system call normally.
            // SAFETY: This is unsafe due to vulnerability to TOCTOU,
            // however if we're here stat sandboxing is disabled,
            // so this is safe to call.
            return unsafe { Ok(request.continue_syscall()) };
        };

        if arg_stat == 4 {
            // statx
            #[allow(clippy::cast_possible_truncation)]
            let flags = req.data.args[2] as nix::libc::c_uint;
            #[allow(clippy::cast_possible_truncation)]
            let mut mask = req.data.args[3] as nix::libc::c_uint;

            // SAFETY: The sidechannel check below requires the mask
            // to have the following items:
            // 1. STATX_TYPE (to check for char/block device)
            // 2. STATX_MODE (to check for world readable/writable)
            // To ensure that here, we inject these two flags into
            // mask noting if they were set originally. This can be
            // in three ways,
            // (a) Explicitly setting STATX_{TYPE,MODE}.
            // (b) Explicitly setting STATX_BASIC_STATS.
            // (c) Setting the catch-all STATX_ALL flag.
            // After the statx call if the flags STATX_{TYPE,MODE}
            // were not set we clear stx_mode's type and mode bits
            // as necessary and also remove STATX_{TYPE,MODE} from
            // stx_mask as necessary.
            let orig_mask = mask;
            let basic_stx = (orig_mask & STATX_BASIC_STATS) != 0;
            if !basic_stx {
                mask |= STATX_TYPE | STATX_MODE;
            }

            let none = nix::libc::AT_EMPTY_PATH as nix::libc::c_uint;
            let mut flags = if base.is_empty() {
                flags | none
            } else {
                flags & !none
            };

            // SAFETY: After this point we are not permitted to resolve
            // symbolic links any longer or else we risk TOCTOU.
            flags |= nix::libc::AT_SYMLINK_NOFOLLOW as nix::libc::c_uint;

            let mut statx = retry_on_eintr(|| statx(Some(root.as_raw_fd()), &base, flags, mask))?;

            // SAFETY: Check if the file is a sidechannel device and
            // update its access and modification times to match the
            // creation time if it is. This prevents timing attacks on
            // block or character devices like /dev/ptmx using stat.
            if is_sidechannel_device(statx.stx_mode.into()) {
                statx.stx_atime = statx.stx_ctime;
                statx.stx_mtime = statx.stx_ctime;
            }

            // SAFETY: Restore mask, type and mode, see the comment above.
            #[allow(clippy::cast_possible_truncation)]
            if !basic_stx {
                if (orig_mask & STATX_TYPE) == 0 {
                    statx.stx_mode &= !nix::libc::S_IFMT as u16;
                    statx.stx_mask &= !STATX_TYPE;
                }
                if (orig_mask & STATX_MODE) == 0 {
                    statx.stx_mode &= nix::libc::S_IFMT as u16;
                    statx.stx_mask &= !STATX_MODE;
                }
            }

            // SAFETY: The following block creates an immutable byte
            // slice representing the memory of `statx`. We ensure that
            // the slice covers the entire memory of `statx` using
            // `std::mem::size_of_val`. Since `statx` is a stack
            // variable and we're only borrowing its memory for the
            // duration of the slice, there's no risk of `statx` being
            // deallocated while the slice exists. Additionally, we
            // ensure that the slice is not used outside of its valid
            // lifetime.
            let statx = unsafe {
                std::slice::from_raw_parts(
                    std::ptr::addr_of!(statx) as *const u8,
                    std::mem::size_of_val(&statx),
                )
            };
            let addr = req.data.args[4];
            if addr != 0 {
                proc.write_mem(statx, addr, request)?;
            }
        } else {
            // "stat" | "fstat" | "lstat" | "newfstatat"
            let mut flags = if base.is_empty() {
                nix::libc::AT_EMPTY_PATH
            } else {
                0
            };

            // SAFETY: After this point we are not permitted to resolve
            // symbolic links any longer or else we risk TOCTOU.
            flags |= nix::libc::AT_SYMLINK_NOFOLLOW;

            // SAFETY: In libc we trust.
            let mut stat = retry_on_eintr(|| fstatat64(Some(root.as_raw_fd()), &base, flags))?;

            // SAFETY: Check if the file is a sidechannel device and
            // update its access and modification times to match the
            // creation time if it is. This prevents timing attacks on
            // block or character devices like /dev/ptmx using stat.
            if is_sidechannel_device(stat.st_mode) {
                stat.st_atime = stat.st_ctime;
                stat.st_mtime = stat.st_ctime;
                stat.st_atime_nsec = stat.st_ctime_nsec;
                stat.st_mtime_nsec = stat.st_ctime_nsec;
            }

            let addr = req.data.args[arg_stat];
            if addr != 0 {
                if is32 {
                    let stat32: crate::compat::stat32 = stat.into();

                    // SAFETY: The following block creates an immutable
                    // byte slice representing the memory of `stat`.  We
                    // ensure that the slice covers the entire memory of
                    // `stat` using `std::mem::size_of_val`. Since
                    // `stat` is a stack variable and we're only
                    // borrowing its memory for the duration of the
                    // slice, there's no risk of `stat` being
                    // deallocated while the slice exists.
                    // Additionally, we ensure that the slice is not
                    // used outside of its valid lifetime.
                    let stat = unsafe {
                        std::slice::from_raw_parts(
                            std::ptr::addr_of!(stat32) as *const u8,
                            std::mem::size_of_val(&stat32),
                        )
                    };
                    proc.write_mem(stat, addr, request)?;
                } else {
                    // SAFETY: The following block creates an immutable
                    // byte slice representing the memory of `stat`.  We
                    // ensure that the slice covers the entire memory of
                    // `stat` using `std::mem::size_of_val`. Since
                    // `stat` is a stack variable and we're only
                    // borrowing its memory for the duration of the
                    // slice, there's no risk of `stat` being
                    // deallocated while the slice exists.
                    // Additionally, we ensure that the slice is not
                    // used outside of its valid lifetime.
                    let stat = unsafe {
                        std::slice::from_raw_parts(
                            std::ptr::addr_of!(stat) as *const u8,
                            std::mem::size_of_val(&stat),
                        )
                    };
                    proc.write_mem(stat, addr, request)?;
                }
            }
        }

        // stat system call successfully emulated.
        Ok(request.return_syscall(0))
    })
}

/// A helper function to handle utime* syscalls.
fn syscall_utime_handler(
    request: &UNotifyEventRequest,
    args: &[PathArg],
    atime: &TimeSpec,
    mtime: &TimeSpec,
) -> Result<ScmpNotifResp, Errno> {
    let PathArg(root, base) = &args[0];

    if base.is_empty() {
        // proc magic symlink, assume WR.
        futimens(root.as_raw_fd(), atime, mtime)
    } else {
        // SAFETY: After this point we are not permitted to resolve
        // symbolic links any longer or else we risk TOCTOU.
        utimensat(
            Some(root.as_raw_fd()),
            base,
            atime,
            mtime,
            UtimensatFlags::NoFollowSymlink,
        )
    }
    .map(|_| request.return_syscall(0))
}

/// A helper function to handle link{,at} syscalls.
fn syscall_link_handler(
    request: &UNotifyEventRequest,
    args: &[PathArg],
) -> Result<ScmpNotifResp, Errno> {
    let PathArg(src, src_base) = &args[0];
    let PathArg(dst, dst_base) = &args[1];

    // SAFETY: linkat does not follow symbolic links in old path by
    // default unless AT_SYMLINK_FOLLOW flag is passed. As such,
    // AT_SYMLINK_NOFOLLOW is an invalid flag for linkat.
    linkat(
        Some(src.as_raw_fd()),
        src_base,
        Some(dst.as_raw_fd()),
        dst_base,
        AtFlags::empty(),
    )
    .map(|_| request.return_syscall(0))
}

/// A helper function to handle symlink{,at} syscalls.
fn syscall_symlink_handler(
    request: &UNotifyEventRequest,
    proc: &RemoteProcess,
    req: &ScmpNotifReq,
    sandbox: &SandboxGuard,
    arg: SyscallPathArgument,
) -> Result<ScmpNotifResp, Errno> {
    // If sandboxing for all the selected capabilities is off, return immediately.
    if !sandbox.enabled(Capability::CAP_WRITE) {
        // SAFETY: This is unsafe due to vulnerability to TOCTOU,
        // however since sandboxing for the respective Capability
        // is off, this is safe here.
        return unsafe { Ok(request.continue_syscall()) };
    }
    let kallsyms = sandbox.allow_unsafe_perf();

    // SAFETY: symlink() returns ENOENT if target is an empty string.
    #[allow(clippy::cast_possible_truncation)]
    let target = remote_path_n!(proc, req, 0, &request)?;
    if target.is_empty() {
        return Err(Errno::ENOENT);
    }

    let (path, _) = proc.read_path(request, sandbox, &arg, false, false, kallsyms)?;
    sandbox_path_1(&path, Capability::CAP_WRITE, "symlink", request, sandbox)?;

    // SAFETY: After this point we are not permitted to resolve
    // symbolic links any longer or else we risk TOCTOU.
    let (root, base) = path_split(&path);
    let fd = safe_open_path(&root, OFlag::O_DIRECTORY)?;

    symlinkat(&target, Some(fd.as_raw_fd()), base.as_os_str()).map(|_| request.return_syscall(0))
}

/// A helper function to handle getxattr-family syscalls.
fn syscall_getxattr_handler<P: AsRef<Path>>(
    request: &UNotifyEventRequest,
    proc: &RemoteProcess,
    root: &OwnedFd,
    base: P,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.get_request();

    let base = if base.as_ref().is_empty() {
        // SAFETY: Do not handle magic symlinks.
        return Err(Errno::ENOTSUP);
    } else {
        CString::new(base.as_ref().as_os_str().as_bytes()).or(Err(Errno::EINVAL))?
    };

    let name = if req.data.args[1] != 0 {
        const SIZ: usize = nix::libc::PATH_MAX as usize;
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        proc.read_mem(&mut buf, req.data.args[1], request)?;
        Some(buf)
    } else {
        None
    };
    let name = if let Some(ref name) = name {
        CStr::from_bytes_until_nul(name)
            .or(Err(Errno::E2BIG))?
            .as_ptr()
    } else {
        std::ptr::null()
    };

    /*
     * We allow getting user.syd* but not setting it.
    #[allow(clippy::cast_possible_wrap)]
    if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
        // SAFETY: Deny user.syd* extended attributes. name is either
        // NULL or a valid nul-terminated C-String.
        // SAFETY: Deny with ENODATA for stealth.
        // SAFETY: Deny only if the Sandbox is locked for the process.
        unsafe { denyxattr(name) }?;
    }
    */

    // SAFETY: The size argument to the getxattr call
    // must not be fully trusted, it can be overly large,
    // and allocating a Vector of that capacity may overflow.
    #[allow(clippy::cast_possible_truncation)]
    let len = req.data.args[3] as usize;
    let len = len.min(nix::libc::PATH_MAX as usize); // Cap count at PATH_MAX.
    let mut buf = if len > 0 {
        let mut buf = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        buf.resize(len, 0);
        Some(buf)
    } else {
        None
    };
    let ptr = match buf.as_mut() {
        Some(b) => b.as_mut_ptr(),
        None => std::ptr::null_mut(),
    };

    let mut stack = [0u8; MINI_STACK_SIZE];
    let (pipe_ro, pipe_rw) = pipe2_raw(OFlag::O_CLOEXEC | OFlag::O_DIRECT)?;
    #[allow(clippy::blocks_in_conditions)]
    let pid_fd = match safe_clone(
        Box::new(|| -> isize {
            // Close the read end of the pipe.
            let _ = close(pipe_ro);

            // SAFETY: We use fchdir which is TOCTOU-free!
            if let Err(errno) = fchdir(root.as_raw_fd()) {
                return errno as isize;
            }

            // SAFETY: We do not resolve symbolic links here!
            let n = unsafe {
                nix::libc::lgetxattr(
                    base.as_ptr(),
                    name,
                    ptr as *mut _ as *mut nix::libc::c_void,
                    len,
                )
            };
            #[allow(clippy::cast_sign_loss)]
            let n = if n == -1 {
                return Errno::last() as isize;
            } else {
                n as usize
            };

            let n = n.to_le_bytes();
            let l = n.len();
            let n = n.as_ptr().cast();
            let r = loop {
                // SAFETY: In libc we trust.
                let r = unsafe { nix::libc::write(pipe_rw, n, l) };
                if r != -1 || Errno::last() != Errno::EINTR {
                    break r;
                }
            };
            let e = Errno::last();
            let _ = close(pipe_rw);
            if r >= 0 {
                0
            } else {
                e as isize
            }
        }),
        &mut stack[..],
        nix::libc::CLONE_VM | nix::libc::CLONE_VFORK | nix::libc::CLONE_SIGHAND,
        Some(nix::libc::SIGCHLD),
    ) {
        Ok(pid_fd) => pid_fd,
        Err(_) => {
            let _ = close(pipe_ro);
            let _ = close(pipe_rw);
            return Err(Errno::EACCES);
        }
    };

    // Close the write end of the pipe.
    let _ = close(pipe_rw);
    loop {
        break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
            Ok(WaitStatus::Exited(_, 0)) => {
                let mut n = [0u8; std::mem::size_of::<usize>()];
                let l = n.len();
                let p = n.as_mut_ptr().cast();
                let r = loop {
                    // SAFETY: In libc we trust.
                    let r = unsafe { nix::libc::read(pipe_ro, p, l) };
                    if r != -1 || Errno::last() != Errno::EINTR {
                        break r;
                    }
                };
                let e = Errno::last();
                let _ = close(pipe_ro);
                if r > 0 {
                    let n = usize::from_le_bytes(n);
                    if let Some(buf) = buf {
                        proc.write_mem(&buf[..n], req.data.args[2], request)?;
                    }
                    #[allow(clippy::cast_possible_wrap)]
                    Ok(request.return_syscall(n as i64))
                } else {
                    Err(e)
                }
            }
            Ok(WaitStatus::Exited(_, n)) => {
                let _ = close(pipe_ro);
                Err(Errno::from_raw(n))
            }
            Err(Errno::EINTR) => continue,
            _ => {
                let _ = close(pipe_ro);
                Err(Errno::EACCES)
            }
        };
    }
}

/// A helper function to handle setxattr-family syscalls.
fn syscall_setxattr_handler(
    request: &UNotifyEventRequest,
    proc: &RemoteProcess,
    sandbox: &SandboxGuard,
    fd: &OwnedFd,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.get_request();
    let name = if req.data.args[1] != 0 {
        const SIZ: usize = nix::libc::PATH_MAX as usize;
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        proc.read_mem(&mut buf, req.data.args[1], request)?;
        Some(buf)
    } else {
        None
    };
    let name = if let Some(ref name) = name {
        CStr::from_bytes_until_nul(name)
            .or(Err(Errno::E2BIG))?
            .as_ptr()
    } else {
        std::ptr::null()
    };

    #[allow(clippy::cast_possible_wrap)]
    if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
        // SAFETY: Deny user.syd* extended attributes. name is either
        // NULL or a valid nul-terminated C-String.
        // SAFETY: Deny with ENOTSUP for stealth (XXX: this may be
        // easily bypassed).
        // SAFETY: Deny only if the Sandbox is locked for the process.
        unsafe { denyxattr(name) }.or(Err(Errno::ENOTSUP))?;
    }

    // SAFETY: The size argument to the setxattr call
    // must not be fully trusted, it can be overly large,
    // and allocating a Vector of that capacity may overflow.
    let (buf, len) = if req.data.args[3] == 0 {
        (None, 0)
    } else {
        let len = usize::try_from(req.data.args[3]).or(Err(Errno::E2BIG))?;
        let len = len.min(nix::libc::PATH_MAX as usize); // Cap count at PATH_MAX.
        let mut buf = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        buf.resize(len, 0);
        proc.read_mem(&mut buf, req.data.args[2], request)?;
        (Some(buf), len)
    };
    let buf = buf.as_ref().map_or(std::ptr::null(), |b| b.as_ptr()) as *const nix::libc::c_void;

    #[allow(clippy::cast_possible_truncation)]
    let flags = req.data.args[4] as nix::libc::c_int;

    // SAFETY: In libc we trust.
    if unsafe { nix::libc::fsetxattr(fd.as_raw_fd(), name, buf, len, flags) } == 0 {
        Ok(request.return_syscall(0))
    } else {
        Err(Errno::last())
    }
}

/// A helper function to handle listxattr-family syscalls.
fn syscall_listxattr_handler<P: AsRef<Path>>(
    request: &UNotifyEventRequest,
    proc: &RemoteProcess,
    root: &OwnedFd,
    base: P,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.get_request();

    let base = if base.as_ref().is_empty() {
        // SAFETY: Do not handle magic symlinks.
        return Err(Errno::ENOTSUP);
    } else {
        CString::new(base.as_ref().as_os_str().as_bytes()).or(Err(Errno::EINVAL))?
    };

    // SAFETY: The size argument to the llistxattr call
    // must not be fully trusted, it can be overly large,
    // and allocating a Vector of that capacity may overflow.
    let len = usize::try_from(req.data.args[2])
        .or(Err(Errno::E2BIG))?
        .min(10240); // Cap count at 10240.
    let mut buf = if len > 0 {
        let mut buf = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        buf.resize(len, 0);
        Some(buf)
    } else {
        None
    };
    let ptr = buf
        .as_mut()
        .map_or(std::ptr::null_mut(), |b| b.as_mut_ptr()) as *mut nix::libc::c_char;

    let mut stack = [0u8; MINI_STACK_SIZE];
    let (pipe_ro, pipe_rw) = pipe2_raw(OFlag::O_CLOEXEC | OFlag::O_DIRECT)?;
    #[allow(clippy::blocks_in_conditions)]
    let pid_fd = match safe_clone(
        Box::new(|| -> isize {
            // Close the read end of the pipe.
            let _ = close(pipe_ro);

            // SAFETY: We use fchdir which is TOCTOU-free!
            if let Err(errno) = fchdir(root.as_raw_fd()) {
                return errno as isize;
            }

            // SAFETY: We do not resolve symbolic links here!
            let n = unsafe { nix::libc::llistxattr(base.as_ptr(), ptr, len) };
            #[allow(clippy::cast_sign_loss)]
            let n = if n == -1 {
                return Errno::last() as isize;
            } else {
                n as usize
            };

            let n = n.to_le_bytes();
            let l = n.len();
            let n = n.as_ptr().cast();
            let r = loop {
                // SAFETY: In libc we trust.
                let r = unsafe { nix::libc::write(pipe_rw, n, l) };
                if r != -1 || Errno::last() != Errno::EINTR {
                    break r;
                }
            };
            let e = Errno::last();
            let _ = close(pipe_rw);
            if r >= 0 {
                0
            } else {
                e as isize
            }
        }),
        &mut stack[..],
        nix::libc::CLONE_VM | nix::libc::CLONE_VFORK | nix::libc::CLONE_SIGHAND,
        Some(nix::libc::SIGCHLD),
    ) {
        Ok(pid_fd) => pid_fd,
        Err(_) => {
            let _ = close(pipe_ro);
            let _ = close(pipe_rw);
            return Err(Errno::EACCES);
        }
    };

    // Close the write end of the pipe.
    let _ = close(pipe_rw);
    loop {
        break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
            Ok(WaitStatus::Exited(_, 0)) => {
                let mut n = [0u8; std::mem::size_of::<usize>()];
                let l = n.len();
                let p = n.as_mut_ptr().cast();
                let r = loop {
                    // SAFETY: In libc we trust.
                    let r = unsafe { nix::libc::read(pipe_ro, p, l) };
                    if r != -1 || Errno::last() != Errno::EINTR {
                        break r;
                    }
                };
                let e = Errno::last();
                let _ = close(pipe_ro);
                if r > 0 {
                    let n = usize::from_le_bytes(n);
                    if let Some(buf) = buf {
                        /*
                         * We allow getting user.syd* but not setting it.
                        // SAFETY: Filter out attributes that start with "user.syd".
                        // SAFETY: Deny only if the Sandbox is locked for the process.
                        #[allow(clippy::cast_possible_wrap)]
                        let buf = if Sandbox::locked_once()
                            || sandbox.locked_for_pid(req.pid as nix::libc::pid_t)
                        {
                            filterxattr(&buf[..n], n)?
                        } else {
                            buf[..n].to_vec()
                        };
                        */
                        proc.write_mem(&buf, req.data.args[1], request)?;
                    }
                    #[allow(clippy::cast_possible_wrap)]
                    Ok(request.return_syscall(n as i64))
                } else {
                    Err(e)
                }
            }
            Ok(WaitStatus::Exited(_, n)) => {
                let _ = close(pipe_ro);
                Err(Errno::from_raw(n))
            }
            Err(Errno::EINTR) => continue,
            _ => {
                let _ = close(pipe_ro);
                Err(Errno::EACCES)
            }
        };
    }
}

/// A helper function to handle removexattr-family syscalls.
fn syscall_removexattr_handler(
    request: &UNotifyEventRequest,
    proc: &RemoteProcess,
    sandbox: &SandboxGuard,
    fd: &OwnedFd,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.get_request();

    let name = if req.data.args[1] != 0 {
        const SIZ: usize = nix::libc::PATH_MAX as usize;
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        proc.read_mem(&mut buf, req.data.args[1], request)?;
        Some(buf)
    } else {
        None
    };
    let name = if let Some(ref name) = name {
        CStr::from_bytes_until_nul(name)
            .or(Err(Errno::E2BIG))?
            .as_ptr()
    } else {
        std::ptr::null()
    };

    #[allow(clippy::cast_possible_wrap)]
    if Sandbox::locked_once() || sandbox.locked_for_pid(req.pid as nix::libc::pid_t) {
        // SAFETY: Deny user.syd* extended attributes.
        // name is either NULL or a valid nul-terminated C-String.
        // SAFETY: Deny with ENODATA for stealth.
        // SAFETY: Deny only if the Sandbox is locked for the process.
        unsafe { denyxattr(name) }?;
    }

    // SAFETY: In libc we trust.
    if unsafe { nix::libc::fremovexattr(fd.as_raw_fd(), name) } == 0 {
        Ok(request.return_syscall(0))
    } else {
        Err(Errno::last())
    }
}

/// A helper function to handle network-related syscalls.
///
/// This function abstracts the common logic involved in handling network syscalls such as `bind`,
/// `connect`, `and `sendto` in a seccomp-based sandboxing environment. It reduces code duplication
/// across different syscall handler functions.
///
/// # Returns
///
/// Returns `ScmpNotifResp` indicating the result of the syscall handling:
/// - If successful, it contains a continued syscall.
/// - If an error occurs, it contains a failed syscall with an `EACCES` error code.
#[allow(clippy::cognitive_complexity)]
fn syscall_network_handler(
    request: &UNotifyEventRequest,
    args: &[u64; 6],
    op: u8,
) -> ScmpNotifResp {
    syscall_handler!(request, |_req: &ScmpNotifReq, proc: &RemoteProcess| {
        let sandbox = if op == 0x2 {
            // We need UpgradableRead to handle bind_map and safe_bind.
            request.get_sandbox_upgradable()
        } else {
            request.get_sandbox()
        };
        let allow_safe_bind = sandbox.allow_safe_bind();
        let allow_safe_kcapi = sandbox.allow_safe_kcapi();
        let allow_unsupp_socket = sandbox.allow_unsupp_socket();
        let kallsyms = sandbox.allow_unsafe_perf();
        let force_cloexec = sandbox.force_cloexec();
        let cap = match op {
            0x1 => {
                // socketcall(2) -> socket(2) indirection.
                // SAFETY: Limit available domains based on sandbox flags.
                let domain = nix::libc::c_int::try_from(args[0]).or(Err(Errno::EAFNOSUPPORT))?;
                if !allow_unsupp_socket {
                    let mut domains: SmallVec<[nix::libc::c_int; 4]> =
                        smallvec![nix::libc::AF_UNIX, nix::libc::AF_INET, nix::libc::AF_INET6,];
                    if allow_safe_kcapi {
                        domains.push(nix::libc::AF_ALG);
                    }
                    if domains.contains(&domain) {
                        // SAFETY: Safe domain, allow.
                    } else if domain == nix::libc::AF_NETLINK {
                        // Restrict AF_NETLINK to the allowlisted families.
                        let netlink_family =
                            nix::libc::c_int::try_from(args[2]).or(Err(Errno::EAFNOSUPPORT))?;
                        if sandbox.netlink_families.to_vec().contains(&netlink_family) {
                            // SAFETY: Safe netlink family, allow.
                        } else {
                            return Err(Errno::EAFNOSUPPORT);
                        }
                    } else {
                        return Err(Errno::EAFNOSUPPORT);
                    }
                } else if !allow_safe_kcapi && domain == nix::libc::AF_ALG {
                    return Err(Errno::EAFNOSUPPORT);
                } else {
                    // SAFETY: allow_unsupp_socket:1
                    // Safe domain, allow.
                }

                let stype = nix::libc::c_int::try_from(args[1]).or(Err(Errno::EINVAL))?;
                let proto = nix::libc::c_int::try_from(args[2]).or(Err(Errno::EAFNOSUPPORT))?;
                let cloexec = force_cloexec || stype & nix::libc::SOCK_CLOEXEC != 0;
                let stype = stype | nix::libc::SOCK_CLOEXEC;

                let fd = loop {
                    // SAFETY: We use libc version for convenience.
                    let fd = unsafe { nix::libc::socket(domain, stype, proto) };
                    if fd == -1 {
                        if Errno::last() == Errno::EINTR {
                            continue;
                        }
                        return Err(Errno::last());
                    }
                    // SAFETY: socket returns a valid FD.
                    break unsafe { OwnedFd::from_raw_fd(fd) };
                };
                return request.send_fd(fd.as_raw_fd(), cloexec);
            }
            0x2 | 0x6 => Capability::CAP_BIND,
            _ => Capability::CAP_CONNECT,
        };

        // Return immediately if sandboxing is not enabled for current capability,
        if !sandbox.enabled(cap) {
            // SAFETY: This is unsafe due to vulnerability to TOCTOU,
            // however since the sandboxing for the requested capability
            // is disabled this is safe here.
            return unsafe { Ok(request.continue_syscall()) };
        }

        // SAFETY: Get the file descriptor before access check
        // as it may change after which is a TOCTOU vector.
        // This also allows us to early return on invalid file
        // descriptors without having to resort to access()'ing
        // /proc/$pid/fd/$fd which will return ENOENT with
        // /proc mounted as hidepid=2.
        let fd = proc.get_fd(args[0] as RawFd, request)?;

        match op {
            0x5 | 0x12 => {
                // accept{,4} uses a different data structure, so we handle it in its own branch.
                return handle_accept(&fd, proc, request, sandbox, args, op);
            }
            0x6 => {
                // getsockname is used for informational purposes only.
                return handle_getsockname(&fd, proc, request, sandbox, args);
            }
            0x10 => {
                // sendmsg uses a different data structure, so we handle it in its own branch.
                return handle_sendmsg(
                    &fd,
                    proc,
                    request,
                    sandbox,
                    args,
                    allow_unsupp_socket,
                    kallsyms,
                );
            }
            0x14 => {
                // sendmmsg uses a different data structure, so we handle it in its own branch.
                return handle_sendmmsg(
                    &fd,
                    proc,
                    request,
                    sandbox,
                    args,
                    allow_unsupp_socket,
                    kallsyms,
                );
            }
            _ => {} // fall through.
        }

        let idx = if op == 0xb /* sendto */ { 4 } else { 1 };
        let addr_remote = args[idx];
        #[allow(clippy::arithmetic_side_effects)]
        #[allow(clippy::cast_possible_truncation)]
        let addr_len = args[idx + 1] as nix::libc::socklen_t;
        if addr_remote == 0 && addr_len == 0 {
            if op == 0xb {
                // sendto
                // SAFETY: Connection mode socket.
                // Note, we don't hook into sendto anymore when both
                // these arguments are NULL so we can never be here
                // practically. However, this can still happen in
                // theory, e.g. when the syscall is originating from a
                // multiplexed socketcall() syscall. In this case
                // continuing the system call here obviously would open a
                // TOCTOU window so instead we pass a None address to
                // handle_sendto() where we'll invoke the send() syscall
                // on our own and return the result to the sandbox
                // process.
                return handle_sendto(&fd, args, proc, request, None, None);
            } else {
                return Err(Errno::EFAULT);
            }
        } else if addr_remote == 0 || addr_len == 0 {
            return Err(Errno::EFAULT);
        } // else we have a valid address to check for access.
        let (addr, root) = canon_addr(
            proc,
            request,
            get_addr(proc, request, addr_remote, addr_len)?,
            cap,
            kallsyms,
        )?;
        match addr_family(&addr) {
            PF_UNIX | PF_INET | PF_INET6 => {
                // Check for access.
                sandbox_addr(proc, request, &sandbox, &addr, &root, op, cap)?;
            }
            PF_NETLINK => {
                // SAFETY: We do not check Netlink address for access.
                // We apply filtering on netlink families at socket level.
            }
            PF_ALG if allow_safe_kcapi && op == 0x2 => {
                // SAFETY: Admin requested access to KCAPI.
            }
            PF_ALG => {
                // a. SAFETY: Access to KCAPI is disabled by default.
                // b. Non-bind() call is not supported for AF_ALG socket.
                return Err(Errno::EOPNOTSUPP);
            }
            _ if allow_unsupp_socket => {
                // SAFETY: This is also restricted at socket(2) boundary
                // where there is no pointer-dereference in access
                // check. However, socketcall() multiplexing is an
                // exception which introduces a TOCTOU window here.
                return unsafe { Ok(request.continue_syscall()) };
            }
            _ => return Err(Errno::EAFNOSUPPORT),
        };

        // Emulate syscall.
        match op {
            0x2 => handle_bind(&fd, &addr, root, allow_safe_bind, request, sandbox),
            0x3 => handle_connect(&fd, &addr, root, request),
            0xb => handle_sendto(&fd, args, proc, request, Some(&addr), root),
            _ => unreachable!(),
        }
    })
}

#[allow(clippy::cognitive_complexity)]
fn handle_bind(
    fd: &OwnedFd,
    addr: &SockaddrStorage,
    root: Option<AddrRoot>,
    allow_safe_bind: bool,
    request: &UNotifyEventRequest,
    mut sandbox: SandboxGuard,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: Ensure root is allocated on the heap,
    // so we can send it safely.
    #[allow(clippy::disallowed_types)]
    let root = match root {
        None => None,
        Some(AddrRoot::Path(path)) => Some(std::path::PathBuf::from(OsString::from_vec(
            path.into_vec(),
        ))),
        _ => unreachable!("bind called with invalid root!"),
    };
    if addr.as_unix_addr().is_some() {
        let req = request.get_request();
        let pid = req.pid();
        let fd = fd.as_raw_fd();
        let size = addr.len();
        let addr = addr.as_ptr();
        let force_umask = sandbox.umask;
        let mut stack = [0u8; MINI_STACK_SIZE];
        #[allow(clippy::blocks_in_conditions)]
        let pid_fd = match safe_clone(
            Box::new(|| -> isize {
                // SAFETY:
                // 1. Honour directory for too long sockets.
                //    Note, the current working directory is per-thread here.
                // 2. We cannot resolve symlinks in root or we risk TOCTOU!
                // 3. safe_open_path, i.e. O_PATH won't work because we may need getxattr.
                let mask = if let Some(ref root) = root {
                    let dirfd = match safe_open(root, OFlag::O_DIRECTORY) {
                        Ok(dirfd) => dirfd,
                        Err(errno) => return errno as isize,
                    };

                    // SAFETY: force_umask overrides POSIX ACLs.
                    let mask = if let Some(mask) = force_umask {
                        mask.bits()
                    } else if fhasposixacl(&dirfd) {
                        0
                    } else {
                        match proc_umask(pid) {
                            Ok(mask) => mask.bits(),
                            Err(_) => return Errno::EACCES as isize,
                        }
                    };
                    if let Err(errno) = fchdir(dirfd.as_raw_fd()) {
                        return errno as isize;
                    }
                    mask
                } else {
                    0
                };
                // SAFETY: Honour process' umask.
                // Note, the umask is per-thread here.
                let _ = unsafe { nix::libc::umask(mask) };
                loop {
                    // SAFETY: bind() does not work through dangling
                    // symbolic links even with SO_REUSEADDR. When called
                    // with a dangling symlink as argument, bind() fails
                    // with EADDRINUSE unlike creat() which is going to
                    // attempt to create the symlink target. Hence basename
                    // in addr here is not vulnerable to TOCTOU.
                    break if unsafe { nix::libc::bind(fd, addr, size) } == 0 {
                        0
                    } else if Errno::last() == Errno::EINTR {
                        continue;
                    } else {
                        Errno::last() as isize
                    };
                }
            }),
            &mut stack[..],
            nix::libc::CLONE_VM | nix::libc::CLONE_VFORK | nix::libc::CLONE_SIGHAND,
            Some(nix::libc::SIGCHLD),
        ) {
            Ok(pid_fd) => pid_fd,
            Err(_) => {
                return Err(Errno::EADDRNOTAVAIL);
            }
        };

        loop {
            break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
                Ok(WaitStatus::Exited(_, 0)) => Ok(()),
                Err(Errno::EINTR) => continue,
                _ => Err(Errno::EADDRNOTAVAIL),
            };
        }?;
    } else {
        // SAFETY: addr is not a UNIX domain socket.
        bind(fd.as_raw_fd(), addr)?;
    }

    // Handle allow_safe_bind and bind_map.
    let cmd = match addr.family() {
        Some(AddressFamily::Unix) => {
            let addr = addr.as_unix_addr().ok_or(Errno::EINVAL)?;
            match (addr.path(), addr.as_abstract()) {
                (Some(path), _) => {
                    let path = path.as_os_str().as_bytes();
                    let null = memchr::memchr(0, path).unwrap_or(path.len());
                    let mut p = SmallPathBuf::new();
                    if let Some(ref root) = root {
                        p.push(root.as_ref());
                    }
                    p.push(Path::new(OsStr::from_bytes(&path[..null])));
                    // Handle bind_map after successful bind for UNIX sockets.
                    // We ignore errors because there's nothing we can do
                    // about them.
                    sandbox = sandbox.upgrade();
                    let _ = sandbox.add_bind(fd, &p);
                    if !allow_safe_bind {
                        return Ok(request.return_syscall(0));
                    }
                    Some(format!("allow/net/connect+{}", p.as_path().display()))
                }
                (_, Some(path)) => {
                    if !allow_safe_bind {
                        return Ok(request.return_syscall(0));
                    }
                    let null = memchr::memchr(0, path).unwrap_or(path.len());
                    let path = SmallPathBuf::from(&path[..null]);
                    Some(format!("allow/net/connect+{}", path.as_path().display()))
                }
                _ => {
                    // Unnamed UNIX socket.
                    None
                }
            }
        }
        Some(AddressFamily::Inet) => {
            if !allow_safe_bind {
                return Ok(request.return_syscall(0));
            }
            let addr = addr.as_sockaddr_in().ok_or(Errno::EINVAL)?;
            let mut port = addr.port();
            let addr = IpAddr::V4(addr.ip());
            if port == 0 {
                port = getsockname::<SockaddrStorage>(fd.as_raw_fd())?
                    .as_sockaddr_in()
                    .ok_or(Errno::EINVAL)?
                    .port();
            }
            Some(format!("allow/net/connect+{addr}!{port}"))
        }
        Some(AddressFamily::Inet6) => {
            if !allow_safe_bind {
                return Ok(request.return_syscall(0));
            }
            let addr = addr.as_sockaddr_in6().ok_or(Errno::EINVAL)?;
            let mut port = addr.port();
            let addr = IpAddr::V6(addr.ip());
            if port == 0 {
                port = getsockname::<SockaddrStorage>(fd.as_raw_fd())?
                    .as_sockaddr_in6()
                    .ok_or(Errno::EINVAL)?
                    .port();
            }
            Some(format!("allow/net/connect+{addr}!{port}"))
        }
        _ => None,
    };
    if let Some(cmd) = cmd {
        // Clear caches to ensure consistency.
        request.cache.0.clear();
        request.cache.1.clear();

        // Upgrade to a Write lock.
        sandbox = sandbox.upgrade();
        let _ = sandbox.config(&cmd);
    }
    Ok(request.return_syscall(0))
}

fn handle_connect(
    fd: &OwnedFd,
    addr: &SockaddrStorage,
    root: Option<AddrRoot>,
    request: &UNotifyEventRequest,
) -> Result<ScmpNotifResp, Errno> {
    let result = retry_on_eintr(|| connect(fd.as_raw_fd(), addr));
    drop(root); // Explicitly close the UNIX socket proc indirection fd.
    result.map(|_| request.return_syscall(0))
}

fn handle_sendto(
    fd: &OwnedFd,
    args: &[u64; 6],
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    addr: Option<&SockaddrStorage>,
    root: Option<AddrRoot>,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: The length argument to the sendto call
    // must not be fully trusted, it can be overly large,
    // and allocating a Vector of that capacity may overflow.
    let len = usize::try_from(args[2])
        .or(Err(Errno::EINVAL))?
        .min(1000000); // Cap count at 1mio.
    #[allow(clippy::cast_possible_truncation)]
    let flags = MsgFlags::from_bits_truncate(args[3] as nix::libc::c_int);
    let mut buf = Vec::new();
    buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
    buf.resize(len, 0);
    proc.read_mem(&mut buf, args[1], request)?;

    if let Some(addr) = addr {
        // Connection-less socket.
        let n = retry_on_eintr(|| sendto(fd.as_raw_fd(), &buf, addr, flags))?;
        drop(root); // Explicitly close the UNIX socket proc indirection fd.
        #[allow(clippy::cast_possible_wrap)]
        Ok(request.return_syscall(n as i64))
    } else {
        // Connection mode socket, no address specified.
        let n = retry_on_eintr(|| send(fd.as_raw_fd(), &buf, flags))?;
        #[allow(clippy::cast_possible_wrap)]
        Ok(request.return_syscall(n as i64))
    }
}

fn handle_accept(
    fd: &OwnedFd,
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: SandboxGuard,
    args: &[u64; 6],
    op: u8,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: Check for trace/allow_safe_bind:1.
    // In this case we disable the checks here,
    // as the checks at bind() boundary are considered
    // trusted.
    // Note, as of 3.26.1 trace/allow_safe_bind:1 is a
    // static option and we don't hook into accept{,4}
    // if it's set to true at startup. However, we still
    // need this check here for the socketcall indirection.
    if sandbox.allow_safe_bind() {
        // SAFETY: No pointer-dereference in access check.
        return unsafe { Ok(request.continue_syscall()) };
    }

    // Determine the socket family.
    let fml = getsockdomain(fd).or(Err(op2errno(op)))?;
    let ipv6 = match fml {
        nix::libc::AF_INET6 => true,
        nix::libc::AF_INET => false,
        _ => {
            // Not an IPv{4,6} socket, continue.
            // SAFETY: No pointer-dereference in access check.
            return unsafe { Ok(request.continue_syscall()) };
        }
    };

    // Determine address length if specified.
    let addrlen = if args[2] != 0 {
        const SIZEOF_SOCKLEN_T: usize = std::mem::size_of::<nix::libc::socklen_t>();
        let mut buf = [0u8; SIZEOF_SOCKLEN_T];
        if proc.read_mem(&mut buf, args[2], request)? == SIZEOF_SOCKLEN_T {
            // libc defines socklen_t as u32,
            // however we should check for negative values
            // and return EINVAL as necessary.
            let len = i32::from_ne_bytes(buf);
            let len = nix::libc::socklen_t::try_from(len).or(Err(Errno::EINVAL))?;
            if args[1] == 0 {
                // address length is positive however address is NULL,
                // return EFAULT.
                return Err(Errno::EFAULT);
            }
            Some(len)
        } else {
            // Invalid/short read, assume invalid address length.
            return Err(Errno::EINVAL);
        }
    } else {
        None
    };

    #[allow(clippy::cast_possible_truncation)]
    let mut flags = if op == 0x12 {
        // accept4
        SockFlag::from_bits_truncate(args[3] as i32)
    } else {
        // accept
        SockFlag::empty()
    };
    let cloexec = sandbox.force_cloexec() || flags.contains(SockFlag::SOCK_CLOEXEC);
    flags.insert(SockFlag::SOCK_CLOEXEC);

    // Do the accept call.
    let (fd, addr) = do_accept4(&fd, flags, ipv6)?;

    // Check for access.
    if ipv6 {
        sandbox_addr_inet6(proc, request, &sandbox, &addr, op, Capability::CAP_CONNECT)?;
    } else {
        sandbox_addr_inet(proc, request, &sandbox, &addr, op, Capability::CAP_CONNECT)?;
    }

    // Write address buffer as necessary.
    if let Some(addrlen) = addrlen {
        let ptr = addr.as_ptr() as *const u8;
        let len = addr.len() as usize;

        // Create a byte slice from the socket address pointer.
        // SAFETY: `ptr` is a valid pointer to memory of at least `len`
        // bytes, as it is provided by the `SockaddrStorage` instance.
        // The `SockaddrStorage` type ensures that the memory pointed to
        // by `ptr` is valid and properly aligned.
        let buf = unsafe { std::slice::from_raw_parts(ptr, len) };

        // Convert this slice into a vector and truncate it at addrlen.
        let mut buf = buf.to_vec();
        buf.truncate(addrlen as usize);

        // Write the truncated socket address into memory.
        proc.write_mem(&buf, args[1], request)?;

        // Convert `len` into a vector of bytes.
        let buf = addr.len().to_ne_bytes();

        // Write `len` into memory.
        proc.write_mem(&buf, args[2], request)?;
    }

    // Send the fd and return.
    request.send_fd(fd.as_raw_fd(), cloexec)
}

fn do_accept4<Fd: AsFd>(
    sockfd: &Fd,
    flags: SockFlag,
    ipv6: bool,
) -> Result<(OwnedFd, SockaddrStorage), Errno> {
    // Allocate storage for the address.
    let mut addr: [u8; std::mem::size_of::<nix::libc::sockaddr_in6>()] =
        [0u8; std::mem::size_of::<nix::libc::sockaddr_in6>()];

    // Set the initial length based on whether it's IPv6 or IPv4.
    #[allow(clippy::cast_possible_truncation)]
    let mut len: nix::libc::socklen_t = if ipv6 {
        std::mem::size_of::<nix::libc::sockaddr_in6>() as nix::libc::socklen_t
    } else {
        std::mem::size_of::<nix::libc::sockaddr_in>() as nix::libc::socklen_t
    };

    // Cast the storage buffer to a sockaddr pointer.
    #[allow(clippy::cast_ptr_alignment)]
    let ptr = addr.as_mut_ptr() as *mut nix::libc::sockaddr;

    let fd = retry_on_eintr(|| {
        // SAFETY: Call the unsafe libc::accept4 function.
        Errno::result(unsafe {
            nix::libc::accept4(sockfd.as_fd().as_raw_fd(), ptr, &mut len, flags.bits())
        })
    })
    .map(|fd| {
        // SAFETY: accept4 returns a valid FD.
        unsafe { OwnedFd::from_raw_fd(fd) }
    })?;

    // SAFETY:
    // Convert the raw address into a SockaddrStorage structure.
    // accept4 returned success so the pointer is valid.
    let addr = unsafe { SockaddrStorage::from_raw(ptr, Some(len)) }.ok_or(Errno::EINVAL)?;

    Ok((fd, addr))
}

fn handle_getsockname(
    fd: &OwnedFd,
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: SandboxGuard,
    args: &[u64; 6],
) -> Result<ScmpNotifResp, Errno> {
    // Get socket inode.
    let inode = retry_on_eintr(|| fstatx(fd.as_raw_fd(), STATX_INO)).map(|statx| statx.stx_ino)?;

    // Lookup path by inode in sandbox bind map.
    let addr = if let Some(addr) = sandbox.get_bind(inode) {
        addr
    } else {
        // Not a UNIX socket, continue.
        // SAFETY: No pointer-dereference in access check.
        return unsafe { Ok(request.continue_syscall()) };
    };

    // Determine address length.
    let addrlen = if args[2] != 0 {
        const SIZEOF_SOCKLEN_T: usize = std::mem::size_of::<nix::libc::socklen_t>();
        let mut buf = [0u8; SIZEOF_SOCKLEN_T];
        if proc.read_mem(&mut buf, args[2], request)? == SIZEOF_SOCKLEN_T {
            // libc defines socklen_t as u32,
            // however we should check for negative values
            // and return EINVAL as necessary.
            let len = i32::from_ne_bytes(buf);
            let len = nix::libc::socklen_t::try_from(len).or(Err(Errno::EINVAL))?;
            if args[1] == 0 {
                // address length is positive however address is NULL,
                // return EFAULT.
                return Err(Errno::EFAULT);
            }
            len
        } else {
            // Invalid/short read, assume invalid address length.
            return Err(Errno::EINVAL);
        }
    } else {
        // addrlen must not be NULL.
        return Err(Errno::EFAULT);
    };

    // Write address buffer.
    let ptr = addr.as_ptr() as *const u8;
    let len = addr.len() as usize;

    // Create a byte slice from the socket address pointer.
    // SAFETY: `ptr` is a valid pointer to memory of at least `len`
    // bytes, as it is provided by the `SockaddrStorage` instance.
    // The `SockaddrStorage` type ensures that the memory pointed to
    // by `ptr` is valid and properly aligned.
    let buf = unsafe { std::slice::from_raw_parts(ptr, len) };

    // Convert this slice into a vector and truncate it at addrlen.
    let mut buf = buf.to_vec();
    buf.truncate(addrlen as usize);

    // Write the truncated socket address into memory.
    proc.write_mem(&buf, args[1], request)?;

    // Convert `len` into a vector of bytes.
    let buf = addr.len().to_ne_bytes();

    // Write `len` into memory.
    proc.write_mem(&buf, args[2], request)?;

    Ok(request.return_syscall(0))
}

#[allow(clippy::cognitive_complexity)]
#[allow(clippy::too_many_arguments)]
fn handle_sendmsg(
    fd: &OwnedFd,
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: SandboxGuard,
    args: &[u64; 6],
    allow_unsupp_socket: bool,
    kallsyms: bool,
) -> Result<ScmpNotifResp, Errno> {
    #[allow(clippy::cast_possible_truncation)]
    let flags = MsgFlags::from_bits_truncate(args[2] as nix::libc::c_int);

    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;
    let mut buf = if is32 {
        // SAFETY: socketcall is the same number on all:
        // x86, mips, mipsel, ppc, ppc64, ppc64le, s390 and s390x.
        const SIZ: usize = std::mem::size_of::<msghdr32>();
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        buf
    } else {
        const SIZ: usize = std::mem::size_of::<msghdr>();
        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        buf
    };
    proc.read_mem(&mut buf, args[1], request)?;

    let msg = if is32 {
        // SAFETY: See below.
        let msg: msghdr32 = unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const _) };
        crate::compat::msghdr::from(msg)
    } else {
        // SAFETY: The following unsafe block assumes that:
        // 1. The memory layout of open_how in our Rust environment
        //    matches that of the target process.
        // 2. The proc.read_mem call has populated buf with valid data
        //    of the appropriate size (ensured by the size check above).
        // 3. The buffer is appropriately aligned for reading an
        //    open_how struct. If the remote process's representation of
        //    open_how was correctly aligned, our local buffer should be
        //    too, since it's an array on the stack.
        unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const _) }
    };

    let addr_remote = msg.msg_name;
    let addr_len = msg.msg_namelen;

    // Step 1: Handle the address.
    let addr = if !addr_remote.is_null() && addr_len > 0 {
        let (addr, root) = canon_addr(
            proc,
            request,
            get_addr(proc, request, addr_remote as u64, addr_len)?,
            Capability::CAP_CONNECT,
            kallsyms,
        )?;
        match addr_family(&addr) {
            PF_UNIX | PF_INET | PF_INET6 => {
                // Check for access.
                sandbox_addr(
                    proc,
                    request,
                    &sandbox,
                    &addr,
                    &root,
                    0x10,
                    Capability::CAP_CONNECT,
                )?;
            }
            PF_ALG | PF_NETLINK => {
                // SAFETY: We do not check AF_ALG or AF_NETLINK for access.
            }
            _ if allow_unsupp_socket => {
                // SAFETY: No pointer-dereference in access check.
                return unsafe { Ok(request.continue_syscall()) };
            }
            _ => return Err(Errno::EAFNOSUPPORT),
        };

        Some((addr, root))
    } else {
        // Connection-mode socket.
        // SAFETY: We cannot continue here due to the added level of
        // pointer indirection.
        None
    };

    // Step 2: Handle control messages.
    #[allow(clippy::collection_is_never_read)]
    let mut control_messages = Vec::new();
    let control_data = if !msg.msg_control.is_null() && msg.msg_controllen > 0 {
        #[allow(clippy::useless_conversion)]
        let cmsg_len = usize::try_from(msg.msg_controllen)
            .or(Err(Errno::EINVAL))?
            .min(1000000); // SAFETY: Cap at 1mio.
        let mut cmsg_buf = Vec::new();
        cmsg_buf.try_reserve(cmsg_len).or(Err(Errno::ENOMEM))?;
        cmsg_buf.resize(cmsg_len, 0);
        proc.read_mem(&mut cmsg_buf, msg.msg_control as u64, request)?;
        Some(parse_control_messages(proc, request, &cmsg_buf)?)
    } else {
        None
    };

    if let Some((
        ref _control_fds, // Keep the OwnedFd alive!
        ref control_raw_fds,
        ref control_creds,
        ref control_ivs,
        ref control_ops,
        ref control_aead_assoclens,
        ref control_udp_gso_segments,
        ref control_ipv4_packet_infos,
        ref control_ipv6_packet_infos,
        ref control_rxq_ovfls,
        ref control_tx_times,
    )) = control_data
    {
        for raw_fds in control_raw_fds {
            control_messages.push(ControlMessage::ScmRights(raw_fds));
        }

        for creds in control_creds {
            control_messages.push(ControlMessage::ScmCredentials(creds));
        }

        for iv_data in control_ivs {
            control_messages.push(ControlMessage::AlgSetIv(iv_data));
        }

        for op in control_ops {
            control_messages.push(ControlMessage::AlgSetOp(op));
        }

        for assoclen in control_aead_assoclens {
            control_messages.push(ControlMessage::AlgSetAeadAssoclen(assoclen));
        }

        for gso_segments in control_udp_gso_segments {
            control_messages.push(ControlMessage::UdpGsoSegments(gso_segments));
        }

        for pktinfo in control_ipv4_packet_infos {
            control_messages.push(ControlMessage::Ipv4PacketInfo(pktinfo));
        }

        for pktinfo in control_ipv6_packet_infos {
            control_messages.push(ControlMessage::Ipv6PacketInfo(pktinfo));
        }

        for rxq_ovfl in control_rxq_ovfls {
            control_messages.push(ControlMessage::RxqOvfl(rxq_ovfl));
        }

        for tx_time in control_tx_times {
            control_messages.push(ControlMessage::TxTime(tx_time));
        }
    }

    // Step 3: Handle the payload which is an array of struct iovecs.
    let mut io_buffers: Vec<Vec<u8>> = Vec::new();
    let mut io_slices: Vec<IoSlice> = Vec::new();
    if !(msg.msg_iov.is_null() || msg.msg_iovlen == 0) {
        // SAFETY: The msg_iovlen member of the msghdr struct
        // must not be fully trusted, it can be overly large,
        // and allocating a Vector of that capacity may overflow.
        #[allow(clippy::useless_conversion)]
        let len = usize::try_from(msg.msg_iovlen)
            .or(Err(Errno::EINVAL))?
            .min(1000000); // Cap count at 1mio
        let size = if is32 {
            len.checked_mul(std::mem::size_of::<crate::compat::iovec32>())
        } else {
            len.checked_mul(std::mem::size_of::<nix::libc::iovec>())
        }
        .ok_or(Errno::EINVAL)?;
        let mut buf = Vec::new();
        buf.try_reserve(size).or(Err(Errno::ENOMEM))?;
        buf.resize(size, 0);
        proc.read_mem(&mut buf, msg.msg_iov as u64, request)?;

        // SAFETY: This operation assumes that the buffer (`buf`) contains a valid sequence of bytes
        // that correctly represent an array of `iovec` structures. This is ensured by the preceding
        // code that reads memory into `buf` with proper length calculation. The length `len` is
        // derived from `msg.msg_iovlen` and capped to prevent overflow, ensuring that we do not
        // exceed the allocation size of `buf`. The conversion to a pointer and then to a slice
        // of `iovec` is safe under these conditions, assuming the memory layout of `iovec` is
        // correct and `buf` is correctly sized and aligned.
        let mut iovecs: Vec<nix::libc::iovec> = Vec::new();
        if is32 {
            for chunk in buf.chunks(std::mem::size_of::<crate::compat::iovec32>()) {
                // SAFETY: See above.
                let iov32: crate::compat::iovec32 =
                    unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const _) };
                iovecs.push(iov32.into());
            }
        } else {
            for chunk in buf.chunks(std::mem::size_of::<nix::libc::iovec>()) {
                // SAFETY: See above.
                iovecs.push(unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const _) });
            }
        };

        for iov in iovecs {
            if iov.iov_base.is_null() || iov.iov_len == 0 {
                // XXX: This happens with socketcall on x86, why?
                continue;
            }

            // Cap the length to a maximum value to avoid large allocations.
            // SAFETY: The maximum length cap prevents excessive memory
            // allocation based on untrusted `iov_len`.
            let iov_len = iov.iov_len.min(1000000); // Cap count at 1mio

            // Allocate a buffer to read into. This buffer size is now capped.
            let mut data_buf = Vec::new();
            data_buf.try_reserve(iov_len).or(Err(Errno::ENOMEM))?;
            data_buf.resize(iov_len, 0);

            // Read the memory from the remote process into our buffer.
            // SAFETY: This operation relies on the correctness of
            // `iov_base` as a pointer into the remote process's memory and
            // the capped `iov_len`.
            proc.read_mem(&mut data_buf, iov.iov_base as u64, request)?;

            // Keep the pointer accessible, IoSlice needs a valid reference.
            io_buffers.push(data_buf);
        }
        for buffer in &io_buffers {
            io_slices.push(IoSlice::new(buffer));
        }
    }

    if let Some((addr, _)) = addr {
        // UNIX domain/abstract socket.
        let n = retry_on_eintr(|| {
            sendmsg(
                fd.as_raw_fd(),
                &io_slices,
                &control_messages,
                flags,
                Some(&addr),
            )
        })?;
        #[allow(clippy::cast_possible_wrap)]
        Ok(request.return_syscall(n as i64))
    } else {
        // Connection-mode socket.
        let n = retry_on_eintr(|| {
            sendmsg::<SockaddrStorage>(fd.as_raw_fd(), &io_slices, &control_messages, flags, None)
        })?;
        #[allow(clippy::cast_possible_wrap)]
        Ok(request.return_syscall(n as i64))
    }
}

#[allow(clippy::cognitive_complexity)]
#[allow(clippy::too_many_arguments)]
fn handle_sendmmsg(
    fd: &OwnedFd,
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    sandbox: SandboxGuard,
    args: &[u64; 6],
    allow_unsupp_socket: bool,
    kallsyms: bool,
) -> Result<ScmpNotifResp, Errno> {
    #[allow(clippy::cast_possible_truncation)]
    let flags = MsgFlags::from_bits_truncate(args[3] as nix::libc::c_int);

    // NULL check was performed already.
    let addr = args[1];
    let vlen = usize::try_from(args[2]).or(Err(Errno::EINVAL))?;
    if vlen == 0 {
        return Ok(request.return_syscall(0));
    }
    let vlen = vlen.min(1024); // Cap at IOV_MAX

    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;

    let size = if is32 {
        vlen.checked_mul(std::mem::size_of::<crate::compat::mmsghdr32>())
    } else {
        vlen.checked_mul(std::mem::size_of::<crate::compat::mmsghdr>())
    }
    .ok_or(Errno::EINVAL)?;

    // Read mmsghdr structures from remote process memory
    let mut buf = Vec::new();
    buf.try_reserve(size).or(Err(Errno::ENOMEM))?;
    buf.resize(size, 0);
    proc.read_mem(&mut buf, addr, request)?;

    let mut mmsghdrs: Vec<crate::compat::mmsghdr> = Vec::new();
    if is32 {
        for chunk in buf.chunks(std::mem::size_of::<crate::compat::mmsghdr32>()) {
            // SAFETY: See the relevant comment in handle_sendmsg.
            let mmsghdr: crate::compat::mmsghdr32 =
                unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const _) };
            mmsghdrs.push(mmsghdr.into());
        }
    } else {
        for chunk in buf.chunks(std::mem::size_of::<crate::compat::mmsghdr>()) {
            // SAFETY: See the relevant comment in handle_sendmsg.
            mmsghdrs.push(unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const _) });
        }
    };

    // Prepare a series of sendmsg calls.
    for mmsg in &mut mmsghdrs {
        let msg = &mut mmsg.msg_hdr;

        // Step 1: Handle the address.
        let addr_remote = msg.msg_name;
        let addr_len = msg.msg_namelen;
        let addr_root = if !addr_remote.is_null() && addr_len > 0 {
            let (addr, root) = canon_addr(
                proc,
                request,
                get_addr(proc, request, addr_remote as u64, addr_len)?,
                Capability::CAP_CONNECT,
                kallsyms,
            )?;
            match addr_family(&addr) {
                PF_UNIX | PF_INET | PF_INET6 => {
                    // Check for access.
                    sandbox_addr(
                        proc,
                        request,
                        &sandbox,
                        &addr,
                        &root,
                        0x14,
                        Capability::CAP_CONNECT,
                    )?;
                }
                PF_ALG | PF_NETLINK => {
                    // SAFETY: We do not check AF_ALG and AF_NETLINK for access.
                }
                _ if allow_unsupp_socket => {
                    // SAFETY: No pointer-dereference in access check.
                    return unsafe { Ok(request.continue_syscall()) };
                }
                _ => return Err(Errno::EAFNOSUPPORT),
            };

            Some((addr, root))
        } else {
            // Connection-mode socket.
            // SAFETY: We cannot continue here due to the added level of
            // pointer indirection.
            None
        };

        // Step 2: Handle control messages.
        let mut control_data = Vec::new();
        let control_datum = if !msg.msg_control.is_null() && msg.msg_controllen > 0 {
            #[allow(clippy::useless_conversion)]
            let cmsg_len = usize::try_from(msg.msg_controllen)
                .or(Err(Errno::EINVAL))?
                .min(1000000); // SAFETY: Cap at 1mio.
            let mut cmsg_buf = Vec::new();
            cmsg_buf.try_reserve(cmsg_len).or(Err(Errno::ENOMEM))?;
            cmsg_buf.resize(cmsg_len, 0);
            proc.read_mem(&mut cmsg_buf, msg.msg_control as u64, request)?;
            Some(parse_control_messages(proc, request, &cmsg_buf)?)
        } else {
            None
        };
        control_data.push(control_datum); // Keep OwnedFd alive!

        let mut control_messages = Vec::new();
        for control_datum in &control_data {
            if let Some((
                ref _control_fds, // Keep the OwnedFd alive!
                ref control_raw_fds,
                ref control_creds,
                ref control_ivs,
                ref control_ops,
                ref control_aead_assoclens,
                ref control_udp_gso_segments,
                ref control_ipv4_packet_infos,
                ref control_ipv6_packet_infos,
                ref control_rxq_ovfls,
                ref control_tx_times,
            )) = &control_datum
            {
                for raw_fds in control_raw_fds {
                    control_messages.push(ControlMessage::ScmRights(raw_fds));
                }

                for creds in control_creds {
                    control_messages.push(ControlMessage::ScmCredentials(creds));
                }

                for iv_data in control_ivs {
                    control_messages.push(ControlMessage::AlgSetIv(iv_data));
                }

                for op in control_ops {
                    control_messages.push(ControlMessage::AlgSetOp(op));
                }

                for assoclen in control_aead_assoclens {
                    control_messages.push(ControlMessage::AlgSetAeadAssoclen(assoclen));
                }

                for gso_segments in control_udp_gso_segments {
                    control_messages.push(ControlMessage::UdpGsoSegments(gso_segments));
                }

                for pktinfo in control_ipv4_packet_infos {
                    control_messages.push(ControlMessage::Ipv4PacketInfo(pktinfo));
                }

                for pktinfo in control_ipv6_packet_infos {
                    control_messages.push(ControlMessage::Ipv6PacketInfo(pktinfo));
                }

                for rxq_ovfl in control_rxq_ovfls {
                    control_messages.push(ControlMessage::RxqOvfl(rxq_ovfl));
                }

                for tx_time in control_tx_times {
                    control_messages.push(ControlMessage::TxTime(tx_time));
                }
            }
        }

        // Step 3: Handle the payload which is an array of struct iovecs.
        let mut io_buffers = Vec::new();
        let mut io_slices: Vec<IoSlice> = Vec::new();
        if !(msg.msg_iov.is_null() || msg.msg_iovlen == 0) {
            // SAFETY: The msg_iovlen member of the msghdr struct
            // must not be fully trusted, it can be overly large,
            // and allocating a Vector of that capacity may overflow.
            #[allow(clippy::useless_conversion)]
            let len = usize::try_from(msg.msg_iovlen)
                .or(Err(Errno::EINVAL))?
                .min(1000000); // Cap count at 1mio.
            let size = if is32 {
                len.checked_mul(std::mem::size_of::<crate::compat::iovec32>())
            } else {
                len.checked_mul(std::mem::size_of::<nix::libc::iovec>())
            }
            .ok_or(Errno::EINVAL)?;
            let mut buf = Vec::new();
            buf.try_reserve(size).or(Err(Errno::ENOMEM))?;
            buf.resize(size, 0);
            proc.read_mem(&mut buf, msg.msg_iov as u64, request)?;

            let mut iovecs: Vec<nix::libc::iovec> = Vec::new();
            if is32 {
                for chunk in buf.chunks(std::mem::size_of::<crate::compat::iovec32>()) {
                    // SAFETY: See the relevant comment in handle_sendmsg.
                    let iov32: crate::compat::iovec32 =
                        unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const _) };
                    iovecs.push(iov32.into());
                }
            } else {
                for chunk in buf.chunks(std::mem::size_of::<nix::libc::iovec>()) {
                    // SAFETY: See the relevant comment in handle_sendmsg.
                    iovecs.push(unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const _) });
                }
            };

            for iov in iovecs {
                // Cap the length to a maximum value to avoid large allocations.
                // SAFETY: The maximum length cap prevents excessive memory
                // allocation based on untrusted `iov_len`.
                let iov_len = iov.iov_len.min(1000000); // Cap count at 1mio

                // Allocate a buffer to read into. This buffer size is now capped.
                let mut data_buf = Vec::new();
                data_buf.try_reserve(iov_len).or(Err(Errno::ENOMEM))?;
                data_buf.resize(iov_len, 0);

                // Read the memory from the remote process into our buffer.
                // SAFETY: This operation relies on the correctness of
                // `iov_base` as a pointer into the remote process's memory and
                // the capped `iov_len`.
                proc.read_mem(&mut data_buf, iov.iov_base as u64, request)?;

                // Keep the pointer accessible, IoSlice needs a valid reference.
                io_buffers.push(data_buf);
            }
            for buffer in &io_buffers {
                io_slices.push(IoSlice::new(buffer));
            }

            // Make the sendmsg call.
            let n = if let Some((addr, _)) = addr_root {
                // Connection-less socket.
                retry_on_eintr(|| {
                    sendmsg(
                        fd.as_raw_fd(),
                        &io_slices,
                        &control_messages,
                        flags,
                        Some(&addr),
                    )
                })
            } else {
                // Connection-mode socket.
                retry_on_eintr(|| {
                    sendmsg::<SockaddrStorage>(
                        fd.as_raw_fd(),
                        &io_slices,
                        &control_messages,
                        flags,
                        None,
                    )
                })
            }?;
            mmsg.msg_len = n.try_into().or(Err(Errno::EINVAL))?;
        }
    }

    // Write back mmsghdr structures to remote process memory
    let mut buf: Vec<u8> = Vec::new();
    if is32 {
        for mmsghdr in &mmsghdrs {
            let mmsghdr32: crate::compat::mmsghdr32 = (*mmsghdr).into();
            // SAFETY: Convert each mmsghdr (or mmsghdr32 within the
            // conversion logic) back to its byte representation.
            let bytes: [u8; std::mem::size_of::<crate::compat::mmsghdr32>()] =
                unsafe { std::mem::transmute(mmsghdr32) };
            buf.extend_from_slice(&bytes);
        }
    } else {
        for mmsghdr in &mmsghdrs {
            // SAFETY: See above.
            let bytes: [u8; std::mem::size_of::<crate::compat::mmsghdr>()] =
                unsafe { std::mem::transmute(*mmsghdr) };
            buf.extend_from_slice(&bytes);
        }
    }
    proc.write_mem(&buf, addr, request)?;

    // FIXME: We do not handle partial success.
    #[allow(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(mmsghdrs.len() as i64))
}

// SAFETY: Below lie daemons...
#[allow(clippy::type_complexity)]
fn parse_control_messages(
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    cmsg_buf: &[u8],
) -> Result<
    (
        Vec<Vec<OwnedFd>>,
        Vec<Vec<RawFd>>,
        Vec<UnixCredentials>,
        Vec<Vec<u8>>,          // ivs
        Vec<nix::libc::c_int>, // ops
        Vec<u32>,              // aead_assoclens
        Vec<u16>,              // udp_gso_segments
        Vec<nix::libc::in_pktinfo>,
        Vec<nix::libc::in6_pktinfo>,
        Vec<u32>, // rxq_ovfls
        Vec<u64>, // tx_times
    ),
    Errno,
> {
    let mut control_fds = Vec::new();
    let mut control_raw_fds = Vec::new();
    let mut control_creds = Vec::new();
    let mut control_ivs = Vec::new();
    let mut control_ops = Vec::new();
    let mut control_aead_assoclens = Vec::new();
    let mut control_udp_gso_segments = Vec::new();
    let mut control_ipv4_packet_infos = Vec::new();
    let mut control_ipv6_packet_infos = Vec::new();
    let mut control_rxq_ovfls = Vec::new();
    let mut control_tx_times = Vec::new();

    let mut offset = 0;
    let req = request.get_request();
    let is32 = scmp_arch_bits(req.data.arch) == 32;
    while offset < cmsg_buf.len() {
        // SAFETY: Ensuring alignment for `cmsghdr` by starting from a u8 pointer.  The
        // `cmsg_buf` is originally a u8 buffer, which may not satisfy the alignment
        // requirements of `cmsghdr`.  This cast assumes that the buffer provided by
        // `proc.read_mem` is correctly aligned for `cmsghdr` structures, which is true if
        // the buffer is initially populated in a manner adhering to the alignment
        // requirements of `cmsghdr`.  The caller is responsible for ensuring that `offset`
        // is correctly aligned for `cmsghdr` when accessing the buffer.
        #[allow(clippy::cast_ptr_alignment)]
        let (cmsg_header, cmsg_len0): (crate::compat::cmsghdr, usize) = if is32 {
            // SAFETY: See the comment above.
            let cmsg_header_32: crate::compat::cmsghdr32 = unsafe {
                std::ptr::read_unaligned(
                    cmsg_buf[offset..].as_ptr() as *const crate::compat::cmsghdr32
                )
            };
            (cmsg_header_32.into(), cmsg_len_32(0))
        } else {
            (
                // SAFETY: See the comment above.
                unsafe {
                    std::ptr::read_unaligned(
                        cmsg_buf[offset..].as_ptr() as *const crate::compat::cmsghdr
                    )
                },
                // SAFETY: See the comment above.
                unsafe { nix::libc::CMSG_LEN(0) } as usize,
            )
        };
        if cmsg_header.cmsg_len < cmsg_len0 {
            return Err(Errno::EINVAL); // Invalid header length
        }
        #[allow(clippy::useless_conversion)]
        let data_len: usize = cmsg_header.cmsg_len.try_into().or(Err(Errno::EINVAL))?;
        let data_len = data_len.checked_sub(cmsg_len0).ok_or(Errno::EINVAL)?;

        let data_off = offset.checked_add(cmsg_len0).ok_or(Errno::EINVAL)?;
        let data_end = data_off.checked_add(data_len).ok_or(Errno::EINVAL)?;
        if data_end > cmsg_buf.len() {
            return Err(Errno::EINVAL); // Data goes beyond buffer.
        }
        let data = &cmsg_buf[data_off..data_end];

        match (cmsg_header.cmsg_level, cmsg_header.cmsg_type) {
            (nix::libc::SOL_SOCKET, nix::libc::SCM_RIGHTS) => {
                #[allow(clippy::arithmetic_side_effects)]
                let fd_count = data_len / std::mem::size_of::<RawFd>();
                let mut raw_fds = Vec::with_capacity(fd_count);
                let mut fds = Vec::with_capacity(fd_count);
                // SAFETY: Multiplying `i` by `std::mem::size_of::<RawFd>()` calculates the
                // offset for each file descriptor in the control message data. This is safe
                // under the assumption that `data_len` (used to derive `fd_count`)
                // correctly represents a buffer containing `RawFd`s. `data_len` is checked
                // to ensure it's an exact multiple of `std::mem::size_of::<RawFd>()`,
                // preventing out-of-bounds access. Accessing the file descriptor using this
                // offset and converting it with `proc.get_fd()` is based on the valid and
                // expected layout of file descriptors in the control message. This layout
                // and access method align with the conventions used by the underlying
                // system for `SCM_RIGHTS` control messages, ensuring that we read valid
                // file descriptor values from the buffer.
                for i in 0..fd_count {
                    #[allow(clippy::arithmetic_side_effects)]
                    let fd_offset = i * std::mem::size_of::<RawFd>();
                    #[allow(clippy::cast_ptr_alignment)]
                    // SAFETY: See the comment above.
                    let fd = unsafe { *(data[fd_offset..].as_ptr() as *const RawFd) };
                    let fd = proc.get_fd(fd, request)?;
                    raw_fds.push(fd.as_raw_fd());
                    fds.push(fd); // Keep a ref to the OwnedFd.
                }
                control_raw_fds.push(raw_fds);
                control_fds.push(fds);
            }
            (nix::libc::SOL_SOCKET, nix::libc::SCM_CREDENTIALS) => {
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: Casting `data.as_ptr()` to `*const nix::libc::ucred` is safe
                // under the assumption that `data` contains bytes that correctly represent
                // a `libc::ucred` structure, and that `data_len` matches the size of
                // `libc::ucred`.  This assumption is based on the control message type
                // `SCM_CREDENTIALS`, which is expected to contain exactly one `libc::ucred`
                // structure representing the credentials of the sending process.  The
                // conversion to `UnixCredentials` is a safe operation that simply wraps the
                // raw credentials in a Rust-friendly type. The use of `unsafe` is necessary
                // to dereference the raw pointer obtained from the byte buffer, but the
                // operation is ensured to be valid by adhering to the expected control
                // message format and size.  This access pattern is consistent with the
                // standard way of handling `SCM_CREDENTIALS` control messages.
                let mut creds = *(unsafe { &*(data.as_ptr() as *const nix::libc::ucred) });
                // SAFETY: The sender must specify its own pid (unless it has the capability
                // CAP_SYS_ADMIN, in which case the PID of any existing process may be specified.)
                creds.pid = Pid::this().as_raw();
                let unix_creds = UnixCredentials::from(creds);
                control_creds.push(unix_creds); // Keep a ref to the UnixCredentials.
            }
            (nix::libc::SOL_ALG, nix::libc::ALG_SET_IV) => {
                // IV data is directly contained in the data part of the control message
                // First four bytes represent the length of the IV.
                if data_len < 4 {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                let iv_size = u32::from_ne_bytes([data[0], data[1], data[2], data[3]]) as usize;
                let iv_size = iv_size.saturating_add(4);
                if iv_size <= data_len {
                    // Extract the IV while respecting the indicated size,
                    // if the size is valid.
                    let iv_data = Vec::from(&data[4..iv_size]);
                    control_ivs.push(iv_data); // Store the IV data
                } else {
                    return Err(Errno::EINVAL); // Data length mismatch.
                }
            }
            (nix::libc::SOL_ALG, nix::libc::ALG_SET_OP) => {
                // SAFETY: Casting `data.as_ptr()` to `*const nix::libc::c_int` is based on
                // the expectation that `data` contains a buffer representing an operation
                // code of type `c_int` for the `ALG_SET_OP` control message.  This cast
                // assumes the beginning of `data` is correctly aligned for an `i32`, which
                // is valid if the control message was constructed correctly by the sender
                // according to the `AF_ALG` socket requirements. The check `data_len !=
                // std::mem::size_of::<nix::libc::c_int>()` ensures that the buffer length
                // exactly matches the size of an `i32`, mitigating the risk of undefined
                // behavior due to incorrect buffer size. However, this operation bypasses
                // Rust's guarantees on data alignment, relying on the correct alignment by
                // the sender and adherence to the protocol's specification, which mandates
                // proper alignment for control message data.
                if data_len != std::mem::size_of::<nix::libc::c_int>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let op = unsafe { *(data.as_ptr() as *const nix::libc::c_int) };
                control_ops.push(op); // Store the operation code
            }
            (nix::libc::SOL_ALG, nix::libc::ALG_SET_AEAD_ASSOCLEN) => {
                // SAFETY: The cast from `*const u8` to `*const u32` here assumes that the
                // data buffer, although initially handled as a sequence of bytes, is
                // correctly aligned for a `u32`. This assumption is contingent upon the
                // sender properly constructing the control message with the
                // `ALG_SET_AEAD_ASSOCLEN` type, ensuring the alignment meets the
                // requirements for `u32` data. The prerequisite check `data_len !=
                // std::mem::size_of::<u32>()` ensures the buffer is exactly the size of a
                // `u32`, mitigating risks associated with accessing beyond the buffer or
                // misinterpreting the data type.  While this operation inherently trusts
                // the message sender to adhere to alignment requirements, it aligns with
                // common practices for handling similarly structured control messages in
                // systems programming, where protocol adherence guarantees data alignment.
                if data_len != std::mem::size_of::<u32>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let assoclen = unsafe { *(data.as_ptr() as *const u32) };
                control_aead_assoclens.push(assoclen); // Store the AEAD assoclen
            }
            (nix::libc::SOL_UDP, nix::libc::UDP_SEGMENT) => {
                // SAFETY: This unsafe block casts a pointer from `*const u8` to `*const
                // u16` under the assumption that the data at `data.as_ptr()` is correctly
                // aligned for `u16`. This is based on the expectation that the sender of
                // the control message aligns the data according to the `u16` requirements
                // when constructing the message for `UDP_SEGMENT`. The check `data_len !=
                // std::mem::size_of::<u16>()` ensures that the buffer is precisely the size
                // expected for a single `u16` value, thus avoiding potential overreads or
                // misinterpretation of the buffer content. This cast and dereference
                // operation is predicated on the alignment and size of the data being
                // appropriate for a `u16`, as per the protocol's definition for UDP segment
                // control messages, thereby justifying the bypass of Rust's alignment
                // safety checks.
                if data_len != std::mem::size_of::<u16>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let gso_segments = unsafe { *(data.as_ptr() as *const u16) };
                control_udp_gso_segments.push(gso_segments); // Store the GSO segment count
            }
            (nix::libc::IPPROTO_IP, nix::libc::IP_PKTINFO) => {
                // SAFETY: The cast from `*const u8` to `*const libc::in_pktinfo` assumes
                // that the alignment requirements for `libc::in_pktinfo` are met. This
                // assumption is based on the contract that control message data, in this
                // case for `IP_PKTINFO`, is correctly aligned according to the
                // specifications of the underlying C and network protocols. The preceding
                // size check ensures that the buffer `data` contains exactly the amount of
                // bytes necessary to represent a single `libc::in_pktinfo` structure,
                // thereby avoiding both overreads and misinterpretation of the data.  The
                // dereference to access the `in_pktinfo` is then justified under the
                // assumption of proper alignment and correct data length, as mandated by
                // the control message's protocol definition.
                if data_len != std::mem::size_of::<libc::in_pktinfo>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let pktinfo = unsafe { &*(data.as_ptr() as *const nix::libc::in_pktinfo) };
                control_ipv4_packet_infos.push(*pktinfo); // Store the IPv4 packet info
            }
            (nix::libc::IPPROTO_IPV6, nix::libc::IPV6_PKTINFO) => {
                // SAFETY: The cast from `*const u8` to `*const libc::in6_pktinfo` is made
                // under the assumption that the buffer is properly aligned for the
                // `libc::in6_pktinfo` structure. This is based on the expectation that the
                // sender of the control message correctly aligns the packet information
                // according to the IPv6 standard requirements. The check ensuring
                // `data_len` matches the size of `libc::in6_pktinfo` guarantees the buffer
                // contains enough data to represent an `in6_pktinfo` structure without
                // overreading. Aligning to and dereferencing the pointer to access the data
                // is therefore considered safe, assuming adherence to the protocol by the
                // message sender and that the data has been formatted and aligned correctly
                // for the type of control message being processed.
                if data_len != std::mem::size_of::<libc::in6_pktinfo>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let pktinfo = unsafe { &*(data.as_ptr() as *const nix::libc::in6_pktinfo) };
                control_ipv6_packet_infos.push(*pktinfo); // Store the IPv6 packet info
            }
            (nix::libc::SOL_SOCKET, nix::libc::SO_RXQ_OVFL) => {
                // SAFETY: Casting from `*const u8` to `*const u32` here assumes that the
                // starting position of `data` is correctly aligned for `u32`. This
                // assumption is valid if the control message, specifically for
                // `SO_RXQ_OVFL`, is constructed with alignment considerations for `u32` as
                // per the protocol's specification. The precondition check that `data_len`
                // equals the size of `u32` ensures that we are accessing exactly one `u32`
                // value, preventing any overread or misinterpretation of the buffer's
                // content. This operation presumes that the control message's sender aligns
                // the data correctly and that the entire length of `data` is intended to
                // represent a single `u32` value, corresponding to the RX queue overflow
                // count. The correctness of this operation depends on adherence to these
                // alignment and size specifications by the sender.
                if data_len != std::mem::size_of::<u32>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let rxq_ovfl = unsafe { *(data.as_ptr() as *const u32) };
                control_rxq_ovfls.push(rxq_ovfl); // Store the Rx queue overflow count
            }
            (nix::libc::SOL_SOCKET, nix::libc::SCM_TXTIME) => {
                // SAFETY: The casting from `*const u8` to `*const u64` is contingent upon
                // the assumption that the `data` buffer is aligned according to `u64`
                // alignment requirements. This operation is premised on the protocol's or
                // sender's adherence to correctly aligning the data for a `u64` value,
                // which is the expected format for `SCM_TXTIME` control messages. The check
                // against `data_len` being equal to the size of a `u64` ensures that only a
                // single `u64` value is accessed, mitigating the risk of buffer overreads
                // and ensuring the data is interpreted correctly as a transmission time.
                // This cast and dereference assume that the control message's composition
                // and alignment practices properly account for the alignment needs of a
                // `u64`, making the operation safe under these controlled conditions.
                if data_len != std::mem::size_of::<u64>() {
                    return Err(Errno::EINVAL); // Data length mismatch
                }
                #[allow(clippy::cast_ptr_alignment)]
                // SAFETY: See the comment above.
                let tx_time = unsafe { *(data.as_ptr() as *const u64) };
                control_tx_times.push(tx_time); // Store the Tx time
            }
            // TODO: DO we want to CONTINUE here if allow_unsupported_socket?
            _ => return Err(Errno::EINVAL),
        }

        // SAFETY: Incrementing `offset` by the result of `CMSG_SPACE(data_len as u32)` is safe
        // under the assumption that `data_len` accurately reflects the length of the current
        // control message's data, and the calculation of space accounts for any padding needed
        // for alignment in subsequent control messages.  The use of `CMSG_SPACE` ensures that
        // `offset` is correctly aligned for the start of the next control message in the
        // buffer, adhering to the alignment requirements of control messages. This operation
        // is guarded by checks on `data_len` and buffer bounds to prevent arithmetic overflows
        // or buffer over-reads, ensuring that the new `offset` value is within the bounds of
        // `cmsg_buf`. The reliance on `CMSG_SPACE` for alignment adjustment is standard
        // practice for parsing sequences of control messages in a buffer, provided that the
        // control message buffer (`cmsg_buf`) is initially aligned and structured correctly
        // according to control message protocols.
        #[allow(clippy::arithmetic_side_effects)]
        #[allow(clippy::cast_possible_truncation)]
        if !is32 {
            // SAFETY: See the comment above.
            offset += unsafe { nix::libc::CMSG_SPACE(data_len as u32) } as usize;
        } else {
            offset += cmsg_space_32(data_len as u32);
        }
    }

    Ok((
        control_fds,
        control_raw_fds,
        control_creds,
        control_ivs,
        control_ops,
        control_aead_assoclens,
        control_udp_gso_segments,
        control_ipv4_packet_infos,
        control_ipv6_packet_infos,
        control_rxq_ovfls,
        control_tx_times,
    ))
}

fn get_addr(
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    addr_remote: u64,
    addr_len: nix::libc::socklen_t,
) -> Result<SockaddrStorage, Errno> {
    // SAFETY: Do not fully trust addr_len.
    #[allow(clippy::arithmetic_side_effects)]
    #[allow(clippy::cast_possible_truncation)]
    let addr_len = addr_len.min(
        (std::mem::size_of::<nix::libc::sockaddr_un>() + UNIX_PATH_MAX) as nix::libc::socklen_t,
    );
    let mut addr = Vec::new();
    addr.try_reserve(addr_len as usize).or(Err(Errno::ENOMEM))?;
    addr.resize(addr_len as usize, 0);
    proc.read_mem(&mut addr, addr_remote, request)?;
    let addr = addr.as_ptr().cast();

    // SAFETY: Invoking `SockaddrStorage::from_raw` is safe because:
    // 1. The memory location of `sockaddr_ptr` is valid, correctly aligned.
    // 2. The memory is allocated based on a valid `sockaddr` structure.
    // 3. There are no concurrent writes to the memory location while reading.
    match unsafe {
        #[allow(clippy::cast_ptr_alignment)]
        SockaddrStorage::from_raw(addr, Some(addr_len))
    } {
        Some(addr) => Ok(addr),
        None => {
            // Invalid socket address.
            Err(Errno::EINVAL)
        }
    }
}

// Canonicalizes UNIX domain socket names.
// Returns address and directory.
// Directory is None for non-UNIX addresses.
fn canon_addr(
    proc: &RemoteProcess,
    request: &UNotifyEventRequest,
    addr: SockaddrStorage,
    cap: Capability,
    kallsyms: bool,
) -> Result<(SockaddrStorage, Option<AddrRoot>), Errno> {
    #[allow(clippy::cast_possible_truncation)]
    if let Some(path) = addr.as_unix_addr().and_then(|a| a.path()) {
        // SAFETY: Path may have trailing nul-bytes.
        // Truncate the path at the first occurrence of a null byte
        // Note this is _not_ an abstract UNIX socket so it's safe.
        let path = path.as_os_str().as_bytes();
        let null = memchr::memchr(0, path).unwrap_or(path.len());
        let path = SmallPathBuf::from(&path[..null]);

        // SAFETY: Make sure relative UNIX socket paths match process CWD.
        let pid = request.get_request().pid();
        let cwd = if path.is_relative() {
            Some(proc.read_directory(request, &SyscallPathArgument::default())?)
        } else {
            None
        };
        // If bind, the path may or may not exist depending on SO_REUSEADDR
        // Else, the path must exist. Always resolve symlinks.
        let miss = if cap == Capability::CAP_BIND {
            Normal
        } else {
            Existing
        };

        let path = canonicalize(pid, &path, cwd.as_deref(), miss, true, false, kallsyms)?;

        let (cstr, root) = if cap == Capability::CAP_BIND {
            // SAFETY: We split the address into directory and basename
            // regardless of UNIX_PATH_MAX as we are later going to use
            // the handler thread to mitigate the TOCTOU vector in the
            // basename of the UNIX socket address. This is only used
            // for bind() which does not resolve symbolic links in
            // basename.
            let (root, name) = path_split(&path);
            let cstr = CString::new(name.as_os_str().as_bytes()).or(Err(Errno::EINVAL))?;
            (cstr, AddrRoot::Path(root.as_ref().into()))
        } else {
            // SAFETY: We open a FD to the path and then use the
            // proc path /proc/self/fd/$fd in address' path argument
            // to avoid symlink TOCTOU because connect and sendto
            // follow symlinks in basename unlike bind.
            let fd = safe_open_path(&path, OFlag::empty())?;
            let mut buf = itoa::Buffer::new();
            let mut proc = TinyPathBuf::new();
            proc.push(Path::new("/proc/self/fd"));
            proc.push(Path::new(buf.format(fd.as_raw_fd())));
            let cstr = CString::new(proc.as_os_str().as_bytes()).or(Err(Errno::EINVAL))?;
            (cstr, AddrRoot::File((path, fd)))
        };

        // Create sockaddr_un struct.
        let mut sockaddr = nix::libc::sockaddr_un {
            sun_family: nix::libc::AF_UNIX as nix::libc::sa_family_t,
            sun_path: [0; UNIX_PATH_MAX],
        };

        // Manually copy the bytes.
        // TODO: Is there a better way?
        #[allow(clippy::cast_possible_wrap)]
        for (dst, &src) in sockaddr.sun_path.iter_mut().zip(cstr.as_bytes_with_nul()) {
            *dst = src as nix::libc::c_char;
        }

        // Calculate the correct size of the sockaddr_un struct,
        // including the family and the path. The size is the offset of
        // the sun_path field plus the length of the path (including the
        // null terminator).
        #[allow(clippy::arithmetic_side_effects)]
        let size = std::mem::size_of::<nix::libc::sa_family_t>() + cstr.as_bytes_with_nul().len();

        // SAFETY: We are converting a sockaddr_un to a
        // SockaddrStorage using a raw pointer. The sockaddr_un
        // is valid for the duration of this operation, ensuring
        // the safety of the pointer. However, this operation is
        // inherently unsafe due to direct pointer manipulation.
        let addr = unsafe {
            SockaddrStorage::from_raw(
                std::ptr::addr_of!(sockaddr) as *const _,
                Some(size as nix::libc::socklen_t),
            )
        }
        .ok_or(Errno::EINVAL)?;

        Ok((addr, Some(root)))
    } else {
        // No need to canonicalize.
        Ok((addr, None))
    }
}
