mirror of
https://github.com/openai/codex.git
synced 2026-05-02 12:21:26 +03:00
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:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user