Use a private desktop for Windows sandbox instead of Winsta0\Default (#14400)

## Summary
- launch Windows sandboxed children on a private desktop instead of
`Winsta0\Default`
- make private desktop the default while keeping
`windows.sandbox_private_desktop=false` as the escape hatch
- centralize process launch through the shared
`create_process_as_user(...)` path
- scope the private desktop ACL to the launching logon SID

## Why
Today sandboxed Windows commands run on the visible shared desktop. That
leaves an avoidable same-desktop attack surface for window interaction,
spoofing, and related UI/input issues. This change moves sandboxed
commands onto a dedicated per-launch desktop by default so the sandbox
no longer shares `Winsta0\Default` with the user session.

The implementation stays conservative on security with no silent
fallback back to `Winsta0\Default`

If private-desktop setup fails on a machine, users can still opt out
explicitly with `windows.sandbox_private_desktop=false`.

## Validation
- `cargo build -p codex-cli`
- elevated-path `codex exec` desktop-name probe returned
`CodexSandboxDesktop-*`
- elevated-path `codex exec` smoke sweep for shell commands, nested
`pwsh`, jobs, and hidden `notepad` launch
- unelevated-path full private-desktop compatibility sweep via `codex
exec` with `-c windows.sandbox=unelevated`
This commit is contained in:
iceweasel-oai
2026-03-13 10:13:39 -07:00
committed by GitHub
parent 9c9867c9fa
commit 6b3d82daca
30 changed files with 416 additions and 70 deletions

View File

@@ -1,3 +1,4 @@
use crate::desktop::LaunchDesktop;
use crate::logging;
use crate::winutil::format_last_error;
use crate::winutil::quote_windows_arg;
@@ -22,6 +23,12 @@ use windows_sys::Win32::System::Threading::PROCESS_INFORMATION;
use windows_sys::Win32::System::Threading::STARTF_USESTDHANDLES;
use windows_sys::Win32::System::Threading::STARTUPINFOW;
pub struct CreatedProcess {
pub process_info: PROCESS_INFORMATION,
pub startup_info: STARTUPINFOW,
_desktop: LaunchDesktop,
}
pub fn make_env_block(env: &HashMap<String, String>) -> Vec<u16> {
let mut items: Vec<(String, String)> =
env.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
@@ -68,7 +75,8 @@ pub unsafe fn create_process_as_user(
env_map: &HashMap<String, String>,
logs_base_dir: Option<&Path>,
stdio: Option<(HANDLE, HANDLE, HANDLE)>,
) -> Result<(PROCESS_INFORMATION, STARTUPINFOW)> {
use_private_desktop: bool,
) -> Result<CreatedProcess> {
let cmdline_str = argv
.iter()
.map(|a| quote_windows_arg(a))
@@ -80,9 +88,9 @@ pub unsafe fn create_process_as_user(
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.
let desktop = to_wide("Winsta0\\Default");
si.lpDesktop = desktop.as_ptr() as *mut u16;
// 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 {
@@ -107,6 +115,10 @@ pub unsafe fn create_process_as_user(
}
};
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(),
@@ -114,25 +126,30 @@ pub unsafe fn create_process_as_user(
std::ptr::null_mut(),
std::ptr::null_mut(),
inherit_handles as i32,
CREATE_UNICODE_ENVIRONMENT,
creation_flags,
env_block.as_ptr() as *mut c_void,
to_wide(cwd).as_ptr(),
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={}",
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
cwd.display(),
cmdline_str,
env_block.len(),
env_block_len,
si.dwFlags,
creation_flags,
);
logging::debug_log(&msg, logs_base_dir);
return Err(anyhow!("CreateProcessAsUserW failed: {}", err));
}
Ok((pi, si))
Ok(CreatedProcess {
process_info: pi,
startup_info: si,
_desktop: desktop,
})
}