Skip to content

Linux Seccomp与Namespace

追踪 Bubblewrap namespace、inner helper验证、Seccomp BPF、proxy bridge、signal转发与child收尾。

基于rust-v0.150.0
CodexRustSecurityLinuxSeccomp

Linux Seccomp与Namespace ​

Linux sandbox 不是一次 syscall 就完成的。Bubblewrap outer stage 先建立 filesystem、user、PID、IPC 和可选 network namespace;inner helper 在新的 filesystem view 内验证 descriptor-backed mounts 与 capabilities,再安装 no_new_privs 和 Seccomp,最后 fork/exec 用户命令。父 helper 留在 namespace 中负责信号转发、child 回收和退出状态映射。

网络限制也分两层。Isolated 与 ProxyOnly 都通过 Bubblewrap --unshare-net 脱离 host network namespace;Restricted Seccomp 随后只保留 AF_UNIX IPC,ProxyRouted Seccomp 则允许 AF_INET/AF_INET6 去访问隔离 namespace 内的本地 bridge,同时禁止可连接 host Unix socket 的 socket(AF_UNIX)。因此“允许 IP socket”并不等于恢复 host network。

本文承接Linux Landlock策略和跨平台Sandbox抽象。前文说明 Bubblewrap 是默认 filesystem owner,本篇深入 Namespace、inner verification、Seccomp 和进程生命周期;不重复 bind/ro-bind 的完整 mount 算法,那部分由下一篇负责。

1. Network mode ​

1.1 三种模式 ​

BwrapNetworkMode 只描述 Bubblewrap namespace 层,不直接描述 Seccomp。FullAccess 保留 host network namespace;Isolated 新建 network namespace且没有 bridge;ProxyOnly 同样新建 namespace,但 inner helper 会建立 proxy route。

源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: BwrapNetworkMode, BwrapNetworkMode::should_unshare_network

rust
/// Network policy modes for bubblewrap.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum BwrapNetworkMode {
    /// Keep access to the host network namespace.
    #[default]
    FullAccess,
    /// Remove access to the host network namespace.
    Isolated,
    /// Intended proxy-only mode.
    ///
    /// Bubblewrap enforces this by unsharing the network namespace. The
    /// proxy-routing bridge is established by the helper process after startup.
    ProxyOnly,
}

impl BwrapNetworkMode {
    fn should_unshare_network(self) -> bool {
        !matches!(self, Self::FullAccess)
    }
}

1.2 Policy映射 ​

bwrap_network_mode 让 managed proxy 优先于 NetworkSandboxPolicy::Enabled。这保证即使 profile 原本允许网络,只要本次 execution 要求 managed routing,Bubblewrap 仍必须隔离 host network。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: bwrap_network_mode

rust
fn bwrap_network_mode(
    network_sandbox_policy: NetworkSandboxPolicy,
    allow_network_for_proxy: bool,
) -> BwrapNetworkMode {
    if allow_network_for_proxy {
        BwrapNetworkMode::ProxyOnly
    } else if network_sandbox_policy.is_enabled() {
        BwrapNetworkMode::FullAccess
    } else {
        BwrapNetworkMode::Isolated
    }
}

测试 proxy_only_mode_takes_precedence_over_full_network_policy 输入 Enabled + proxy flag,断言结果仍是 ProxyOnly,而不是 FullAccess。

2. Namespace argv ​

2.1 基础隔离 ​

无论 restricted filesystem 还是需要 network-only wrapper 的 full filesystem,Bubblewrap 都显式创建 user、PID 与 IPC namespace,并丢弃全部 capabilities。这样即使 caller 在容器内是 uid 0,也不依赖 ambient CAP_SYS_ADMIN 建立其余 namespace。

源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: create_bwrap_flags

rust
let mut args = Vec::new();
args.push("--new-session".to_string());
args.push("--die-with-parent".to_string());
args.extend(filesystem_args);
// Request a user namespace explicitly rather than relying on bubblewrap's
// auto-enable behavior, which is skipped when the caller runs as uid 0.
args.push("--unshare-user".to_string());
args.push("--unshare-pid".to_string());
args.push("--unshare-ipc".to_string());
if options.network_mode.should_unshare_network() {
    args.push("--unshare-net".to_string());
}
if options.mount_proc {
    args.push("--proc".to_string());
    args.push("/proc".to_string());
}
if normalized_command_cwd.as_path() != command_cwd {
    args.push("--chdir".to_string());
    args.push(path_to_string(normalized_command_cwd.as_path()));
}
args.push("--cap-drop".to_string());
args.push("ALL".to_string());
args.push("--".to_string());
args.extend(command);

--new-session 建立新的 session,--die-with-parent 让 Bubblewrap 随 launcher 消亡;PID namespace 与 fresh /proc 必须配套理解,否则 sandbox 内的 process view 会与 host 泄漏。

2.2 Full filesystem ​

当 filesystem 是 full disk write,但 network mode 是 Isolated 或 ProxyOnly 时,helper 仍需要 Bubblewrap。它 bind 整个 /,重新提供 minimal /dev 与 /dev/shm,再加入 namespace 和 capability flags。

源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: create_bwrap_flags_full_filesystem

rust
let mut args = vec![
    "--new-session".to_string(),
    "--die-with-parent".to_string(),
    "--bind".to_string(),
    "/".to_string(),
    "/".to_string(),
    // Preserve nodev on the root bind while exposing only standard devices.
    "--dev".to_string(),
    "/dev".to_string(),
    // Restore shared memory without exposing other host devices.
    "--bind-try".to_string(),
    "/dev/shm".to_string(),
    "/dev/shm".to_string(),
    "--unshare-user".to_string(),
    "--unshare-pid".to_string(),
    "--unshare-ipc".to_string(),
];
if options.network_mode.should_unshare_network() {
    args.push("--unshare-net".to_string());
}
if options.mount_proc {
    args.push("--proc".to_string());
    args.push("/proc".to_string());
}
args.push("--cap-drop".to_string());
args.push("ALL".to_string());

这说明是否需要 Bubblewrap 由 filesystem 和 network 两类约束共同决定,不能只看 writable roots。

3. Outer与inner ​

3.1 Inner command ​

outer stage 不直接执行用户 command,而是构造一次 helper re-entry。inner argv 携带 PermissionProfile JSON、policy cwd、可选 command cwd、--apply-seccomp-then-exec 和可选 proxy route spec,最后才接原始 command。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: InnerSeccompCommandArgs, build_inner_seccomp_command

rust
struct InnerSeccompCommandArgs<'a> {
    sandbox_policy_cwd: &'a Path,
    command_cwd: Option<&'a Path>,
    permission_profile: &'a PermissionProfile,
    allow_network_for_proxy: bool,
    proxy_route_spec: Option<String>,
    command: Vec<String>,
}

fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String> {
    let permission_profile_json = serde_json::to_string(args.permission_profile)
        .unwrap_or_else(|err| panic!("failed to serialize permission profile: {err}"));
    let mut inner = vec![
        std::env::current_exe()
            .unwrap_or_else(|err| panic!("failed to resolve current executable path: {err}"))
            .to_string_lossy()
            .to_string(),
        "--sandbox-policy-cwd".to_string(),
        args.sandbox_policy_cwd.to_string_lossy().to_string(),
    ];
    if let Some(command_cwd) = args.command_cwd {
        inner.push("--command-cwd".to_string());
        inner.push(command_cwd.to_string_lossy().to_string());
    }
    inner.extend([
        "--permission-profile".to_string(),
        permission_profile_json,
        "--apply-seccomp-then-exec".to_string(),
    ]);

managed proxy 模式必须同时包含 --allow-network-for-proxy 与 --proxy-route-spec。缺少 spec 会在 outer stage 构造 inner argv 时直接 panic,不能让 command 在隔离 namespace 中无路由地启动。

3.2 proc fallback ​

run_bwrap_with_proc_fallback 先 preflight /proc mount。某些容器允许 user/PID namespace,却拒绝 --proc /proc;此时 helper静默把 mount_proc 改为 false,再构造正式 argv。它不会取消其他 namespace 或 filesystem 限制。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_bwrap_with_proc_fallback

rust
let network_mode = bwrap_network_mode(network_sandbox_policy, allow_network_for_proxy);
let mut mount_proc = mount_proc;
let command_cwd = command_cwd.unwrap_or(sandbox_policy_cwd);

if mount_proc
    && !preflight_proc_mount_support(network_mode)
        .unwrap_or_else(|err| exit_with_bwrap_build_error(err))
{
    // Keep the retry silent so sandbox-internal diagnostics do not leak into the
    // child process stderr stream.
    mount_proc = false;
}

let options = BwrapOptions {
    mount_proc,
    network_mode,
    ..Default::default()
};
let mut bwrap_args = build_bwrap_argv(
    inner,
    file_system_sandbox_policy,
    sandbox_policy_cwd,
    command_cwd,
    options,
)
.unwrap_or_else(|err| exit_with_bwrap_build_error(err));
apply_inner_command_argv0(&mut bwrap_args.args);
run_or_exec_bwrap(bwrap_args);

这个 fallback只改变 /proc 可见性。若 Bubblewrap 本身不可用、user namespace 被禁用或 mount policy 构造失败,仍走明确错误路径。

4. Inner验证 ​

4.1 FD mount认证 ​

Bubblewrap 对某些 fallback mount 传递打开的 file descriptor。inner stage 在用户代码运行前检查 marker:descriptor 必须大于标准流、不能重复、destination 必须绝对,descriptor 与 destination 的 device/inode 必须一致。无论成功失败,descriptor 都设置 FD_CLOEXEC 并关闭,避免可写 host descriptor 泄露给 bridge 或用户进程。

源码位置:codex-rs/linux-sandbox/src/fd_mount.rs :: verify_fd_mounts

rust
let descriptor_metadata = file.metadata()?;
let destination_metadata = fs::symlink_metadata(destination)?;
if (descriptor_metadata.dev(), descriptor_metadata.ino())
    != (destination_metadata.dev(), destination_metadata.ino())
{
    return Err(io::Error::new(
        io::ErrorKind::PermissionDenied,
        format!(
            "descriptor-backed mount does not match its destination: {}",
            destination.display()
        ),
    ));
}

// Closing immediately prevents a writable host directory descriptor
// from reaching bridge workers or the sandboxed command.
drop(file);

测试覆盖 matching inode、swapped destination、symlink destination、标准流、relative destination 和 duplicate descriptor。

4.2 Capability归零 ​

Bubblewrap argv 已含 --cap-drop ALL,但 inner stage 不盲信 outer launcher。它通过 capget 读取两个 capability set,要求 effective 和 permitted 均为零;否则拒绝执行用户 command。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main inner stage

rust
let mut capability_header = [LINUX_CAPABILITY_VERSION_3, 0];
let mut capability_sets = [[0_u32; 3]; 2];
let result = unsafe {
    libc::syscall(
        libc::SYS_capget,
        capability_header.as_mut_ptr(),
        capability_sets.as_mut_ptr(),
    )
};
if result < 0 {
    panic!(
        "failed to verify Linux sandbox capabilities: {}",
        std::io::Error::last_os_error()
    );
}
if capability_sets
    .into_iter()
    .any(|[effective, permitted, _]| effective != 0 || permitted != 0)
{
    panic!("Linux sandbox retained effective or permitted capabilities");
}

这是一道发布屏障:只有 outer namespace、mount 和 capability 状态符合预期,inner 才继续安装 Seccomp。

5. Seccomp filter ​

5.1 no_new_privs ​

Seccomp filter 安装前调用 PR_SET_NO_NEW_PRIVS。代码故意不在 Bubblewrap outer stage过早设置,因为一些 system bwrap 部署依赖 setuid;先建立 namespace,再在 inner stage不可逆收紧。

源码位置:codex-rs/linux-sandbox/src/landlock.rs :: apply_permission_profile_to_current_thread, set_no_new_privs

rust
if network_seccomp_mode.is_some()
    || (apply_landlock_fs && !file_system_sandbox_policy.has_full_disk_write_access())
{
    set_no_new_privs()?;
}

if let Some(mode) = network_seccomp_mode {
    install_network_seccomp_filter_on_current_thread(mode)?;
}

5.2 通用进程限制 ​

两种 network mode 都先拒绝 ptrace、process_vm_readv/writev 和 io_uring_setup/enter/register。这些规则防止进程检查其他地址空间或绕过同步 syscall 集合使用 io_uring。

源码位置:codex-rs/linux-sandbox/src/landlock.rs :: install_network_seccomp_filter_on_current_thread

rust
let mut rules: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();

deny_syscall(&mut rules, libc::SYS_ptrace);
deny_syscall(&mut rules, libc::SYS_process_vm_readv);
deny_syscall(&mut rules, libc::SYS_process_vm_writev);
deny_syscall(&mut rules, libc::SYS_io_uring_setup);
deny_syscall(&mut rules, libc::SYS_io_uring_enter);
deny_syscall(&mut rules, libc::SYS_io_uring_register);

5.3 Restricted模式 ​

Restricted mode 无条件拒绝连接、监听和多种 socket 操作。socket 与 socketpair 使用参数条件:domain 不等于 AF_UNIX 时命中 deny rule,因此只保留本地 IPC。

源码位置:codex-rs/linux-sandbox/src/landlock.rs :: NetworkSeccompMode::Restricted

rust
deny_syscall(&mut rules, libc::SYS_connect);
deny_syscall(&mut rules, libc::SYS_accept);
deny_syscall(&mut rules, libc::SYS_accept4);
deny_syscall(&mut rules, libc::SYS_bind);
deny_syscall(&mut rules, libc::SYS_listen);
deny_syscall(&mut rules, libc::SYS_getpeername);
deny_syscall(&mut rules, libc::SYS_getsockname);
deny_syscall(&mut rules, libc::SYS_shutdown);
deny_syscall(&mut rules, libc::SYS_sendto);
deny_syscall(&mut rules, libc::SYS_sendmmsg);
deny_syscall(&mut rules, libc::SYS_recvmmsg);
deny_syscall(&mut rules, libc::SYS_getsockopt);
deny_syscall(&mut rules, libc::SYS_setsockopt);

let unix_only_rule = SeccompRule::new(vec![SeccompCondition::new(
    0,
    SeccompCmpArgLen::Dword,
    SeccompCmpOp::Ne,
    libc::AF_UNIX as u64,
)?])?;
rules.insert(libc::SYS_socket, vec![unix_only_rule.clone()]);
rules.insert(libc::SYS_socketpair, vec![unix_only_rule]);

recvfrom 没有被无条件拒绝,源码注释说明部分工具依赖 socketpair 与 child process 管理。Seccomp 规则是经过兼容性取舍的 allow-by-default filter,不是“禁止一切未列 syscall”。

5.4 ProxyRouted模式 ​

ProxyRouted mode 的 namespace 已与 host 隔离,所以允许 AF_INET/AF_INET6 仅能访问 namespace 内配置的 loopback bridge。普通 socket(AF_UNIX) 被拒绝,避免连接 host-mounted Unix socket;socketpair(AF_UNIX) 保留,因为它不能连接既有外部 endpoint。

源码位置:codex-rs/linux-sandbox/src/landlock.rs :: NetworkSeccompMode::ProxyRouted

rust
let deny_non_ip_socket = SeccompRule::new(vec![
    SeccompCondition::new(
        0,
        SeccompCmpArgLen::Dword,
        SeccompCmpOp::Ne,
        libc::AF_INET as u64,
    )?,
    SeccompCondition::new(
        0,
        SeccompCmpArgLen::Dword,
        SeccompCmpOp::Ne,
        libc::AF_INET6 as u64,
    )?,
])?;
let deny_non_unix_socketpair = SeccompRule::new(vec![SeccompCondition::new(
    0,
    SeccompCmpArgLen::Dword,
    SeccompCmpOp::Ne,
    libc::AF_UNIX as u64,
)?])?;
rules.insert(libc::SYS_socket, vec![deny_non_ip_socket]);
rules.insert(libc::SYS_socketpair, vec![deny_non_unix_socketpair]);

filter default action 是 Allow,命中 action 是 Errno(EPERM)。因此被拒绝 syscall 通常表现为权限错误,而不是 SIGSYS;SIGSYS 仍可能来自其它 filter 或平台路径。

6. Proxy bridge ​

6.1 Host计划 ​

outer stage从 proxy env 中只接受 parseable loopback endpoint,移除 attribution token,再为每个 distinct endpoint 创建 Unix socket 和 host bridge。无法得到 route 时返回 InvalidInput,不进入 inner command。

源码位置:codex-rs/linux-sandbox/src/proxy_routing.rs :: prepare_host_proxy_route_spec

rust
let (attribution_token, plan) = extract_attribution_token_and_plan(std::env::vars().collect());
unsafe {
    std::env::remove_var(PROXY_ATTRIBUTION_TOKEN_ENV_KEY);
}

if plan.routes.is_empty() {
    let message = if plan.has_proxy_config {
        "managed proxy mode requires parseable loopback proxy endpoints"
    } else {
        "managed proxy mode requires proxy environment variables"
    };
    return Err(io::Error::new(io::ErrorKind::InvalidInput, message));
}

let socket_dir = create_proxy_socket_dir()?;
let readable_socket_dir = AbsolutePathBuf::relative_to_current_dir(&socket_dir)?;

socket directory 会作为额外 readable root进入 Bubblewrap view,host bridge 将 Unix socket 流量转发到原 loopback proxy。

6.2 Netns激活 ​

inner stage读取 route spec,在新 network namespace 中为每个 UDS 建立 localhost TCP listener,并重写 HTTP/SOCKS/WebSocket proxy env 指向新端口。loopback interface 不可用时会尝试标记 lo 为 UP 并设置 127.0.0.1。

源码位置:codex-rs/linux-sandbox/src/proxy_routing.rs :: activate_proxy_routes_in_netns

rust
let spec: ProxyRouteSpec = serde_json::from_str(serialized_spec).map_err(io::Error::other)?;
if spec.routes.is_empty() {
    return Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "proxy routing spec contained no routes",
    ));
}

let mut local_port_by_uds_path: BTreeMap<PathBuf, u16> = BTreeMap::new();
for route in &spec.routes {
    if local_port_by_uds_path.contains_key(&route.uds_path) {
        continue;
    }
    let local_port = spawn_local_bridge(route.uds_path.as_path())?;
    local_port_by_uds_path.insert(route.uds_path.clone(), local_port);
}

for route in spec.routes {
    let local_port = local_port_by_uds_path
        .get(&route.uds_path)
        .ok_or_else(|| io::Error::other("missing local bridge port"))?;
    let original_value = std::env::var(&route.env_key)?;
    let rewritten = rewrite_proxy_env_value(&original_value, *local_port)
        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid proxy URL"))?;
    unsafe {
        std::env::set_var(route.env_key, rewritten);
    }
}

此时 ProxyRouted Seccomp 才安装。顺序不能反转:如果先禁止 AF_UNIX socket 或 network setup,bridge 无法激活。

7. Child生命周期 ​

7.1 inner child ​

完成 mount、capability、proxy 和 Seccomp 后,inner helper fork 用户 command。child 恢复默认 signal handler 并 execvp;parent 安装 forwarder、循环 waitpid(-1),只在目标 command 退出时恢复 handler并按原 status 退出。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main inner stage

rust
let signal_mask = ForwardedSignalMask::block();
let command_pid = unsafe { libc::fork() };
if command_pid < 0 {
    let err = std::io::Error::last_os_error();
    panic!("failed to fork sandboxed command: {err}");
}

if command_pid == 0 {
    reset_forwarded_signal_handlers_to_default();
    signal_mask.restore();
    exec_or_panic(command);
}

let signal_forwarders = install_bwrap_signal_forwarders(command_pid);
signal_mask.restore();
loop {
    let mut status = 0;
    let reaped_pid = unsafe { libc::waitpid(-1, &mut status, 0) };
    if reaped_pid == command_pid {
        let exit_signal_mask = ForwardedSignalMask::block();
        signal_forwarders.restore();
        exit_signal_mask.restore();
        exit_with_wait_status(status);
    }
    if reaped_pid >= 0 {
        continue;
    }
    let err = std::io::Error::last_os_error();
    if err.raw_os_error() != Some(libc::EINTR) {
        panic!("failed to reap sandboxed child: {err}");
    }
}

使用 waitpid(-1) 是因为 command 可能产生其它可回收 child;只有主 command status 决定 helper 终态。

7.2 Signal转发 ​

outer helper 把需要转发的 signal 同时发给 process group -pid 和 child pid,覆盖已经建组和尚未建组的竞态。安装 handler 前到达的信号保存在 atomic 中,handler 安装后 replay。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: install_bwrap_signal_forwarders, forward_signal_to_bwrap_child, send_signal_to_bwrap_child

rust
fn install_bwrap_signal_forwarders(pid: libc::pid_t) -> ForwardedSignalHandlers {
    BWRAP_CHILD_PID.store(pid, Ordering::SeqCst);
    let mut previous = Vec::with_capacity(FORWARDED_SIGNALS.len());
    for signal in FORWARDED_SIGNALS {
        let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
        let mut previous_action: libc::sigaction = unsafe { std::mem::zeroed() };
        action.sa_sigaction = forward_signal_to_bwrap_child as *const () as libc::sighandler_t;
        unsafe {
            libc::sigemptyset(&mut action.sa_mask);
            if libc::sigaction(*signal, &action, &mut previous_action) < 0 {
                panic!("failed to install bubblewrap signal forwarder");
            }
        }
        previous.push((*signal, previous_action));
    }
    replay_pending_forwarded_signal(pid);
    ForwardedSignalHandlers { previous }
}

fn send_signal_to_bwrap_child(pid: libc::pid_t, signal: libc::c_int) {
    unsafe {
        libc::kill(-pid, signal);
        libc::kill(pid, signal);
    }
}

bwrap_signal_forwarder_terminates_child_and_keeps_parent_alive 用 supervisor/child 两层 fork验证:supervisor 收到 SIGTERM 后仍能等待 child,child 由 SIGTERM 终止。

7.3 Synthetic清理 ​

若 filesystem policy创建 synthetic mount或 protected-create target,outer helper不能直接 exec_bwrap。它 fork Bubblewrap child、建立 process group和 parent-death signal,启动 protected target monitor,等待 child后清理 mount registry,并将新建 protected path视为 policy violation。

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_bwrap_in_child_with_synthetic_mount_cleanup

rust
let setup_signal_mask = ForwardedSignalMask::block();
let synthetic_mount_registrations = register_synthetic_mount_targets(&synthetic_mount_targets);
let protected_create_registrations =
    register_protected_create_targets(&protected_create_targets);
let exec_start_pipe = create_exec_start_pipe(!protected_create_targets.is_empty());
let parent_pid = unsafe { libc::getpid() };
let pid = unsafe { libc::fork() };

if pid == 0 {
    reset_forwarded_signal_handlers_to_default();
    setup_signal_mask.restore();
    let setpgid_res = unsafe { libc::setpgid(0, 0) };
    if setpgid_res < 0 {
        panic!("failed to place bubblewrap child in its own process group");
    }
    terminate_with_parent(parent_pid);
    wait_for_parent_exec_start(exec_start_pipe[0], exec_start_pipe[1]);
    exec_bwrap(args, preserved_files);
}

let protected_create_monitor = ProtectedCreateMonitor::start(&protected_create_targets);
let signal_forwarders = install_bwrap_signal_forwarders(pid);
release_child_exec_start(exec_start_pipe[1]);
setup_signal_mask.restore();
let status = wait_for_bwrap_child(pid);

cleanup owner必须留在 sandbox 外层,用户 command不能删除 marker或伪造 registration。

8. 测试边界 ​

本篇直接涉及的 Linux helper 源码中有 53 项 unit test,覆盖 namespace argv、argv0 compatibility、network mode、proc preflight、FD mount、proxy route、capability/mode输入、signal forwarding和synthetic cleanup;这些测试由 target_os = "linux" 条件编译。当前非 Linux 主机可核对源码并编译非 Linux stub,但不能运行 Linux namespace/Seccomp 断言。

当前可执行的近场测试仍包括 codex-sandboxing 的 4 项 helper argv测试,确认 PermissionProfile、legacy/proxy flag 和 command separator 进入 Linux helper命令。Linux target上应执行:

bash
cargo test -p codex-linux-sandbox --lib -- --nocapture --test-threads=1
cargo test -p codex-linux-sandbox --test all -- --nocapture --test-threads=1
cargo test -p codex-sandboxing --lib landlock::tests:: -- --nocapture --test-threads=1

关键输入与断言包括:

  • BwrapNetworkMode::Isolated 和 ProxyOnly 的 argv 都包含 --unshare-net;FullAccess 不包含。
  • normal argv包含 --unshare-user、--unshare-pid、--unshare-ipc 和 --cap-drop ALL。
  • matching descriptor mount验证成功后 FD 已关闭;mismatched inode、symlink destination和duplicate FD均失败并关闭 FD。
  • managed proxy inner command必须包含 route spec;没有 spec时构造失败。
  • signal forwarding test要求 child 被 SIGTERM终止,而 supervisor保持存活并成功回收。

这些结果证明构造、状态检查和 Linux fixture 行为;边界在于 namespace创建仍依赖 host kernel、user namespace配置、Bubblewrap版本和container能力。Seccomp filter只对 x86_64/aarch64生成,其他架构当前走 unimplemented!,不能宣称全架构支持。

9. 继续阅读 ​

读者可以从 bwrap_network_mode 进入 create_bwrap_flags,再跟随 inner argv 到 run_main 的 apply_seccomp_then_exec 分支;随后分别阅读 verify_fd_mounts、capability检查、activate_proxy_routes_in_netns、install_network_seccomp_filter_on_current_thread 和 signal/waitpid 收尾。这样能够区分 namespace、route bridge、Seccomp 和process supervisor各自保证什么。

下一篇Bubblewrap命令构造将展开 filesystem mount顺序、read/write/deny重叠、glob mask、symlink和synthetic mount的具体 argv算法。