Compare commits

...

1 Commits

Author SHA1 Message Date
viyatb-oai
7db3018aa7 fix: bound system bwrap startup probe
Co-authored-by: Codex noreply@openai.com
2026-04-28 18:26:33 -07:00
2 changed files with 65 additions and 6 deletions

View File

@@ -1,9 +1,14 @@
use crate::policy_transforms::should_require_platform_sandbox;
use codex_protocol::models::PermissionProfile;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Output;
use std::process::Stdio;
use std::thread;
use std::time::Duration;
use std::time::Instant;
const SYSTEM_BWRAP_PROGRAM: &str = "bwrap";
const MISSING_BWRAP_WARNING: &str = concat!(
@@ -26,6 +31,8 @@ const USER_NAMESPACE_FAILURES: [&str; 4] = [
"setting up uid map: Permission denied",
"No permissions to create a new namespace",
];
const SYSTEM_BWRAP_PROBE_TIMEOUT: Duration = Duration::from_millis(500);
const SYSTEM_BWRAP_PROBE_POLL_INTERVAL: Duration = Duration::from_millis(50);
pub fn system_bwrap_warning(permission_profile: &PermissionProfile) -> Option<String> {
if !should_warn_about_system_bwrap(permission_profile) {
@@ -54,15 +61,15 @@ fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option<Str
return Some(MISSING_BWRAP_WARNING.to_string());
};
if !system_bwrap_has_user_namespace_access(system_bwrap_path) {
if !system_bwrap_has_user_namespace_access(system_bwrap_path, SYSTEM_BWRAP_PROBE_TIMEOUT) {
return Some(USER_NAMESPACE_WARNING.to_string());
}
None
}
fn system_bwrap_has_user_namespace_access(system_bwrap_path: &Path) -> bool {
let output = match Command::new(system_bwrap_path)
fn system_bwrap_has_user_namespace_access(system_bwrap_path: &Path, timeout: Duration) -> bool {
let mut child = match Command::new(system_bwrap_path)
.args([
"--unshare-user",
"--unshare-net",
@@ -71,13 +78,45 @@ fn system_bwrap_has_user_namespace_access(system_bwrap_path: &Path) -> bool {
"/",
"/bin/true",
])
.output()
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
{
Ok(output) => output,
Ok(child) => child,
Err(_) => return true,
};
output.status.success() || !is_user_namespace_failure(&output)
let deadline = Instant::now() + timeout;
loop {
match child.try_wait() {
Ok(Some(status)) => {
let stderr = child.stderr.take().map_or_else(Vec::new, |mut stderr| {
let mut bytes = Vec::new();
let _ = stderr.read_to_end(&mut bytes);
bytes
});
let output = Output {
status,
stdout: Vec::new(),
stderr,
};
return output.status.success() || !is_user_namespace_failure(&output);
}
Ok(None) => {
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
return true;
}
thread::sleep(SYSTEM_BWRAP_PROBE_POLL_INTERVAL);
}
Err(_) => {
let _ = child.kill();
let _ = child.wait();
return true;
}
}
}
}
pub(crate) fn is_wsl1() -> bool {

View File

@@ -2,6 +2,8 @@ use super::*;
use pretty_assertions::assert_eq;
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;
use std::time::Instant;
use tempfile::tempdir;
#[test]
@@ -44,6 +46,24 @@ exit 1
assert_eq!(system_bwrap_warning_for_path(Some(fake_bwrap_path)), None);
}
#[test]
fn system_bwrap_probe_times_out_without_reporting_a_warning() {
let fake_bwrap = write_fake_bwrap(
r#"#!/bin/sh
sleep 1
exit 0
"#,
);
let fake_bwrap_path: &Path = fake_bwrap.as_ref();
let started_at = Instant::now();
assert!(system_bwrap_has_user_namespace_access(
fake_bwrap_path,
Duration::from_millis(10),
));
assert!(started_at.elapsed() < Duration::from_millis(500));
}
#[test]
fn detects_wsl1_proc_version_formats() {
assert!(proc_version_indicates_wsl1(