Skip to content

Linux沙箱降级与诊断

从 bwrap 探测、bundled fallback、proc 预检到错误映射,解释 Linux 沙箱何时降级、何时必须失败。

基于rust-v0.150.0
CodexRustSecurityLinuxBubblewrap

Linux沙箱降级与诊断 ​

Linux 沙箱的“降级”是若干个彼此独立的决策:启动时可以提示缺少系统 bwrap,兼容旧版 bwrap 时可以改写参数,受限容器不能挂载新的 /proc 时可以只关闭这一个挂载;但 helper 路径、工作目录、命令构造或完整的 Bubblewrap 启动失败,都不会把原命令放回宿主机执行。理解这些分支,关键是沿着“请求转换 → helper 两阶段启动 → bwrap launcher → 诊断消费者”阅读,而不是把所有 warning 都看成同一种 fallback。

阅读前可先查看Bubblewrap命令构造和Linux Seccomp与Namespace,前者解释 mount argv 如何生成,后者解释 namespace、capability 与 Seccomp 如何在 inner stage 生效。

本文从 Linux 实现和对应测试出发,重点覆盖:

  • bwrap 的 PATH 搜索、工作目录排除和 user namespace 探测;
  • system/bundled launcher 的选择、能力探测与摘要校验;
  • /proc 预检的最小权限模型和单点降级;
  • WSL1、helper 缺失、cwd 无效、构造失败的错误映射;
  • 启动 warning 与运行时命令失败分别由谁消费。

1. 请求先决定模式 ​

run_main 先解析权限配置。默认路径先准备可选的代理路由,再构造 inner helper 参数,最后进入 run_bwrap_with_proc_fallback;只有显式传入 --use-legacy-landlock 才执行 Landlock 分支。因此,bwrap 的失败不是“自动换成 Landlock”的信号。

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

rust
if !use_legacy_landlock {
    // Outer stage: bubblewrap first, then re-enter this binary in the
    // sandboxed environment to apply seccomp. This path never falls back
    // to legacy Landlock on failure.
    let proxy_route_spec = if allow_network_for_proxy {
        let (proxy_route_spec, socket_dir) = prepare_host_proxy_route_spec()
            .unwrap_or_else(|err| panic!("failed to prepare host proxy routing bridge: {err}"));
        file_system_sandbox_policy = file_system_sandbox_policy.with_additional_readable_roots(
            &sandbox_policy_cwd,
            std::slice::from_ref(&socket_dir),
        );
        Some(proxy_route_spec)
    } else {
        None
    };
    let inner = build_inner_seccomp_command(InnerSeccompCommandArgs {
        sandbox_policy_cwd: &sandbox_policy_cwd,
        command_cwd: command_cwd.as_deref(),
        permission_profile: &permission_profile,
        allow_network_for_proxy,
        proxy_route_spec,
        command,
    });
    run_bwrap_with_proc_fallback(
        &sandbox_policy_cwd,
        command_cwd.as_deref(),
        &file_system_sandbox_policy,
        network_sandbox_policy,
        inner,
        !no_proc,
        allow_network_for_proxy,
    );
}

// Legacy path: Landlock enforcement only, when bwrap sandboxing is not enabled.
if let Err(e) = apply_permission_profile_to_current_thread(
    &permission_profile,
    &sandbox_policy_cwd,
    /*apply_landlock_fs*/ true,
    allow_network_for_proxy,
    /*proxy_routed_network*/ false,
) {
    panic!("error applying legacy Linux sandbox restrictions: {e:?}");
}
exec_or_panic(command);

ensure_legacy_landlock_mode_supports_policy 还会拒绝需要直接运行时约束、却被要求走 legacy Landlock 的权限组合。降级入口本身仍受策略约束,不是无条件的逃生通道。

2. PATH搜索提示 ​

系统 bwrap 的发现函数把当前工作目录作为排除边界。它先用 which_in_all 找到所有候选,再 canonicalize;只要候选路径位于非根 cwd 内,就跳过。这防止项目目录中名为 bwrap 的文件被当作系统 launcher。

源码位置:codex-rs/sandboxing/src/bwrap.rs :: find_system_bwrap_in_path、find_system_bwrap_in_search_paths

rust
pub fn find_system_bwrap_in_path() -> Option<PathBuf> {
    let search_path = std::env::var_os("PATH")?;
    let cwd = std::env::current_dir().ok()?;
    find_system_bwrap_in_search_paths(std::env::split_paths(&search_path), &cwd)
}

fn find_system_bwrap_in_search_paths(
    search_paths: impl IntoIterator<Item = PathBuf>,
    cwd: &Path,
) -> Option<PathBuf> {
    let search_path = std::env::join_paths(search_paths).ok()?;
    let cwd = std::fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf());
    let cwd_is_root = cwd.parent().is_none();
    which::which_in_all(SYSTEM_BWRAP_PROGRAM, Some(search_path), &cwd)
        .ok()?
        .find_map(|path| {
            let path = std::fs::canonicalize(path).ok()?;
            if !cwd_is_root && path.starts_with(&cwd) {
                None
            } else {
                Some(path)
            }
        })
}

warning 只在需要平台沙箱的权限配置下产生。WSL1 先于 PATH 结果被判断;没有系统 bwrap 时则提示安装,并明确说明接下来会使用 bundled bwrap;探测到 user namespace 失败时给出能力提示。

源码位置:codex-rs/sandboxing/src/bwrap.rs :: system_bwrap_warning

rust
pub fn system_bwrap_warning(permission_profile: &PermissionProfile) -> Option<String> {
    if !should_warn_about_system_bwrap(permission_profile) {
        return None;
    }

    let system_bwrap_path = find_system_bwrap_in_path();
    system_bwrap_warning_for_path(system_bwrap_path.as_deref())
}

源码位置:codex-rs/sandboxing/src/bwrap.rs :: system_bwrap_warning_for_path

rust
fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option<String> {
    if is_wsl1() {
        return Some(WSL1_BWRAP_WARNING.to_string());
    }

    let Some(system_bwrap_path) = system_bwrap_path else {
        return Some(MISSING_BWRAP_WARNING.to_string());
    };

    if !system_bwrap_has_user_namespace_access(system_bwrap_path, SYSTEM_BWRAP_PROBE_TIMEOUT) {
        return Some(USER_NAMESPACE_WARNING.to_string());
    }

    None
}

3. Launcher能力探测 ​

exec_bwrap 总是先插入 --as-pid-1。系统 launcher 必须从 --help 输出中确认该能力和 --perms;--argv0 与 --ro-bind-fd 则分别记录为可选能力。系统 bwrap 不满足必需能力时,选择逻辑才会继续寻找 bundled 版本。

源码位置:codex-rs/linux-sandbox/src/launcher.rs :: exec_bwrap

rust
pub(crate) fn exec_bwrap(mut argv: Vec<String>, preserved_files: Vec<File>) -> ! {
    argv.insert(1, "--as-pid-1".to_string());

    match preferred_bwrap_launcher() {
        BubblewrapLauncher::System(launcher) => {
            if !launcher.supports_ro_bind_fd {
                translate_legacy_bwrap_fd_mounts(&mut argv)
                    .unwrap_or_else(|error| panic!("invalid legacy bubblewrap fd mount: {error}"));
            }
            exec_system_bwrap(&launcher.program, argv, preserved_files)
        }
        BubblewrapLauncher::Bundled(launcher) => launcher.exec(argv, preserved_files),
        BubblewrapLauncher::Unavailable => {
            panic!(
                "bubblewrap is unavailable: no system bwrap was found on PATH and no bundled \
                 codex-resources/bwrap binary was found next to the Codex executable"
            )
        }
    }
}

源码位置:codex-rs/linux-sandbox/src/launcher.rs :: preferred_bwrap_launcher

rust
fn preferred_bwrap_launcher() -> BubblewrapLauncher {
    static LAUNCHER: OnceLock<BubblewrapLauncher> = OnceLock::new();
    LAUNCHER
        .get_or_init(|| {
            if let Some(path) = find_system_bwrap_in_path()
                && let Some(launcher) = system_bwrap_launcher_for_path(&path)
            {
                return BubblewrapLauncher::System(launcher);
            }

            match bundled_bwrap::launcher() {
                Some(launcher) => BubblewrapLauncher::Bundled(launcher),
                None => BubblewrapLauncher::Unavailable,
            }
        })
        .clone()
}

源码位置:codex-rs/linux-sandbox/src/launcher.rs :: system_bwrap_launcher_for_path_with_probe

rust
fn system_bwrap_launcher_for_path_with_probe(
    system_bwrap_path: &Path,
    system_bwrap_capabilities: impl FnOnce(&Path) -> Option<SystemBwrapCapabilities>,
) -> Option<SystemBwrapLauncher> {
    if !system_bwrap_path.is_file() {
        return None;
    }

    let Some(SystemBwrapCapabilities {
        supports_argv0,
        supports_perms: true,
        supports_ro_bind_fd,
    }) = system_bwrap_capabilities(system_bwrap_path)
    else {
        return None;
    };
    let system_bwrap_path = match AbsolutePathBuf::from_absolute_path(system_bwrap_path) {
        Ok(path) => path,
        Err(err) => panic!(
            "failed to normalize system bubblewrap path {}: {err}",
            system_bwrap_path.display()
        ),
    };
    Some(SystemBwrapLauncher {
        program: system_bwrap_path,
        supports_argv0,
        supports_ro_bind_fd,
    })
}

源码位置:codex-rs/linux-sandbox/src/launcher.rs :: system_bwrap_capabilities

rust
fn system_bwrap_capabilities(system_bwrap_path: &Path) -> Option<SystemBwrapCapabilities> {
    // bubblewrap added `--argv0` in v0.9.0:
    // https://github.com/containers/bubblewrap/releases/tag/v0.9.0
    // Older distro packages (for example Ubuntu 20.04/22.04) ship builds that
    // reject `--argv0`, so use the system binary's no-argv0 compatibility path
    // in that case.
    let output = match Command::new(system_bwrap_path).arg("--help").output() {
        Ok(output) => output,
        Err(_) => return None,
    };
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    if !stdout.contains("--as-pid-1") && !stderr.contains("--as-pid-1") {
        return None;
    }
    Some(SystemBwrapCapabilities {
        supports_argv0: stdout.contains("--argv0") || stderr.contains("--argv0"),
        supports_perms: stdout.contains("--perms") || stderr.contains("--perms"),
        supports_ro_bind_fd: stdout.contains("--ro-bind-fd") || stderr.contains("--ro-bind-fd"),
    })
}

bundled 版本也不是“找到文件就执行”。它从安装上下文或兼容布局查找可执行文件,打开文件后先校验可选的 CODEX_BWRAP_SHA256 摘要;校验失败使用专用退出码,避免把未认证的二进制当作沙箱边界。

源码位置:codex-rs/linux-sandbox/src/bundled_bwrap.rs :: launcher、BundledBwrapLauncher::exec

rust
pub(crate) fn launcher() -> Option<BundledBwrapLauncher> {
    let current_exe = std::env::current_exe().ok()?;
    find_for_install_context(InstallContext::current())
        .or_else(|| find_legacy_for_exe(&current_exe))
        .map(|program| BundledBwrapLauncher { program })
}

impl BundledBwrapLauncher {
    pub(crate) fn exec(&self, argv: Vec<String>, preserved_files: Vec<File>) -> ! {
        let bwrap_file = File::open(self.program.as_path()).unwrap_or_else(|err| {
            panic!(
                "failed to open bundled bubblewrap {}: {err}",
                self.program.as_path().display()
            )
        });
        if let Err(err) = verify_digest(&bwrap_file, expected_sha256(), self.program.as_path()) {
            eprintln!("{err}");
            std::process::exit(crate::BUNDLED_BWRAP_DIGEST_VERIFICATION_FAILURE_EXIT_CODE);
        }

        make_files_inheritable(&preserved_files);

        let fd_path = format!("/proc/self/fd/{}", bwrap_file.as_raw_fd());
        let program_cstring = CString::new(fd_path.as_str())
            .unwrap_or_else(|err| panic!("invalid bundled bubblewrap fd path: {err}"));
        let cstrings = argv_to_cstrings(&argv);
        let mut argv_ptrs: Vec<*const c_char> = cstrings
            .iter()
            .map(CString::as_c_str)
            .map(CStr::as_ptr)
            .collect();
        argv_ptrs.push(std::ptr::null());

        // SAFETY: `program_cstring` and every entry in `argv_ptrs` are valid C
        // strings for the duration of the call. On success `execv` does not return.
        unsafe {
            libc::execv(program_cstring.as_ptr(), argv_ptrs.as_ptr());
        }
        let err = std::io::Error::last_os_error();
        panic!(
            "failed to exec bundled bubblewrap {} via {fd_path}: {err}",
            self.program.as_path().display()
        );
    }
}

4. /proc降级边界 ​

run_bwrap_with_proc_fallback 先根据网络策略选择 FullAccess、Isolated 或 ProxyOnly,再运行一个最小预检。预检失败只有在 stderr 命中 proc mount 特征时才把 mount_proc 设为 false;预检构造错误和正式 argv 构造错误都会进入 exit_with_bwrap_build_error。

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

rust
fn run_bwrap_with_proc_fallback(
    sandbox_policy_cwd: &Path,
    command_cwd: Option<&Path>,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    network_sandbox_policy: NetworkSandboxPolicy,
    inner: Vec<String>,
    mount_proc: bool,
    allow_network_for_proxy: bool,
) -> ! {
    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);
}

源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: preflight_proc_mount_support、build_preflight_bwrap_argv

rust
fn preflight_proc_mount_support(network_mode: BwrapNetworkMode) -> CodexResult<bool> {
    let preflight_argv = build_preflight_bwrap_argv(network_mode)?;
    let stderr = run_bwrap_in_child_capture_stderr(preflight_argv);
    Ok(!is_proc_mount_failure(stderr.as_str()))
}

fn build_preflight_bwrap_argv(
    network_mode: BwrapNetworkMode,
) -> CodexResult<crate::bwrap::BwrapArgs> {
    let file_system_sandbox_policy =
        FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
            path: FileSystemPath::Special {
                value: FileSystemSpecialPath::Minimal,
            },
            access: FileSystemAccessMode::Read,
            missing_path_behavior: None,
        }]);
    let preflight_command = vec![resolve_true_command()];
    build_bwrap_argv(
        preflight_command,
        &file_system_sandbox_policy,
        Path::new("/"),
        Path::new("/"),
        BwrapOptions {
            mount_proc: true,
            network_mode,
            ..Default::default()
        },
    )
}

预检用最小文件系统策略,不把整个宿主根目录 bind 进去;因此“关闭 fresh proc”不会被误解为“用一次全盘 bind 的实验替代正式沙箱”。linux_run_main_tests.rs 对 --tmpfs /、--proc /proc 以及不存在的 --ro-bind / / 都有直接断言。

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

rust
fn is_proc_mount_failure(stderr: &str) -> bool {
    stderr.contains("Can't mount proc")
        && stderr.contains("/newroot/proc")
        && (stderr.contains("Invalid argument")
            || stderr.contains("Operation not permitted")
            || stderr.contains("Permission denied"))
}

匹配条件同时要求 mount 对象、目标路径和三种已知错误文本,普通 bind mount 的 Operation not permitted 不会触发降级。

5. 兼容旧版 bwrap ​

当系统 bwrap 没有 --argv0 时,helper 会把 -- 后的第一个命令替换为当前 helper 路径;有 --argv0 时则插入固定的 codex-linux-sandbox argv0。没有 --ro-bind-fd 时,launcher 把 descriptor mount 改写为 /proc/self/fd/<fd>,同时插入 --verify-fd-mount,并要求 inner stage 存在 --apply-seccomp-then-exec。这些改写都是兼容旧版能力,不是放宽认证。

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

rust
fn apply_inner_command_argv0_for_launcher(
    argv: &mut Vec<String>,
    supports_argv0: bool,
    argv0_fallback_command: String,
) {
    let command_separator_index = argv
        .iter()
        .position(|arg| arg == "--")
        .unwrap_or_else(|| panic!("bubblewrap argv is missing command separator '--'"));

    if supports_argv0 {
        argv.splice(
            command_separator_index..command_separator_index,
            ["--argv0".to_string(), CODEX_LINUX_SANDBOX_ARG0.to_string()],
        );
        return;
    }

    let command_index = command_separator_index + 1;
    let Some(command) = argv.get_mut(command_index) else {
        panic!("bubblewrap argv is missing inner command after '--'");
    };
    *command = argv0_fallback_command;
}

6. 错误映射与消费者 ​

Sandbox transform 阶段把错误分成不同语义:无效 cwd 是 InvalidRequest,helper 缺失保留 LandlockSandboxExecutableNotProvided 这一历史错误名,WSL1 是 UnsupportedOperation。这一步发生在命令真正启动前。

源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxTransformError、SandboxType::LinuxSeccomp

rust
#[derive(Debug)]
pub enum SandboxTransformError {
    InvalidCommandCwd {
        cwd: PathUri,
        source: io::Error,
    },
    InvalidSandboxPolicyCwd {
        cwd: PathUri,
        source: io::Error,
    },
    MissingLinuxSandboxExecutable,
    EnvironmentNetworkProxy(String),
    #[cfg(target_os = "macos")]
    SeatbeltPreparation(String),
    #[cfg(target_os = "linux")]
    Wsl1UnsupportedForBubblewrap,
    #[cfg(not(target_os = "macos"))]
    SeatbeltUnavailable,
    #[cfg(target_os = "windows")]
    WindowsSandboxPreparation(String),
}

源码位置:codex-rs/sandboxing/src/manager.rs :: SandboxType::LinuxSeccomp

rust
SandboxType::LinuxSeccomp => {
    let pending = pending_sandboxed_request?;
    let exe = codex_linux_sandbox_exe
        .ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
    let allow_proxy_network = allow_network_for_proxy(enforce_managed_network);
    #[cfg(target_os = "linux")]
    ensure_linux_bubblewrap_is_supported(
        &pending
            .effective_permission_profile
            .file_system_sandbox_policy(),
        use_legacy_landlock,
        allow_proxy_network,
        is_wsl1(),
    )?;
    let mut args = create_linux_sandbox_command_args_for_permission_profile(
        os_argv_to_strings(argv),
        pending.native_command_cwd.as_path(),
        &pending.effective_permission_profile,
        pending.native_sandbox_policy_cwd.as_path(),
        use_legacy_landlock,
        allow_proxy_network,
    );
    let mut full_command = Vec::with_capacity(1 + args.len());
    full_command.push(os_string_to_command_component(exe.as_os_str().to_owned()));
    full_command.append(&mut args);
    (
        full_command,
        Some(linux_sandbox_arg0_override(exe)),
        Some(pending),
    )
}

源码位置:codex-rs/sandboxing/src/lib.rs :: From<SandboxTransformError> for CodexErr

rust
impl From<SandboxTransformError> for CodexErr {
    fn from(err: SandboxTransformError) -> Self {
        match err {
            error @ SandboxTransformError::InvalidCommandCwd { .. }
            | error @ SandboxTransformError::InvalidSandboxPolicyCwd { .. } => {
                CodexErr::InvalidRequest(error.to_string())
            }
            SandboxTransformError::MissingLinuxSandboxExecutable => {
                CodexErr::LandlockSandboxExecutableNotProvided
            }
            SandboxTransformError::EnvironmentNetworkProxy(message) => {
                CodexErr::UnsupportedOperation(message)
            }
            #[cfg(target_os = "macos")]
            SandboxTransformError::SeatbeltPreparation(message) => {
                CodexErr::UnsupportedOperation(message)
            }
            #[cfg(target_os = "linux")]
            SandboxTransformError::Wsl1UnsupportedForBubblewrap => {
                CodexErr::UnsupportedOperation(crate::bwrap::WSL1_BWRAP_WARNING.to_string())
            }
            #[cfg(not(target_os = "macos"))]
            SandboxTransformError::SeatbeltUnavailable => CodexErr::UnsupportedOperation(
                "seatbelt sandbox is only available on macOS".to_string(),
            ),
            #[cfg(target_os = "windows")]
            SandboxTransformError::WindowsSandboxPreparation(message) => {
                CodexErr::UnsupportedOperation(message)
            }
        }
    }
}

运行时 warning 则由 exec 模式在 session 配置完成后展示给用户,并且只在非 JSON 模式处理;它不是把失败写成“命令已被阻止”的事件。

源码位置:codex-rs/exec/src/lib.rs :: run_exec 内的启动提示路径

rust
// Print the effective configuration and initial request so users can see what Codex
// is using.
event_processor.print_config_summary(&config, &prompt_summary, &session_configured);
if !json_mode
    && let Some(message) =
        codex_core::config::system_bwrap_warning(config.permissions.permission_profile())
{
    event_processor.process_warning(message);
}

相反,exit_with_bwrap_build_error 会打印构造错误并以非零状态退出;它没有调用 legacy Landlock,也没有执行未包装的用户命令。

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

rust
fn exit_with_bwrap_build_error(err: codex_protocol::error::CodexErr) -> ! {
    eprintln!("error building bubblewrap command: {err}");
    std::process::exit(1);
}

7. 测试与边界 ​

测试把诊断输入做成可控 fixture:假的 bwrap 输出指定 stderr,验证 WSL1 字符串识别、无关错误不触发 warning、probe timeout 不制造确定性 warning;proc 预检则检查生成的 argv,而不是在不具备 Linux namespace 的主机上假装执行成功。

源码位置:codex-rs/sandboxing/src/bwrap_tests.rs :: system_bwrap_warning_reports_user_namespace_failures

rust
#[test]
fn system_bwrap_warning_reports_user_namespace_failures() {
    for failure in USER_NAMESPACE_FAILURES {
        let fake_bwrap = write_fake_bwrap(&format!(
            r#"#!/bin/sh
echo '{failure}' >&2
exit 1
"#
        ));
        let fake_bwrap_path: &Path = fake_bwrap.as_ref();

        assert_eq!(
            system_bwrap_warning_for_path(Some(fake_bwrap_path)),
            Some(USER_NAMESPACE_WARNING.to_string()),
            "{failure}",
        );
    }
}

源码位置:codex-rs/sandboxing/src/bwrap_tests.rs :: system_bwrap_probe_times_out_without_reporting_a_warning

rust
#[test]
fn system_bwrap_probe_times_out_without_reporting_a_warning() {
    let fake_bwrap = write_fake_bwrap(
        r#"#!/bin/sh
sleep 1
exit 0
"#,
    );
    let fake_bwrap_path: &Path = fake_bwrap.as_ref();
    let started_at = Instant::now();

    assert!(system_bwrap_has_user_namespace_access(
        fake_bwrap_path,
        Duration::from_millis(10),
    ));
    assert!(started_at.elapsed() < Duration::from_millis(500));
}

源码位置:codex-rs/linux-sandbox/src/linux_run_main_tests.rs :: detects_proc_mount_operation_not_permitted_failure

rust
#[test]
fn detects_proc_mount_operation_not_permitted_failure() {
    let stderr = "bwrap: Can't mount proc on /newroot/proc: Operation not permitted";
    assert!(is_proc_mount_failure(stderr));
}

源码位置:codex-rs/linux-sandbox/src/linux_run_main_tests.rs :: ignores_non_proc_mount_errors

rust
#[test]
fn ignores_non_proc_mount_errors() {
    let stderr = "bwrap: Can't bind mount /dev/null: Operation not permitted";
    assert!(!is_proc_mount_failure(stderr));
}

源码位置:codex-rs/linux-sandbox/src/linux_run_main_tests.rs :: proc_mount_preflight_does_not_bind_the_full_filesystem

rust
#[test]
fn proc_mount_preflight_does_not_bind_the_full_filesystem() {
    let argv = build_preflight_bwrap_argv(BwrapNetworkMode::FullAccess)
        .expect("build preflight argv")
        .args;

    assert!(argv.windows(2).any(|window| window == ["--tmpfs", "/"]));
    assert!(argv.windows(2).any(|window| window == ["--proc", "/proc"]));
    assert!(!argv.windows(3).any(|window| window == ["--ro-bind", "/", "/"]));
}

这些断言能证明“哪些 stderr 允许关闭 proc”“预检不会把全盘暴露给 probe”以及“timeout 不等价于 user namespace 失败”。它们不能证明当前宿主一定能创建 namespace;Linux-only 的执行、Seccomp、capability 和 signal 测试仍需 Linux target 及相应内核配置。

在源码仓库中可按下面的粒度运行相关测试:

text
cd codex-rs
cargo test -p codex-sandboxing bwrap -- --test-threads=1
cargo test -p codex-linux-sandbox linux_run_main -- --test-threads=1

8. 阅读闭环 ​

阅读顺序建议是:先看 run_main 的默认/legacy 分叉,再看 PATH 排除和 warning,再进入 system/bundled launcher 的能力验证,最后追 /proc 预检和错误消费者。读完后应能回答三个问题:为什么 bundled bwrap 是“候选执行器”而不是无条件 fallback;为什么 proc 失败只影响 fresh proc mount;为什么构造错误与 warning 的用户可见结果不同。

下一篇进入 Windows sandbox 的 token 与可读路径授权。