Files
codex/codex-rs/sandboxing/src/landlock.rs
viyatb-oai 937cb5081d fix: fix old system bubblewrap compatibility without falling back to vendored bwrap (#15693)
Fixes #15283.

## Summary
Older system bubblewrap builds reject `--argv0`, which makes our Linux
sandbox fail before the helper can re-exec. This PR keeps using system
`/usr/bin/bwrap` whenever it exists and only falls back to vendored
bwrap when the system binary is missing. That matters on stricter
AppArmor hosts, where the distro bwrap package also provides the policy
setup needed for user namespaces.

For old system bwrap, we avoid `--argv0` instead of switching binaries:
- pass the sandbox helper a full-path `argv0`,
- keep the existing `current_exe() + --argv0` path when the selected
launcher supports it,
- otherwise omit `--argv0` and re-exec through the helper's own
`argv[0]` path, whose basename still dispatches as
`codex-linux-sandbox`.

Also updates the launcher/warning tests and docs so they match the new
behavior: present-but-old system bwrap uses the compatibility path, and
only absent system bwrap falls back to vendored.

### Validation

1. Install Ubuntu 20.04 in a VM
2. Compile codex and run without bubblewrap installed - see a warning
about falling back to the vendored bwrap
3. Install bwrap and verify version is 0.4.0 without `argv0` support
4. run codex and use apply_patch tool without errors

<img width="802" height="631" alt="Screenshot 2026-03-25 at 11 48 36 PM"
src="https://github.com/user-attachments/assets/77248a29-aa38-4d7c-9833-496ec6a458b8"
/>
<img width="807" height="634" alt="Screenshot 2026-03-25 at 11 47 32 PM"
src="https://github.com/user-attachments/assets/5af8b850-a466-489b-95a6-455b76b5050f"
/>
<img width="812" height="635" alt="Screenshot 2026-03-25 at 11 45 45 PM"
src="https://github.com/user-attachments/assets/438074f0-8435-4274-a667-332efdd5cb57"
/>
<img width="801" height="623" alt="Screenshot 2026-03-25 at 11 43 56 PM"
src="https://github.com/user-attachments/assets/0dc8d3f5-e8cf-4218-b4b4-a4f7d9bf02e3"
/>

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-03-25 23:51:39 -07:00

118 lines
4.1 KiB
Rust

use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use std::path::Path;
/// Basename used when the Codex executable self-invokes as the Linux sandbox
/// helper.
pub const CODEX_LINUX_SANDBOX_ARG0: &str = "codex-linux-sandbox";
pub fn allow_network_for_proxy(enforce_managed_network: bool) -> bool {
// When managed network requirements are active, request proxy-only
// networking from the Linux sandbox helper. Without managed requirements,
// preserve existing behavior.
enforce_managed_network
}
/// Converts the sandbox policies into the CLI invocation for
/// `codex-linux-sandbox`.
///
/// The helper performs the actual sandboxing (bubblewrap by default + seccomp) after
/// parsing these arguments. Policy JSON flags are emitted before helper feature
/// flags so the argv order matches the helper's CLI shape. See
/// `docs/linux_sandbox.md` for the Linux semantics.
#[allow(clippy::too_many_arguments)]
pub fn create_linux_sandbox_command_args_for_policies(
command: Vec<String>,
command_cwd: &Path,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
use_legacy_landlock: bool,
allow_network_for_proxy: bool,
) -> Vec<String> {
let sandbox_policy_json = serde_json::to_string(sandbox_policy)
.unwrap_or_else(|err| panic!("failed to serialize sandbox policy: {err}"));
let file_system_policy_json = serde_json::to_string(file_system_sandbox_policy)
.unwrap_or_else(|err| panic!("failed to serialize filesystem sandbox policy: {err}"));
let network_policy_json = serde_json::to_string(&network_sandbox_policy)
.unwrap_or_else(|err| panic!("failed to serialize network sandbox policy: {err}"));
let sandbox_policy_cwd = sandbox_policy_cwd
.to_str()
.unwrap_or_else(|| panic!("cwd must be valid UTF-8"))
.to_string();
let command_cwd = command_cwd
.to_str()
.unwrap_or_else(|| panic!("command cwd must be valid UTF-8"))
.to_string();
let mut linux_cmd: Vec<String> = vec![
"--sandbox-policy-cwd".to_string(),
sandbox_policy_cwd,
"--command-cwd".to_string(),
command_cwd,
"--sandbox-policy".to_string(),
sandbox_policy_json,
"--file-system-sandbox-policy".to_string(),
file_system_policy_json,
"--network-sandbox-policy".to_string(),
network_policy_json,
];
if use_legacy_landlock {
linux_cmd.push("--use-legacy-landlock".to_string());
}
if allow_network_for_proxy {
linux_cmd.push("--allow-network-for-proxy".to_string());
}
linux_cmd.push("--".to_string());
linux_cmd.extend(command);
linux_cmd
}
/// Converts the sandbox cwd and execution options into the CLI invocation for
/// `codex-linux-sandbox`.
#[cfg_attr(not(test), allow(dead_code))]
fn create_linux_sandbox_command_args(
command: Vec<String>,
command_cwd: &Path,
sandbox_policy_cwd: &Path,
use_legacy_landlock: bool,
allow_network_for_proxy: bool,
) -> Vec<String> {
let command_cwd = command_cwd
.to_str()
.unwrap_or_else(|| panic!("command cwd must be valid UTF-8"))
.to_string();
let sandbox_policy_cwd = sandbox_policy_cwd
.to_str()
.unwrap_or_else(|| panic!("cwd must be valid UTF-8"))
.to_string();
let mut linux_cmd: Vec<String> = vec![
"--sandbox-policy-cwd".to_string(),
sandbox_policy_cwd,
"--command-cwd".to_string(),
command_cwd,
];
if use_legacy_landlock {
linux_cmd.push("--use-legacy-landlock".to_string());
}
if allow_network_for_proxy {
linux_cmd.push("--allow-network-for-proxy".to_string());
}
// Separator so that command arguments starting with `-` are not parsed as
// options of the helper itself.
linux_cmd.push("--".to_string());
// Append the original tool command.
linux_cmd.extend(command);
linux_cmd
}
#[cfg(test)]
#[path = "landlock_tests.rs"]
mod tests;