mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Harden Windows sandbox helper cleanup (#40808)
## Why Windows filesystem operations can complete while their helper process remains alive, and elevated sandbox processes must not survive a disconnected control pipe. ## What changed - Read newline-delimited filesystem helper responses without imposing an operation deadline, then bound the time spent reaping the helper. - Drain helper stderr concurrently, retain a bounded diagnostic on failure, and kill helpers that do not exit after responding. - Terminate the elevated runner's job or process when its input pipe closes or fails. ## Testing Added coverage for delayed responses, noisy stderr, stuck helpers, duplicated file handles, and termination of elevated descendants after a control-pipe disconnect. GitOrigin-RevId: c0d3d5d76e13318de17638df4db71fcfb9a7397f
This commit is contained in:
1 parent
04907ab95a
commit
21c58c90f2
5 files changed
+475
-14
No files matched your search
@@ -10,6 +10,9 @@
|
||||
#![allow(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
mod cwd_junction;
|
||||
#[cfg(test)]
|
||||
#[path = "win/input_loop_tests.rs"]
|
||||
mod input_loop_tests;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
@@ -442,8 +445,10 @@ fn spawn_input_loop(
|
||||
loop {
|
||||
let msg = match read_frame(&mut reader) {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
Ok(None) | Err(_) => {
|
||||
terminate_job_or_process(&job, process, log_dir.as_deref());
|
||||
break;
|
||||
}
|
||||
};
|
||||
match msg.message {
|
||||
Message::Stdin { payload } => {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::os::windows::io::FromRawHandle;
|
||||
use std::os::windows::io::OwnedHandle;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use codex_utils_pty::JobObject;
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
use tokio::io::BufReader;
|
||||
use tokio::process::Command;
|
||||
use windows_sys::Win32::Foundation::WAIT_OBJECT_0;
|
||||
use windows_sys::Win32::System::Threading::OpenProcess;
|
||||
use windows_sys::Win32::System::Threading::PROCESS_SYNCHRONIZE;
|
||||
use windows_sys::Win32::System::Threading::WaitForSingleObject;
|
||||
|
||||
use super::spawn_input_loop;
|
||||
|
||||
#[tokio::test]
|
||||
async fn disconnected_control_pipe_terminates_elevated_helper_and_descendants() -> anyhow::Result<()>
|
||||
{
|
||||
let mut command = Command::new("python");
|
||||
command
|
||||
.args([
|
||||
"-u",
|
||||
"-c",
|
||||
"import subprocess,sys; child=subprocess.Popen([sys.executable,'-c','import time; time.sleep(60)']); print(child.pid,flush=True); child.wait()",
|
||||
])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
let job = Arc::new(JobObject::create_without_breakaway()?);
|
||||
let mut helper = job.spawn_contained(&mut command)?;
|
||||
let stdout = helper.stdout.take().context("helper stdout")?;
|
||||
let mut stdout = BufReader::new(stdout);
|
||||
let mut descendant_pid = String::new();
|
||||
tokio::time::timeout(
|
||||
Duration::from_secs(/*secs*/ 10),
|
||||
stdout.read_line(&mut descendant_pid),
|
||||
)
|
||||
.await??;
|
||||
let descendant_pid = descendant_pid.trim().parse()?;
|
||||
let descendant = unsafe {
|
||||
OpenProcess(
|
||||
PROCESS_SYNCHRONIZE,
|
||||
/*bInheritHandle*/ 0,
|
||||
descendant_pid,
|
||||
)
|
||||
};
|
||||
anyhow::ensure!(descendant != 0, "failed to open sandbox descendant");
|
||||
let descendant = unsafe { OwnedHandle::from_raw_handle(descendant as _) };
|
||||
let process = helper.raw_handle().context("helper process handle")? as _;
|
||||
|
||||
let input_loop = spawn_input_loop(
|
||||
tempfile::tempfile()?,
|
||||
/*stdin_handle*/ None,
|
||||
Arc::new(Mutex::new(None)),
|
||||
Arc::clone(&job),
|
||||
process,
|
||||
/*log_dir*/ None,
|
||||
);
|
||||
input_loop
|
||||
.join()
|
||||
.expect("elevated runner input loop should stop after pipe disconnect");
|
||||
|
||||
let status = tokio::time::timeout(Duration::from_secs(/*secs*/ 2), helper.wait())
|
||||
.await
|
||||
.context("elevated helper survived control-pipe disconnect")??;
|
||||
anyhow::ensure!(!status.success(), "elevated helper unexpectedly succeeded");
|
||||
let descendant_status = unsafe {
|
||||
WaitForSingleObject(
|
||||
descendant.as_raw_handle() as _,
|
||||
/*dwMilliseconds*/ 2_000,
|
||||
)
|
||||
};
|
||||
anyhow::ensure!(
|
||||
descendant_status == WAIT_OBJECT_0,
|
||||
"elevated descendant survived control-pipe disconnect"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in new issue
Block a user