mirror of
https://github.com/openai/codex.git
synced 2026-11-14 06:48:26 +00:00
fix: warn when bwrap cannot create user namespaces (#15893)
## Summary - add a Linux startup warning when system `bwrap` is present but cannot create user namespaces - keep the Linux-specific probe, sandbox-policy gate, and stderr matching in `codex-sandboxing` - polish the missing-`bwrap` warning to point users at the sandbox prerequisites and OS package-manager install path ## Details - probes system `bwrap` with `--unshare-user`, `--unshare-net`, and a minimal bind before command execution - detects known bubblewrap setup failures for `RTM_NEWADDR`, `RTM_NEWLINK`, uid-map permission denial, and `No permissions to create a new namespace` - preserves the existing suppression for sandbox-bypassed policies such as `danger-full-access` and `external-sandbox` - updates the Linux sandbox docs to call out the user-namespace requirement --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
1 parent
5fe9ef06ce
commit
806e5f7c69
4 files changed
+87
-25
No files matched your search
@@ -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
|
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
|
`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
|
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
|
### Windows
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
the helper falls back to the vendored bubblewrap path compiled into this
|
||||||
binary.
|
binary.
|
||||||
Codex also surfaces a startup warning when `bwrap` is missing so users know it
|
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**
|
**Current Behavior**
|
||||||
- Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported.
|
- Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported.
|
||||||
@@ -28,6 +29,8 @@ is falling back to the vendored helper.
|
|||||||
path.
|
path.
|
||||||
- If `bwrap` is missing, Codex also surfaces a startup warning instead of
|
- If `bwrap` is missing, Codex also surfaces a startup warning instead of
|
||||||
printing directly from the sandbox helper.
|
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
|
- Legacy Landlock + mount protections remain available as an explicit legacy
|
||||||
fallback path.
|
fallback path.
|
||||||
- Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`)
|
- Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`)
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::process::Output;
|
||||||
|
|
||||||
const SYSTEM_BWRAP_PROGRAM: &str = "bwrap";
|
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> {
|
pub fn system_bwrap_warning(sandbox_policy: &SandboxPolicy) -> Option<String> {
|
||||||
if !should_warn_about_system_bwrap(sandbox_policy) {
|
if !should_warn_about_system_bwrap(sandbox_policy) {
|
||||||
return None;
|
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 {
|
fn should_warn_about_system_bwrap(sandbox_policy: &SandboxPolicy) -> bool {
|
||||||
@@ -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> {
|
fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option<String> {
|
||||||
match system_bwrap_path {
|
let Some(system_bwrap_path) = system_bwrap_path else {
|
||||||
Some(_) => None,
|
return Some(MISSING_BWRAP_WARNING.to_string());
|
||||||
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(),
|
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> {
|
pub fn find_system_bwrap_in_path() -> Option<PathBuf> {
|
||||||
|
|||||||
@@ -6,30 +6,42 @@ use tempfile::tempdir;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn system_bwrap_warning_reports_missing_system_bwrap() {
|
fn system_bwrap_warning_reports_missing_system_bwrap() {
|
||||||
let warning = system_bwrap_warning_for_lookup(/*system_bwrap_path*/ None)
|
assert_eq!(
|
||||||
.expect("missing system bwrap should emit a warning");
|
system_bwrap_warning_for_path(/*system_bwrap_path*/ None),
|
||||||
|
Some(MISSING_BWRAP_WARNING.to_string())
|
||||||
assert!(warning.contains("could not find system bubblewrap"));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn system_bwrap_warning_skips_too_old_system_bwrap() {
|
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_unrelated_bwrap_failures() {
|
||||||
let fake_bwrap = write_fake_bwrap(
|
let fake_bwrap = write_fake_bwrap(
|
||||||
r#"#!/bin/sh
|
r#"#!/bin/sh
|
||||||
if [ "$1" = "--help" ]; then
|
echo 'bwrap: Unknown option --argv0' >&2
|
||||||
echo 'usage: bwrap [OPTION...] COMMAND'
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
exit 1
|
exit 1
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let fake_bwrap_path: &Path = fake_bwrap.as_ref();
|
let fake_bwrap_path: &Path = fake_bwrap.as_ref();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(system_bwrap_warning_for_path(Some(fake_bwrap_path)), None);
|
||||||
system_bwrap_warning_for_lookup(Some(fake_bwrap_path.to_path_buf())),
|
|
||||||
None,
|
|
||||||
"Do not warn even if bwrap does not support `--argv0`",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -102,5 +114,5 @@ fn write_named_fake_bwrap_in(dir: &Path) -> PathBuf {
|
|||||||
fs::write(&path, "#!/bin/sh\n").expect("write fake bwrap");
|
fs::write(&path, "#!/bin/sh\n").expect("write fake bwrap");
|
||||||
let permissions = fs::Permissions::from_mode(0o755);
|
let permissions = fs::Permissions::from_mode(0o755);
|
||||||
fs::set_permissions(&path, permissions).expect("chmod fake bwrap");
|
fs::set_permissions(&path, permissions).expect("chmod fake bwrap");
|
||||||
path
|
fs::canonicalize(path).expect("canonicalize fake bwrap")
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user