mirror of
https://github.com/openai/codex.git
synced 2026-05-05 20:07:02 +00:00
420 lines
14 KiB
Rust
420 lines
14 KiB
Rust
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::to_wide;
|
|
use anyhow::Result;
|
|
use anyhow::anyhow;
|
|
use std::collections::HashMap;
|
|
use std::ffi::c_void;
|
|
use std::path::Path;
|
|
use std::ptr;
|
|
use windows_sys::Win32::Foundation::CloseHandle;
|
|
use windows_sys::Win32::Foundation::GetLastError;
|
|
use windows_sys::Win32::Foundation::HANDLE;
|
|
use windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT;
|
|
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
|
use windows_sys::Win32::Foundation::SetHandleInformation;
|
|
use windows_sys::Win32::Storage::FileSystem::ReadFile;
|
|
use windows_sys::Win32::System::Console::GetStdHandle;
|
|
use windows_sys::Win32::System::Console::STD_ERROR_HANDLE;
|
|
use windows_sys::Win32::System::Console::STD_INPUT_HANDLE;
|
|
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
|
|
use windows_sys::Win32::System::Pipes::CreatePipe;
|
|
use windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT;
|
|
use windows_sys::Win32::System::Threading::CreateProcessAsUserW;
|
|
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 {
|
|
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();
|
|
items.sort_by(|a, b| {
|
|
a.0.to_uppercase()
|
|
.cmp(&b.0.to_uppercase())
|
|
.then(a.0.cmp(&b.0))
|
|
});
|
|
let mut w: Vec<u16> = Vec::new();
|
|
for (k, v) in items {
|
|
let mut s = to_wide(format!("{k}={v}"));
|
|
s.pop();
|
|
w.extend_from_slice(&s);
|
|
w.push(0);
|
|
}
|
|
w.push(0);
|
|
w
|
|
}
|
|
|
|
pub(crate) fn format_create_process_as_user_error(
|
|
err: i32,
|
|
cwd: &Path,
|
|
cmdline: &str,
|
|
env_block_len: usize,
|
|
si_flags: u32,
|
|
creation_flags: u32,
|
|
use_private_desktop: bool,
|
|
) -> String {
|
|
let mut msg = format!(
|
|
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
|
|
err,
|
|
format_last_error(err),
|
|
cwd.display(),
|
|
cmdline,
|
|
env_block_len,
|
|
si_flags,
|
|
creation_flags,
|
|
);
|
|
if use_private_desktop {
|
|
msg.push_str(" | private_desktop=true");
|
|
msg.push_str(
|
|
" | hint=try setting `windows.sandbox_private_desktop = false` if this machine cannot launch sandboxed processes on a private desktop",
|
|
);
|
|
}
|
|
msg
|
|
}
|
|
|
|
unsafe fn ensure_inheritable_stdio(si: &mut STARTUPINFOW) -> Result<()> {
|
|
for kind in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
|
|
let h = GetStdHandle(kind);
|
|
if h == 0 || h == INVALID_HANDLE_VALUE {
|
|
return Err(anyhow!("GetStdHandle failed: {}", GetLastError()));
|
|
}
|
|
if SetHandleInformation(h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) == 0 {
|
|
return Err(anyhow!("SetHandleInformation failed: {}", GetLastError()));
|
|
}
|
|
}
|
|
si.dwFlags |= STARTF_USESTDHANDLES;
|
|
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
Ok(())
|
|
}
|
|
|
|
/// # Safety
|
|
/// Caller must provide a valid primary token handle (`h_token`) with appropriate access,
|
|
/// and the `argv`, `cwd`, and `env_map` must remain valid for the duration of the call.
|
|
pub unsafe fn create_process_as_user(
|
|
h_token: HANDLE,
|
|
argv: &[String],
|
|
cwd: &Path,
|
|
env_map: &HashMap<String, String>,
|
|
logs_base_dir: Option<&Path>,
|
|
stdio: Option<(HANDLE, HANDLE, HANDLE)>,
|
|
use_private_desktop: bool,
|
|
) -> Result<CreatedProcess> {
|
|
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 desktop = LaunchDesktop::prepare(use_private_desktop, logs_base_dir)?;
|
|
let mut pi: PROCESS_INFORMATION = std::mem::zeroed();
|
|
let cwd_wide = to_wide(cwd);
|
|
let env_block_len = env_block.len();
|
|
match stdio {
|
|
Some((stdin_h, stdout_h, stderr_h)) => {
|
|
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()
|
|
));
|
|
}
|
|
}
|
|
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_create_process_as_user_error(
|
|
err,
|
|
cwd,
|
|
&cmdline_str,
|
|
env_block_len,
|
|
si.StartupInfo.dwFlags,
|
|
creation_flags,
|
|
use_private_desktop,
|
|
);
|
|
logging::debug_log(&msg, logs_base_dir);
|
|
return Err(anyhow!(msg));
|
|
}
|
|
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)?;
|
|
|
|
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_create_process_as_user_error(
|
|
err,
|
|
cwd,
|
|
&cmdline_str,
|
|
env_block_len,
|
|
si.dwFlags,
|
|
creation_flags,
|
|
use_private_desktop,
|
|
);
|
|
logging::debug_log(&msg, logs_base_dir);
|
|
return Err(anyhow!(msg));
|
|
}
|
|
Ok(CreatedProcess {
|
|
process_info: pi,
|
|
startup_info: si,
|
|
_desktop: desktop,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::format_create_process_as_user_error;
|
|
use std::path::Path;
|
|
|
|
#[test]
|
|
fn create_process_error_includes_private_desktop_hint() {
|
|
let message = format_create_process_as_user_error(
|
|
5,
|
|
Path::new("C:\\workspace"),
|
|
"cmd /c dir",
|
|
42,
|
|
256,
|
|
1024,
|
|
true,
|
|
);
|
|
|
|
assert!(message.contains("private_desktop=true"));
|
|
assert!(message.contains("windows.sandbox_private_desktop = false"));
|
|
}
|
|
|
|
#[test]
|
|
fn create_process_error_omits_private_desktop_hint_when_disabled() {
|
|
let message = format_create_process_as_user_error(
|
|
5,
|
|
Path::new("C:\\workspace"),
|
|
"cmd /c dir",
|
|
42,
|
|
256,
|
|
1024,
|
|
false,
|
|
);
|
|
|
|
assert!(!message.contains("private_desktop=true"));
|
|
assert!(!message.contains("windows.sandbox_private_desktop = false"));
|
|
}
|
|
}
|
|
|
|
/// Controls whether the child's stdin handle is kept open for writing.
|
|
#[allow(dead_code)]
|
|
pub enum StdinMode {
|
|
Closed,
|
|
Open,
|
|
}
|
|
|
|
/// Controls how stderr is wired for a pipe-spawned process.
|
|
#[allow(dead_code)]
|
|
pub enum StderrMode {
|
|
MergeStdout,
|
|
Separate,
|
|
}
|
|
|
|
/// Handles returned by `spawn_process_with_pipes`.
|
|
#[allow(dead_code)]
|
|
pub struct PipeSpawnHandles {
|
|
pub process: PROCESS_INFORMATION,
|
|
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],
|
|
cwd: &Path,
|
|
env_map: &HashMap<String, String>,
|
|
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;
|
|
let mut out_r: HANDLE = 0;
|
|
let mut out_w: HANDLE = 0;
|
|
let mut err_r: HANDLE = 0;
|
|
let mut err_w: HANDLE = 0;
|
|
unsafe {
|
|
if CreatePipe(&mut in_r, &mut in_w, ptr::null_mut(), 0) == 0 {
|
|
return Err(anyhow!("CreatePipe stdin failed: {}", GetLastError()));
|
|
}
|
|
if CreatePipe(&mut out_r, &mut out_w, ptr::null_mut(), 0) == 0 {
|
|
CloseHandle(in_r);
|
|
CloseHandle(in_w);
|
|
return Err(anyhow!("CreatePipe stdout failed: {}", GetLastError()));
|
|
}
|
|
if matches!(stderr_mode, StderrMode::Separate)
|
|
&& CreatePipe(&mut err_r, &mut err_w, ptr::null_mut(), 0) == 0
|
|
{
|
|
CloseHandle(in_r);
|
|
CloseHandle(in_w);
|
|
CloseHandle(out_r);
|
|
CloseHandle(out_w);
|
|
return Err(anyhow!("CreatePipe stderr failed: {}", GetLastError()));
|
|
}
|
|
}
|
|
|
|
let stderr_handle = match stderr_mode {
|
|
StderrMode::MergeStdout => out_w,
|
|
StderrMode::Separate => err_w,
|
|
};
|
|
|
|
let stdio = Some((in_r, out_w, stderr_handle));
|
|
let spawn_result = unsafe {
|
|
create_process_as_user(
|
|
h_token,
|
|
argv,
|
|
cwd,
|
|
env_map,
|
|
logs_base_dir,
|
|
stdio,
|
|
use_private_desktop,
|
|
)
|
|
};
|
|
let created = match spawn_result {
|
|
Ok(v) => v,
|
|
Err(err) => {
|
|
unsafe {
|
|
CloseHandle(in_r);
|
|
CloseHandle(in_w);
|
|
CloseHandle(out_r);
|
|
CloseHandle(out_w);
|
|
if matches!(stderr_mode, StderrMode::Separate) {
|
|
CloseHandle(err_r);
|
|
CloseHandle(err_w);
|
|
}
|
|
}
|
|
return Err(err);
|
|
}
|
|
};
|
|
let CreatedProcess {
|
|
process_info: pi,
|
|
_desktop: desktop,
|
|
..
|
|
} = created;
|
|
|
|
unsafe {
|
|
CloseHandle(in_r);
|
|
CloseHandle(out_w);
|
|
if matches!(stderr_mode, StderrMode::Separate) {
|
|
CloseHandle(err_w);
|
|
}
|
|
if matches!(stdin_mode, StdinMode::Closed) {
|
|
CloseHandle(in_w);
|
|
}
|
|
}
|
|
|
|
Ok(PipeSpawnHandles {
|
|
process: pi,
|
|
stdin_write: match stdin_mode {
|
|
StdinMode::Open => Some(in_w),
|
|
StdinMode::Closed => None,
|
|
},
|
|
stdout_read: out_r,
|
|
stderr_read: match stderr_mode {
|
|
StderrMode::Separate => Some(err_r),
|
|
StderrMode::MergeStdout => None,
|
|
},
|
|
desktop,
|
|
})
|
|
}
|
|
|
|
/// Reads a HANDLE until EOF and invokes `on_chunk` for each read.
|
|
pub fn read_handle_loop<F>(handle: HANDLE, mut on_chunk: F) -> std::thread::JoinHandle<()>
|
|
where
|
|
F: FnMut(&[u8]) + Send + 'static,
|
|
{
|
|
std::thread::spawn(move || {
|
|
let mut buf = [0u8; 8192];
|
|
loop {
|
|
let mut read_bytes: u32 = 0;
|
|
let ok = unsafe {
|
|
ReadFile(
|
|
handle,
|
|
buf.as_mut_ptr(),
|
|
buf.len() as u32,
|
|
&mut read_bytes,
|
|
ptr::null_mut(),
|
|
)
|
|
};
|
|
if ok == 0 || read_bytes == 0 {
|
|
break;
|
|
}
|
|
on_chunk(&buf[..read_bytes as usize]);
|
|
}
|
|
unsafe {
|
|
CloseHandle(handle);
|
|
}
|
|
})
|
|
}
|