mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add Windows sandbox unified exec runtime support (#15578)
## Summary This is the runtime/foundation half of the Windows sandbox unified-exec work. - add Windows sandbox `unified_exec` session support in `windows-sandbox-rs` for both: - the legacy restricted-token backend - the elevated runner backend - extend the PTY/process runtime so driver-backed sessions can support: - stdin streaming - stdout/stderr separation - exit propagation - PTY resize hooks - add Windows sandbox runtime coverage in `codex-windows-sandbox` / `codex-utils-pty` This PR does **not** enable Windows sandbox `UnifiedExec` for product callers yet because hooking this up to app-server comes in the next PR. Windows sandbox advertising is intentionally kept aligned with `main`, so sandboxed Windows callers still fall back to `ShellCommand`. This PR isolates the runtime/session layer so it can be reviewed independently from product-surface enablement. --------- Co-authored-by: jif-oai <jif@openai.com> Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
32 files changed
+2620
-210
No files matched your search
@@ -15,10 +15,12 @@ use anyhow::Result;
|
||||
use codex_windows_sandbox::ErrorPayload;
|
||||
use codex_windows_sandbox::ExitPayload;
|
||||
use codex_windows_sandbox::FramedMessage;
|
||||
use codex_windows_sandbox::LaunchDesktop;
|
||||
use codex_windows_sandbox::Message;
|
||||
use codex_windows_sandbox::OutputPayload;
|
||||
use codex_windows_sandbox::OutputStream;
|
||||
use codex_windows_sandbox::PipeSpawnHandles;
|
||||
use codex_windows_sandbox::ResizePayload;
|
||||
use codex_windows_sandbox::SandboxPolicy;
|
||||
use codex_windows_sandbox::SpawnReady;
|
||||
use codex_windows_sandbox::SpawnRequest;
|
||||
@@ -56,7 +58,9 @@ use windows_sys::Win32::Storage::FileSystem::CreateFileW;
|
||||
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
|
||||
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
|
||||
use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING;
|
||||
use windows_sys::Win32::System::Console::COORD;
|
||||
use windows_sys::Win32::System::Console::ClosePseudoConsole;
|
||||
use windows_sys::Win32::System::Console::ResizePseudoConsole;
|
||||
use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject;
|
||||
use windows_sys::Win32::System::JobObjects::CreateJobObjectW;
|
||||
use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
@@ -86,6 +90,8 @@ struct IpcSpawnedProcess {
|
||||
stderr_handle: HANDLE,
|
||||
stdin_handle: Option<HANDLE>,
|
||||
hpc_handle: Option<HANDLE>,
|
||||
_desktop_owner: Option<LaunchDesktop>,
|
||||
_pipe_handles: Option<PipeSpawnHandles>,
|
||||
}
|
||||
|
||||
unsafe fn create_job_kill_on_close() -> Result<HANDLE> {
|
||||
@@ -166,7 +172,7 @@ fn effective_cwd(req_cwd: &Path, log_dir: Option<&Path>) -> PathBuf {
|
||||
Err(err) => {
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: read_acl_mutex_exists failed: {err}; assuming read ACL helper is running"
|
||||
"junction: failed to probe ACL mutex state: {err}; defaulting to junction cwd"
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
@@ -174,10 +180,6 @@ fn effective_cwd(req_cwd: &Path, log_dir: Option<&Path>) -> PathBuf {
|
||||
}
|
||||
};
|
||||
if use_junction {
|
||||
log_note(
|
||||
"junction: read ACL helper running; using junction CWD",
|
||||
log_dir,
|
||||
);
|
||||
cwd_junction::create_cwd_junction(req_cwd, log_dir).unwrap_or_else(|| req_cwd.to_path_buf())
|
||||
} else {
|
||||
req_cwd.to_path_buf()
|
||||
@@ -187,16 +189,6 @@ fn effective_cwd(req_cwd: &Path, log_dir: Option<&Path>) -> PathBuf {
|
||||
fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
|
||||
let log_dir = req.codex_home.clone();
|
||||
hide_current_user_profile_dir(req.codex_home.as_path());
|
||||
log_note(
|
||||
&format!(
|
||||
"runner start cwd={} cmd={:?} real_codex_home={}",
|
||||
req.cwd.display(),
|
||||
req.command,
|
||||
req.real_codex_home.display()
|
||||
),
|
||||
Some(&req.codex_home),
|
||||
);
|
||||
|
||||
let policy = parse_policy(&req.policy_json_or_preset).context("parse policy_json_or_preset")?;
|
||||
let mut cap_psids: Vec<*mut c_void> = Vec::new();
|
||||
for sid in &req.cap_sids {
|
||||
@@ -240,16 +232,10 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
|
||||
}
|
||||
|
||||
let effective_cwd = effective_cwd(&req.cwd, Some(log_dir.as_path()));
|
||||
log_note(
|
||||
&format!(
|
||||
"runner: effective cwd={} (requested {})",
|
||||
effective_cwd.display(),
|
||||
req.cwd.display()
|
||||
),
|
||||
Some(log_dir.as_path()),
|
||||
);
|
||||
|
||||
let mut hpc_handle: Option<HANDLE> = None;
|
||||
let mut desktop_owner = None;
|
||||
let mut pipe_handles = None;
|
||||
let (pi, stdout_handle, stderr_handle, stdin_handle) = if req.tty {
|
||||
let (pi, conpty) = codex_windows_sandbox::spawn_conpty_process_as_user(
|
||||
h_token,
|
||||
@@ -259,8 +245,9 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
|
||||
req.use_private_desktop,
|
||||
Some(log_dir.as_path()),
|
||||
)?;
|
||||
let (hpc, input_write, output_read) = conpty.into_raw();
|
||||
let (hpc, input_write, output_read, desktop) = conpty.into_raw();
|
||||
hpc_handle = Some(hpc);
|
||||
desktop_owner = desktop;
|
||||
let stdin_handle = if req.stdin_open {
|
||||
Some(input_write)
|
||||
} else {
|
||||
@@ -281,29 +268,29 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
|
||||
} else {
|
||||
StdinMode::Closed
|
||||
};
|
||||
let pipe_handles: PipeSpawnHandles = spawn_process_with_pipes(
|
||||
let spawned_pipes: PipeSpawnHandles = spawn_process_with_pipes(
|
||||
h_token,
|
||||
&req.command,
|
||||
&effective_cwd,
|
||||
&req.env,
|
||||
stdin_mode,
|
||||
StderrMode::Separate,
|
||||
/*use_private_desktop*/ false,
|
||||
req.use_private_desktop,
|
||||
Some(log_dir.as_path()),
|
||||
)?;
|
||||
(
|
||||
pipe_handles.process,
|
||||
pipe_handles.stdout_read,
|
||||
pipe_handles
|
||||
.stderr_read
|
||||
.unwrap_or(windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE),
|
||||
pipe_handles.stdin_write,
|
||||
)
|
||||
let pi = spawned_pipes.process;
|
||||
let stdout_handle = spawned_pipes.stdout_read;
|
||||
let stderr_handle = spawned_pipes
|
||||
.stderr_read
|
||||
.unwrap_or(windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE);
|
||||
let stdin_handle = spawned_pipes.stdin_write;
|
||||
pipe_handles = Some(spawned_pipes);
|
||||
(pi, stdout_handle, stderr_handle, stdin_handle)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
CloseHandle(h_token);
|
||||
}
|
||||
|
||||
Ok(IpcSpawnedProcess {
|
||||
log_dir,
|
||||
pi,
|
||||
@@ -311,6 +298,8 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
|
||||
stderr_handle,
|
||||
stdin_handle,
|
||||
hpc_handle,
|
||||
_desktop_owner: desktop_owner,
|
||||
_pipe_handles: pipe_handles,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -346,21 +335,17 @@ fn spawn_output_reader(
|
||||
fn spawn_input_loop(
|
||||
mut reader: File,
|
||||
stdin_handle: Option<HANDLE>,
|
||||
hpc_handle: Arc<StdMutex<Option<HANDLE>>>,
|
||||
process_handle: Arc<StdMutex<Option<HANDLE>>>,
|
||||
log_dir: Option<PathBuf>,
|
||||
_log_dir: Option<PathBuf>,
|
||||
) -> std::thread::JoinHandle<()> {
|
||||
std::thread::spawn(move || {
|
||||
let mut stdin_handle = stdin_handle;
|
||||
loop {
|
||||
let msg = match read_frame(&mut reader) {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => break,
|
||||
Err(err) => {
|
||||
log_note(
|
||||
&format!("runner input read failed: {err}"),
|
||||
log_dir.as_deref(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
Err(_) => break,
|
||||
};
|
||||
match msg.message {
|
||||
Message::Stdin { payload } => {
|
||||
@@ -380,6 +365,30 @@ fn spawn_input_loop(
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::CloseStdin { .. } => {
|
||||
if let Some(handle) = stdin_handle.take() {
|
||||
unsafe {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Resize {
|
||||
payload: ResizePayload { rows, cols },
|
||||
} => {
|
||||
if let Ok(guard) = hpc_handle.lock()
|
||||
&& let Some(hpc) = guard.as_ref()
|
||||
{
|
||||
unsafe {
|
||||
let _ = ResizePseudoConsole(
|
||||
*hpc,
|
||||
COORD {
|
||||
X: cols as i16,
|
||||
Y: rows as i16,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Terminate { .. } => {
|
||||
if let Ok(guard) = process_handle.lock()
|
||||
&& let Some(handle) = guard.as_ref()
|
||||
@@ -450,7 +459,7 @@ pub fn main() -> Result<()> {
|
||||
let stdout_handle = ipc_spawn.stdout_handle;
|
||||
let stderr_handle = ipc_spawn.stderr_handle;
|
||||
let stdin_handle = ipc_spawn.stdin_handle;
|
||||
let hpc_handle = ipc_spawn.hpc_handle;
|
||||
let hpc_handle = Arc::new(StdMutex::new(ipc_spawn.hpc_handle));
|
||||
|
||||
let h_job = unsafe { create_job_kill_on_close().ok() };
|
||||
if let Some(job) = h_job {
|
||||
@@ -474,7 +483,6 @@ pub fn main() -> Result<()> {
|
||||
} else {
|
||||
anyhow::bail!("runner spawn_ready write failed: pipe_write lock poisoned");
|
||||
} {
|
||||
log_note(&format!("runner spawn_ready write failed: {err}"), log_dir);
|
||||
let _ = send_error(&pipe_write, "spawn_failed", err.to_string());
|
||||
return Err(err);
|
||||
}
|
||||
@@ -499,6 +507,7 @@ pub fn main() -> Result<()> {
|
||||
let _input_thread = spawn_input_loop(
|
||||
pipe_read,
|
||||
stdin_handle,
|
||||
Arc::clone(&hpc_handle),
|
||||
Arc::clone(&process_handle),
|
||||
log_dir_owned,
|
||||
);
|
||||
@@ -517,9 +526,6 @@ pub fn main() -> Result<()> {
|
||||
GetExitCodeProcess(pi.hProcess, &mut raw_exit);
|
||||
exit_code = raw_exit as i32;
|
||||
}
|
||||
if let Some(hpc) = hpc_handle {
|
||||
ClosePseudoConsole(hpc);
|
||||
}
|
||||
if pi.hThread != 0 {
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
@@ -530,10 +536,20 @@ pub fn main() -> Result<()> {
|
||||
CloseHandle(job);
|
||||
}
|
||||
}
|
||||
let _ = out_thread.join();
|
||||
if let Some(err_thread) = err_thread {
|
||||
let _ = err_thread.join();
|
||||
|
||||
if let Ok(mut guard) = hpc_handle.lock()
|
||||
&& let Some(hpc) = guard.take()
|
||||
{
|
||||
unsafe {
|
||||
ClosePseudoConsole(hpc);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = out_thread.join();
|
||||
if let Some(thread) = err_thread {
|
||||
let _ = thread.join();
|
||||
}
|
||||
|
||||
let exit_msg = FramedMessage {
|
||||
version: 1,
|
||||
message: Message::Exit {
|
||||
|
||||
@@ -33,8 +33,8 @@ pub struct FramedMessage {
|
||||
|
||||
/// IPC message variants exchanged between parent and runner.
|
||||
///
|
||||
/// `SpawnRequest`, `Stdin`, and `Terminate` are parent->runner commands. `SpawnReady`,
|
||||
/// `Output`, `Exit`, and `Error` are runner->parent events/results.
|
||||
/// `SpawnRequest`, `Stdin`, `CloseStdin`, `Resize`, and `Terminate` are parent->runner commands.
|
||||
/// `SpawnReady`, `Output`, `Exit`, and `Error` are runner->parent events/results.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum Message {
|
||||
@@ -42,6 +42,8 @@ pub enum Message {
|
||||
SpawnReady { payload: SpawnReady },
|
||||
Output { payload: OutputPayload },
|
||||
Stdin { payload: StdinPayload },
|
||||
CloseStdin { payload: EmptyPayload },
|
||||
Resize { payload: ResizePayload },
|
||||
Exit { payload: ExitPayload },
|
||||
Error { payload: ErrorPayload },
|
||||
Terminate { payload: EmptyPayload },
|
||||
@@ -93,6 +95,13 @@ pub struct StdinPayload {
|
||||
pub data_b64: String,
|
||||
}
|
||||
|
||||
/// PTY resize request sent from parent to runner.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ResizePayload {
|
||||
pub rows: u16,
|
||||
pub cols: u16,
|
||||
}
|
||||
|
||||
/// Exit status sent from runner to parent.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ExitPayload {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
use crate::identity::SandboxCreds;
|
||||
use crate::ipc_framed::FramedMessage;
|
||||
use crate::ipc_framed::Message;
|
||||
use crate::ipc_framed::SpawnRequest;
|
||||
use crate::ipc_framed::read_frame;
|
||||
use crate::ipc_framed::write_frame;
|
||||
use crate::runner_pipe::PIPE_ACCESS_INBOUND;
|
||||
use crate::runner_pipe::PIPE_ACCESS_OUTBOUND;
|
||||
use crate::runner_pipe::connect_pipe;
|
||||
use crate::runner_pipe::create_named_pipe;
|
||||
use crate::runner_pipe::find_runner_exe;
|
||||
use crate::runner_pipe::pipe_pair;
|
||||
use crate::winutil::quote_windows_arg;
|
||||
use crate::winutil::to_wide;
|
||||
use anyhow::Result;
|
||||
use std::ffi::c_void;
|
||||
use std::fs::File;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::os::windows::io::FromRawHandle;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use windows_sys::Win32::System::Diagnostics::Debug::SetErrorMode;
|
||||
use windows_sys::Win32::System::Pipes::PeekNamedPipe;
|
||||
use windows_sys::Win32::System::Threading::CreateProcessWithLogonW;
|
||||
use windows_sys::Win32::System::Threading::LOGON_WITH_PROFILE;
|
||||
use windows_sys::Win32::System::Threading::PROCESS_INFORMATION;
|
||||
use windows_sys::Win32::System::Threading::STARTUPINFOW;
|
||||
|
||||
const RUNNER_SPAWN_READY_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
const RUNNER_SPAWN_READY_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||
const RUNNER_ERROR_MODE_FLAGS: u32 = 0x0001 | 0x0002;
|
||||
|
||||
pub(crate) struct RunnerTransport {
|
||||
pipe_write: File,
|
||||
pipe_read: File,
|
||||
}
|
||||
|
||||
impl RunnerTransport {
|
||||
pub(crate) fn send_spawn_request(&mut self, request: SpawnRequest) -> Result<()> {
|
||||
let spawn_request = FramedMessage {
|
||||
version: 1,
|
||||
message: Message::SpawnRequest {
|
||||
payload: Box::new(request),
|
||||
},
|
||||
};
|
||||
write_frame(&mut self.pipe_write, &spawn_request)
|
||||
}
|
||||
|
||||
pub(crate) fn read_spawn_ready(&mut self) -> Result<()> {
|
||||
wait_for_complete_frame(&self.pipe_read, RUNNER_SPAWN_READY_TIMEOUT)?;
|
||||
let msg = read_frame(&mut self.pipe_read)?
|
||||
.ok_or_else(|| anyhow::anyhow!("runner pipe closed before spawn_ready"))?;
|
||||
match msg.message {
|
||||
Message::SpawnReady { .. } => Ok(()),
|
||||
Message::Error { payload } => Err(anyhow::anyhow!("runner error: {}", payload.message)),
|
||||
other => Err(anyhow::anyhow!(
|
||||
"expected spawn_ready from runner, got {other:?}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_files(self) -> (File, File) {
|
||||
(self.pipe_write, self.pipe_read)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_runner_transport(
|
||||
codex_home: &Path,
|
||||
cwd: &Path,
|
||||
sandbox_creds: &SandboxCreds,
|
||||
log_dir: Option<&Path>,
|
||||
) -> Result<RunnerTransport> {
|
||||
let (pipe_in_name, pipe_out_name) = pipe_pair();
|
||||
let h_pipe_in =
|
||||
create_named_pipe(&pipe_in_name, PIPE_ACCESS_OUTBOUND, &sandbox_creds.username)?;
|
||||
let h_pipe_out =
|
||||
create_named_pipe(&pipe_out_name, PIPE_ACCESS_INBOUND, &sandbox_creds.username)?;
|
||||
|
||||
let runner_exe = find_runner_exe(codex_home, log_dir);
|
||||
let runner_cmdline = runner_exe
|
||||
.to_str()
|
||||
.map(str::to_owned)
|
||||
.unwrap_or_else(|| "codex-command-runner.exe".to_string());
|
||||
let runner_full_cmd = format!(
|
||||
"{} {} {}",
|
||||
quote_windows_arg(&runner_cmdline),
|
||||
quote_windows_arg(&format!("--pipe-in={pipe_in_name}")),
|
||||
quote_windows_arg(&format!("--pipe-out={pipe_out_name}"))
|
||||
);
|
||||
let mut cmdline_vec = to_wide(&runner_full_cmd);
|
||||
let exe_w = to_wide(&runner_cmdline);
|
||||
let cwd_w = to_wide(cwd);
|
||||
let user_w = to_wide(&sandbox_creds.username);
|
||||
let domain_w = to_wide(".");
|
||||
let password_w = to_wide(&sandbox_creds.password);
|
||||
let mut si: STARTUPINFOW = unsafe { std::mem::zeroed() };
|
||||
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||
let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
|
||||
let env_block: Option<Vec<u16>> = None;
|
||||
|
||||
let previous_error_mode = unsafe { SetErrorMode(RUNNER_ERROR_MODE_FLAGS) };
|
||||
let spawn_res = unsafe {
|
||||
CreateProcessWithLogonW(
|
||||
user_w.as_ptr(),
|
||||
domain_w.as_ptr(),
|
||||
password_w.as_ptr(),
|
||||
LOGON_WITH_PROFILE,
|
||||
exe_w.as_ptr(),
|
||||
cmdline_vec.as_mut_ptr(),
|
||||
windows_sys::Win32::System::Threading::CREATE_NO_WINDOW
|
||||
| windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT,
|
||||
env_block
|
||||
.as_ref()
|
||||
.map(|block| block.as_ptr() as *const c_void)
|
||||
.unwrap_or(ptr::null()),
|
||||
cwd_w.as_ptr(),
|
||||
&si,
|
||||
&mut pi,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
SetErrorMode(previous_error_mode);
|
||||
}
|
||||
if spawn_res == 0 {
|
||||
let err = unsafe { GetLastError() } as i32;
|
||||
unsafe {
|
||||
CloseHandle(h_pipe_in);
|
||||
CloseHandle(h_pipe_out);
|
||||
}
|
||||
return Err(anyhow::anyhow!("CreateProcessWithLogonW failed: {err}"));
|
||||
}
|
||||
|
||||
let connect_result = (|| -> Result<()> {
|
||||
connect_pipe(h_pipe_in)?;
|
||||
connect_pipe(h_pipe_out)?;
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
unsafe {
|
||||
if pi.hThread != 0 {
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
if pi.hProcess != 0 {
|
||||
CloseHandle(pi.hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = connect_result {
|
||||
unsafe {
|
||||
CloseHandle(h_pipe_in);
|
||||
CloseHandle(h_pipe_out);
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let pipe_write = unsafe { File::from_raw_handle(h_pipe_in as _) };
|
||||
let pipe_read = unsafe { File::from_raw_handle(h_pipe_out as _) };
|
||||
Ok(RunnerTransport {
|
||||
pipe_write,
|
||||
pipe_read,
|
||||
})
|
||||
}
|
||||
|
||||
fn wait_for_complete_frame(pipe_read: &File, timeout: Duration) -> Result<()> {
|
||||
let handle = pipe_read.as_raw_handle() as HANDLE;
|
||||
let deadline = Instant::now() + timeout;
|
||||
let mut len_buf = [0u8; 4];
|
||||
|
||||
loop {
|
||||
let mut bytes_read = 0u32;
|
||||
let mut total_available = 0u32;
|
||||
let ok = unsafe {
|
||||
PeekNamedPipe(
|
||||
handle,
|
||||
len_buf.as_mut_ptr() as *mut c_void,
|
||||
len_buf.len() as u32,
|
||||
&mut bytes_read,
|
||||
&mut total_available,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if ok == 0 {
|
||||
let err = unsafe { GetLastError() } as i32;
|
||||
return Err(anyhow::anyhow!(
|
||||
"PeekNamedPipe failed while waiting for spawn_ready: {err}"
|
||||
));
|
||||
}
|
||||
|
||||
if bytes_read == len_buf.len() as u32 {
|
||||
let frame_len = u32::from_le_bytes(len_buf) as usize;
|
||||
let total_len = frame_len
|
||||
.checked_add(len_buf.len())
|
||||
.ok_or_else(|| anyhow::anyhow!("runner frame length overflow"))?;
|
||||
if total_available as usize >= total_len {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if Instant::now() >= deadline {
|
||||
return Err(anyhow::anyhow!(
|
||||
"timed out after {}ms waiting for runner spawn_ready",
|
||||
timeout.as_millis()
|
||||
));
|
||||
}
|
||||
|
||||
std::thread::sleep(RUNNER_SPAWN_READY_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ use std::path::PathBuf;
|
||||
use std::ptr;
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use windows_sys::Win32::Foundation::HLOCAL;
|
||||
use windows_sys::Win32::Foundation::LocalFree;
|
||||
use windows_sys::Win32::Security::Authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW;
|
||||
use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR;
|
||||
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
|
||||
@@ -43,7 +45,8 @@ pub fn find_runner_exe(codex_home: &Path, log_dir: Option<&Path>) -> PathBuf {
|
||||
/// Generates a unique named-pipe path used to communicate with the runner process.
|
||||
pub fn pipe_pair() -> (String, String) {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let base = format!(r"\\.\pipe\codex-runner-{:x}", rng.gen::<u128>());
|
||||
let nonce: u128 = rng.r#gen();
|
||||
let base = format!(r"\\.\pipe\codex-runner-{nonce:x}");
|
||||
(format!("{base}-in"), format!("{base}-out"))
|
||||
}
|
||||
|
||||
@@ -86,6 +89,9 @@ pub fn create_named_pipe(name: &str, access: u32, sandbox_username: &str) -> io:
|
||||
&mut sa as *mut SECURITY_ATTRIBUTES,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
LocalFree(sd as HLOCAL);
|
||||
}
|
||||
if h == 0 || h == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE {
|
||||
return Err(io::Error::from_raw_os_error(unsafe {
|
||||
GetLastError() as i32
|
||||
|
||||
Reference in new issue
Block a user