Files
codex/codex-rs/linux-sandbox/src/vendored_bwrap.rs
viyatb-oai 0d1539e74c fix(linux-sandbox): prefer system /usr/bin/bwrap when available (#14963)
## Problem
Ubuntu/AppArmor hosts started failing in the default Linux sandbox path
after the switch to vendored/default bubblewrap in `0.115.0`.

The clearest report is in
[#14919](https://github.com/openai/codex/issues/14919), especially [this
investigation
comment](https://github.com/openai/codex/issues/14919#issuecomment-4076504751):
on affected Ubuntu systems, `/usr/bin/bwrap` works, but a copied or
vendored `bwrap` binary fails with errors like `bwrap: setting up uid
map: Permission denied` or `bwrap: loopback: Failed RTM_NEWADDR:
Operation not permitted`.

The root cause is Ubuntu's `/etc/apparmor.d/bwrap-userns-restrict`
profile, which grants `userns` access specifically to `/usr/bin/bwrap`.
Once Codex started using a vendored/internal bubblewrap path, that path
was no longer covered by the distro AppArmor exception, so sandbox
namespace setup could fail even when user namespaces were otherwise
enabled and `uidmap` was installed.

## What this PR changes
- prefer system `/usr/bin/bwrap` whenever it is available
- keep vendored bubblewrap as the fallback when `/usr/bin/bwrap` is
missing
- when `/usr/bin/bwrap` is missing, surface a Codex startup warning
through the app-server/TUI warning path instead of printing directly
from the sandbox helper with `eprintln!`
- use the same launcher decision for both the main sandbox execution
path and the `/proc` preflight path
- document the updated Linux bubblewrap behavior in the Linux sandbox
and core READMEs

## Why this fix
This still fixes the Ubuntu/AppArmor regression from
[#14919](https://github.com/openai/codex/issues/14919), but it keeps the
runtime rule simple and platform-agnostic: if the standard system
bubblewrap is installed, use it; otherwise fall back to the vendored
helper.

The warning now follows that same simple rule. If Codex cannot find
`/usr/bin/bwrap`, it tells the user that it is falling back to the
vendored helper, and it does so through the existing startup warning
plumbing that reaches the TUI and app-server instead of low-level
sandbox stderr.

## Testing
- `cargo test -p codex-linux-sandbox`
- `cargo test -p codex-app-server --lib`
- `cargo test -p codex-tui-app-server
tests::embedded_app_server_start_failure_is_returned`
- `cargo clippy -p codex-linux-sandbox --all-targets`
- `cargo clippy -p codex-app-server --all-targets`
- `cargo clippy -p codex-tui-app-server --all-targets`
2026-03-17 23:05:34 +00:00

79 lines
2.8 KiB
Rust

//! Build-time bubblewrap entrypoint.
//!
//! On Linux targets, the build script compiles bubblewrap's C sources and
//! exposes a `bwrap_main` symbol that we can call via FFI.
#[cfg(vendored_bwrap_available)]
mod imp {
use std::ffi::CString;
use std::fs::File;
use std::os::raw::c_char;
unsafe extern "C" {
fn bwrap_main(argc: libc::c_int, argv: *const *const c_char) -> libc::c_int;
}
fn argv_to_cstrings(argv: &[String]) -> Vec<CString> {
let mut cstrings: Vec<CString> = Vec::with_capacity(argv.len());
for arg in argv {
match CString::new(arg.as_str()) {
Ok(value) => cstrings.push(value),
Err(err) => panic!("failed to convert argv to CString: {err}"),
}
}
cstrings
}
/// Run the build-time bubblewrap `main` function and return its exit code.
///
/// On success, bubblewrap will `execve` into the target program and this
/// function will never return. A return value therefore implies failure.
pub(crate) fn run_vendored_bwrap_main(
argv: &[String],
_preserved_files: &[File],
) -> libc::c_int {
let cstrings = argv_to_cstrings(argv);
let mut argv_ptrs: Vec<*const c_char> = cstrings.iter().map(|arg| arg.as_ptr()).collect();
argv_ptrs.push(std::ptr::null());
// SAFETY: We provide a null-terminated argv vector whose pointers
// remain valid for the duration of the call.
unsafe { bwrap_main(cstrings.len() as libc::c_int, argv_ptrs.as_ptr()) }
}
/// Execute the build-time bubblewrap `main` function with the given argv.
pub(crate) fn exec_vendored_bwrap(argv: Vec<String>, preserved_files: Vec<File>) -> ! {
let exit_code = run_vendored_bwrap_main(&argv, &preserved_files);
std::process::exit(exit_code);
}
}
#[cfg(not(vendored_bwrap_available))]
mod imp {
use std::fs::File;
/// Panics with a clear error when the build-time bwrap path is not enabled.
pub(crate) fn run_vendored_bwrap_main(
_argv: &[String],
_preserved_files: &[File],
) -> libc::c_int {
panic!(
r#"build-time bubblewrap is not available in this build.
codex-linux-sandbox should always compile vendored bubblewrap on Linux targets.
Notes:
- ensure the target OS is Linux
- libcap headers must be available via pkg-config
- bubblewrap sources expected at codex-rs/vendor/bubblewrap (default)"#
);
}
/// Panics with a clear error when the build-time bwrap path is not enabled.
pub(crate) fn exec_vendored_bwrap(_argv: Vec<String>, _preserved_files: Vec<File>) -> ! {
let _ = run_vendored_bwrap_main(&[], &[]);
unreachable!("run_vendored_bwrap_main should always panic in this configuration")
}
}
pub(crate) use imp::exec_vendored_bwrap;