Fix Windows sandbox process application resolution

This commit is contained in:
David Wiesen
2026-06-14 10:15:33 -07:00
parent dfd03ea01b
commit 0618ba0d69
3 changed files with 62 additions and 4 deletions

View File

@@ -92,5 +92,8 @@ features = [
]
version = "0.52"
[target.'cfg(windows)'.dependencies.which]
workspace = true
[dev-dependencies]
pretty_assertions = { workspace = true }

View File

@@ -3,6 +3,7 @@ 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::resolve_application_path;
use crate::winutil::to_wide;
use anyhow::Result;
use anyhow::anyhow;
@@ -86,6 +87,8 @@ pub unsafe fn create_process_as_user(
) -> Result<CreatedProcess> {
let cmdline_str = argv_to_command_line(argv);
let mut cmdline: Vec<u16> = to_wide(&cmdline_str);
let application_path = resolve_application_path(argv, env_map, cwd);
let application_name = application_path.as_deref().map(to_wide);
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();
@@ -122,7 +125,9 @@ pub unsafe fn create_process_as_user(
let creation_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
let ok = CreateProcessAsUserW(
h_token,
std::ptr::null(),
application_name
.as_ref()
.map_or(std::ptr::null(), |wide| wide.as_ptr()),
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
@@ -136,9 +141,12 @@ pub unsafe fn create_process_as_user(
if ok == 0 {
let err = GetLastError() as i32;
let msg = format!(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
"CreateProcessAsUserW failed: {} ({}) | app={} | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
application_path
.as_deref()
.map_or("<unresolved>", |path| path.to_string_lossy().as_ref()),
cwd.display(),
cmdline_str,
env_block_len,
@@ -163,7 +171,9 @@ pub unsafe fn create_process_as_user(
let creation_flags = CREATE_UNICODE_ENVIRONMENT;
let ok = CreateProcessAsUserW(
h_token,
std::ptr::null(),
application_name
.as_ref()
.map_or(std::ptr::null(), |wide| wide.as_ptr()),
cmdline.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
@@ -177,9 +187,12 @@ pub unsafe fn create_process_as_user(
if ok == 0 {
let err = GetLastError() as i32;
let msg = format!(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
"CreateProcessAsUserW failed: {} ({}) | app={} | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
application_path
.as_deref()
.map_or("<unresolved>", |path| path.to_string_lossy().as_ref()),
cwd.display(),
cmdline_str,
env_block_len,

View File

@@ -1,6 +1,10 @@
use anyhow::Result;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use std::path::PathBuf;
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::HLOCAL;
@@ -73,6 +77,25 @@ pub fn argv_to_command_line(argv: &[String]) -> String {
.join(" ")
}
#[cfg(target_os = "windows")]
pub fn resolve_application_path(
argv: &[String],
env_map: &HashMap<String, String>,
cwd: &Path,
) -> Option<PathBuf> {
let program = argv.first().filter(|program| !program.is_empty())?;
let candidate = Path::new(program);
if candidate.components().count() > 1 {
return Some(candidate.to_path_buf());
}
let search_path = env_map
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case("PATH"))
.map(|(_, value)| OsString::from(value));
which::which_in(program, search_path, cwd).ok()
}
// Produce a readable description for a Win32 error code.
pub fn format_last_error(err: i32) -> String {
unsafe {
@@ -208,7 +231,11 @@ fn sid_bytes_from_string(sid_str: &str) -> Result<Vec<u8>> {
#[cfg(test)]
mod tests {
use super::argv_to_command_line;
use super::resolve_application_path;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
#[test]
fn argv_to_command_line_quotes_each_argument_independently() {
@@ -238,4 +265,19 @@ mod tests {
"pwsh.exe -Command \"Write-Output \\\"hello world\\\"\""
);
}
#[cfg(target_os = "windows")]
#[test]
fn resolve_application_path_preserves_explicit_paths() {
let resolved = resolve_application_path(
&[r"C:\Program Files\Git\cmd\git.exe".to_string()],
&HashMap::new(),
Path::new(r"C:\"),
);
assert_eq!(
resolved,
Some(PathBuf::from(r"C:\Program Files\Git\cmd\git.exe"))
);
}
}