mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
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
This commit is contained in:
committed by
copyberry
parent
45c9c74e29
commit
636e505c5c
@@ -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"],
|
||||
|
||||
@@ -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": [],
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
78
codex-rs/linux-sandbox/tests/suite/bundled_bwrap.rs
Normal file
78
codex-rs/linux-sandbox/tests/suite/bundled_bwrap.rs
Normal file
@@ -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"
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Aggregates all former standalone integration tests as modules.
|
||||
mod bundled_bwrap;
|
||||
mod landlock;
|
||||
mod managed_proxy;
|
||||
|
||||
3
defs.bzl
3
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"],
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user