use std::ffi::CStr; use std::ffi::CString; use std::ffi::OsString; use std::fs::File; use std::io::Read; use std::os::fd::AsRawFd; use std::os::raw::c_char; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::path::PathBuf; use std::sync::OnceLock; use crate::bazel_bwrap; use crate::exec_util::argv_to_cstrings; use crate::exec_util::make_files_inheritable; use codex_install_context::InstallContext; use codex_utils_absolute_path::AbsolutePathBuf; use sha2::Digest as _; use sha2::Sha256; const SHA256_HEX_LEN: usize = 64; const NULL_SHA256_DIGEST: [u8; 32] = [0; 32]; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct BundledBwrapLauncher { program: AbsolutePathBuf, } pub(crate) fn launcher() -> Option { let current_exe = std::env::current_exe().ok()?; find_program( InstallContext::current(), ¤t_exe, Path::new("."), |key| std::env::var_os(key), ) .map(|program| BundledBwrapLauncher { program }) } /// Discover the bundled fallback for an explicit launcher and its environment. pub fn find_bundled_bwrap_for_exe( exe: &Path, command_cwd: &Path, env: impl Fn(&str) -> Option, ) -> Option { let exe = std::fs::canonicalize(exe).ok()?; let context = InstallContext::from_exe( /*is_macos*/ false, Some(&exe), /*method_override*/ None, ); find_program(&context, &exe, command_cwd, env) } fn find_program( context: &InstallContext, exe: &Path, cwd: &Path, env: impl Fn(&str) -> Option, ) -> Option { find_for_install_context(context) .or_else(|| find_legacy_for_exe(exe, bazel_bwrap::candidate(cwd, env))) } impl BundledBwrapLauncher { pub(crate) fn exec(&self, argv: Vec, preserved_files: Vec) -> ! { let bwrap_file = File::open(self.program.as_path()).unwrap_or_else(|err| { panic!( "failed to open bundled bubblewrap {}: {err}", self.program.as_path().display() ) }); 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); let fd_path = format!("/proc/self/fd/{}", bwrap_file.as_raw_fd()); let program_cstring = CString::new(fd_path.as_str()) .unwrap_or_else(|err| panic!("invalid bundled bubblewrap fd path: {err}")); let cstrings = argv_to_cstrings(&argv); let mut argv_ptrs: Vec<*const c_char> = cstrings .iter() .map(CString::as_c_str) .map(CStr::as_ptr) .collect(); argv_ptrs.push(std::ptr::null()); // SAFETY: `program_cstring` and every entry in `argv_ptrs` are valid C // strings for the duration of the call. On success `execv` does not return. unsafe { libc::execv(program_cstring.as_ptr(), argv_ptrs.as_ptr()); } let err = std::io::Error::last_os_error(); panic!( "failed to exec bundled bubblewrap {} via {fd_path}: {err}", self.program.as_path().display() ); } } fn find_for_install_context(context: &InstallContext) -> Option { context .bundled_resource("bwrap") .filter(|path| is_executable_file(path)) } fn find_legacy_for_exe(exe: &Path, bazel_candidate: Option) -> Option { legacy_candidates_for_exe(exe, bazel_candidate) .into_iter() .find(|candidate| is_executable_file(candidate)) .map(|path| { AbsolutePathBuf::from_absolute_path(&path).unwrap_or_else(|err| { panic!( "failed to normalize bundled bubblewrap path {}: {err}", path.display() ) }) }) } fn legacy_candidates_for_exe(exe: &Path, bazel_candidate: Option) -> Vec { let Some(exe_dir) = exe.parent() else { return Vec::new(); }; let mut candidates = Vec::new(); candidates.push(exe_dir.join("codex-resources").join("bwrap")); if let Some(package_target_dir) = exe_dir.parent() { candidates.push(package_target_dir.join("codex-resources").join("bwrap")); } candidates.push(exe_dir.join("bwrap")); if let Some(path) = bazel_candidate { candidates.push(path); } candidates } fn is_executable_file(path: &Path) -> bool { let Ok(metadata) = path.metadata() else { return false; }; metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 } fn expected_sha256() -> Option<[u8; 32]> { static EXPECTED: OnceLock> = OnceLock::new(); *EXPECTED.get_or_init(|| { let raw_digest = option_env!("CODEX_BWRAP_SHA256")?; let digest = parse_sha256_hex(raw_digest) .unwrap_or_else(|err| panic!("invalid CODEX_BWRAP_SHA256 value: {err}")); (digest != NULL_SHA256_DIGEST).then_some(digest) }) } fn verify_digest(file: &File, expected: Option<[u8; 32]>, path: &Path) -> Result<(), String> { let Some(expected) = expected else { return Ok(()); }; let mut file = file .try_clone() .map_err(|err| format!("failed to clone bundled bubblewrap fd: {err}"))?; let mut hasher = Sha256::new(); let mut buffer = [0_u8; 8192]; loop { let read = file.read(&mut buffer).map_err(|err| { format!( "failed to read bundled bubblewrap {} for digest verification: {err}", path.display() ) })?; if read == 0 { break; } hasher.update(&buffer[..read]); } let actual: [u8; 32] = hasher.finalize().into(); if actual == expected { return Ok(()); } Err(format!( "bundled bubblewrap digest mismatch for {}: expected sha256:{}, got sha256:{}", path.display(), bytes_to_hex(&expected), bytes_to_hex(&actual), )) } fn parse_sha256_hex(raw: &str) -> Result<[u8; 32], String> { if raw.len() != SHA256_HEX_LEN { return Err(format!( "expected {SHA256_HEX_LEN} hex characters, got {}", raw.len() )); } let mut digest = [0_u8; 32]; for (index, byte) in digest.iter_mut().enumerate() { let start = index * 2; *byte = u8::from_str_radix(&raw[start..start + 2], 16) .map_err(|err| format!("invalid hex byte at offset {start}: {err}"))?; } Ok(digest) } fn bytes_to_hex(bytes: &[u8; 32]) -> String { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut hex = String::with_capacity(SHA256_HEX_LEN); for byte in bytes { hex.push(HEX[(byte >> 4) as usize] as char); hex.push(HEX[(byte & 0x0f) as usize] as char); } hex } #[cfg(test)] #[path = "bundled_bwrap_tests.rs"] mod tests;