//
// Syd: rock-solid application kernel
// src/confine.rs: Sandboxing utilities
//
// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
// SPDX-License-Identifier: GPL-3.0

use std::{
    ffi::CStr,
    fmt::Display,
    fs::{exists, read_to_string, OpenOptions},
    io::Write,
    os::{
        fd::{AsFd, AsRawFd, RawFd},
        unix::{fs::OpenOptionsExt, process::ExitStatusExt},
    },
    path::Path,
    process::Command,
    str::FromStr,
    sync::atomic::Ordering,
};

use btoi::btoi;
use libc::{
    c_int, c_ulong, prctl, EFAULT, EINVAL, ENOTTY, EOPNOTSUPP, RTLD_LOCAL, RTLD_NOLOAD, RTLD_NOW,
};
use libloading::{os::unix::Library, Error as LibraryError};
use libseccomp::{
    scmp_cmp, ScmpAction, ScmpArch, ScmpArgCompare, ScmpCompareOp, ScmpFilterContext, ScmpSyscall,
};
use memchr::arch::all::is_equal;
use nix::{
    dir::Dir,
    errno::Errno,
    fcntl::OFlag,
    sched::{unshare, CloneFlags},
    sys::{
        resource::{rlim_t, setrlimit, Resource},
        stat::Mode,
        wait::{Id, WaitPidFlag},
    },
    unistd::{read, write, Gid, Pid, Uid},
};
use procfs_core::process::{MMPermissions, MMapPath, MemoryMap};
use serde::{Serialize, Serializer};

use crate::{
    caps,
    compat::{seccomp_data, seccomp_notif, waitid, Persona, SHM_EXEC},
    config::{
        DENY_SETSOCKOPT, FADVISE_SYSCALLS, HAVE_RWF_NOAPPEND, MMAP_MIN_ADDR, SYD_MADVISE,
        SYSCALL_PTR_ARGS, UNSAFE_PERSONA,
    },
    cookie::safe_socket,
    err::{err2no, SydResult},
    fd::AT_BADFD,
    fs::{nlmsg_align, readlinkat, safe_clone, seccomp_export_pfc},
    info,
    landlock::{
        path_beneath_rules, Access, AccessFs, AccessNet, NetPort, RestrictSelfFlags,
        RestrictionStatus, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetError, RulesetStatus,
        Scope, ABI,
    },
    lookup::FileType,
    mount::api::MountAttrFlags,
    path::{mask_path, XPath, XPathBuf},
    proc::{proc_find_vma, ProcmapQueryFlags, Vma},
    retry::retry_on_eintr,
    rwrite, rwriteln,
    sandbox::{RawIoctlMap, Sandbox},
    sealbox::{mprotect_xonly, mseal},
};

// Used as dummy errno(3) after SECCOMP_IOCTL_NOTIF_ADDFD with SECCOMP_ADDFD_FLAG_SEND flag.
pub(crate) const EIDRM: i32 = -libc::EIDRM;

// Used as dummy errno(3) to initiate Ghost Mode.
pub(crate) const EOWNERDEAD: i32 = -libc::EOWNERDEAD;

/// Confine current process using MDWE prctl(2).
///
/// Use `no_inherit` to prevent inheriting the restriction to children.
pub fn confine_mdwe(no_inherit: bool) -> Result<(), Errno> {
    const PR_SET_MDWE: c_int = 65;
    const PR_MDWE_REFUSE_EXEC_GAIN: c_ulong = 1;
    const PR_MDWE_NO_INHERIT: c_ulong = 2;

    let mut flags = PR_MDWE_REFUSE_EXEC_GAIN;
    if no_inherit {
        flags |= PR_MDWE_NO_INHERIT;
    }

    // SAFETY: In libc we trust.
    Errno::result(unsafe { prctl(PR_SET_MDWE, flags, 0, 0, 0) }).map(drop)
}

/// Confine the given `Resource` using setrlimit(2).
pub fn confine_rlimit(resource: Resource, lim: Option<rlim_t>) -> Result<(), Errno> {
    let lim = lim.unwrap_or(0);
    setrlimit(resource, lim, lim)
}

/// Confine the rlimit given `Resource`s to zero.
pub fn confine_rlimit_zero(resources: &[Resource]) -> Result<(), Errno> {
    for resource in resources {
        confine_rlimit(*resource, None)?;
    }
    Ok(())
}

/// Confine executable system mappings using mseal(2) and mprotect(2).
///
/// This is best effort and does not bail on errors.
/// This function must be called after logging is initialized.
/// This function must be called after /proc fd is opened with `proc_init`.
pub fn confine_executable_maps() -> Result<(), Errno> {
    const SKIP_XONLY: &[&[u8]] = &[b"[vdso]", b"[vsyscall]"]; // sorted

    // Don't retry if mseal(2) returns ENOSYS.
    // This is the case for example on 32-bit.
    let mut mseal_nosys = false;

    for vma in proc_find_vma(Pid::this(), ProcmapQueryFlags::VMA_EXECUTABLE)? {
        if SKIP_XONLY.binary_search(&vma.name_bytes()).is_err() {
            let _ = confine_vma_xonly(&vma);
        }
        if mseal_nosys {
            continue;
        }
        mseal_nosys = confine_vma_mseal(&vma) == Err(Errno::ENOSYS);
    }

    Ok(())
}

#[expect(clippy::cognitive_complexity)]
fn confine_vma_xonly(vma: &Vma) -> Result<(), Errno> {
    match mprotect_xonly(vma.as_ptr(), vma.len()) {
        Ok(()) => {
            info!("ctx": "seal_executable_maps", "op": "mprotect_xonly",
                "msg": format!("made vma `{}' at {:#x} execute-only",
                    vma.name(), vma.addr()),
                "vma": &vma);
            Ok(())
        }
        Err(errno) => {
            info!("ctx": "seal_executable_maps", "op": "mprotect_xonly",
                "msg": format!("error making vma `{}' at {:#x} execute-only: {errno}",
                    vma.name(), vma.addr()),
                "err": errno as i32, "vma": &vma);
            Err(errno)
        }
    }
}

#[expect(clippy::cognitive_complexity)]
fn confine_vma_mseal(vma: &Vma) -> Result<(), Errno> {
    match mseal(vma.as_ptr(), vma.len()) {
        Ok(()) => {
            info!("ctx": "seal_executable_maps", "op": "mseal",
                "msg": format!("sealed vma `{}' at {:#x}",
                    vma.name(), vma.addr()),
                "vma": &vma);
            Ok(())
        }
        Err(errno) => {
            info!("ctx": "seal_executable_maps", "op": "mseal",
                "msg": format!("error sealing vma `{}' at {:#x}: {errno}",
                    vma.name(), vma.addr()),
                "err": errno as i32, "vma": &vma);
            Err(errno)
        }
    }
}

/// Install a standalone seccomp(2) filter to deny the given set of syscalls with the given action.
///
/// The filter supports non-native system calls.
/// Invalid system call names are skipped.
/// Returns `Err(Errno::EINVAL)` if action is `ScmpAction::Allow`.
pub fn confine_scmp(action: ScmpAction, sysnames: &[&str]) -> SydResult<()> {
    // Prevent nonsensical use.
    if action == ScmpAction::Allow {
        return Err(Errno::EINVAL.into());
    }

    let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
    // We don't want ECANCELED, we want actual errnos.
    let _ = ctx.set_api_sysrawrc(true);
    // We kill for bad system call and bad arch.
    let _ = ctx.set_act_badarch(ScmpAction::KillProcess);
    // Use a binary tree sorted by syscall number.
    let _ = ctx.set_ctl_optimize(2);

    seccomp_add_architectures(&mut ctx)?;

    // Load the system call set into filter.
    for sysname in sysnames {
        let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
            syscall
        } else {
            continue;
        };

        ctx.add_rule(action, syscall)?;
    }

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

    Ok(())
}

/// Apply W^X memory restrictions using _seccomp_(2).
pub fn confine_scmp_wx() -> SydResult<()> {
    let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
    // We don't want ECANCELED, we want actual errnos.
    let _ = ctx.set_api_sysrawrc(true);
    // We kill for bad system call and bad arch.
    let _ = ctx.set_act_badarch(ScmpAction::KillProcess);
    // Use a binary tree sorted by syscall number.
    let _ = ctx.set_ctl_optimize(2);

    seccomp_add_architectures(&mut ctx)?;

    // Seccomp W^X restrictions:
    //
    // - Prevent mmap(addr<${mmap_min_addr}, MAP_FIXED).
    // - 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.
    // - Deny unsafe personality(2) personas.

    const MAP_FIXED: u64 = libc::MAP_FIXED as u64;
    const MAP_FIXED_NOREPLACE: u64 = crate::compat::MAP_FIXED_NOREPLACE as u64;
    const W: u64 = libc::PROT_WRITE as u64;
    const X: u64 = libc::PROT_EXEC as u64;
    const WX: u64 = W | X;
    const SHM_X: u64 = SHM_EXEC as u64;
    const MAP_A: u64 = libc::MAP_ANONYMOUS as u64;
    const MAP_S: u64 = libc::MAP_SHARED as u64;

    let mmap_min_addr = *MMAP_MIN_ADDR;
    for sysname in ["mmap", "mmap2"] {
        let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
            syscall
        } else {
            continue;
        };

        // Prevent fixed mappings under mmap_min_addr.
        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),
            ],
        )?;

        // Prevent writable and executable memory.
        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"] {
        let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
            syscall
        } else {
            continue;
        };

        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            syscall,
            &[scmp_cmp!($arg2 & X == X)],
        )?;
    }

    if let Ok(syscall) = ScmpSyscall::from_name("shmat") {
        ctx.add_rule_conditional(
            ScmpAction::KillProcess,
            syscall,
            &[scmp_cmp!($arg2 & SHM_X == SHM_X)],
        )?;
    }

    if let Ok(syscall) = ScmpSyscall::from_name("personality") {
        for persona in UNSAFE_PERSONA {
            let persona = persona.bits();
            ctx.add_rule_conditional(
                ScmpAction::KillProcess,
                syscall,
                &[scmp_cmp!($arg0 & persona == persona)],
            )?;
        }
    }

    ctx.load()?;

    Ok(())
}

/// Add per-architecture seccomp(2) filters to deny given ioctl(2) requests.
///
/// Set `ssb` to true to disable Speculative Store Bypass mitigations.
pub fn confine_scmp_ioctl(denylist: &RawIoctlMap, ssb: bool) -> SydResult<()> {
    let syscall = ScmpSyscall::from_name("ioctl").or(Err(Errno::ENOSYS))?;
    for arch in SCMP_ARCH.iter().copied() {
        let denylist = if let Some(denylist) = denylist.get(&arch) {
            denylist
        } else {
            continue;
        };

        // Prepare per-architecture seccomp(2) filter.
        let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // Enable Speculative Store Bypass mitigations.
        ctx.set_ctl_ssb(ssb)?;

        // Do not synchronize filter to all threads.
        ctx.set_ctl_tsync(false)?;

        // Allow bad/unsupported architectures,
        // this is a per-architecture filter.
        ctx.set_act_badarch(ScmpAction::Allow)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // We don't want ECANCELED, we want actual errnos.
        let _ = ctx.set_api_sysrawrc(true);

        // Remove native architecture from filter,
        // and add the specific architecture.
        ctx.remove_arch(ScmpArch::native())?;
        ctx.add_arch(arch)?;

        #[expect(clippy::useless_conversion)]
        for request in denylist.iter().copied() {
            let request = request.into();
            ctx.add_rule_conditional(
                ScmpAction::Errno(ENOTTY),
                syscall,
                &[scmp_cmp!($arg1 == request)],
            )?;
            if let Some(request) = extend_ioctl(request) {
                ctx.add_rule_conditional(
                    ScmpAction::Errno(ENOTTY),
                    syscall,
                    &[scmp_cmp!($arg1 == request)],
                )?;
            }
        }

        ctx.load()?;
    }

    Ok(())
}

/// Add per-architecture seccomp(2) filters to deny kernel pointer arguments.
///
/// Set `ssb` to true to disable Speculative Store Bypass mitigations.
#[expect(clippy::cognitive_complexity)]
pub fn confine_scmp_kptr(ssb: bool) -> SydResult<()> {
    // For the following syscalls return EINVAL not EFAULT.
    // This list must be sorted, it's binary searched.
    const SYSCALL_EINVAL: &[&str] = &[
        "madvise",
        "map_shadow_stack",
        "mbind",
        "mlock",
        "mlock2",
        "mmap",
        "mmap2",
        "mprotect",
        "mremap",
        "mseal",
        "msync",
        "munlock",
        "munmap",
    ];
    // keyctl(2) pointer argument index depends on operation.
    // Defined manually because bionic libc doesn't define them.
    const KEYCTL_PTR: &[(u64, &[u32])] = &[
        (1 /*KEYCTL_JOIN_SESSION_KEYRING*/, &[1]),
        (2 /*KEYCTL_UPDATE*/, &[2]),
        (6 /*KEYCTL_DESCRIBE*/, &[2]),
        (10 /*KEYCTL_SEARCH*/, &[2, 3]),
        (11 /*KEYCTL_READ*/, &[2]),
        (12 /*KEYCTL_INSTANTIATE*/, &[2]),
        (17 /*KEYCTL_GET_SECURITY*/, &[2]),
        (20 /*KEYCTL_INSTANTIATE_IOV*/, &[2]),
        (23 /*KEYCTL_DH_COMPUTE*/, &[1, 2, 4]),
        (29 /*KEYCTL_RESTRICT_KEYRING*/, &[2, 3]),
    ];
    // prctl(2) pointer argument index depends on operation.
    // PR_SET_MM isn't here because it's treated specially below.
    // PR_SET_SECCOMP is special too.
    // PR_SET_SYSCALL_USER_DISPATCH is special too.
    const PRCTL_PTR: &[(u64, &[u32])] = &[
        (libc::PR_GET_CHILD_SUBREAPER as u64, &[1]),
        (libc::PR_GET_ENDIAN as u64, &[1]),
        (libc::PR_GET_FPEMU as u64, &[1]),
        (libc::PR_GET_FPEXC as u64, &[1]),
        (libc::PR_SET_VMA as u64, &[2, 4]),
        (libc::PR_SET_NAME as u64, &[1]),
        (libc::PR_GET_NAME as u64, &[1]),
        (libc::PR_GET_PDEATHSIG as u64, &[1]),
        (libc::PR_GET_TID_ADDRESS as u64, &[1]),
        (libc::PR_GET_TSC as u64, &[1]),
        (libc::PR_GET_UNALIGN as u64, &[1]),
        (0x41555856 /* PR_GET_AUXV */, &[1]),
    ];
    for arch in SCMP_ARCH.iter().copied() {
        // Prepare per-architecture seccomp(2) filter.
        let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // Enable Speculative Store Bypass mitigations.
        ctx.set_ctl_ssb(ssb)?;

        // Do not synchronize filter to all threads.
        ctx.set_ctl_tsync(false)?;

        // Allow bad/unsupported architectures,
        // this is a per-architecture filter.
        ctx.set_act_badarch(ScmpAction::Allow)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // We don't want ECANCELED, we want actual errnos.
        let _ = ctx.set_api_sysrawrc(true);

        // Remove native architecture from filter,
        // and add the specific architecture.
        ctx.remove_arch(ScmpArch::native())?;
        ctx.add_arch(arch)?;

        let is32 = scmp_arch_bits(arch) == 32;
        for (sysname, args) in SYSCALL_PTR_ARGS {
            if !SydArch(arch).has_syscall(sysname) {
                continue;
            }

            let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
                syscall
            } else {
                continue;
            };

            let errno = if SYSCALL_EINVAL.binary_search(sysname).is_ok() {
                EINVAL
            } else {
                EFAULT
            };

            // Handle special system calls.
            if args.is_empty() {
                if is_equal(sysname.as_bytes(), b"keyctl") {
                    for (op, args) in KEYCTL_PTR {
                        for arg in args.iter().copied() {
                            ctx.add_rule_conditional(
                                ScmpAction::Errno(errno),
                                syscall,
                                &[scmp_cmp!($arg0 == *op), scmp_kernel_ptr(arch, arg)],
                            )?;
                        }
                    }
                } else if is_equal(sysname.as_bytes(), b"prctl") {
                    for (op, args) in PRCTL_PTR {
                        for arg in args.iter().copied() {
                            ctx.add_rule_conditional(
                                ScmpAction::Errno(errno),
                                syscall,
                                &[scmp_cmp!($arg0 == *op), scmp_kernel_ptr(arch, arg)],
                            )?;
                        }
                    }
                    // Handle PR_SET_MM specially:
                    // All suboperations except PR_SET_MM_EXE_FILE
                    // expect a pointer as third argument.
                    let op = libc::PR_SET_MM as u64;
                    let subop = libc::PR_SET_MM_EXE_FILE as u64;
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(errno),
                        syscall,
                        &[
                            scmp_cmp!($arg0 == op),
                            scmp_cmp!($arg1 != subop),
                            scmp_kernel_ptr(arch, 2),
                        ],
                    )?;
                    // Handle PR_SET_SECCOMP specially.
                    // Third argument is a pointer only if suboperation is SECCOMP_MODE_FILTER.
                    let op = libc::PR_SET_SECCOMP as u64;
                    let subop = libc::SECCOMP_MODE_FILTER;
                    ctx.add_rule_conditional(
                        ScmpAction::Errno(errno),
                        syscall,
                        &[
                            scmp_cmp!($arg0 == op),
                            scmp_cmp!($arg1 == subop.into()),
                            scmp_kernel_ptr(arch, 2),
                        ],
                    )?;
                    // Handle PR_SET_SYSCALL_USER_DISPATCH specially.
                    // Fourth argument is a pointer only if suboperation is
                    // PR_SYS_DISPATCH_ON_{EXC,INC}LUSIVE_ON.
                    // PR_SYS_DISPATCH_ON is an alias for PR_SYS_DISPATCH_EXCLUSIVE_ON.
                    let op = 59u64 /* PR_SET_SYSCALL_USER_DISPATCH */;
                    let subops = [
                        1, /*PR_SYS_DISPATCH_EXCLUSIVE_ON*/
                        2, /*PR_SYS_DISPATCH_INCLUSIVE_ON*/
                    ];
                    for subop in subops {
                        ctx.add_rule_conditional(
                            ScmpAction::Errno(errno),
                            syscall,
                            &[
                                scmp_cmp!($arg0 == op),
                                scmp_cmp!($arg1 == subop),
                                scmp_kernel_ptr(arch, 3),
                            ],
                        )?;
                    }
                } else {
                    unreachable!("BUG: Invalid syscall `{sysname}' in SYSCALL_PTR_ARGS!");
                }
                continue;
            }

            for mut arg in args.iter().copied() {
                #[expect(clippy::arithmetic_side_effects)]
                if is32 && is_equal(sysname.as_bytes(), b"fanotify_mark") {
                    // mask argument of fanotify_mark(2) is 64-bit,
                    // therefore we must increment index by one for path ptr.
                    arg += 1;
                }
                if arg == 1
                    && matches!(arch, ScmpArch::S390X | ScmpArch::S390)
                    && is_equal(sysname.as_bytes(), b"clone")
                {
                    // On s390/s390x the first two parameters to clone are switched.
                    arg = 0;
                }
                ctx.add_rule_conditional(
                    ScmpAction::Errno(errno),
                    syscall,
                    &[scmp_kernel_ptr(arch, arg)],
                )?;
            }
        }

        let arch = SydArch(arch);
        match ExportMode::from_env() {
            None => ctx.load()?,
            Some(ExportMode::BerkeleyPacketFilter) => {
                #[expect(clippy::disallowed_methods)]
                let file = OpenOptions::new()
                    .write(true)
                    .create_new(true)
                    .mode(0o400)
                    .open(format!("syd_ptr_{arch}.bpf"))?;
                ctx.export_bpf(file)?;
            }
            Some(ExportMode::PseudoFiltercode) => {
                // Lock stdout to prevent concurrent access.
                let mut stdout = std::io::stdout().lock();

                rwriteln!(stdout, "# Syd pointer rules for arch:{arch}")?;
                rwrite!(stdout, "{}", seccomp_export_pfc(&ctx)?)?;
            }
        };
    }

    Ok(())
}

/// pwritev2(2) flag for per-IO negation of O_APPEND
pub const RWF_NOAPPEND: u64 = 0x00000020;

/// Deny pwritev2(2) system call when flags include
/// RWF_NOAPPEND with the EOPNOTSUPP errno.
///
/// Optimized so that:
///   - if SCMP_ARCH contains X32: install per-arch filters (X32 uses $arg4)
///   - else: install a single filter using $arg5 (libseccomp/natural ABI)
///
/// Set `ssb` to true to disable Speculative Store Bypass mitigations.
pub fn confine_scmp_pwritev2(ssb: bool) -> SydResult<()> {
    if !*HAVE_RWF_NOAPPEND {
        // RWF_NOAPPEND not supported, nothing to do.
        return Ok(());
    }

    let syscall = if let Ok(syscall) = ScmpSyscall::from_name("pwritev2") {
        syscall
    } else {
        // pwritev2(2) not supported, nothing to do.
        return Ok(());
    };

    if !SCMP_ARCH.contains(&ScmpArch::X32) {
        // Fast path: all supported archs have flags at $arg5.
        let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // Disable Speculative Store Bypass mitigations
        // with trace/allow_unsafe_exec_speculative:1
        ctx.set_ctl_ssb(ssb)?;

        // 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)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // We don't want ECANCELED, we want actual errnos.
        let _ = ctx.set_api_sysrawrc(true);

        // Add supported architectures.
        seccomp_add_architectures(&mut ctx)?;

        // Deny pwritev2(2) using RWF_NOAPPEND with EOPNOTSUPP.
        let rule = scmp_cmp!($arg5 & RWF_NOAPPEND == RWF_NOAPPEND);
        ctx.add_rule_conditional(ScmpAction::Errno(EOPNOTSUPP), syscall, &[rule])?;

        // Load the arch-agnostic filter and return.
        return Ok(ctx.load()?);
    }

    // Slow path with x32 flags at $arg4 and others at $arg5.
    // Install per-arch filters with the correct index.
    for arch in SCMP_ARCH {
        // Prepare per-architecture seccomp(2) filter.
        let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // Disable Speculative Store Bypass mitigations
        // with trace/allow_unsafe_exec_speculative:1
        ctx.set_ctl_ssb(ssb)?;

        // Do not synchronize filter to all threads.
        ctx.set_ctl_tsync(false)?;

        // Allow bad/unsupported architectures,
        // this is a per-architecture filter.
        ctx.set_act_badarch(ScmpAction::Allow)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // We don't want ECANCELED, we want actual errnos.
        let _ = ctx.set_api_sysrawrc(true);

        // Remove native architecture from filter,
        // and add the specific architecture.
        ctx.remove_arch(ScmpArch::native())?;
        ctx.add_arch(*arch)?;

        // x32: flags is $arg4; everybody else here: $arg5.
        let rule = if *arch == ScmpArch::X32 {
            scmp_cmp!($arg4 & RWF_NOAPPEND == RWF_NOAPPEND)
        } else {
            scmp_cmp!($arg5 & RWF_NOAPPEND == RWF_NOAPPEND)
        };
        ctx.add_rule_conditional(ScmpAction::Errno(EOPNOTSUPP), syscall, &[rule])?;

        // Load the arch-specific filter.
        ctx.load()?;
    }

    Ok(())
}

/// Allow clone(2) operations without namespaces.
pub fn confine_scmp_clone(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    let syscall = match ScmpSyscall::from_name("clone") {
        Ok(s) => s,
        Err(_) => {
            info!("ctx": "confine", "op": "allow_syscall",
                "msg": "invalid or unsupported syscall clone");
            return Ok(());
        }
    };

    let ns_mask = CloneFlags::CLONE_NEWNS
        | CloneFlags::CLONE_NEWUTS
        | CloneFlags::CLONE_NEWIPC
        | CloneFlags::CLONE_NEWUSER
        | CloneFlags::CLONE_NEWNET
        | CloneFlags::CLONE_NEWPID
        | CloneFlags::CLONE_NEWCGROUP
        | CLONE_NEWTIME;
    #[expect(clippy::cast_sign_loss)]
    let ns_mask = ns_mask.bits() as u64;

    // On s390/s390x the first two parameters to clone are switched.
    let filter = if !cfg!(target_arch = "s390x") {
        scmp_cmp!($arg0 & ns_mask == 0)
    } else {
        scmp_cmp!($arg1 & ns_mask == 0)
    };
    ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[filter])?;

    Ok(())
}

/// Deny clone3(2) with ENOSYS for compatibility.
pub fn confine_scmp_clone3(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    let syscall = match ScmpSyscall::from_name("clone3") {
        Ok(s) => s,
        Err(_) => {
            info!("ctx": "confine", "op": "allow_syscall",
                "msg": "invalid or unsupported syscall clone3");
            return Ok(());
        }
    };

    ctx.add_rule(ScmpAction::Errno(libc::ENOSYS), syscall)?;

    Ok(())
}

/// Allow writes to sandbox `SYD_LOG_FD` only.
///
/// If logging is disabled:
///
/// a. If `max` is `None`, deny write(2) completely.
/// b. If `max` is `Some(limit)`, allow writes up to `max` bytes.
///
/// It is OK for the `SYD_LOG_FD` to be negative,
/// in which case no rule will be inserted
/// for the fd.
///
/// # Exceptions
///
/// 1. Allow write(2) globally if profiling is enabled.
/// 2. Allow write(2) globally if `chk_mem` is true,
///    and `Sandbox::memory_access` is less than 2.
///    This is required for proc_pid_mem(5) access.
pub fn confine_scmp_write(
    ctx: &mut ScmpFilterContext,
    max: Option<u64>,
    chk_mem: bool,
) -> SydResult<()> {
    let syscall = match ScmpSyscall::from_name("write") {
        Ok(syscall) => syscall,
        Err(_) => {
            info!("ctx": "confine", "op": "allow_syscall",
                "msg": "invalid or unsupported syscall write");
            return Ok(());
        }
    };

    if cfg!(feature = "prof") || (chk_mem && Sandbox::memory_access() < 2) {
        ctx.add_rule(ScmpAction::Allow, syscall)?;
        return Ok(());
    }

    if let Ok(log_fd) = u64::try_from(crate::log::LOG_FD.load(Ordering::Relaxed)) {
        ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg0 == log_fd)])?;
        if let Some(max) = max {
            ctx.add_rule_conditional(
                ScmpAction::Allow,
                syscall,
                &[scmp_cmp!($arg0 != log_fd), scmp_cmp!($arg2 <= max)],
            )?;
        }
    } else if let Some(max) = max {
        ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg2 <= max)])?;
    } // else deny write(2) completely.

    Ok(())
}

/// Allow fadvise family system calls.
pub fn confine_scmp_fadvise(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    for sysname in FADVISE_SYSCALLS {
        if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
            ctx.add_rule(ScmpAction::Allow, syscall)?;
        } else {
            info!("ctx": "confine", "op": "allow_syscall",
                "msg": format!("invalid or unsupported syscall {sysname}"));
        }
    }

    Ok(())
}

/// Confine madvise(2) advice.
pub fn confine_scmp_madvise(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    if let Ok(syscall) = ScmpSyscall::from_name("madvise") {
        for advice in SYD_MADVISE {
            ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg2 == *advice)])?;
        }
    } else {
        info!("ctx": "confine", "op": "allow_syscall",
            "msg": "invalid or unsupported syscall madvise");
    }

    Ok(())
}

/// Confine MSG_OOB flag for network system calls.
pub fn confine_scmp_msg_oob(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    let oob = libc::MSG_OOB as u64;
    for (idx, sysname) in [
        "recvmsg", "sendmsg", "send", "sendto", "sendmmsg", "recv", "recvfrom", "recvmmsg",
    ]
    .iter()
    .enumerate()
    {
        // MsgFlags is arg==2 for {recv,send}msg, and
        //             arg==3 for send/recv, sendto/recvfrom, and sendmmsg/recvmmsg.
        let sys = if let Ok(sys) = ScmpSyscall::from_name(sysname) {
            sys
        } else {
            info!("ctx": "confine", "op": "allow_syscall",
                "msg": "invalid or unsupported syscall {sysname}");
            continue;
        };

        let cmp = if idx <= 1 {
            scmp_cmp!($arg2 & oob == oob)
        } else {
            scmp_cmp!($arg3 & oob == oob)
        };
        ctx.add_rule_conditional(ScmpAction::Errno(libc::EOPNOTSUPP), sys, &[cmp])?;
    }

    Ok(())
}

/// Confine setsockopt(2) options.
pub fn confine_scmp_setsockopt(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    if let Ok(syscall) = ScmpSyscall::from_name("setsockopt") {
        for &(level, optname) in DENY_SETSOCKOPT {
            // setsockopt(fd, level, optname, optval, optlen)
            //   arg1 = level
            //   arg2 = optname
            #[expect(clippy::cast_sign_loss)]
            ctx.add_rule_conditional(
                ScmpAction::Errno(opt2errno(level, optname)),
                syscall,
                &[
                    scmp_cmp!($arg1 == level as u64),
                    scmp_cmp!($arg2 == optname as u64),
                ],
            )?;
        }
    } else {
        info!("ctx": "confine", "op": "deny_syscall",
            "msg": "invalid or unsupported syscall setsockopt");
    }

    Ok(())
}

/// Convenience `Command` run wrapper which returns:
///
/// - Same exit code as the process on clean exit.
/// - 128 plus signal number on unclean termination.
/// - `Errno` number if executing the process fails.
pub fn run_cmd(cmd: &mut Command) -> u8 {
    #![allow(clippy::arithmetic_side_effects)]
    #![allow(clippy::cast_possible_truncation)]
    #![allow(clippy::cast_sign_loss)]
    match cmd.status() {
        Ok(status) => {
            if let Some(code) = status.code() {
                code as u8
            } else if let Some(sig) = status.signal() {
                128 + (sig as u8)
            } else {
                127
            }
        }
        Err(error) => err2no(&error) as i32 as u8,
    }
}

/// Simple wrapper over ScmpSyscall and ScmpArch to provide Display.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Sydcall(pub ScmpSyscall, pub u32);

impl Display for Sydcall {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let arch = match scmp_arch(self.1) {
            Ok(arch) => arch,
            Err(_) => return write!(f, "?"),
        };

        match self.0.get_name_by_arch(arch).ok() {
            Some(name) => write!(f, "{name}"),
            None => write!(f, "?"),
        }
    }
}

impl Serialize for Sydcall {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let arch = match scmp_arch(self.1) {
            Ok(arch) => arch,
            Err(_) => return serializer.serialize_none(),
        };

        match self.0.get_name_by_arch(arch).ok() {
            Some(name) => serializer.serialize_str(&name),
            None => serializer.serialize_none(),
        }
    }
}

/// A wrapper around `ScmpArch` providing utility functions and `std::fmt::Display`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct SydArch(pub ScmpArch);

impl SydArch {
    /// Returns the native architecture.
    pub fn native() -> Self {
        ScmpArch::native().into()
    }

    /// Returns true if the given architecture supports the given system call natively.
    pub fn has_syscall(&self, name: &str) -> bool {
        ScmpSyscall::from_name_by_arch(name, self.0)
            .map(|sys| sys.as_raw_syscall())
            .map(|sno| sno >= 0)
            .unwrap_or(false)
    }

    /// Returns true if native architecture supports the given system call natively.
    pub fn has_native_syscall(name: &str) -> bool {
        Self::native().has_syscall(name)
    }

    /// Returns true if the given architecture supports the socketcall(2) syscall natively.
    pub fn has_socketcall(&self) -> bool {
        self.has_syscall("socketcall")
    }

    /// Returns true if the given architecture supports the ipc(2) syscall natively.
    pub fn has_ipc(&self) -> bool {
        self.has_syscall("ipc")
    }

    /// Returns true if native architecture supports the socketcall(2) natively.
    pub fn has_native_socketcall() -> bool {
        Self::native().has_socketcall()
    }

    /// Returns true if native architecture supports the ipc(2) natively.
    pub fn has_native_ipc() -> bool {
        Self::native().has_ipc()
    }
}

impl Display for SydArch {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let arch = format!("{:?}", self.0).to_ascii_lowercase();
        let arch = if arch == { "x8664" } { "x86_64" } else { &arch };
        write!(f, "{arch}")
    }
}

impl Serialize for SydArch {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let arch = format!("{:?}", self.0).to_ascii_lowercase();
        let arch = if arch == { "x8664" } { "x86_64" } else { &arch };
        serializer.serialize_str(arch)
    }
}

impl From<ScmpArch> for SydArch {
    fn from(arch: ScmpArch) -> Self {
        SydArch(arch)
    }
}

impl From<SydArch> for ScmpArch {
    fn from(arch: SydArch) -> Self {
        arch.0
    }
}

impl From<&ScmpArch> for SydArch {
    fn from(arch: &ScmpArch) -> Self {
        SydArch(*arch)
    }
}

impl From<&SydArch> for ScmpArch {
    fn from(arch: &SydArch) -> Self {
        arch.0
    }
}

/// A wrapper type that wraps MemoryMap and provides `Serialize`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SydMemoryMap(pub MemoryMap);

impl SydMemoryMap {
    /// Checks if the memory map points to a stack.
    pub fn is_stack(&self) -> bool {
        matches!(self.0.pathname, MMapPath::Stack | MMapPath::TStack(_))
    }
}

impl Display for SydMemoryMap {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let mmap = &self.0;

        // Build permissions string.
        let perms = format!(
            "{}{}{}{}",
            if mmap.perms.contains(MMPermissions::READ) {
                "r"
            } else {
                "-"
            },
            if mmap.perms.contains(MMPermissions::WRITE) {
                "w"
            } else {
                "-"
            },
            if mmap.perms.contains(MMPermissions::EXECUTE) {
                "x"
            } else {
                "-"
            },
            if mmap.perms.contains(MMPermissions::SHARED) {
                "s"
            } else if mmap.perms.contains(MMPermissions::PRIVATE) {
                "p"
            } else {
                "-"
            }
        );

        // Map pathname.
        let pathname = match &mmap.pathname {
            MMapPath::Path(path) => mask_path(path),
            MMapPath::Heap => "[heap]".to_string(),
            MMapPath::Stack => "[stack]".to_string(),
            MMapPath::TStack(tid) => format!("[stack:{tid}]"),
            MMapPath::Vdso => "[vdso]".to_string(),
            MMapPath::Vvar => "[vvar]".to_string(),
            MMapPath::Vsyscall => "[vsyscall]".to_string(),
            MMapPath::Rollup => "[rollup]".to_string(),
            MMapPath::Anonymous => "[anon]".to_string(),
            MMapPath::Vsys(key) => format!("[vsys:{key}]"),
            MMapPath::Other(pseudo_path) => mask_path(Path::new(pseudo_path)),
        };

        // Format output line.
        write!(
            f,
            "{:x}-{:x} {perms:<4} {:08x} {:02x}:{:02x} {:<10} {pathname}",
            mmap.address.0, mmap.address.1, mmap.offset, mmap.dev.0, mmap.dev.1, mmap.inode,
        )
    }
}

impl Serialize for SydMemoryMap {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

/// A type that wraps personality(2) return value and implements Display.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct SydPersona(pub Persona);

impl SydPersona {
    /// Get current personalities.
    pub fn get() -> Result<Self, Errno> {
        // SAFETY: In libc we trust.
        #[expect(clippy::cast_sign_loss)]
        Errno::result(unsafe { libc::personality(0xFFFFFFFF) })
            .map(|pers| Persona::from_bits_retain(pers as u64))
            .map(Self)
    }

    /// Set current personalities.
    pub fn set(&self) -> Result<(), Errno> {
        #[cfg(target_os = "android")]
        {
            // SAFETY: In bionic we trust.
            Errno::result(unsafe { libc::personality(self.bits() as libc::c_uint) }).map(drop)
        }
        #[cfg(not(target_os = "android"))]
        {
            // SAFETY: In libc we trust.
            Errno::result(unsafe { libc::personality(self.bits() as libc::c_ulong) }).map(drop)
        }
    }

    /// Return raw bits.
    pub fn bits(&self) -> u64 {
        self.0.bits()
    }
}

impl Display for SydPersona {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        // Execution domain constants, taken from sys/personality.h
        const PER_LINUX: u64 = 0;
        const PER_LINUX_32BIT: u64 = PER_LINUX | ADDR_LIMIT_32BIT;
        const PER_LINUX_FDPIC: u64 = PER_LINUX | FDPIC_FUNCPTRS;
        const PER_SVR4: u64 = 1 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO;
        const PER_SVR3: u64 = 2 | STICKY_TIMEOUTS | SHORT_INODE;
        const PER_SCOSVR3: u64 = 3 | STICKY_TIMEOUTS | WHOLE_SECONDS | SHORT_INODE;
        const PER_OSR5: u64 = 3 | STICKY_TIMEOUTS | WHOLE_SECONDS;
        const PER_WYSEV386: u64 = 4 | STICKY_TIMEOUTS | SHORT_INODE;
        const PER_ISCR4: u64 = 5 | STICKY_TIMEOUTS;
        const PER_BSD: u64 = 6;
        const PER_SUNOS: u64 = PER_BSD | STICKY_TIMEOUTS;
        const PER_XENIX: u64 = 7 | STICKY_TIMEOUTS | SHORT_INODE;
        const PER_LINUX32: u64 = 8;
        const PER_LINUX32_3GB: u64 = PER_LINUX32 | ADDR_LIMIT_3GB;
        const PER_IRIX32: u64 = 9 | STICKY_TIMEOUTS;
        const PER_IRIXN32: u64 = 0xa | STICKY_TIMEOUTS;
        const PER_IRIX64: u64 = 0x0b | STICKY_TIMEOUTS;
        const PER_RISCOS: u64 = 0xc;
        const PER_SOLARIS: u64 = 0xd | STICKY_TIMEOUTS;
        const PER_UW7: u64 = 0xe | STICKY_TIMEOUTS | MMAP_PAGE_ZERO;
        const PER_OSF4: u64 = 0xf;
        const PER_HPUX: u64 = 0x10;
        const PER_MASK: u64 = 0xff;

        // Flag constants, taken from sys/personality.h
        const UNAME26: u64 = 0x0020000;
        const ADDR_NO_RANDOMIZE: u64 = 0x0040000;
        const FDPIC_FUNCPTRS: u64 = 0x0080000;
        const MMAP_PAGE_ZERO: u64 = 0x0100000;
        const ADDR_COMPAT_LAYOUT: u64 = 0x0200000;
        const READ_IMPLIES_EXEC: u64 = 0x0400000;
        const ADDR_LIMIT_32BIT: u64 = 0x0800000;
        const SHORT_INODE: u64 = 0x1000000;
        const WHOLE_SECONDS: u64 = 0x2000000;
        const STICKY_TIMEOUTS: u64 = 0x4000000;
        const ADDR_LIMIT_3GB: u64 = 0x8000000;

        let domain = match self.0.bits() & PER_MASK {
            PER_LINUX => "linux",
            PER_LINUX_32BIT => "linux_32bit",
            PER_LINUX_FDPIC => "linux_fdpic",
            PER_SVR4 => "svr4",
            PER_SVR3 => "svr3",
            PER_SCOSVR3 => "scosvr3",
            PER_OSR5 => "osr5",
            PER_WYSEV386 => "wysev386",
            PER_ISCR4 => "iscr4",
            PER_BSD => "bsd",
            PER_SUNOS => "sunos",
            PER_XENIX => "xenix",
            PER_LINUX32 => "linux32",
            PER_LINUX32_3GB => "linux32_3gb",
            PER_IRIX32 => "irix32",
            PER_IRIXN32 => "irixn32",
            PER_IRIX64 => "irix64",
            PER_RISCOS => "riscos",
            PER_SOLARIS => "solaris",
            PER_UW7 => "uw7",
            PER_OSF4 => "osf4",
            PER_HPUX => "hpux",
            _ => "unknown",
        };

        let flags = [
            (UNAME26, "uname26"),
            (ADDR_NO_RANDOMIZE, "addr-no-randomize"),
            (FDPIC_FUNCPTRS, "fdpic-funcptrs"),
            (MMAP_PAGE_ZERO, "mmap-page-zero"),
            (ADDR_COMPAT_LAYOUT, "addr-compat-layout"),
            (READ_IMPLIES_EXEC, "read-implies-exec"),
            (ADDR_LIMIT_32BIT, "addr-limit-32bit"),
            (SHORT_INODE, "short-inode"),
            (WHOLE_SECONDS, "whole-seconds"),
            (STICKY_TIMEOUTS, "sticky-timeouts"),
            (ADDR_LIMIT_3GB, "addr-limit-3gb"),
        ]
        .iter()
        .filter_map(|&(flag, name)| {
            if self.0.bits() & flag == flag {
                Some(name)
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
        .join(",");

        if flags.is_empty() {
            write!(f, "{domain}")
        } else {
            write!(f, "{domain},{flags}")
        }
    }
}

impl Serialize for SydPersona {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

/// SydMountAttrFlags wraps MountAttrFlags and provides from_name.
pub(crate) struct SydMountAttrFlags(pub(crate) MountAttrFlags);

impl SydMountAttrFlags {
    pub(crate) fn from_name(name: &str) -> Option<Self> {
        match name {
            "ro" => Some(Self(MountAttrFlags::MOUNT_ATTR_RDONLY)),
            "nosuid" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOSUID)),
            "nodev" => Some(Self(MountAttrFlags::MOUNT_ATTR_NODEV)),
            "noexec" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOEXEC)),
            "noatime" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOATIME)),
            "relatime" => Some(Self(MountAttrFlags::empty())), // default
            "strictatime" => Some(Self(MountAttrFlags::MOUNT_ATTR_STRICTATIME)),
            "nodiratime" => Some(Self(MountAttrFlags::MOUNT_ATTR_NODIRATIME)),
            "nosymfollow" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOSYMFOLLOW)),
            _ => None,
        }
    }

    /// Convert MountAttrFlags to a vector of flag names.
    pub(crate) fn to_names(&self) -> Vec<&str> {
        let mut names = Vec::with_capacity(self.0.iter().count());

        if self.0.contains(MountAttrFlags::MOUNT_ATTR_RDONLY) {
            names.push("ro");
        }
        if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOSUID) {
            names.push("nosuid");
        }
        if self.0.contains(MountAttrFlags::MOUNT_ATTR_NODEV) {
            names.push("nodev");
        }
        if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOEXEC) {
            names.push("noexec");
        }
        if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOATIME) {
            names.push("noatime");
        }
        if self.0.contains(MountAttrFlags::MOUNT_ATTR_STRICTATIME) {
            names.push("strictatime");
        } else if self.0.contains(MountAttrFlags::MOUNT_ATTR_NODIRATIME) {
            names.push("nodiratime");
        } else {
            names.push("relatime"); // default
        }
        if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOSYMFOLLOW) {
            names.push("nosymfollow");
        }

        names
    }
}

#[inline]
pub(crate) fn op2name(op: u8) -> &'static str {
    match op {
        0x1 => "socket",
        0x2 => "bind",
        0x3 => "connect",
        0x5 => "accept",
        0x6 => "getsockname",
        0x7 => "getpeername",
        0x8 => "socketpair",
        0x9 => "send",
        0xb => "sendto",
        0xc => "recvfrom",
        0xf => "getsockopt",
        0x10 => "sendmsg",
        0x11 => "recvmsg",
        0x12 => "accept4",
        0x13 => "recvmmsg",
        u8::MAX => "recvmmsg_time64",
        0x14 => "sendmmsg",
        _ => unreachable!("BUG: op2name called with unsupported op:{op:#x}, report a bug!"),
    }
}

pub(crate) fn op2errno(op: u8) -> Errno {
    match op {
        0x2 /*bind*/ => Errno::EADDRNOTAVAIL,
        0x3 /*connect*/=> Errno::ECONNREFUSED,
        0xb | 0x10 | 0x14 /*send{to,{m,}msg}*/ => Errno::ENOTCONN,
        0x5 | 0x12 /*accept{,4}*/ => Errno::ECONNABORTED,
        0x8 /* socketpair */ => Errno::EOPNOTSUPP,
        _ => Errno::EACCES,
    }
}

// errno(3) for setsockopt(2) options.
// Default is to no-op.
pub(crate) fn opt2errno(level: i32, optname: i32) -> i32 {
    match (level, optname) {
        (libc::SOL_SOCKET, libc::SO_DEBUG) => libc::EACCES,
        // SO_BINDTODEVICE + SO_BINDTOIFINDEX
        (libc::SOL_SOCKET, 25) => libc::EPERM,
        (libc::SOL_SOCKET, 62) => libc::EPERM,
        _ => 0,
    }
}

/// Check if pointer is likely valid.
///
/// Returns false for values lower than `MMAP_MIN_ADDR`.
/// Returns false if pointer is not a valid user-space pointer.
pub fn is_valid_ptr(ptr: u64, arch: ScmpArch) -> bool {
    (*MMAP_MIN_ADDR..=limit_kernel_ptr(arch)).contains(&ptr)
}

/// Returns a seccomp(2) argument comparison to identify
/// kernel pointers for the given architecture.
pub fn scmp_kernel_ptr(arch: ScmpArch, arg: u32) -> ScmpArgCompare {
    ScmpArgCompare::new(arg, ScmpCompareOp::Greater, limit_kernel_ptr(arch))
}

// Return the limit of the kernel pointers for the given architecture.
fn limit_kernel_ptr(arch: ScmpArch) -> u64 {
    if arch == ScmpArch::X32 || scmp_arch_bits(arch) == 32 {
        0x0000_0000_ffff_f000
    } else {
        0x7fff_ffff_ffff_ffff
    }
}

/// Checks if the given namespaces are enabled.
pub fn ns_enabled(ns_flags: CloneFlags) -> Result<bool, Errno> {
    // CLONE_SIGHAND|CLONE_V{FORK,M} are not included intentionally.
    const SAFE_CLONE_FLAGS: libc::c_int = libc::CLONE_FS | libc::CLONE_FILES | libc::CLONE_IO;

    // All set, spawn the thread to check unprivileged userns.
    let mut stack = [0u8; crate::config::MINI_STACK_SIZE];
    let pid_fd = safe_clone(
        Box::new(|| -> isize {
            if unshare(ns_flags).is_ok() {
                0
            } else {
                127
            }
        }),
        &mut stack[..],
        SAFE_CLONE_FLAGS,
        Some(libc::SIGCHLD),
    )?;

    loop {
        break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
            Ok(crate::compat::WaitStatus::Exited(_, 0)) => Ok(true),
            Ok(_) => Ok(false),
            Err(Errno::EINTR) => continue,
            Err(errno) => Err(errno),
        };
    }
}

/// Checks if the given LandLock ABI is supported.
/// Returns:
/// - 0: Fully enforced
/// - 1: Partially enforced
/// - 2: Not enforced
/// - 127: Unsupported
pub fn lock_enabled(abi: ABI) -> u8 {
    let path_ro = vec![XPathBuf::from("/")];
    let path_rw = vec![XPathBuf::from("/")];
    // Landlock network is ABI>=4.
    let port_if = if abi as i32 >= ABI::V4 as i32 {
        Some((2525, 22))
    } else {
        None
    };

    // A helper function to wrap the operations and reduce duplication
    fn landlock_operation(
        abi: ABI,
        path_ro: &[XPathBuf],
        path_rw: &[XPathBuf],
        port_if: Option<(u16, u16)>,
    ) -> Result<RestrictionStatus, RulesetError> {
        // from_all includes IoctlDev of ABI >= 5 as necessary.
        let mut ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;
        let ruleset_ref = &mut ruleset;

        let mut network_rules: Vec<Result<NetPort, RulesetError>> = vec![];
        if let Some((port_bind, port_conn)) = port_if {
            ruleset_ref.handle_access(AccessNet::BindTcp)?;
            network_rules.push(Ok(NetPort::new(port_bind, AccessNet::BindTcp)));

            ruleset_ref.handle_access(AccessNet::ConnectTcp)?;
            network_rules.push(Ok(NetPort::new(port_conn, AccessNet::ConnectTcp)));
        }

        // Landlock network is ABI>=6.
        if abi as i32 >= ABI::V6 as i32 {
            ruleset_ref.scope(Scope::AbstractUnixSocket)?;
            ruleset_ref.scope(Scope::Signal)?;
        }

        ruleset
            .create()?
            .add_rules(path_beneath_rules(path_ro, AccessFs::from_read(abi)))?
            .add_rules(path_beneath_rules(path_rw, AccessFs::from_all(abi)))?
            .add_rules(network_rules)?
            .restrict_self(RestrictSelfFlags::empty())
    }

    match landlock_operation(abi, &path_ro, &path_rw, port_if) {
        Ok(status) => match status.ruleset {
            RulesetStatus::FullyEnforced => 0,
            RulesetStatus::PartiallyEnforced => 1,
            RulesetStatus::NotEnforced => 2,
        },
        Err(_) => 127,
    }
}

/// Returns the name of the libsecc☮mp native architecture.
pub(crate) fn seccomp_arch_native_name() -> Option<&'static str> {
    match ScmpArch::native() {
        ScmpArch::X86 => Some("x86"),
        ScmpArch::X8664 => Some("x86_64"),
        ScmpArch::X32 => Some("x32"),
        ScmpArch::Arm => Some("arm"),
        ScmpArch::Aarch64 => Some("aarch64"),
        ScmpArch::Loongarch64 => Some("loongarch64"),
        ScmpArch::M68k => Some("m68k"),
        ScmpArch::Mips => Some("mips"),
        ScmpArch::Mips64 => Some("mips64"),
        ScmpArch::Mips64N32 => Some("mips64n32"),
        ScmpArch::Mipsel => Some("mipsel"),
        ScmpArch::Mipsel64 => Some("mipsel64"),
        ScmpArch::Mipsel64N32 => Some("mipsel64n32"),
        ScmpArch::Ppc => Some("ppc"),
        ScmpArch::Ppc64 => Some("ppc64"),
        ScmpArch::Ppc64Le => Some("ppc64le"),
        ScmpArch::S390 => Some("s390"),
        ScmpArch::S390X => Some("s390x"),
        ScmpArch::Parisc => Some("parisc"),
        ScmpArch::Parisc64 => Some("parisc64"),
        ScmpArch::Riscv64 => Some("riscv64"),
        ScmpArch::Sheb => Some("sheb"),
        ScmpArch::Sh => Some("sh"),
        _ => None,
    }
}

const SECCOMP_ARCH_LIST: &[ScmpArch] = &[
    ScmpArch::X86,
    ScmpArch::X8664,
    ScmpArch::X32,
    ScmpArch::Arm,
    ScmpArch::Aarch64,
    ScmpArch::Loongarch64,
    ScmpArch::M68k,
    ScmpArch::Mips,
    ScmpArch::Mips64,
    ScmpArch::Mips64N32,
    ScmpArch::Mipsel,
    ScmpArch::Mipsel64,
    ScmpArch::Mipsel64N32,
    ScmpArch::Ppc,
    ScmpArch::Ppc64,
    ScmpArch::Ppc64Le,
    ScmpArch::S390,
    ScmpArch::S390X,
    ScmpArch::Parisc,
    ScmpArch::Parisc64,
    ScmpArch::Riscv64,
    ScmpArch::Sheb,
    ScmpArch::Sh,
];

/// Print list of libseccomp's supported architectures
/// Used by `syd --arch list`
pub fn print_seccomp_architectures() {
    let native = ScmpArch::native();
    for arch in SECCOMP_ARCH_LIST {
        let mut repr = format!("{arch:?}").to_ascii_lowercase();
        if repr == "x8664" {
            // Fix potential confusion.
            repr = "x86_64".to_string();
        }
        if *arch == native {
            println!("- {repr} [*]")
        } else {
            println!("- {repr}");
        }
    }
}

// x32 bit for arch-specific syscalls.
pub(crate) const X32_SYSCALL_BIT: i32 = 0x4000_0000;

// List of libseccomp supported architectures for the current system.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64",))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X8664, ScmpArch::X86, ScmpArch::X32];
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32",))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X32, ScmpArch::X86];
#[cfg(target_arch = "x86")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X86];
#[cfg(target_arch = "arm")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Arm];
#[cfg(target_arch = "aarch64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Aarch64, ScmpArch::Arm];
#[cfg(target_arch = "m68k")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::M68k];
#[cfg(all(target_arch = "mips", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips];
#[cfg(all(target_arch = "mips", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mipsel];
#[cfg(all(target_arch = "mips32r6", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips];
#[cfg(all(target_arch = "mips32r6", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mipsel];
#[cfg(all(target_arch = "mips64", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips64, ScmpArch::Mips64N32, ScmpArch::Mips];
#[cfg(all(target_arch = "mips64", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] =
    &[ScmpArch::Mipsel64, ScmpArch::Mipsel64N32, ScmpArch::Mipsel];
#[cfg(all(target_arch = "mips64r6", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips64, ScmpArch::Mips64N32, ScmpArch::Mips];
#[cfg(all(target_arch = "mips64r6", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] =
    &[ScmpArch::Mipsel64, ScmpArch::Mipsel64N32, ScmpArch::Mipsel];
#[cfg(all(target_arch = "powerpc", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc];
#[cfg(all(target_arch = "powerpc64", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc64, ScmpArch::Ppc];
#[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc64Le];
//#[cfg(target_arch = "parisc")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Parisc];
//#[cfg(target_arch = "parisc64")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Parisc64, ScmpArch::Parisc];
#[cfg(target_arch = "riscv64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Riscv64];
#[cfg(target_arch = "s390x")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::S390X, ScmpArch::S390];
#[cfg(target_arch = "loongarch64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Loongarch64];

/// Add all supported architectures to the given filter.
pub fn seccomp_add_architectures(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    // Add architectures based on the current architecture
    for arch in SCMP_ARCH {
        seccomp_add_arch(ctx, *arch)?;
    }
    Ok(())
}

fn seccomp_add_arch(ctx: &mut ScmpFilterContext, arch: ScmpArch) -> SydResult<()> {
    Ok(ctx.add_arch(arch).map(drop)?)
}

/// Check if arch is 64-bit or 32-bit.
#[inline]
pub const fn scmp_arch_bits(arch: ScmpArch) -> usize {
    match arch {
        ScmpArch::X8664
        | ScmpArch::X32
        | ScmpArch::Aarch64
        | ScmpArch::Loongarch64
        | ScmpArch::Mips64
        | ScmpArch::Mips64N32
        | ScmpArch::Mipsel64
        | ScmpArch::Mipsel64N32
        | ScmpArch::Ppc64
        | ScmpArch::Ppc64Le
        | ScmpArch::Parisc64
        | ScmpArch::Riscv64
        | ScmpArch::S390X => 64,
        ScmpArch::X86
        | ScmpArch::Arm
        | ScmpArch::M68k
        | ScmpArch::Mips
        | ScmpArch::Mipsel
        | ScmpArch::Ppc
        | ScmpArch::Parisc
        | ScmpArch::S390
        | ScmpArch::Sheb
        | ScmpArch::Sh => 32,
        _ => 64, // sane default for non-exhaustive enum.
    }
}

/// Helper function to determine if the architecture is big-endian.
#[inline]
pub fn scmp_big_endian(arch: ScmpArch) -> bool {
    matches!(
        arch,
        ScmpArch::Mips
            | ScmpArch::Mips64
            | ScmpArch::Ppc
            | ScmpArch::Ppc64
            | ScmpArch::S390
            | ScmpArch::S390X
            | ScmpArch::Parisc
            | ScmpArch::Parisc64
    )
}

/// Represents seccomp notify data.
/// We redefine this because libseccomp struct is non-exhaustive.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ScmpNotifData {
    pub(crate) syscall: ScmpSyscall,
    pub(crate) arch: ScmpArch,
    pub(crate) instr_pointer: u64,
    pub(crate) args: [u64; 6],
}

/// Represents a seccomp notify request.
/// We redefine this because libseccomp struct is non-exhaustive.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScmpNotifReq {
    pub(crate) id: u64,
    pub(crate) pid: u32,
    pub(crate) flags: u32,
    pub(crate) data: ScmpNotifData,
}

impl ScmpNotifData {
    fn from_sys(data: seccomp_data) -> Result<Self, Errno> {
        Ok(Self {
            syscall: ScmpSyscall::from(data.nr),
            arch: scmp_arch(data.arch)?,
            instr_pointer: data.instruction_pointer,
            args: data.args,
        })
    }
}

impl ScmpNotifReq {
    pub(crate) fn from_sys(req: seccomp_notif) -> Result<Self, Errno> {
        Ok(Self {
            id: req.id,
            pid: req.pid,
            flags: req.flags,
            data: ScmpNotifData::from_sys(req.data)?,
        })
    }

    #[inline(always)]
    pub(crate) fn pid(&self) -> Pid {
        #[expect(clippy::cast_possible_wrap)]
        Pid::from_raw(self.pid as libc::pid_t)
    }
}

/// Helper function to convert raw arch value to ScmpArch.
///
/// We need this because ScmpArch::from_sys is not imported.
pub const fn scmp_arch(arch: u32) -> Result<ScmpArch, Errno> {
    match arch {
        libseccomp_sys::SCMP_ARCH_NATIVE => Ok(ScmpArch::Native),
        libseccomp_sys::SCMP_ARCH_X86 => Ok(ScmpArch::X86),
        libseccomp_sys::SCMP_ARCH_X86_64 => Ok(ScmpArch::X8664),
        libseccomp_sys::SCMP_ARCH_X32 => Ok(ScmpArch::X32),
        libseccomp_sys::SCMP_ARCH_ARM => Ok(ScmpArch::Arm),
        libseccomp_sys::SCMP_ARCH_AARCH64 => Ok(ScmpArch::Aarch64),
        libseccomp_sys::SCMP_ARCH_LOONGARCH64 => Ok(ScmpArch::Loongarch64),
        libseccomp_sys::SCMP_ARCH_M68K => Ok(ScmpArch::M68k),
        libseccomp_sys::SCMP_ARCH_MIPS => Ok(ScmpArch::Mips),
        libseccomp_sys::SCMP_ARCH_MIPS64 => Ok(ScmpArch::Mips64),
        libseccomp_sys::SCMP_ARCH_MIPS64N32 => Ok(ScmpArch::Mips64N32),
        libseccomp_sys::SCMP_ARCH_MIPSEL => Ok(ScmpArch::Mipsel),
        libseccomp_sys::SCMP_ARCH_MIPSEL64 => Ok(ScmpArch::Mipsel64),
        libseccomp_sys::SCMP_ARCH_MIPSEL64N32 => Ok(ScmpArch::Mipsel64N32),
        libseccomp_sys::SCMP_ARCH_PPC => Ok(ScmpArch::Ppc),
        libseccomp_sys::SCMP_ARCH_PPC64 => Ok(ScmpArch::Ppc64),
        libseccomp_sys::SCMP_ARCH_PPC64LE => Ok(ScmpArch::Ppc64Le),
        libseccomp_sys::SCMP_ARCH_S390 => Ok(ScmpArch::S390),
        libseccomp_sys::SCMP_ARCH_S390X => Ok(ScmpArch::S390X),
        libseccomp_sys::SCMP_ARCH_PARISC => Ok(ScmpArch::Parisc),
        libseccomp_sys::SCMP_ARCH_PARISC64 => Ok(ScmpArch::Parisc64),
        libseccomp_sys::SCMP_ARCH_RISCV64 => Ok(ScmpArch::Riscv64),
        libseccomp_sys::SCMP_ARCH_SHEB => Ok(ScmpArch::Sheb),
        libseccomp_sys::SCMP_ARCH_SH => Ok(ScmpArch::Sh),
        _ => Err(Errno::ENOSYS),
    }
}

/// Helper function to convert ScmpArch to raw arch values.
///
/// We need this because ScmpArch::from_sys is not imported.
/// This function panics on invalid/unsupported architecture.
pub const fn scmp_arch_raw(arch: ScmpArch) -> u32 {
    match arch {
        ScmpArch::Native => libseccomp_sys::SCMP_ARCH_NATIVE,
        ScmpArch::X86 => libseccomp_sys::SCMP_ARCH_X86,
        ScmpArch::X8664 => libseccomp_sys::SCMP_ARCH_X86_64,
        ScmpArch::X32 => libseccomp_sys::SCMP_ARCH_X32,
        ScmpArch::Arm => libseccomp_sys::SCMP_ARCH_ARM,
        ScmpArch::Aarch64 => libseccomp_sys::SCMP_ARCH_AARCH64,
        ScmpArch::Loongarch64 => libseccomp_sys::SCMP_ARCH_LOONGARCH64,
        ScmpArch::M68k => libseccomp_sys::SCMP_ARCH_M68K,
        ScmpArch::Mips => libseccomp_sys::SCMP_ARCH_MIPS,
        ScmpArch::Mips64 => libseccomp_sys::SCMP_ARCH_MIPS64,
        ScmpArch::Mips64N32 => libseccomp_sys::SCMP_ARCH_MIPS64N32,
        ScmpArch::Mipsel => libseccomp_sys::SCMP_ARCH_MIPSEL,
        ScmpArch::Mipsel64 => libseccomp_sys::SCMP_ARCH_MIPSEL64,
        ScmpArch::Mipsel64N32 => libseccomp_sys::SCMP_ARCH_MIPSEL64N32,
        ScmpArch::Ppc => libseccomp_sys::SCMP_ARCH_PPC,
        ScmpArch::Ppc64 => libseccomp_sys::SCMP_ARCH_PPC64,
        ScmpArch::Ppc64Le => libseccomp_sys::SCMP_ARCH_PPC64LE,
        ScmpArch::S390 => libseccomp_sys::SCMP_ARCH_S390,
        ScmpArch::S390X => libseccomp_sys::SCMP_ARCH_S390X,
        ScmpArch::Parisc => libseccomp_sys::SCMP_ARCH_PARISC,
        ScmpArch::Parisc64 => libseccomp_sys::SCMP_ARCH_PARISC64,
        ScmpArch::Riscv64 => libseccomp_sys::SCMP_ARCH_RISCV64,
        ScmpArch::Sheb => libseccomp_sys::SCMP_ARCH_SHEB,
        ScmpArch::Sh => libseccomp_sys::SCMP_ARCH_SH,
        _ => unreachable!(),
    }
}

/// Confine creation of the given file type using mknod(2) and mknodat(2).
pub(crate) fn scmp_add_mknod(
    ctx: &mut ScmpFilterContext,
    action: ScmpAction,
    f_type: FileType,
) -> SydResult<()> {
    const S_IFMT: u64 = libc::S_IFMT as u64;
    let f_type = u64::from(f_type.mode().ok_or(Errno::EINVAL)?);

    let sysname = "mknod";
    if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
        ctx.add_rule_conditional(action, syscall, &[scmp_cmp!($arg1 & S_IFMT == f_type)])?;
    } else {
        info!("ctx": "confine", "op": "deny_syscall",
            "msg": format!("invalid or unsupported syscall {sysname}"));
    }

    let sysname = "mknodat";
    if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
        ctx.add_rule_conditional(action, syscall, &[scmp_cmp!($arg2 & S_IFMT == f_type)])?;
    } else {
        info!("ctx": "confine", "op": "deny_syscall",
            "msg": format!("invalid or unsupported syscall {sysname}"));
    }

    Ok(())
}

/// Add UID/GID change rules for SafeSetId.
#[expect(clippy::cognitive_complexity)]
pub(crate) fn scmp_add_setid_rules(
    tag: &str,
    ctx: &mut ScmpFilterContext,
    safe_setuid: bool,
    safe_setgid: bool,
    transit_uids: &[(Uid, Uid)],
    transit_gids: &[(Gid, Gid)],
) -> SydResult<()> {
    const NULL_ID: u64 = u64::MAX;
    let op_a = format!("allow_{tag}_syscall");
    let op_f = format!("filter_{tag}_syscall");

    // SAFETY: Signal system calls are necessary to handle reserved signals.
    for sysname in ["sigreturn", "rt_sigreturn"] {
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                ctx.add_rule(ScmpAction::Allow, syscall)?;
            }
            Err(_) => {
                info!("ctx": "confine", "op": &op_a,
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }
    }

    // SAFETY: Only allow defined UID transitions.
    if safe_setuid {
        let source_uid = Uid::current();

        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": "confine", "op": &op_f,
                    "msg": format!("invalid or unsupported syscall {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": "confine", "op": &op_f,
                    "msg": format!("invalid or unsupported syscall {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": "confine", "op": &op_f,
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }
    }

    // SAFETY: Only allow defined GID transitions.
    if safe_setgid {
        let source_gid = Gid::current();

        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": "confine", "op": &op_f,
                    "msg": format!("invalid or unsupported syscall {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": "confine", "op": &op_f,
                    "msg": format!("invalid or unsupported syscall {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": "confine", "op": &op_f,
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }
    }

    Ok(())
}

/// CLONE_NEWTIME constant to create time namespaces.
pub const CLONE_NEWTIME: CloneFlags = CloneFlags::from_bits_retain(128);

pub(crate) const NAMESPACE_FLAGS: &[libc::c_int] = &[
    libc::CLONE_NEWNS,
    libc::CLONE_NEWIPC,
    libc::CLONE_NEWNET,
    libc::CLONE_NEWPID,
    libc::CLONE_NEWUTS,
    libc::CLONE_NEWUSER,
    libc::CLONE_NEWCGROUP,
    CLONE_NEWTIME.bits(),
];

pub(crate) const NAMESPACE_FLAGS_ALL: libc::c_int = libc::CLONE_NEWNS
    | libc::CLONE_NEWIPC
    | libc::CLONE_NEWNET
    | libc::CLONE_NEWPID
    | libc::CLONE_NEWUTS
    | libc::CLONE_NEWUSER
    | libc::CLONE_NEWCGROUP
    | CLONE_NEWTIME.bits();

pub(crate) const NAMESPACE_NAMES: &[&str] = &[
    "user", "mount", "ipc", "net", "pid", "uts", "cgroup", "time",
];

/// Convert a CLONE namespace flag to its String representation.
pub fn nsflag_name(flag: libc::c_int) -> String {
    match flag {
        libc::CLONE_NEWNS => "mount",
        libc::CLONE_NEWIPC => "ipc",
        libc::CLONE_NEWNET => "net",
        libc::CLONE_NEWPID => "pid",
        libc::CLONE_NEWUTS => "uts",
        libc::CLONE_NEWUSER => "user",
        libc::CLONE_NEWCGROUP => "cgroup",
        n if n == CLONE_NEWTIME.bits() => "time",
        _ => "?",
    }
    .to_string()
}

/// Check for CONFIG_CROSS_MEMORY_ATTACH support.
pub fn check_cross_memory_attach() -> bool {
    // SAFETY: We explicitly call the system call with
    // invalid arguments just to check for host Linux
    // kernel support.
    !matches!(
        Errno::result(unsafe {
            libc::process_vm_readv(0, std::ptr::null(), 0, std::ptr::null(), 0, 0)
        }),
        Err(Errno::ENOSYS)
    )
}

/// Check whether getrandom(2) is in VDSO.
pub fn check_vdso_has_getrandom() -> bool {
    has_vdso_symbol(c"__vdso_getrandom")
}

/// Enumerate vDSO call names present in this process by probing the already-mapped vDSO.
///
/// Uses dlopen(3) with RTLD_NOLOAD and dlsym(3).
pub fn vdso_list_calls() -> Result<Vec<&'static CStr>, libloading::Error> {
    const KERN_LEN: usize = 9; // "__kernel_"
    const VDSO_LEN: usize = 7; // "__vdso_"
    const VDSO_IDX: usize = 10; // index into array.
    const VDSO_CALL_NAMES: &[&CStr] = &[
        c"__kernel_clock_getres",
        c"__kernel_clock_getres_time64",
        c"__kernel_clock_gettime",
        c"__kernel_clock_gettime64",
        c"__kernel_getcpu",
        c"__kernel_getrandom",
        c"__kernel_gettimeofday",
        c"__kernel_get_tbfreq",    // powerpc
        c"__kernel_riscv_hwprobe", // riscv
        c"__kernel_time",
        c"__vdso_clock_getres",
        c"__vdso_clock_getres_time64",
        c"__vdso_clock_gettime",
        c"__vdso_clock_gettime64",
        c"__vdso_getcpu",
        c"__vdso_getrandom",
        c"__vdso_gettimeofday",
        c"__vdso_get_tbfreq",    // powerpc
        c"__vdso_riscv_hwprobe", // riscv
        c"__vdso_time",
    ];

    let vdso = vdso_open()?;
    let mut out = Vec::with_capacity(VDSO_CALL_NAMES.len());
    for (idx, sym) in VDSO_CALL_NAMES.iter().enumerate() {
        let sym = sym.to_bytes_with_nul();

        // SAFETY: We perform a pure lookup with a NUL-terminated name;
        // no call or dereference occurs.
        if unsafe { vdso.get::<*const ()>(sym) }.is_ok() {
            let plen = if idx < VDSO_IDX { KERN_LEN } else { VDSO_LEN };
            // SAFETY:
            // 1. All entries start with "__vdso_"
            // 2. We keep the trailing NUL, producing a valid CStr
            //    that borrows from the static symbol literal.
            out.push(unsafe { CStr::from_bytes_with_nul_unchecked(&sym[plen..]) });
        }
    }
    Ok(out)
}

/// Probe vDSO for symbol WITHOUT loading anything.
///
/// Returns true if the vDSO object exports the symbol.
pub fn has_vdso_symbol(sym: &CStr) -> bool {
    let vdso = if let Ok(vdso) = vdso_open() {
        vdso
    } else {
        return false;
    };

    // SAFETY: We perform a pure lookup with a NUL-terminated name;
    // no call or dereference occurs.
    unsafe { vdso.get::<*const ()>(sym.to_bytes_with_nul()).is_ok() }
}

/// Open vDSO and return the Library instance.
///
/// Attempts to load `linux-vdso.so.1` first, and falls back to `linux-vdso64.so.1` (ppc64).
pub fn vdso_open() -> Result<Library, LibraryError> {
    match vdso_open_by_name("linux-vdso.so.1") {
        Ok(lib) => Ok(lib),
        Err(LibraryError::DlOpen { .. }) => vdso_open_by_name("linux-vdso64.so.1"),
        Err(error) => Err(error),
    }
}

/// Open vDSO by name and return the Library instance.
pub fn vdso_open_by_name(name: &str) -> Result<Library, LibraryError> {
    // SAFETY: Library::open with RTLD_NOLOAD only obtains a handle
    // to the already-mapped vDSO; it does not load code.
    unsafe { Library::open(Some(name), RTLD_NOLOAD | RTLD_LOCAL | RTLD_NOW) }
}

/// Check support for CONFIG_UNIX_DIAG in Linux kernel.
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn check_unix_diag() -> Result<bool, Errno> {
    const SOCK_DIAG_BY_FAMILY: u16 = 20;
    const NL_HDR_LEN: usize = 16;
    const UD_REQ_LEN: usize = 24;

    let nlmsg_done: u16 = libc::NLMSG_DONE as u16;
    let nlmsg_error: u16 = libc::NLMSG_ERROR as u16;

    // Open NETLINK_SOCK_DIAG.
    let nl = match safe_socket(
        libc::AF_NETLINK,
        libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
        libc::NETLINK_SOCK_DIAG,
    ) {
        Ok(fd) => fd,
        Err(Errno::EPROTONOSUPPORT | Errno::EAFNOSUPPORT | Errno::ENOTSUP) => return Ok(false),
        Err(errno) => return Err(errno),
    };

    // Build AF_UNIX dump request; match all states, no exact lookup.
    let total_len = (NL_HDR_LEN + UD_REQ_LEN) as u32;
    let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];
    let mut p = 0usize;

    // nlmsghdr
    req[p..p + 4].copy_from_slice(&total_len.to_ne_bytes());
    p += 4;
    req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes());
    p += 2;
    let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_DUMP) as u16; // ROOT|MATCH
    req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes());
    p += 2;
    req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes());
    p += 4; // seq
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // pid

    // unix_diag_req
    req[p] = libc::AF_UNIX as u8;
    p += 1; // sdiag_family
    req[p] = 0;
    p += 1; // sdiag_protocol
    req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
    p += 2; // pad
    req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
    p += 4; // udiag_states = all
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // udiag_ino = 0 (dump)
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // udiag_show = none
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // cookie[0]
    req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
    p += 4; // cookie[1]
    assert_eq!(p, req.len());

    // Send
    let mut off = 0;
    while off < req.len() {
        let n = retry_on_eintr(|| write(&nl, &req[off..]))?;
        if n == 0 {
            return Err(Errno::EIO);
        }
        off += n;
    }

    // Classify first reply: ENOENT => no handler; anything else => handler exists.
    let mut rbuf = [0u8; 8192];
    loop {
        let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
        if n == 0 {
            return Err(Errno::EIO);
        }

        let mut pos = 0usize;
        while pos + NL_HDR_LEN <= n {
            let nlmsg_len = {
                let b: [u8; 4] = rbuf[pos..pos + 4].try_into().or(Err(Errno::EOVERFLOW))?;
                u32::from_ne_bytes(b) as usize
            };
            if nlmsg_len == 0 || pos + nlmsg_len > n {
                return Err(Errno::EIO);
            }
            let nlmsg_type = {
                let b: [u8; 2] = rbuf[pos + 4..pos + 6]
                    .try_into()
                    .or(Err(Errno::EOVERFLOW))?;
                u16::from_ne_bytes(b)
            };

            if nlmsg_type == nlmsg_error {
                if nlmsg_len < NL_HDR_LEN + 4 {
                    return Err(Errno::EIO);
                }
                let b: [u8; 4] = rbuf[pos + NL_HDR_LEN..pos + NL_HDR_LEN + 4]
                    .try_into()
                    .or(Err(Errno::EOVERFLOW))?;
                let neg = i32::from_ne_bytes(b);

                if neg == 0 {
                    return Ok(true);
                } // ACK
                if neg == -libc::ENOENT {
                    return Ok(false);
                } // no handler
                  // Any other error => handler exists but rejected the request.
                return Ok(true);
            }

            if nlmsg_type == SOCK_DIAG_BY_FAMILY || nlmsg_type == nlmsg_done {
                return Ok(true); // got data or DONE => handler present.
            }

            pos = nlmsg_align(pos + nlmsg_len);
        }
    }
}

/// Probes the process link-map (RTLD_DEFAULT)
/// for a symbol by name using dlsym(3).
pub fn has_symbol(sym: &CStr) -> bool {
    // SAFETY:
    // 1. Library::this() yields a handle to
    //    the current process namespace (RTLD_DEFAULT),
    //    loads nothing new, and the handle is used only
    //    within this function.
    // 2. We perform a pure lookup with a NUL-terminated name;
    //    no call or dereference occurs.
    unsafe { Library::this().get::<*const ()>(sym.to_bytes_with_nul()) }.is_ok()
}

/// Check for file descriptor leaks above the standard input, output, and error.
///
/// This function examines the `/proc/self/fd` directory to identify
/// open file descriptors. It prints any open file descriptors other
/// than the standard input (0), output (1), and error (2), indicating
/// potential resource leaks.
///
/// # Parameters
/// - `fd_max`: An optional parameter that sets a maximum file
///   descriptor number to check. If not specified, only the standard
///   file descriptors are considered normal.
///
/// # Returns
/// Returns `true` if leaks are found, otherwise `false`.
pub fn check_fd_leaks(fd_max: Option<RawFd>) -> u32 {
    let proc_fd_path = Path::new("/proc/self/fd");
    let mut dir = match Dir::open(proc_fd_path, OFlag::O_RDONLY, Mode::empty()) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("Failed to open /proc/self/fd: {e}");
            return u32::MAX;
        }
    };

    let mut leaks_found: u32 = 0;
    let dir_fd = dir.as_raw_fd();
    let fd_limit = fd_max.unwrap_or(2); // Default limit only std fds

    for entry in dir.iter() {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };

        let fd_str = entry.file_name().to_string_lossy(); // Use lossy conversion
        let fd = match fd_str.parse::<RawFd>() {
            Ok(fd) => fd,
            Err(_) => continue,
        };

        // Ignore standard file descriptors and the directory stream FD itself
        if fd <= fd_limit || fd == dir_fd {
            continue;
        }

        // Create a PathBuf from the string representation of the file descriptor
        let link_path = proc_fd_path.join(fd_str.into_owned()); // Convert Cow<str> into a String and then into a PathBuf

        #[expect(clippy::disallowed_methods)]
        match std::fs::read_link(&link_path) {
            Ok(target_path) => {
                eprintln!("!!! Leaked file descriptor {fd} -> {target_path:?} !!!");
                leaks_found = leaks_found.saturating_add(1);
            }
            Err(error) => {
                eprintln!("Failed to read link for FD {fd}: {error}");
            }
        }
    }

    leaks_found
}

/// Print list of file descriptors to standard error.
pub fn list_fds(pid: Option<Pid>) {
    let mut path = match pid {
        Some(pid) => XPathBuf::from(format!("/proc/{}/fd", pid.as_raw())),
        None => XPathBuf::from("/proc/self/fd"),
    };

    let mut dir = match Dir::open(&path, OFlag::O_RDONLY, Mode::empty()) {
        Ok(dir) => dir,
        Err(errno) => {
            eprintln!("list_fds: Failed to open {path}: {errno}");
            return;
        }
    };

    // Header
    eprintln!(
        "list_fds: {}",
        pid.map(|p| p.as_raw().to_string())
            .unwrap_or_else(|| "self".to_string())
    );
    eprintln!("fd\ttarget");

    let dfd = dir.as_raw_fd();
    for entry in dir.iter() {
        let entry = match entry {
            Ok(entry) => entry,
            Err(_) => continue,
        };

        let fd = match btoi::<RawFd>(entry.file_name().to_bytes()) {
            Ok(fd) => fd,
            Err(_) => continue,
        };

        // Skip our dir FD.
        if fd == dfd {
            continue;
        }

        path.push_fd(fd);
        match readlinkat(AT_BADFD, &path) {
            Ok(target) => eprintln!("{fd}\t{target}"),
            Err(errno) => eprintln!("{fd}\t!!! {errno}"),
        }
        path.pop();
    }
}

/// Extends the ioctl value if necessary.
///
/// In musl, ioctl is defined as:
/// `int ioctl(int fd, int req, ...);`
///
/// In glibc, ioctl is defined as:
/// `int ioctl(int fd, unsigned long request, ...);`
///
/// This difference can cause issues when handling ioctl values that are
/// larger than what a signed 32-bit integer can represent.
/// Specifically, values with the high bit set (0x80000000) or the next
/// highest bit set (0x40000000) can be interpreted differently
/// depending on the implementation.
///
/// In a 32-bit signed integer, the high bit (0x80000000) is used as the
/// sign bit, indicating whether the number is positive or negative. If
/// this bit is set, the number is interpreted as negative. The next
/// highest bit (0x40000000) is the largest value that a signed 32-bit
/// integer can represent without becoming negative.
///
/// Therefore, ioctl values that have either of these bits set can cause
/// compatibility issues between musl and glibc. To ensure
/// compatibility, we need to extend such ioctl values to 64 bits by
/// prefixing them with `0xffffffff`, converting them to their unsigned
/// representation.
///
/// # Arguments
///
/// * `value` - The original ioctl value.
///
/// # Returns
///
/// * `Some(extended_value)` - If the value requires extension.
/// * `None` - If the value does not require extension.
#[inline]
pub fn extend_ioctl(value: u64) -> Option<u64> {
    // Check if the high bit (0x80000000) or the next highest bit
    // (0x40000000) is set.  These bits can cause the value to be
    // interpreted as a negative number in a signed 32-bit context.
    if (value & 0x80000000 == 0x80000000) || (value & 0x40000000 == 0x40000000) {
        // If the value requires extension, return the extended value by
        // prefixing with `0xffffffff`.
        Some(0xffffffff00000000 | value)
    } else {
        // If the value does not require extension, return None.
        None
    }
}

/// Drop a Capability from the Effective, Ambient, Inheritable and Permitted capsets.
pub fn safe_drop_cap(cap: caps::Capability) -> Result<(), caps::errors::CapsError> {
    caps::drop(None, caps::CapSet::Effective, cap)?;
    caps::drop(None, caps::CapSet::Ambient, cap)?;
    caps::drop(None, caps::CapSet::Inheritable, cap)?;
    caps::drop(None, caps::CapSet::Permitted, cap)
}

/// Return true if the given signal has default action Core.
#[inline]
#[expect(unreachable_patterns)]
pub(crate) fn is_coredump(sig: i32) -> bool {
    matches!(
        sig,
        libc::SIGABRT
            | libc::SIGBUS
            | libc::SIGFPE
            | libc::SIGILL
            | libc::SIGIOT
            | libc::SIGKILL
            | libc::SIGQUIT
            | libc::SIGSEGV
            | libc::SIGSYS
            | libc::SIGTRAP
            | libc::SIGXCPU
            | libc::SIGXFSZ
    )
}

/// Returns `Ok(true)` if SELinux is enabled.
#[expect(clippy::disallowed_methods)]
pub fn selinux_enabled() -> Result<bool, Errno> {
    let data = read_to_string("/proc/thread-self/mounts").map_err(|err| err2no(&err))?;
    let data = XPath::from_bytes(data.as_bytes());
    Ok(data.contains(b"selinux"))
}

/// Returns `Ok(true)` if AppArmor is enabled.
#[expect(clippy::disallowed_methods)]
pub fn apparmor_enabled() -> Result<bool, Errno> {
    if !exists("/sys/kernel/security/apparmor").map_err(|err| err2no(&err))? {
        return Ok(false);
    }
    let data =
        read_to_string("/sys/module/apparmor/parameters/enabled").map_err(|err| err2no(&err))?;
    Ok(data.as_bytes().first() == Some(&b'Y'))
}

/// Returns `Ok(true)` if SELinux is enforced.
#[expect(clippy::disallowed_methods)]
pub fn selinux_enforced() -> Result<bool, Errno> {
    let path = if exists("/sys/fs/selinux").map_err(|err| err2no(&err))? {
        "/sys/fs/selinux/enforce"
    } else if exists("/selinux").map_err(|err| err2no(&err))? {
        "/selinux/enforce"
    } else {
        return Ok(false);
    };
    let data: u8 = read_to_string(path)
        .map_err(|err| err2no(&err))?
        .parse()
        .or(Err(Errno::EINVAL))?;
    Ok(data != 0)
}

/// 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),
        }
    }
}

impl ExportMode {
    /// Return the export mode specified by the environment.
    #[expect(clippy::disallowed_methods)]
    pub fn from_env() -> Option<ExportMode> {
        Self::from_str(&std::env::var(crate::config::ENV_DUMP_SCMP).ok()?).ok()
    }
}

#[cfg(target_arch = "x86")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    std::arch::asm!(
        "mov eax, 0x2", // 0x2 is the syscall number for fork on x86
        "int 0x80",     // Interrupt to make the syscall
        out("eax") _,
    );
}

#[cfg(target_arch = "x86_64")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    // Inline assembly for x86-64
    std::arch::asm!(
        "mov rax, 57", // 57 is the syscall number for fork on x86-64
        "syscall",
        out("rax") _,
    );
}

#[cfg(target_arch = "aarch64")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    std::arch::asm!(
        "mov x0, 17",  // SIGCHLD
        "mov x1, 0",   // child_stack (null, not recommended)
        "mov x8, 220", // syscall number for clone
        "svc 0",
        options(nostack),
    );
}

#[cfg(target_arch = "arm")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    std::arch::asm!(
        "mov r7, #2", // 2 is the syscall number for fork on ARM
        "swi #0",     // Software interrupt to make the syscall
        out("r0") _,
        options(nostack),
    );
}

/*
 * error[E0658]: inline assembly is not stable yet on this architecture
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    std::arch::asm!(
        "li 0, 2",     // Load immediate 2 into register r0 (syscall number for fork)
        "sc",          // System call
        out("r3") _,   // Output from r3 (return value of fork)
    );
}
*/

#[cfg(target_arch = "riscv64")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    std::arch::asm!(
        "li a7, 220",  // syscall number for clone on riscv64
        "li a0, 17",   // SIGCHLD
        "li a1, 0",    // child_stack (null, not recommended)
        "ecall",       // make the syscall
        out("a0") _,   // store return value in a0
        options(nostack),
    );
}

/*
 * error[E0658]: inline assembly is not stable yet on this architecture
#[cfg(any(target_arch = "s390x"))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    std::arch::asm!(
        "lgr %r1, 2", // Load syscall number for fork (2) directly into %r1.
        "svc 0",      // Supervisor Call to invoke the syscall.
    );
}
*/

#[cfg(any(
    target_arch = "powerpc",
    target_arch = "powerpc64",
    target_arch = "s390x"
))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    let _ = libc::syscall(libc::SYS_fork);
}

#[cfg(not(any(
    target_arch = "aarch64",
    target_arch = "arm",
    target_arch = "powerpc",
    target_arch = "powerpc64",
    target_arch = "riscv64",
    target_arch = "riscv64",
    target_arch = "s390x",
    target_arch = "x86",
    target_arch = "x86_64",
)))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    let _ = libc::fork();
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_valid_ptr_64() {
        let arch = ScmpArch::X8664;
        assert!(!is_valid_ptr(0, arch));
        assert!(is_valid_ptr(0x7fff_ffff_ffff, arch));
        assert!(is_valid_ptr(0x7fff_ffff_ffff_ffff, arch));
        assert!(!is_valid_ptr(0x8000_0000_0000_0000, arch));
        assert!(!is_valid_ptr(u64::MAX, arch));
    }

    #[test]
    fn test_is_valid_ptr_x32() {
        let arch = ScmpArch::X32;
        assert!(!is_valid_ptr(0, arch));
        assert!(!is_valid_ptr(0xffff_ffff, arch));
        assert!(!is_valid_ptr(0x1_0000_0000, arch));
        assert!(!is_valid_ptr(u64::MAX, arch));
    }

    #[test]
    fn test_is_valid_ptr_x86() {
        let arch = ScmpArch::X86;
        assert!(!is_valid_ptr(0, arch));
        assert!(is_valid_ptr(0xbfff_ffff, arch));
        assert!(is_valid_ptr(0xc000_0000, arch));
        assert!(!is_valid_ptr(0xffff_ffff, arch));
        assert!(!is_valid_ptr(0x1_0000_0000, arch));
        assert!(!is_valid_ptr(u64::MAX, arch));
    }

    #[test]
    fn test_is_valid_ptr_mips() {
        let arch = ScmpArch::Mips;
        assert!(!is_valid_ptr(0, arch));
        assert!(is_valid_ptr(0x7fff_ffff, arch));
        assert!(is_valid_ptr(0x8000_0000, arch));
        assert!(!is_valid_ptr(0xffff_ffff, arch));
        assert!(!is_valid_ptr(0x1_0000_0000, arch));
        assert!(!is_valid_ptr(u64::MAX, arch));
    }

    #[test]
    fn test_extend_ioctl() {
        const IOCTLS: &[(u64, bool)] = &[
            (0x5451, false),
            (0x5450, false),
            (0x541B, false),
            (0x5421, false),
            (0x5452, false),
            (0x4B66, false),
            (0x5401, false),
            (0x5402, false),
            (0x5403, false),
            (0x5404, false),
            (0x5405, false),
            (0x5406, false),
            (0x5407, false),
            (0x5408, false),
            (0x5456, false),
            (0x5457, false),
            (0x5413, false),
            (0x5414, false),
            (0x5409, false),
            (0x5425, false),
            (0x5427, false),
            (0x5428, false),
            (0x540A, false),
            (0x5411, false),
            (0x540B, false),
            (0x80045430, true),
            (0x80045432, true),
            (0x5432, false),
            (0x5433, false),
            (0x5434, false),
            (0x5435, false),
            (0x40045436, true),
            (0x5437, false),
            (0x80045438, true),
            (0x80045439, true),
            (0x80045440, true),
            (0x5441, false),
            (0x540E, false),
            (0x540F, false),
            (0x5410, false),
            (0x5429, false),
            (0x540C, false),
            (0x80045440, true),
            (0x540D, false),
            (0x5424, false),
            (0x5423, false),
            (0x5420, false),
            (0x80045438, true),
            (0x40045431, true),
            (0x80045439, true),
            (0x5441, false),
            (0x80086601, true),
            (0x5419, false),
            (0x541A, false),
            (0x8910, false),
            (0x8912, false),
            (0x8913, false),
            (0x8915, false),
            (0x8917, false),
            (0x8919, false),
            (0x891b, false),
            (0x891d, false),
            (0x891f, false),
            (0x892, false),
            (0x8925, false),
            (0x8927, false),
            (0x8929, false),
            (0x8933, false),
            (0x8935, false),
            (0x8938, false),
            (0x8940, false),
            (0x8942, false),
            (0x8947, false),
            (0x8948, false),
            (0x894C, false),
            (0x2400, false),
            (0x2401, false),
            (0x2402, false),
            (0x2403, false),
            (0x2405, false),
            (0x40082404, true),
            (0x40082406, true),
            (0x80082407, true),
            (0x40042408, true),
            (0x40042409, true),
            (0xc008240a, true),
            (0x4008240b, true),
        ];

        for (request, extend) in IOCTLS.iter() {
            if *extend {
                assert!(
                    extend_ioctl(*request).is_some(),
                    "OOPS: {request}->{extend}"
                );
            } else {
                assert!(
                    extend_ioctl(*request).is_none(),
                    "OOPS: {request}->{extend}"
                );
            }
        }
    }
}
