mirror of
https://github.com/openai/codex.git
synced 2026-05-02 12:21:26 +03:00
## Summary This is PR 2 of the Windows sandbox runner split. PR 1 introduced the framed IPC runner foundation and related Windows sandbox infrastructure without changing the active elevated one-shot execution path. This PR switches that elevated one-shot path over to the new runner IPC transport and removes the old request-file bootstrap that PR 1 intentionally left in place. After this change, ordinary elevated Windows sandbox commands still behave as one-shot executions, but they now run as the simple case of the same helper/IPC transport that later unified_exec work will build on. ## Why this is needed for unified_exec Windows elevated sandboxed execution crosses a user boundary: the CLI launches a helper as the sandbox user and has to manage command execution from outside that security context. For one-shot commands, the old request-file/bootstrap flow was sufficient. For unified_exec, it is not. Unified_exec needs a long-lived bidirectional channel so the parent can: - send a spawn request - receive structured spawn success/failure - stream stdout and stderr incrementally - eventually support stdin writes, termination, and other session lifecycle events This PR does not add long-lived sessions yet. It converts the existing elevated one-shot path to use the same framed IPC transport so that PR 3 can add unified_exec session semantics on top of a transport that is already exercised by normal elevated command execution. ## Scope This PR: - updates `windows-sandbox-rs/src/elevated_impl.rs` to launch the runner with named pipes, send a framed `SpawnRequest`, wait for `SpawnReady`, and collect framed `Output`/`Exit` messages - removes the old `--request-file=...` execution path from `windows-sandbox-rs/src/elevated/command_runner_win.rs` - keeps the public behavior one-shot: no session reuse or interactive unified_exec behavior is introduced here This PR does not: - add Windows unified_exec session support - add background terminal reuse - add PTY session lifecycle management ## Why Windows needs this and Linux/macOS do not On Linux and macOS, the existing sandbox/process model composes much more directly with long-lived process control. The parent can generally spawn and own the child process (or PTY) directly inside the sandbox model we already use. Windows elevated sandboxing is different. The parent is not directly managing the sandboxed process in the same way; it launches across a different user/security context. That means long-lived control requires an explicit helper process plus IPC for spawn, output, exit, and later stdin/session control. So the extra machinery here is not because unified_exec is conceptually different on Windows. It is because the elevated Windows sandbox boundary requires a helper-mediated transport to support it cleanly. ## Validation - `cargo test -p codex-windows-sandbox`
147 lines
5.3 KiB
Rust
147 lines
5.3 KiB
Rust
//! ConPTY helpers for spawning sandboxed processes with a PTY on Windows.
|
||
//!
|
||
//! This module encapsulates ConPTY creation and process spawn with the required
|
||
//! `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE` plumbing. It is shared by both the legacy
|
||
//! restricted‑token path and the elevated runner path when unified_exec runs with
|
||
//! `tty=true`. The helpers are not tied to the IPC layer and can be reused by other
|
||
//! Windows sandbox flows that need a PTY.
|
||
|
||
mod proc_thread_attr;
|
||
|
||
use self::proc_thread_attr::ProcThreadAttributeList;
|
||
use crate::desktop::LaunchDesktop;
|
||
use crate::winutil::format_last_error;
|
||
use crate::winutil::quote_windows_arg;
|
||
use crate::winutil::to_wide;
|
||
use anyhow::Result;
|
||
use codex_utils_pty::RawConPty;
|
||
use std::collections::HashMap;
|
||
use std::ffi::c_void;
|
||
use std::path::Path;
|
||
use windows_sys::Win32::Foundation::CloseHandle;
|
||
use windows_sys::Win32::Foundation::GetLastError;
|
||
use windows_sys::Win32::Foundation::HANDLE;
|
||
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
||
use windows_sys::Win32::System::Console::ClosePseudoConsole;
|
||
use windows_sys::Win32::System::Threading::CreateProcessAsUserW;
|
||
use windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT;
|
||
use windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT;
|
||
use windows_sys::Win32::System::Threading::PROCESS_INFORMATION;
|
||
use windows_sys::Win32::System::Threading::STARTF_USESTDHANDLES;
|
||
use windows_sys::Win32::System::Threading::STARTUPINFOEXW;
|
||
|
||
use crate::process::make_env_block;
|
||
|
||
/// Owns a ConPTY handle and its backing pipe handles.
|
||
pub struct ConptyInstance {
|
||
pub hpc: HANDLE,
|
||
pub input_write: HANDLE,
|
||
pub output_read: HANDLE,
|
||
_desktop: LaunchDesktop,
|
||
}
|
||
|
||
impl Drop for ConptyInstance {
|
||
fn drop(&mut self) {
|
||
unsafe {
|
||
if self.input_write != 0 && self.input_write != INVALID_HANDLE_VALUE {
|
||
CloseHandle(self.input_write);
|
||
}
|
||
if self.output_read != 0 && self.output_read != INVALID_HANDLE_VALUE {
|
||
CloseHandle(self.output_read);
|
||
}
|
||
if self.hpc != 0 && self.hpc != INVALID_HANDLE_VALUE {
|
||
ClosePseudoConsole(self.hpc);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ConptyInstance {
|
||
/// Consume the instance and return raw handles without closing them.
|
||
pub fn into_raw(self) -> (HANDLE, HANDLE, HANDLE) {
|
||
let me = std::mem::ManuallyDrop::new(self);
|
||
(me.hpc, me.input_write, me.output_read)
|
||
}
|
||
}
|
||
|
||
/// Create a ConPTY with backing pipes.
|
||
///
|
||
/// This is public so callers that need lower-level PTY setup can build on the same
|
||
/// primitive, although the common entry point is `spawn_conpty_process_as_user`.
|
||
pub fn create_conpty(cols: i16, rows: i16) -> Result<ConptyInstance> {
|
||
let raw = RawConPty::new(cols, rows)?;
|
||
let (hpc, input_write, output_read) = raw.into_raw_handles();
|
||
|
||
Ok(ConptyInstance {
|
||
hpc: hpc as HANDLE,
|
||
input_write: input_write as HANDLE,
|
||
output_read: output_read as HANDLE,
|
||
_desktop: LaunchDesktop::prepare(false, None)?,
|
||
})
|
||
}
|
||
|
||
/// Spawn a process under `h_token` with ConPTY attached.
|
||
///
|
||
/// This is the main shared ConPTY entry point and is used by both the legacy/direct path
|
||
/// and the elevated runner path whenever a PTY-backed sandboxed process is needed.
|
||
pub fn spawn_conpty_process_as_user(
|
||
h_token: HANDLE,
|
||
argv: &[String],
|
||
cwd: &Path,
|
||
env_map: &HashMap<String, String>,
|
||
use_private_desktop: bool,
|
||
logs_base_dir: Option<&Path>,
|
||
) -> Result<(PROCESS_INFORMATION, ConptyInstance)> {
|
||
let cmdline_str = argv
|
||
.iter()
|
||
.map(|arg| quote_windows_arg(arg))
|
||
.collect::<Vec<_>>()
|
||
.join(" ");
|
||
let mut cmdline: Vec<u16> = to_wide(&cmdline_str);
|
||
let env_block = make_env_block(env_map);
|
||
let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };
|
||
si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;
|
||
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||
si.StartupInfo.hStdInput = INVALID_HANDLE_VALUE;
|
||
si.StartupInfo.hStdOutput = INVALID_HANDLE_VALUE;
|
||
si.StartupInfo.hStdError = INVALID_HANDLE_VALUE;
|
||
let desktop = LaunchDesktop::prepare(use_private_desktop, logs_base_dir)?;
|
||
si.StartupInfo.lpDesktop = desktop.startup_info_desktop();
|
||
|
||
let conpty = create_conpty(80, 24)?;
|
||
let mut attrs = ProcThreadAttributeList::new(1)?;
|
||
attrs.set_pseudoconsole(conpty.hpc)?;
|
||
si.lpAttributeList = attrs.as_mut_ptr();
|
||
|
||
let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
|
||
let ok = unsafe {
|
||
CreateProcessAsUserW(
|
||
h_token,
|
||
std::ptr::null(),
|
||
cmdline.as_mut_ptr(),
|
||
std::ptr::null_mut(),
|
||
std::ptr::null_mut(),
|
||
0,
|
||
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
|
||
env_block.as_ptr() as *mut c_void,
|
||
to_wide(cwd).as_ptr(),
|
||
&si.StartupInfo,
|
||
&mut pi,
|
||
)
|
||
};
|
||
if ok == 0 {
|
||
let err = unsafe { GetLastError() } as i32;
|
||
return Err(anyhow::anyhow!(
|
||
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={}",
|
||
err,
|
||
format_last_error(err),
|
||
cwd.display(),
|
||
cmdline_str,
|
||
env_block.len()
|
||
));
|
||
}
|
||
let mut conpty = conpty;
|
||
conpty._desktop = desktop;
|
||
Ok((pi, conpty))
|
||
}
|