Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion codex-rs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ too old to support `--argv0`, the helper keeps using system bubblewrap and
switches to a no-`--argv0` compatibility path for the inner re-exec. If
`bwrap` is missing, it falls back to the vendored bubblewrap path compiled into
the binary and Codex surfaces a startup warning through its normal notification
path instead of printing directly from the sandbox helper.
path instead of printing directly from the sandbox helper. Codex also surfaces
a startup warning when bubblewrap cannot create user namespaces.

### Windows

Expand Down
5 changes: 4 additions & 1 deletion codex-rs/linux-sandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ no-`--argv0` compatibility path for the inner re-exec. If `bwrap` is missing,
the helper falls back to the vendored bubblewrap path compiled into this
binary.
Codex also surfaces a startup warning when `bwrap` is missing so users know it
is falling back to the vendored helper.
is falling back to the vendored helper. Codex surfaces the same startup warning
path when bubblewrap cannot create user namespaces.

**Current Behavior**
- Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported.
Expand All @@ -28,6 +29,8 @@ is falling back to the vendored helper.
path.
- If `bwrap` is missing, Codex also surfaces a startup warning instead of
printing directly from the sandbox helper.
- If bubblewrap cannot create user namespaces, Codex surfaces a startup warning
instead of waiting for a runtime sandbox failure.
- Legacy Landlock + mount protections remain available as an explicit legacy
fallback path.
- Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`)
Expand Down
62 changes: 54 additions & 8 deletions codex-rs/sandboxing/src/bwrap.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
use codex_protocol::protocol::SandboxPolicy;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Output;

const SYSTEM_BWRAP_PROGRAM: &str = "bwrap";
const MISSING_BWRAP_WARNING: &str = concat!(
"Codex could not find bubblewrap on PATH. ",
"Install bubblewrap with your OS package manager. ",
"See the sandbox prerequisites: ",
"https://developers.openai.com/codex/concepts/sandboxing#prerequisites. ",
"Codex will use the vendored bubblewrap in the meantime.",
);
const USER_NAMESPACE_WARNING: &str =
"Codex's Linux sandbox uses bubblewrap and needs access to create user namespaces.";
const USER_NAMESPACE_FAILURES: [&str; 4] = [
"loopback: Failed RTM_NEWADDR",
"loopback: Failed RTM_NEWLINK",
"setting up uid map: Permission denied",
"No permissions to create a new namespace",
];

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

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

fn should_warn_about_system_bwrap(sandbox_policy: &SandboxPolicy) -> bool {
Expand All @@ -19,14 +37,42 @@ fn should_warn_about_system_bwrap(sandbox_policy: &SandboxPolicy) -> bool {
)
}

fn system_bwrap_warning_for_lookup(system_bwrap_path: Option<PathBuf>) -> Option<String> {
match system_bwrap_path {
Some(_) => None,
None => Some(
"Codex could not find system bubblewrap on PATH. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime."
.to_string(),
),
fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option<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) {
return Some(USER_NAMESPACE_WARNING.to_string());
}

None
}

fn system_bwrap_has_user_namespace_access(system_bwrap_path: &Path) -> bool {
let output = match Command::new(system_bwrap_path)
.args([
"--unshare-user",
"--unshare-net",
"--ro-bind",
"/",
"/",
"/bin/true",
])
.output()
{
Ok(output) => output,
Err(_) => return true,
};

output.status.success() || !is_user_namespace_failure(&output)
}

fn is_user_namespace_failure(output: &Output) -> bool {
let stderr = String::from_utf8_lossy(&output.stderr);
USER_NAMESPACE_FAILURES
.iter()
.any(|failure| stderr.contains(failure))
}

pub fn find_system_bwrap_in_path() -> Option<PathBuf> {
Expand Down
40 changes: 26 additions & 14 deletions codex-rs/sandboxing/src/bwrap_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,42 @@ use tempfile::tempdir;

#[test]
fn system_bwrap_warning_reports_missing_system_bwrap() {
let warning = system_bwrap_warning_for_lookup(/*system_bwrap_path*/ None)
.expect("missing system bwrap should emit a warning");
assert_eq!(
system_bwrap_warning_for_path(/*system_bwrap_path*/ None),
Some(MISSING_BWRAP_WARNING.to_string())
);
}

assert!(warning.contains("could not find system bubblewrap"));
#[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}",
);
}
}

#[test]
fn system_bwrap_warning_skips_too_old_system_bwrap() {
fn system_bwrap_warning_skips_unrelated_bwrap_failures() {
let fake_bwrap = write_fake_bwrap(
r#"#!/bin/sh
if [ "$1" = "--help" ]; then
echo 'usage: bwrap [OPTION...] COMMAND'
exit 0
fi
echo 'bwrap: Unknown option --argv0' >&2
exit 1
"#,
);
let fake_bwrap_path: &Path = fake_bwrap.as_ref();

assert_eq!(
system_bwrap_warning_for_lookup(Some(fake_bwrap_path.to_path_buf())),
None,
"Do not warn even if bwrap does not support `--argv0`",
);
assert_eq!(system_bwrap_warning_for_path(Some(fake_bwrap_path)), None);
}

#[test]
Expand Down Expand Up @@ -102,5 +114,5 @@ fn write_named_fake_bwrap_in(dir: &Path) -> PathBuf {
fs::write(&path, "#!/bin/sh\n").expect("write fake bwrap");
let permissions = fs::Permissions::from_mode(0o755);
fs::set_permissions(&path, permissions).expect("chmod fake bwrap");
path
fs::canonicalize(path).expect("canonicalize fake bwrap")
}
Loading