linux-sandbox: use standalone bundled bwrap (#21255)

**Summary**
- Add `codex-bwrap`, a standalone `bwrap` binary built from the existing
vendored bubblewrap sources.
- Remove the linked vendored bwrap path from `codex-linux-sandbox`;
runtime now prefers system `bwrap` and falls back to bundled
`codex-resources/bwrap`.
- Add bundled SHA-256 verification with missing/all-zero digest as the
dev-mode skip value, then exec the verified file through
`/proc/self/fd`.
- Keep `launcher.rs` focused on choosing and dispatching the preferred
launcher. Bundled lookup, digest verification, and bundled exec now live
in `linux-sandbox/src/bundled_bwrap.rs`; Bazel runfiles lookup lives in
`linux-sandbox/src/bazel_bwrap.rs`; shared argv/fd exec helpers live in
`linux-sandbox/src/exec_util.rs`.
- Teach Bazel tests to surface the Bazel-built `//codex-rs/bwrap:bwrap`
through `CARGO_BIN_EXE_bwrap`; `codex-linux-sandbox` only honors that
fallback in debug Bazel runfiles environments so release/user runtime
lookup stays tied to `codex-resources/bwrap`.
- Allow `codex-exec-server` filesystem helpers to preserve just the
Bazel bwrap/runfiles variables they need in debug Bazel builds, since
those helpers intentionally rebuild a small environment before spawning
`codex-linux-sandbox`.
- Verify the Bazel bwrap target in Linux release CI with a build-only
check. Running `bwrap --version` is too strong for GitHub runners
because bubblewrap still attempts namespace setup there.

**Verification**
- Latest update: `cargo test -p codex-linux-sandbox`
- Latest update: `just fix -p codex-linux-sandbox`
- `cargo check --target x86_64-unknown-linux-gnu -p codex-linux-sandbox`
could not run locally because this macOS machine does not have
`x86_64-linux-gnu-gcc`; GitHub Linux Bazel CI is expected to cover the
Linux-only modules.
- Earlier in this PR: `cargo test -p codex-bwrap`
- Earlier in this PR: `cargo test -p codex-exec-server`
- Earlier in this PR: `cargo check --release -p codex-exec-server`
- Earlier in this PR: `just fix -p codex-linux-sandbox -p
codex-exec-server`
- Earlier in this PR: `bazel test --nobuild
//codex-rs/linux-sandbox:linux-sandbox-all-test
//codex-rs/core:core-all-test
//codex-rs/exec-server:exec-server-file_system-test
//codex-rs/app-server:app-server-all-test` (analysis completed; Bazel
then refuses to run tests under `--nobuild`)
- Earlier in this PR: `bazel build --nobuild //codex-rs/bwrap:bwrap`
- Prior to this update: `just bazel-lock-update`, `just
bazel-lock-check`, and YAML parse check for
`.github/workflows/bazel.yml`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21255).
* #21257
* #21256
* __->__ #21255
This commit is contained in:
Michael Bolin
2026-05-05 17:14:29 -07:00
committed by GitHub
parent 03d3403a41
commit 26f355b67b
27 changed files with 751 additions and 338 deletions
+35
View File
@@ -0,0 +1,35 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "bwrap",
crate_name = "codex_bwrap",
# Bazel wires vendored bubblewrap + libcap via :bwrap-ffi below and sets
# bwrap_available explicitly, so we skip Cargo's build.rs in Bazel builds.
build_script_enabled = False,
deps_extra = select({
"@platforms//os:linux": [":bwrap-ffi"],
"//conditions:default": [],
}),
rustc_flags_extra = select({
"@platforms//os:linux": ["--cfg=bwrap_available"],
"//conditions:default": [],
}),
)
cc_library(
name = "bwrap-ffi",
srcs = ["//codex-rs/vendor:bubblewrap_c_sources"],
hdrs = [
"config.h",
"//codex-rs/vendor:bubblewrap_headers",
],
copts = [
"-D_GNU_SOURCE",
"-Dmain=bwrap_main",
],
includes = ["."],
deps = ["@libcap//:libcap"],
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//visibility:private"],
)
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "codex-bwrap"
version.workspace = true
edition.workspace = true
license.workspace = true
[[bin]]
name = "bwrap"
path = "src/main.rs"
[lints]
workspace = true
[target.'cfg(target_os = "linux")'.dependencies]
libc = { workspace = true }
[build-dependencies]
cc = "1"
pkg-config = "0.3"
+99
View File
@@ -0,0 +1,99 @@
use std::env;
use std::path::Path;
use std::path::PathBuf;
fn main() {
println!("cargo:rustc-check-cfg=cfg(bwrap_available)");
println!("cargo:rerun-if-env-changed=CODEX_BWRAP_SOURCE_DIR");
println!("cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS");
println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
println!("cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR");
println!("cargo:rerun-if-env-changed=CODEX_SKIP_BWRAP_BUILD");
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
let vendor_dir = manifest_dir.join("../vendor/bubblewrap");
for source in ["bubblewrap.c", "bind-mount.c", "network.c", "utils.c"] {
println!(
"cargo:rerun-if-changed={}",
vendor_dir.join(source).display()
);
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os != "linux" || env::var_os("CODEX_SKIP_BWRAP_BUILD").is_some() {
return;
}
if let Err(err) = try_build_bwrap() {
panic!("failed to compile bubblewrap for Linux target: {err}");
}
}
fn try_build_bwrap() -> Result<(), String> {
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|err| err.to_string())?);
let out_dir = PathBuf::from(env::var("OUT_DIR").map_err(|err| err.to_string())?);
let src_dir = resolve_bwrap_source_dir(&manifest_dir)?;
let libcap = pkg_config::Config::new()
.probe("libcap")
.map_err(|err| format!("libcap not available via pkg-config: {err}"))?;
let config_h = out_dir.join("config.h");
std::fs::write(
&config_h,
r#"#pragma once
#define PACKAGE_STRING "bubblewrap built for Codex"
"#,
)
.map_err(|err| format!("failed to write {}: {err}", config_h.display()))?;
let mut build = cc::Build::new();
build
.file(src_dir.join("bubblewrap.c"))
.file(src_dir.join("bind-mount.c"))
.file(src_dir.join("network.c"))
.file(src_dir.join("utils.c"))
.include(&out_dir)
.include(&src_dir)
.define("_GNU_SOURCE", None)
// Rename `main` so the Rust wrapper can expose the Cargo-built binary.
.define("main", Some("bwrap_main"));
for include_path in libcap.include_paths {
// Use -idirafter so target sysroot headers win (musl cross builds),
// while still allowing libcap headers from the host toolchain.
build.flag(format!("-idirafter{}", include_path.display()));
}
build.compile("standalone_bwrap");
println!("cargo:rustc-cfg=bwrap_available");
Ok(())
}
/// Resolve the bubblewrap source directory used for build-time compilation.
///
/// Priority:
/// 1. `CODEX_BWRAP_SOURCE_DIR` points at an existing bubblewrap checkout.
/// 2. The vendored bubblewrap tree under `codex-rs/vendor/bubblewrap`.
fn resolve_bwrap_source_dir(manifest_dir: &Path) -> Result<PathBuf, String> {
if let Ok(path) = env::var("CODEX_BWRAP_SOURCE_DIR") {
let src_dir = PathBuf::from(path);
if src_dir.exists() {
return Ok(src_dir);
}
return Err(format!(
"CODEX_BWRAP_SOURCE_DIR was set but does not exist: {}",
src_dir.display()
));
}
let vendor_dir = manifest_dir.join("../vendor/bubblewrap");
if vendor_dir.exists() {
return Ok(vendor_dir);
}
Err(format!(
"expected vendored bubblewrap at {}, but it was not found.\n\
Set CODEX_BWRAP_SOURCE_DIR to an existing checkout or vendor bubblewrap under codex-rs/vendor.",
vendor_dir.display()
))
}
+1
View File
@@ -0,0 +1 @@
#define PACKAGE_STRING "bubblewrap built for Codex"
+45
View File
@@ -0,0 +1,45 @@
#[cfg(all(target_os = "linux", bwrap_available))]
fn main() {
use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt;
unsafe extern "C" {
fn bwrap_main(argc: libc::c_int, argv: *const *const c_char) -> libc::c_int;
}
let cstrings = std::env::args_os()
.map(|arg| {
CString::new(arg.as_os_str().as_bytes())
.unwrap_or_else(|err| panic!("failed to convert argv to CString: {err}"))
})
.collect::<Vec<_>>();
let mut argv_ptrs = cstrings
.iter()
.map(CString::as_c_str)
.map(CStr::as_ptr)
.collect::<Vec<*const c_char>>();
argv_ptrs.push(std::ptr::null());
// SAFETY: We provide a null-terminated argv vector whose pointers remain
// valid for the duration of the call.
let exit_code = unsafe { bwrap_main(cstrings.len() as libc::c_int, argv_ptrs.as_ptr()) };
std::process::exit(exit_code);
}
#[cfg(all(target_os = "linux", not(bwrap_available)))]
fn main() {
panic!(
r#"bubblewrap is not available in this build.
Notes:
- ensure the target OS is Linux
- libcap headers must be available via pkg-config
- bubblewrap sources expected at codex-rs/vendor/bubblewrap (default)"#
);
}
#[cfg(not(target_os = "linux"))]
fn main() {
panic!("bwrap is only supported on Linux");
}