From 636e505c5cd809bdce37314f77130ffb4e45c46b Mon Sep 17 00:00:00 2001 From: "Adam Perry @ OpenAI" Date: Fri, 14 Aug 2026 04:53:01 +0000 Subject: [PATCH] Verify bundled bwrap in Bazel builds (#38494) ## Why Bazel builds did not embed the SHA-256 digest used to verify the bundled `bwrap` binary at launch. ## What changed - Strip the Bazel-built `bwrap`, generate its SHA-256 digest, and pass that digest to `codex-linux-sandbox` through a compiler environment file. - Report digest verification failures with exit code `8` instead of panicking. - Add a Bazel integration test that accepts the original bundled binary and rejects a tampered copy. GitOrigin-RevId: 224eeb6ae3d1bfb100e048b296f75fd8ba109581 --- codex-rs/bwrap/BUILD.bazel | 20 ++++- codex-rs/linux-sandbox/BUILD.bazel | 4 + codex-rs/linux-sandbox/src/bundled_bwrap.rs | 6 +- codex-rs/linux-sandbox/src/lib.rs | 4 + .../tests/suite/bundled_bwrap.rs | 78 +++++++++++++++++++ codex-rs/linux-sandbox/tests/suite/mod.rs | 1 + defs.bzl | 3 + 7 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 codex-rs/linux-sandbox/tests/suite/bundled_bwrap.rs diff --git a/codex-rs/bwrap/BUILD.bazel b/codex-rs/bwrap/BUILD.bazel index 44adc3feb6..f58d703ef9 100644 --- a/codex-rs/bwrap/BUILD.bazel +++ b/codex-rs/bwrap/BUILD.bazel @@ -12,11 +12,29 @@ codex_rust_crate( "//conditions:default": [], }), rustc_flags_extra = select({ - "@platforms//os:linux": ["--cfg=bwrap_available"], + "@platforms//os:linux": [ + "--cfg=bwrap_available", + # TODO(anp) Extract bwrap symbols before stripping. + "-Cstrip=symbols", + ], "//conditions:default": [], }), ) +genrule( + name = "bwrap-sha256-env", + srcs = [":bwrap"], + outs = ["bwrap.sha256.env"], + cmd = " && ".join([ + '$(execpath @bazel_tools//tools/build_defs/hash:sha256) $(execpath :bwrap) "$@"', + 'digest=$$(<"$@")', + 'printf "CODEX_BWRAP_SHA256=%s\\n" "$$digest" > "$@"', + ]), + target_compatible_with = ["@platforms//os:linux"], + tools = ["@bazel_tools//tools/build_defs/hash:sha256"], + visibility = ["//codex-rs/linux-sandbox:__pkg__"], +) + cc_library( name = "bwrap-ffi", srcs = ["//codex-rs/vendor:bubblewrap_c_sources"], diff --git a/codex-rs/linux-sandbox/BUILD.bazel b/codex-rs/linux-sandbox/BUILD.bazel index 2770b97e09..984f870ba2 100644 --- a/codex-rs/linux-sandbox/BUILD.bazel +++ b/codex-rs/linux-sandbox/BUILD.bazel @@ -6,4 +6,8 @@ codex_rust_crate( extra_binaries = [ "//codex-rs/bwrap:bwrap", ], + rustc_env_files = select({ + "@platforms//os:linux": ["//codex-rs/bwrap:bwrap-sha256-env"], + "//conditions:default": [], + }), ) diff --git a/codex-rs/linux-sandbox/src/bundled_bwrap.rs b/codex-rs/linux-sandbox/src/bundled_bwrap.rs index 972be9dac3..61d10547d3 100644 --- a/codex-rs/linux-sandbox/src/bundled_bwrap.rs +++ b/codex-rs/linux-sandbox/src/bundled_bwrap.rs @@ -40,8 +40,10 @@ impl BundledBwrapLauncher { self.program.as_path().display() ) }); - verify_digest(&bwrap_file, expected_sha256(), self.program.as_path()) - .unwrap_or_else(|err| panic!("{err}")); + if let Err(err) = verify_digest(&bwrap_file, expected_sha256(), self.program.as_path()) { + eprintln!("{err}"); + std::process::exit(crate::BUNDLED_BWRAP_DIGEST_VERIFICATION_FAILURE_EXIT_CODE); + } make_files_inheritable(&preserved_files); diff --git a/codex-rs/linux-sandbox/src/lib.rs b/codex-rs/linux-sandbox/src/lib.rs index 2cbc07f6da..9599d562a0 100644 --- a/codex-rs/linux-sandbox/src/lib.rs +++ b/codex-rs/linux-sandbox/src/lib.rs @@ -22,6 +22,10 @@ mod proxy_lifecycle; #[cfg(target_os = "linux")] mod proxy_routing; +/// Exit status returned when bundled bubblewrap fails digest verification. +#[cfg(target_os = "linux")] +pub const BUNDLED_BWRAP_DIGEST_VERIFICATION_FAILURE_EXIT_CODE: i32 = 8; + #[cfg(target_os = "linux")] pub fn run_main() -> ! { linux_run_main::run_main(); diff --git a/codex-rs/linux-sandbox/tests/suite/bundled_bwrap.rs b/codex-rs/linux-sandbox/tests/suite/bundled_bwrap.rs new file mode 100644 index 0000000000..e5b1d43848 --- /dev/null +++ b/codex-rs/linux-sandbox/tests/suite/bundled_bwrap.rs @@ -0,0 +1,78 @@ +#![cfg(target_os = "linux")] + +use codex_linux_sandbox::BUNDLED_BWRAP_DIGEST_VERIFICATION_FAILURE_EXIT_CODE; +use codex_protocol::models::PermissionProfile; +use pretty_assertions::assert_eq; +use std::path::PathBuf; +use std::process::Command; + +#[test] +fn bazel_build_rejects_tampered_bundled_bwrap() { + // Cargo embeds the bundled bwrap digest only in release builds. + if option_env!("BAZEL_PACKAGE").is_none() { + return; + } + + let bwrap_runfile = + std::env::var("CARGO_BIN_EXE_bwrap").expect("Bazel should provide the bwrap runfile"); + let runfiles_dir = + std::env::var_os("TEST_SRCDIR").expect("Bazel should provide its runfiles directory"); + let bwrap_binary = PathBuf::from(runfiles_dir).join(bwrap_runfile); + + let original_bwrap_bytes = + std::fs::read(&bwrap_binary).expect("built bwrap should be readable"); + + let package = tempfile::tempdir().expect("package directory should be created"); + let resources = package.path().join("codex-resources"); + std::fs::create_dir(&resources).expect("package resource directory should be created"); + + let sandbox_binary = package.path().join("codex-linux-sandbox"); + std::fs::copy(env!("CARGO_BIN_EXE_codex-linux-sandbox"), &sandbox_binary) + .expect("sandbox binary should be copied into the package"); + + let bundled_bwrap = resources.join("bwrap"); + std::fs::copy(&bwrap_binary, &bundled_bwrap) + .expect("built bwrap should be copied into the package"); + + let permission_profile = serde_json::to_string(&PermissionProfile::read_only()) + .expect("read-only permission profile should serialize"); + let run_sandbox = || { + Command::new(&sandbox_binary) + .arg("--sandbox-policy-cwd") + .arg(package.path()) + .arg("--permission-profile") + .arg(&permission_profile) + .arg("--no-proc") + .arg("--") + .arg("/bin/true") + .env_clear() + .env("PATH", package.path()) + .output() + .expect("sandbox binary should start") + }; + + let original_output = run_sandbox(); + assert_ne!( + original_output.status.code(), + Some(BUNDLED_BWRAP_DIGEST_VERIFICATION_FAILURE_EXIT_CODE), + "sandbox should accept the digest of the unmodified bundled bwrap" + ); + + std::fs::remove_file(&bundled_bwrap).expect("read-only bundled bwrap should be replaceable"); + let mut tampered_bwrap_bytes = original_bwrap_bytes; + tampered_bwrap_bytes.push(0); + std::fs::write(&bundled_bwrap, &tampered_bwrap_bytes) + .expect("modified bwrap should be copied into the package"); + let bwrap_permissions = std::fs::metadata(&bwrap_binary) + .expect("built bwrap metadata should be readable") + .permissions(); + std::fs::set_permissions(&bundled_bwrap, bwrap_permissions) + .expect("bundled bwrap should remain executable"); + + let tampered_output = run_sandbox(); + assert_eq!( + tampered_output.status.code(), + Some(BUNDLED_BWRAP_DIGEST_VERIFICATION_FAILURE_EXIT_CODE), + "sandbox should reject the tampered bundled bwrap" + ); +} diff --git a/codex-rs/linux-sandbox/tests/suite/mod.rs b/codex-rs/linux-sandbox/tests/suite/mod.rs index 8ef44a61a4..e24b787861 100644 --- a/codex-rs/linux-sandbox/tests/suite/mod.rs +++ b/codex-rs/linux-sandbox/tests/suite/mod.rs @@ -1,3 +1,4 @@ // Aggregates all former standalone integration tests as modules. +mod bundled_bwrap; mod landlock; mod managed_proxy; diff --git a/defs.bzl b/defs.bzl index 84162331cd..bb2426f112 100644 --- a/defs.bzl +++ b/defs.bzl @@ -193,6 +193,7 @@ def codex_rust_crate( rustc_flags_extra = [], binary_rustc_flags_extra = {}, rustc_env = {}, + rustc_env_files = [], deps_extra = [], integration_compile_data_extra = [], integration_test_args = [], @@ -234,6 +235,7 @@ def codex_rust_crate( binary_rustc_flags_extra: Mapping from binary names to extra rustc flags for those binary targets. rustc_env: Extra rustc_env entries to merge with defaults. + rustc_env_files: Generated compiler environment files for the library target. deps_extra: Extra normal deps beyond @crates resolution. Typically only needed when features add additional deps. integration_compile_data_extra: Extra compile_data for integration tests. @@ -325,6 +327,7 @@ def codex_rust_crate( edition = crate_edition, rustc_flags = rustc_flags_extra, rustc_env = rustc_env, + rustc_env_files = rustc_env_files, visibility = ["//visibility:public"], )