fix: fall back to vendored bubblewrap when system bwrap lacks --argv0 (#15338)

## Why

Fixes [#15283](https://github.com/openai/codex/issues/15283), where
sandboxed tool calls fail on older distro `bubblewrap` builds because
`/usr/bin/bwrap` does not understand `--argv0`. The upstream [bubblewrap
v0.9.0 release
notes](https://github.com/containers/bubblewrap/releases/tag/v0.9.0)
explicitly call out `Add --argv0`. Flipping `use_legacy_landlock`
globally works around that compatibility bug, but it also weakens the
default Linux sandbox and breaks proxy-routed and split-policy cases
called out in review.

The follow-up Linux CI failure was in the new launcher test rather than
the launcher logic: the fake `bwrap` helper stayed open for writing, so
Linux would not exec it. This update also closes the user-visibility gap
from review by surfacing the same startup warning when `/usr/bin/bwrap`
is present but too old for `--argv0`, not only when it is missing.

## What Changed

- keep `use_legacy_landlock` default-disabled
- teach `codex-rs/linux-sandbox/src/launcher.rs` to fall back to the
vendored bubblewrap build when `/usr/bin/bwrap` does not advertise
`--argv0` support
- add launcher tests for supported, unsupported, and missing system
`bwrap`
- write the fake `bwrap` test helper to a closed temp path so the
supported-path launcher test works on Linux too
- extend the startup warning path so Codex warns when `/usr/bin/bwrap`
is missing or too old to support `--argv0`
- mirror the warning/fallback wording across
`codex-rs/linux-sandbox/README.md` and `codex-rs/core/README.md`,
including that the fallback is the vendored bubblewrap compiled into the
binary
- cite the upstream `bubblewrap` release that introduced `--argv0`

## Verification

- `bazel test --config=remote --platforms=//:rbe
//codex-rs/linux-sandbox:linux-sandbox-unit-tests
--test_filter=launcher::tests::prefers_system_bwrap_when_help_lists_argv0
--test_output=errors`
- `cargo test -p codex-core system_bwrap_warning`
- `cargo check -p codex-exec -p codex-tui -p codex-tui-app-server -p
codex-app-server`
- `just argument-comment-lint`
This commit is contained in:
Michael Bolin
2026-03-23 09:46:51 -07:00
committed by GitHub
parent d807d44ae7
commit d1088158b8
9 changed files with 210 additions and 39 deletions

View File

@@ -4,6 +4,8 @@ use std::os::fd::AsRawFd;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::process::Command;
use std::sync::OnceLock;
use crate::vendored_bwrap::exec_vendored_bwrap;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -24,17 +26,41 @@ pub(crate) fn exec_bwrap(argv: Vec<String>, preserved_files: Vec<File>) -> ! {
}
fn preferred_bwrap_launcher() -> BubblewrapLauncher {
if !Path::new(SYSTEM_BWRAP_PATH).is_file() {
static LAUNCHER: OnceLock<BubblewrapLauncher> = OnceLock::new();
LAUNCHER
.get_or_init(|| preferred_bwrap_launcher_for_path(Path::new(SYSTEM_BWRAP_PATH)))
.clone()
}
fn preferred_bwrap_launcher_for_path(system_bwrap_path: &Path) -> BubblewrapLauncher {
if !system_bwrap_supports_argv0(system_bwrap_path) {
return BubblewrapLauncher::Vendored;
}
let system_bwrap_path = match AbsolutePathBuf::from_absolute_path(SYSTEM_BWRAP_PATH) {
let system_bwrap_path = match AbsolutePathBuf::from_absolute_path(system_bwrap_path) {
Ok(path) => path,
Err(err) => panic!("failed to normalize system bubblewrap path {SYSTEM_BWRAP_PATH}: {err}"),
Err(err) => panic!(
"failed to normalize system bubblewrap path {}: {err}",
system_bwrap_path.display()
),
};
BubblewrapLauncher::System(system_bwrap_path)
}
fn system_bwrap_supports_argv0(system_bwrap_path: &Path) -> bool {
// 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 prefer the vendored build in that case.
let output = match Command::new(system_bwrap_path).arg("--help").output() {
Ok(output) => output,
Err(_) => return false,
};
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
stdout.contains("--argv0") || stderr.contains("--argv0")
}
fn exec_system_bwrap(
program: &AbsolutePathBuf,
argv: Vec<String>,
@@ -100,7 +126,57 @@ fn clear_cloexec(fd: libc::c_int) {
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use tempfile::NamedTempFile;
use tempfile::TempPath;
#[test]
fn prefers_system_bwrap_when_help_lists_argv0() {
let fake_bwrap = write_fake_bwrap(
r#"#!/bin/sh
if [ "$1" = "--help" ]; then
echo ' --argv0 PROGRAM'
exit 0
fi
exit 1
"#,
);
let fake_bwrap_path: &Path = fake_bwrap.as_ref();
let expected = AbsolutePathBuf::from_absolute_path(fake_bwrap_path).expect("absolute");
assert_eq!(
preferred_bwrap_launcher_for_path(fake_bwrap_path),
BubblewrapLauncher::System(expected)
);
}
#[test]
fn falls_back_to_vendored_when_system_bwrap_lacks_argv0() {
let fake_bwrap = write_fake_bwrap(
r#"#!/bin/sh
if [ "$1" = "--help" ]; then
echo 'usage: bwrap [OPTION...] COMMAND'
exit 0
fi
exit 1
"#,
);
let fake_bwrap_path: &Path = fake_bwrap.as_ref();
assert_eq!(
preferred_bwrap_launcher_for_path(fake_bwrap_path),
BubblewrapLauncher::Vendored
);
}
#[test]
fn falls_back_to_vendored_when_system_bwrap_is_missing() {
assert_eq!(
preferred_bwrap_launcher_for_path(Path::new("/definitely/not/a/bwrap")),
BubblewrapLauncher::Vendored
);
}
#[test]
fn preserved_files_are_made_inheritable_for_system_exec() {
@@ -131,4 +207,13 @@ mod tests {
}
flags
}
fn write_fake_bwrap(contents: &str) -> TempPath {
// Linux rejects exec-ing a file that is still open for writing.
let path = NamedTempFile::new().expect("temp file").into_temp_path();
fs::write(&path, contents).expect("write fake bwrap");
let permissions = fs::Permissions::from_mode(0o755);
fs::set_permissions(&path, permissions).expect("chmod fake bwrap");
path
}
}