Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
c637d10e83 fix(windows): normalize sandbox cwd for mapped drives 2026-04-25 20:43:35 -07:00
4 changed files with 106 additions and 10 deletions

View File

@@ -35,7 +35,9 @@ use codex_windows_sandbox::encode_bytes;
use codex_windows_sandbox::get_current_token_for_restriction;
use codex_windows_sandbox::hide_current_user_profile_dir;
use codex_windows_sandbox::log_note;
use codex_windows_sandbox::normalize_spawn_cwd;
use codex_windows_sandbox::parse_policy;
use codex_windows_sandbox::path_uses_unc_prefix;
use codex_windows_sandbox::read_frame;
use codex_windows_sandbox::read_handle_loop;
use codex_windows_sandbox::spawn_process_with_pipes;
@@ -167,6 +169,21 @@ fn read_spawn_request(reader: &mut File) -> Result<SpawnRequest> {
/// Pick an effective CWD, using a junction if the ACL helper is active.
fn effective_cwd(req_cwd: &Path, log_dir: Option<&Path>) -> PathBuf {
let normalized_cwd = normalize_spawn_cwd(req_cwd);
if path_uses_unc_prefix(&normalized_cwd) {
if normalized_cwd != req_cwd {
log_note(
&format!(
"junction: using UNC cwd {} resolved from {}",
normalized_cwd.display(),
req_cwd.display()
),
log_dir,
);
}
return normalized_cwd;
}
let use_junction = match read_acl_mutex::read_acl_mutex_exists() {
Ok(exists) => exists,
Err(err) => {
@@ -180,9 +197,10 @@ fn effective_cwd(req_cwd: &Path, log_dir: Option<&Path>) -> PathBuf {
}
};
if use_junction {
cwd_junction::create_cwd_junction(req_cwd, log_dir).unwrap_or_else(|| req_cwd.to_path_buf())
cwd_junction::create_cwd_junction(req_cwd, log_dir)
.unwrap_or_else(|| normalized_cwd.clone())
} else {
req_cwd.to_path_buf()
normalized_cwd
}
}

View File

@@ -149,6 +149,10 @@ pub use logging::log_note;
#[cfg(target_os = "windows")]
pub use path_normalization::canonicalize_path;
#[cfg(target_os = "windows")]
pub use path_normalization::normalize_spawn_cwd;
#[cfg(target_os = "windows")]
pub use path_normalization::path_uses_unc_prefix;
#[cfg(target_os = "windows")]
pub use policy::SandboxPolicy;
#[cfg(target_os = "windows")]
pub use policy::parse_policy;

View File

@@ -1,10 +1,32 @@
use std::path::Path;
use std::path::PathBuf;
#[cfg(target_os = "windows")]
use std::path::Prefix;
pub fn canonicalize_path(path: &Path) -> PathBuf {
dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}
pub fn normalize_spawn_cwd(path: &Path) -> PathBuf {
let simplified = dunce::simplified(path).to_path_buf();
if path_uses_unc_prefix(&simplified) {
return simplified;
}
let canonical = dunce::canonicalize(path).ok();
let canonical = canonical
.as_deref()
.map(dunce::simplified)
.map(Path::to_path_buf);
if let Some(canonical) = canonical
&& path_uses_unc_prefix(&canonical)
{
return canonical;
}
simplified
}
pub fn canonical_path_key(path: &Path) -> String {
canonicalize_path(path)
.to_string_lossy()
@@ -12,17 +34,67 @@ pub fn canonical_path_key(path: &Path) -> String {
.to_ascii_lowercase()
}
pub fn path_uses_unc_prefix(path: &Path) -> bool {
#[cfg(target_os = "windows")]
{
matches!(
path.components().next(),
Some(std::path::Component::Prefix(prefix))
if matches!(prefix.kind(), Prefix::UNC(..) | Prefix::VerbatimUNC(..))
)
}
#[cfg(not(target_os = "windows"))]
{
let _ = path;
false
}
}
#[cfg(test)]
mod tests {
use super::canonical_path_key;
use super::normalize_spawn_cwd;
use super::path_uses_unc_prefix;
use pretty_assertions::assert_eq;
use std::path::Path;
use std::path::PathBuf;
#[test]
fn canonical_path_key_normalizes_case_and_separators() {
let windows_style = Path::new(r"C:\Users\Dev\Repo");
let slash_style = Path::new("c:/users/dev/repo");
assert_eq!(canonical_path_key(windows_style), canonical_path_key(slash_style));
assert_eq!(
canonical_path_key(windows_style),
canonical_path_key(slash_style)
);
}
#[cfg(target_os = "windows")]
#[test]
fn path_uses_unc_prefix_matches_standard_and_verbatim_unc_paths() {
assert!(path_uses_unc_prefix(Path::new(r"\\server\share\repo")));
assert!(path_uses_unc_prefix(Path::new(
r"\\?\UNC\server\share\repo"
)));
assert!(!path_uses_unc_prefix(Path::new(r"C:\repo")));
}
#[test]
fn normalize_spawn_cwd_preserves_regular_local_paths() {
let path = PathBuf::from(r"C:\repo");
assert_eq!(normalize_spawn_cwd(&path), path);
}
#[cfg(target_os = "windows")]
#[test]
fn normalize_spawn_cwd_simplifies_verbatim_unc_paths() {
let path = PathBuf::from(r"\\?\UNC\server\share\repo");
assert_eq!(
normalize_spawn_cwd(&path),
PathBuf::from(r"\\server\share\repo")
);
}
}

View File

@@ -1,29 +1,30 @@
use crate::desktop::LaunchDesktop;
use crate::logging;
use crate::path_normalization::normalize_spawn_cwd;
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::anyhow;
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::GetLastError;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Foundation::SetHandleInformation;
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::CreateProcessAsUserW;
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;
@@ -89,7 +90,8 @@ pub unsafe fn create_process_as_user(
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 spawn_cwd = normalize_spawn_cwd(cwd);
let cwd_wide = to_wide(&spawn_cwd);
let env_block_len = env_block.len();
match stdio {
Some((stdin_h, stdout_h, stderr_h)) => {
@@ -139,7 +141,7 @@ pub unsafe fn create_process_as_user(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
cwd.display(),
spawn_cwd.display(),
cmdline_str,
env_block_len,
si.StartupInfo.dwFlags,
@@ -180,7 +182,7 @@ pub unsafe fn create_process_as_user(
"CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={} | si_flags={} | creation_flags={}",
err,
format_last_error(err),
cwd.display(),
spawn_cwd.display(),
cmdline_str,
env_block_len,
si.dwFlags,