mirror of
https://github.com/openai/codex.git
synced 2026-11-10 16:44:38 +00: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 parent
9c9867c9fa
commit
6b3d82daca
30 files changed
+416
-70
No files matched your search
@@ -73,6 +73,9 @@ features = [
|
||||
"Win32_System_Com",
|
||||
"Win32_Security_Cryptography",
|
||||
"Win32_Security_Authentication_Identity",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_StationsAndDesktops",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_System_Registry",
|
||||
]
|
||||
|
||||
@@ -27,6 +27,7 @@ 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::Diagnostics::Debug::SetErrorMode;
|
||||
use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject;
|
||||
use windows_sys::Win32::System::JobObjects::CreateJobObjectW;
|
||||
use windows_sys::Win32::System::JobObjects::JobObjectExtendedLimitInformation;
|
||||
@@ -55,6 +56,7 @@ struct RunnerRequest {
|
||||
cwd: PathBuf,
|
||||
env_map: HashMap<String, String>,
|
||||
timeout_ms: Option<u64>,
|
||||
use_private_desktop: bool,
|
||||
stdin_pipe: String,
|
||||
stdout_pipe: String,
|
||||
stderr_pipe: String,
|
||||
@@ -103,6 +105,8 @@ pub fn main() -> Result<()> {
|
||||
let req: RunnerRequest = serde_json::from_str(&input).context("parse runner request json")?;
|
||||
let log_dir = Some(req.codex_home.as_path());
|
||||
hide_current_user_profile_dir(req.codex_home.as_path());
|
||||
// Suppress Windows error UI from sandboxed child crashes so callers only observe exit codes.
|
||||
let _ = unsafe { SetErrorMode(0x0001 | 0x0002) }; // SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX
|
||||
log_note(
|
||||
&format!(
|
||||
"runner start cwd={} cmd={:?} real_codex_home={}",
|
||||
@@ -233,9 +237,10 @@ pub fn main() -> Result<()> {
|
||||
&req.env_map,
|
||||
Some(&req.codex_home),
|
||||
stdio,
|
||||
req.use_private_desktop,
|
||||
)
|
||||
};
|
||||
let (proc_info, _si) = match spawn_result {
|
||||
let created = match spawn_result {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_note(&format!("runner: spawn failed: {e:?}"), log_dir);
|
||||
@@ -248,6 +253,8 @@ pub fn main() -> Result<()> {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let proc_info = created.process_info;
|
||||
let _desktop = created;
|
||||
|
||||
// Optional job kill on close.
|
||||
let h_job = unsafe { create_job_kill_on_close().ok() };
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
use crate::logging;
|
||||
use crate::token::get_current_token_for_restriction;
|
||||
use crate::token::get_logon_sid_bytes;
|
||||
use crate::winutil::format_last_error;
|
||||
use crate::winutil::to_wide;
|
||||
use anyhow::Result;
|
||||
use rand::Rng;
|
||||
use rand::SeedableRng;
|
||||
use rand::rngs::SmallRng;
|
||||
use std::path::Path;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Foundation::HLOCAL;
|
||||
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
|
||||
use windows_sys::Win32::Foundation::LocalFree;
|
||||
use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W;
|
||||
use windows_sys::Win32::Security::Authorization::GRANT_ACCESS;
|
||||
use windows_sys::Win32::Security::Authorization::SE_WINDOW_OBJECT;
|
||||
use windows_sys::Win32::Security::Authorization::SetEntriesInAclW;
|
||||
use windows_sys::Win32::Security::Authorization::SetSecurityInfo;
|
||||
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID;
|
||||
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN;
|
||||
use windows_sys::Win32::Security::Authorization::TRUSTEE_W;
|
||||
use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::CloseDesktop;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_CREATEMENU;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::CreateDesktopW;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_CREATEWINDOW;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_DELETE;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_ENUMERATE;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_HOOKCONTROL;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_JOURNALPLAYBACK;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_JOURNALRECORD;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_READOBJECTS;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_READ_CONTROL;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_SWITCHDESKTOP;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_WRITE_DAC;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_WRITE_OWNER;
|
||||
use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_WRITEOBJECTS;
|
||||
|
||||
const DESKTOP_ALL_ACCESS: u32 = DESKTOP_READOBJECTS
|
||||
| DESKTOP_CREATEWINDOW
|
||||
| DESKTOP_CREATEMENU
|
||||
| DESKTOP_HOOKCONTROL
|
||||
| DESKTOP_JOURNALRECORD
|
||||
| DESKTOP_JOURNALPLAYBACK
|
||||
| DESKTOP_ENUMERATE
|
||||
| DESKTOP_WRITEOBJECTS
|
||||
| DESKTOP_SWITCHDESKTOP
|
||||
| DESKTOP_DELETE
|
||||
| DESKTOP_READ_CONTROL
|
||||
| DESKTOP_WRITE_DAC
|
||||
| DESKTOP_WRITE_OWNER;
|
||||
|
||||
pub struct LaunchDesktop {
|
||||
_private_desktop: Option<PrivateDesktop>,
|
||||
startup_name: Vec<u16>,
|
||||
}
|
||||
|
||||
impl LaunchDesktop {
|
||||
pub fn prepare(use_private_desktop: bool, logs_base_dir: Option<&Path>) -> Result<Self> {
|
||||
if use_private_desktop {
|
||||
let private_desktop = PrivateDesktop::create(logs_base_dir)?;
|
||||
let startup_name = to_wide(format!("Winsta0\\{}", private_desktop.name));
|
||||
Ok(Self {
|
||||
_private_desktop: Some(private_desktop),
|
||||
startup_name,
|
||||
})
|
||||
} else {
|
||||
Ok(Self {
|
||||
_private_desktop: None,
|
||||
startup_name: to_wide("Winsta0\\Default"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn startup_info_desktop(&self) -> *mut u16 {
|
||||
self.startup_name.as_ptr() as *mut u16
|
||||
}
|
||||
}
|
||||
|
||||
struct PrivateDesktop {
|
||||
handle: isize,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl PrivateDesktop {
|
||||
fn create(logs_base_dir: Option<&Path>) -> Result<Self> {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let name = format!("CodexSandboxDesktop-{:x}", rng.gen::<u128>());
|
||||
let name_wide = to_wide(&name);
|
||||
let handle = unsafe {
|
||||
CreateDesktopW(
|
||||
name_wide.as_ptr(),
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
DESKTOP_ALL_ACCESS,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if handle == 0 {
|
||||
let err = unsafe { GetLastError() } as i32;
|
||||
logging::debug_log(
|
||||
&format!(
|
||||
"CreateDesktopW failed for {name}: {} ({})",
|
||||
err,
|
||||
format_last_error(err),
|
||||
),
|
||||
logs_base_dir,
|
||||
);
|
||||
return Err(anyhow::anyhow!("CreateDesktopW failed: {err}"));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if let Err(err) = grant_desktop_access(handle, logs_base_dir) {
|
||||
let _ = CloseDesktop(handle);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { handle, name })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn grant_desktop_access(handle: isize, logs_base_dir: Option<&Path>) -> Result<()> {
|
||||
let token = get_current_token_for_restriction()?;
|
||||
let mut logon_sid = get_logon_sid_bytes(token)?;
|
||||
CloseHandle(token);
|
||||
|
||||
let entries = [EXPLICIT_ACCESS_W {
|
||||
grfAccessPermissions: DESKTOP_ALL_ACCESS,
|
||||
grfAccessMode: GRANT_ACCESS,
|
||||
grfInheritance: 0,
|
||||
Trustee: TRUSTEE_W {
|
||||
pMultipleTrustee: ptr::null_mut(),
|
||||
MultipleTrusteeOperation: 0,
|
||||
TrusteeForm: TRUSTEE_IS_SID,
|
||||
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
||||
ptstrName: logon_sid.as_mut_ptr() as *mut c_void as *mut u16,
|
||||
},
|
||||
}];
|
||||
|
||||
let mut updated_dacl = ptr::null_mut();
|
||||
let set_entries_code = SetEntriesInAclW(
|
||||
entries.len() as u32,
|
||||
entries.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
&mut updated_dacl,
|
||||
);
|
||||
if set_entries_code != ERROR_SUCCESS {
|
||||
logging::debug_log(
|
||||
&format!("SetEntriesInAclW failed for private desktop: {set_entries_code}"),
|
||||
logs_base_dir,
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"SetEntriesInAclW failed for private desktop: {set_entries_code}"
|
||||
));
|
||||
}
|
||||
|
||||
let set_security_code = SetSecurityInfo(
|
||||
handle,
|
||||
SE_WINDOW_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
updated_dacl,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if !updated_dacl.is_null() {
|
||||
LocalFree(updated_dacl as HLOCAL);
|
||||
}
|
||||
if set_security_code != ERROR_SUCCESS {
|
||||
logging::debug_log(
|
||||
&format!("SetSecurityInfo failed for private desktop: {set_security_code}"),
|
||||
logs_base_dir,
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"SetSecurityInfo failed for private desktop: {set_security_code}"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Drop for PrivateDesktop {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if self.handle != 0 {
|
||||
let _ = CloseDesktop(self.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,12 +196,14 @@ mod windows_impl {
|
||||
cwd: PathBuf,
|
||||
env_map: HashMap<String, String>,
|
||||
timeout_ms: Option<u64>,
|
||||
use_private_desktop: bool,
|
||||
stdin_pipe: String,
|
||||
stdout_pipe: String,
|
||||
stderr_pipe: String,
|
||||
}
|
||||
|
||||
/// Launches the command runner under the sandbox user and captures its output.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_windows_sandbox_capture(
|
||||
policy_json_or_preset: &str,
|
||||
sandbox_policy_cwd: &Path,
|
||||
@@ -210,6 +212,7 @@ mod windows_impl {
|
||||
cwd: &Path,
|
||||
mut env_map: HashMap<String, String>,
|
||||
timeout_ms: Option<u64>,
|
||||
use_private_desktop: bool,
|
||||
) -> Result<CaptureResult> {
|
||||
let policy = parse_policy(policy_json_or_preset)?;
|
||||
normalize_null_device_env(&mut env_map);
|
||||
@@ -302,6 +305,7 @@ mod windows_impl {
|
||||
cwd: cwd.to_path_buf(),
|
||||
env_map: env_map.clone(),
|
||||
timeout_ms,
|
||||
use_private_desktop,
|
||||
stdin_pipe: stdin_name.clone(),
|
||||
stdout_pipe: stdout_name.clone(),
|
||||
stderr_pipe: stderr_name.clone(),
|
||||
@@ -530,6 +534,7 @@ mod stub {
|
||||
}
|
||||
|
||||
/// Stub implementation for non-Windows targets; sandboxing only works on Windows.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_windows_sandbox_capture(
|
||||
_policy_json_or_preset: &str,
|
||||
_sandbox_policy_cwd: &Path,
|
||||
@@ -538,6 +543,7 @@ mod stub {
|
||||
_cwd: &Path,
|
||||
_env_map: HashMap<String, String>,
|
||||
_timeout_ms: Option<u64>,
|
||||
_use_private_desktop: bool,
|
||||
) -> Result<CaptureResult> {
|
||||
bail!("Windows sandbox is only available on Windows")
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ windows_modules!(
|
||||
allow,
|
||||
audit,
|
||||
cap,
|
||||
desktop,
|
||||
dpapi,
|
||||
env,
|
||||
helper_materialization,
|
||||
@@ -126,6 +127,8 @@ pub use windows_impl::run_windows_sandbox_legacy_preflight;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows_impl::CaptureResult;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use winutil::quote_windows_arg;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use winutil::string_from_sid_bytes;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use winutil::to_wide;
|
||||
@@ -158,19 +161,15 @@ mod windows_impl {
|
||||
use super::env::apply_no_network_to_env;
|
||||
use super::env::ensure_non_interactive_pager;
|
||||
use super::env::normalize_null_device_env;
|
||||
use super::logging::debug_log;
|
||||
use super::logging::log_failure;
|
||||
use super::logging::log_start;
|
||||
use super::logging::log_success;
|
||||
use super::path_normalization::canonicalize_path;
|
||||
use super::policy::parse_policy;
|
||||
use super::policy::SandboxPolicy;
|
||||
use super::process::make_env_block;
|
||||
use super::process::create_process_as_user;
|
||||
use super::token::convert_string_sid_to_sid;
|
||||
use super::token::create_workspace_write_token_with_caps_from;
|
||||
use super::winutil::format_last_error;
|
||||
use super::winutil::quote_windows_arg;
|
||||
use super::winutil::to_wide;
|
||||
use super::workspace_acl::is_command_cwd_root;
|
||||
use super::workspace_acl::protect_workspace_agents_dir;
|
||||
use super::workspace_acl::protect_workspace_codex_dir;
|
||||
@@ -187,14 +186,9 @@ mod windows_impl {
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT;
|
||||
use windows_sys::Win32::System::Pipes::CreatePipe;
|
||||
use windows_sys::Win32::System::Threading::CreateProcessAsUserW;
|
||||
use windows_sys::Win32::System::Threading::GetExitCodeProcess;
|
||||
use windows_sys::Win32::System::Threading::WaitForSingleObject;
|
||||
use windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT;
|
||||
use windows_sys::Win32::System::Threading::INFINITE;
|
||||
use windows_sys::Win32::System::Threading::PROCESS_INFORMATION;
|
||||
use windows_sys::Win32::System::Threading::STARTF_USESTDHANDLES;
|
||||
use windows_sys::Win32::System::Threading::STARTUPINFOW;
|
||||
|
||||
type PipeHandles = ((HANDLE, HANDLE), (HANDLE, HANDLE), (HANDLE, HANDLE));
|
||||
|
||||
@@ -242,6 +236,7 @@ mod windows_impl {
|
||||
pub timed_out: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_windows_sandbox_capture(
|
||||
policy_json_or_preset: &str,
|
||||
sandbox_policy_cwd: &Path,
|
||||
@@ -250,6 +245,7 @@ mod windows_impl {
|
||||
cwd: &Path,
|
||||
mut env_map: HashMap<String, String>,
|
||||
timeout_ms: Option<u64>,
|
||||
use_private_desktop: bool,
|
||||
) -> Result<CaptureResult> {
|
||||
let policy = parse_policy(policy_json_or_preset)?;
|
||||
let apply_network_block = should_apply_network_block(&policy);
|
||||
@@ -358,61 +354,34 @@ mod windows_impl {
|
||||
|
||||
let (stdin_pair, stdout_pair, stderr_pair) = unsafe { setup_stdio_pipes()? };
|
||||
let ((in_r, in_w), (out_r, out_w), (err_r, err_w)) = (stdin_pair, stdout_pair, stderr_pair);
|
||||
let mut si: STARTUPINFOW = unsafe { std::mem::zeroed() };
|
||||
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdInput = in_r;
|
||||
si.hStdOutput = out_w;
|
||||
si.hStdError = err_w;
|
||||
|
||||
let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
|
||||
let cmdline_str = command
|
||||
.iter()
|
||||
.map(|a| quote_windows_arg(a))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let mut cmdline: Vec<u16> = to_wide(&cmdline_str);
|
||||
let env_block = make_env_block(&env_map);
|
||||
let desktop = to_wide("Winsta0\\Default");
|
||||
si.lpDesktop = desktop.as_ptr() as *mut u16;
|
||||
let spawn_res = unsafe {
|
||||
CreateProcessAsUserW(
|
||||
create_process_as_user(
|
||||
h_token,
|
||||
ptr::null(),
|
||||
cmdline.as_mut_ptr(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
1,
|
||||
CREATE_UNICODE_ENVIRONMENT,
|
||||
env_block.as_ptr() as *mut c_void,
|
||||
to_wide(cwd).as_ptr(),
|
||||
&si,
|
||||
&mut pi,
|
||||
&command,
|
||||
cwd,
|
||||
&env_map,
|
||||
logs_base_dir,
|
||||
Some((in_r, out_w, err_w)),
|
||||
use_private_desktop,
|
||||
)
|
||||
};
|
||||
if spawn_res == 0 {
|
||||
let err = unsafe { GetLastError() } as i32;
|
||||
let dbg = format!(
|
||||
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={}",
|
||||
err,
|
||||
format_last_error(err),
|
||||
cwd.display(),
|
||||
cmdline_str,
|
||||
env_block.len(),
|
||||
si.dwFlags,
|
||||
);
|
||||
debug_log(&dbg, logs_base_dir);
|
||||
unsafe {
|
||||
CloseHandle(in_r);
|
||||
CloseHandle(in_w);
|
||||
CloseHandle(out_r);
|
||||
CloseHandle(out_w);
|
||||
CloseHandle(err_r);
|
||||
CloseHandle(err_w);
|
||||
CloseHandle(h_token);
|
||||
let created = match spawn_res {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
unsafe {
|
||||
CloseHandle(in_r);
|
||||
CloseHandle(in_w);
|
||||
CloseHandle(out_r);
|
||||
CloseHandle(out_w);
|
||||
CloseHandle(err_r);
|
||||
CloseHandle(err_w);
|
||||
CloseHandle(h_token);
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::anyhow!("CreateProcessAsUserW failed: {}", err));
|
||||
}
|
||||
};
|
||||
let pi = created.process_info;
|
||||
let _desktop = created;
|
||||
|
||||
unsafe {
|
||||
CloseHandle(in_r);
|
||||
@@ -617,6 +586,7 @@ mod stub {
|
||||
pub timed_out: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_windows_sandbox_capture(
|
||||
_policy_json_or_preset: &str,
|
||||
_sandbox_policy_cwd: &Path,
|
||||
@@ -625,6 +595,7 @@ mod stub {
|
||||
_cwd: &Path,
|
||||
_env_map: HashMap<String, String>,
|
||||
_timeout_ms: Option<u64>,
|
||||
_use_private_desktop: bool,
|
||||
) -> Result<CaptureResult> {
|
||||
bail!("Windows sandbox is only available on Windows")
|
||||
}
|
||||
|
||||
@@ -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