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:
authored and GitHub committed 2026-04-21 10:44:49 -07:00
1 parent 38ba876ea9
commit 8612714aa6
32 files changed
+2620 -210

No files matched your search

+110 -62
View File
@@ -1,7 +1,8 @@
use crate::desktop::LaunchDesktop;
use crate::logging;
use crate::proc_thread_attr::ProcThreadAttributeList;
use crate::winutil::argv_to_command_line;
use crate::winutil::format_last_error;
use crate::winutil::quote_windows_arg;
use crate::winutil::to_wide;
use anyhow::anyhow;
use anyhow::Result;
@@ -23,8 +24,10 @@ use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
use windows_sys::Win32::System::Pipes::CreatePipe;
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 windows_sys::Win32::System::Threading::STARTUPINFOW;
pub struct CreatedProcess {
@@ -81,81 +84,118 @@ pub unsafe fn create_process_as_user(
stdio: Option<(HANDLE, HANDLE, HANDLE)>,
use_private_desktop: bool,
) -> Result<CreatedProcess> {
let cmdline_str = argv
.iter()
.map(|a| quote_windows_arg(a))
.collect::<Vec<_>>()
.join(" ");
let cmdline_str = argv_to_command_line(argv);
let mut cmdline: Vec<u16> = to_wide(&cmdline_str);
let env_block = make_env_block(env_map);
let mut si: STARTUPINFOW = std::mem::zeroed();
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
// Some processes (e.g., PowerShell) can fail with STATUS_DLL_INIT_FAILED
// if lpDesktop is not set when launching with a restricted token.
// Point explicitly at the interactive desktop or a private desktop.
let desktop = LaunchDesktop::prepare(use_private_desktop, logs_base_dir)?;
si.lpDesktop = desktop.startup_info_desktop();
let mut pi: PROCESS_INFORMATION = std::mem::zeroed();
// Ensure handles are inheritable when custom stdio is supplied.
let inherit_handles = match stdio {
let cwd_wide = to_wide(cwd);
let env_block_len = env_block.len();
match stdio {
Some((stdin_h, stdout_h, stderr_h)) => {
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = stdin_h;
si.hStdOutput = stdout_h;
si.hStdError = stderr_h;
for h in [stdin_h, stdout_h, stderr_h] {
if SetHandleInformation(h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) == 0 {
let mut si: STARTUPINFOEXW = std::mem::zeroed();
si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;
// Some processes (e.g., PowerShell) can fail with STATUS_DLL_INIT_FAILED
// if lpDesktop is not set when launching with a restricted token.
// Point explicitly at the interactive desktop or a private desktop.
si.StartupInfo.lpDesktop = desktop.startup_info_desktop();
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
si.StartupInfo.hStdInput = stdin_h;
si.StartupInfo.hStdOutput = stdout_h;
si.StartupInfo.hStdError = stderr_h;
let mut inherited_handles = vec![stdin_h, stdout_h];
if !inherited_handles.contains(&stderr_h) {
inherited_handles.push(stderr_h);
}
for &handle in &inherited_handles {
if SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) == 0 {
return Err(anyhow!(
"SetHandleInformation failed for stdio handle: {}",
GetLastError()
));
}
}
true
let mut attrs = ProcThreadAttributeList::new(/*attr_count*/ 1)?;
attrs.set_handle_list(inherited_handles)?;
si.lpAttributeList = attrs.as_mut_ptr();
let creation_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
let ok = CreateProcessAsUserW(
h_token,
std::ptr::null(),
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
1,
creation_flags,
env_block.as_ptr() as *mut c_void,
cwd_wide.as_ptr(),
&si.StartupInfo,
&mut pi,
);
if ok == 0 {
let err = GetLastError() as i32;
let msg = format!(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
cwd.display(),
cmdline_str,
env_block_len,
si.StartupInfo.dwFlags,
creation_flags,
);
logging::debug_log(&msg, logs_base_dir);
return Err(anyhow!("CreateProcessAsUserW failed: {err}"));
}
Ok(CreatedProcess {
process_info: pi,
startup_info: si.StartupInfo,
_desktop: desktop,
})
}
None => {
let mut si: STARTUPINFOW = std::mem::zeroed();
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
si.lpDesktop = desktop.startup_info_desktop();
ensure_inheritable_stdio(&mut si)?;
true
let creation_flags = CREATE_UNICODE_ENVIRONMENT;
let ok = CreateProcessAsUserW(
h_token,
std::ptr::null(),
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
1,
creation_flags,
env_block.as_ptr() as *mut c_void,
cwd_wide.as_ptr(),
&si,
&mut pi,
);
if ok == 0 {
let err = GetLastError() as i32;
let msg = format!(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
cwd.display(),
cmdline_str,
env_block_len,
si.dwFlags,
creation_flags,
);
logging::debug_log(&msg, logs_base_dir);
return Err(anyhow!("CreateProcessAsUserW failed: {err}"));
}
Ok(CreatedProcess {
process_info: pi,
startup_info: si,
_desktop: desktop,
})
}
};
let creation_flags = CREATE_UNICODE_ENVIRONMENT;
let cwd_wide = to_wide(cwd);
let env_block_len = env_block.len();
let ok = CreateProcessAsUserW(
h_token,
std::ptr::null(),
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
inherit_handles as i32,
creation_flags,
env_block.as_ptr() as *mut c_void,
cwd_wide.as_ptr(),
&si,
&mut pi,
);
if ok == 0 {
let err = GetLastError() as i32;
let msg = format!(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
cwd.display(),
cmdline_str,
env_block_len,
si.dwFlags,
creation_flags,
);
logging::debug_log(&msg, logs_base_dir);
return Err(anyhow!("CreateProcessAsUserW failed: {err}"));
}
Ok(CreatedProcess {
process_info: pi,
startup_info: si,
_desktop: desktop,
})
}
/// Controls whether the child's stdin handle is kept open for writing.
@@ -179,9 +219,11 @@ pub struct PipeSpawnHandles {
pub stdin_write: Option<HANDLE>,
pub stdout_read: HANDLE,
pub stderr_read: Option<HANDLE>,
pub(crate) desktop: LaunchDesktop,
}
/// Spawns a process with anonymous pipes and returns the relevant handles.
#[allow(clippy::too_many_arguments)]
pub fn spawn_process_with_pipes(
h_token: HANDLE,
argv: &[String],
@@ -190,6 +232,7 @@ pub fn spawn_process_with_pipes(
stdin_mode: StdinMode,
stderr_mode: StderrMode,
use_private_desktop: bool,
logs_base_dir: Option<&Path>,
) -> Result<PipeSpawnHandles> {
let mut in_r: HANDLE = 0;
let mut in_w: HANDLE = 0;
@@ -229,7 +272,7 @@ pub fn spawn_process_with_pipes(
argv,
cwd,
env_map,
/*logs_base_dir*/ None,
logs_base_dir,
stdio,
use_private_desktop,
)
@@ -250,7 +293,11 @@ pub fn spawn_process_with_pipes(
return Err(err);
}
};
let pi = created.process_info;
let CreatedProcess {
process_info: pi,
_desktop: desktop,
..
} = created;
unsafe {
CloseHandle(in_r);
@@ -274,6 +321,7 @@ pub fn spawn_process_with_pipes(
StderrMode::Separate => Some(err_r),
StderrMode::MergeStdout => None,
},
desktop,
})
}