Codex Handbook
windows-sandbox-rs/src/process.rs 355 lines
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}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!(                    "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)?;            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,            })        }    }}/// 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);        }    })}