mirror of
https://github.com/openai/codex.git
synced 2026-09-07 15:40:00 +00:00
fix(windows-sandbox): launch legacy sandbox via junction cwd
This commit is contained in:
209
codex-rs/windows-sandbox-rs/src/legacy_cwd.rs
Normal file
209
codex-rs/windows-sandbox-rs/src/legacy_cwd.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use crate::log_note;
|
||||
use crate::path_normalization::normalize_spawn_cwd;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::os::windows::fs::MetadataExt as _;
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
use std::path::Component;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Prefix;
|
||||
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
|
||||
|
||||
fn junction_name_for_path(path: &Path) -> String {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
path.to_string_lossy().hash(&mut hasher);
|
||||
format!("{:x}", hasher.finish())
|
||||
}
|
||||
|
||||
fn junction_root_for_userprofile(userprofile: &str) -> PathBuf {
|
||||
PathBuf::from(userprofile)
|
||||
.join(".codex")
|
||||
.join(".sandbox")
|
||||
.join("cwd")
|
||||
}
|
||||
|
||||
fn drive_letter(path: &Path) -> Option<char> {
|
||||
match path.components().next()? {
|
||||
Component::Prefix(prefix) => match prefix.kind() {
|
||||
Prefix::Disk(drive) | Prefix::VerbatimDisk(drive) => {
|
||||
Some((drive as char).to_ascii_uppercase())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn system_drive_letter(system_drive: Option<&str>) -> Option<char> {
|
||||
drive_letter(Path::new(system_drive?))
|
||||
}
|
||||
|
||||
fn should_materialize_junction(requested_cwd: &Path, system_drive: Option<&str>) -> bool {
|
||||
let Some(requested_drive) = drive_letter(requested_cwd) else {
|
||||
return false;
|
||||
};
|
||||
let Some(system_drive) = system_drive_letter(system_drive) else {
|
||||
return false;
|
||||
};
|
||||
requested_drive != system_drive
|
||||
}
|
||||
|
||||
fn create_cwd_junction(requested_cwd: &Path, log_dir: Option<&Path>) -> Option<PathBuf> {
|
||||
let userprofile = std::env::var("USERPROFILE").ok()?;
|
||||
let junction_root = junction_root_for_userprofile(&userprofile);
|
||||
if let Err(err) = std::fs::create_dir_all(&junction_root) {
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: failed to create {}: {err}",
|
||||
junction_root.display()
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let junction_path = junction_root.join(junction_name_for_path(requested_cwd));
|
||||
if junction_path.exists() {
|
||||
match std::fs::symlink_metadata(&junction_path) {
|
||||
Ok(md) if (md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT) != 0 => {
|
||||
log_note(
|
||||
&format!("junction: reusing existing {}", junction_path.display()),
|
||||
log_dir,
|
||||
);
|
||||
return Some(junction_path);
|
||||
}
|
||||
Ok(_) => {
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: existing path is not a reparse point, recreating {}",
|
||||
junction_path.display()
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: failed to stat existing {}: {err}",
|
||||
junction_path.display()
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = std::fs::remove_dir(&junction_path) {
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: failed to remove existing {}: {err}",
|
||||
junction_path.display()
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let link = junction_path.to_string_lossy().to_string();
|
||||
let target = requested_cwd.to_string_lossy().to_string();
|
||||
let link_quoted = format!("\"{link}\"");
|
||||
let target_quoted = format!("\"{target}\"");
|
||||
log_note(
|
||||
&format!("junction: creating via cmd /c mklink /J {link_quoted} {target_quoted}"),
|
||||
log_dir,
|
||||
);
|
||||
let output = match std::process::Command::new("cmd")
|
||||
.raw_arg("/c")
|
||||
.raw_arg("mklink")
|
||||
.raw_arg("/J")
|
||||
.raw_arg(&link_quoted)
|
||||
.raw_arg(&target_quoted)
|
||||
.output()
|
||||
{
|
||||
Ok(output) => output,
|
||||
Err(err) => {
|
||||
log_note(&format!("junction: mklink failed to run: {err}"), log_dir);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if output.status.success() && junction_path.exists() {
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: created {} -> {}",
|
||||
junction_path.display(),
|
||||
requested_cwd.display()
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
return Some(junction_path);
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
log_note(
|
||||
&format!(
|
||||
"junction: mklink failed status={:?} stdout={} stderr={}",
|
||||
output.status,
|
||||
stdout.trim(),
|
||||
stderr.trim()
|
||||
),
|
||||
log_dir,
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn effective_legacy_spawn_cwd(cwd: &Path, log_dir: Option<&Path>) -> PathBuf {
|
||||
let normalized_cwd = normalize_spawn_cwd(cwd);
|
||||
if should_materialize_junction(
|
||||
&normalized_cwd,
|
||||
std::env::var("SystemDrive").ok().as_deref(),
|
||||
) {
|
||||
create_cwd_junction(&normalized_cwd, log_dir).unwrap_or_else(|| normalized_cwd.clone())
|
||||
} else {
|
||||
normalized_cwd
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::effective_legacy_spawn_cwd;
|
||||
use super::should_materialize_junction;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn skips_system_drive_workspaces() {
|
||||
assert!(!should_materialize_junction(
|
||||
Path::new(r"C:\repo"),
|
||||
Some("C:"),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_junction_for_non_system_drive_workspaces() {
|
||||
assert!(should_materialize_junction(
|
||||
Path::new(r"F:\repo"),
|
||||
Some("C:"),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_unc_paths() {
|
||||
assert!(!should_materialize_junction(
|
||||
Path::new(r"\\server\share\repo"),
|
||||
Some("C:"),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_system_drive_paths_unchanged() {
|
||||
let cwd = PathBuf::from(r"C:\repo");
|
||||
assert_eq!(effective_legacy_spawn_cwd(&cwd, None), cwd);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ windows_modules!(
|
||||
helper_materialization,
|
||||
hide_users,
|
||||
identity,
|
||||
legacy_cwd,
|
||||
logging,
|
||||
path_normalization,
|
||||
policy,
|
||||
@@ -253,6 +254,7 @@ mod windows_impl {
|
||||
use super::allow::compute_allow_paths;
|
||||
use super::cap::load_or_create_cap_sids;
|
||||
use super::cap::workspace_cap_sid_for_cwd;
|
||||
use super::legacy_cwd::effective_legacy_spawn_cwd;
|
||||
use super::logging::log_failure;
|
||||
use super::logging::log_success;
|
||||
use super::path_normalization::canonicalize_path;
|
||||
@@ -461,11 +463,12 @@ 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 effective_cwd = effective_legacy_spawn_cwd(cwd, logs_base_dir);
|
||||
let spawn_res = unsafe {
|
||||
create_process_as_user(
|
||||
h_token,
|
||||
&command,
|
||||
cwd,
|
||||
&effective_cwd,
|
||||
&env_map,
|
||||
logs_base_dir,
|
||||
Some((in_r, out_w, err_w)),
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::windows_common::normalize_windows_tty_input;
|
||||
use crate::acl::revoke_ace;
|
||||
use crate::conpty::spawn_conpty_process_as_user;
|
||||
use crate::desktop::LaunchDesktop;
|
||||
use crate::legacy_cwd::effective_legacy_spawn_cwd;
|
||||
use crate::logging::log_failure;
|
||||
use crate::logging::log_success;
|
||||
use crate::process::StderrMode;
|
||||
@@ -66,11 +67,12 @@ fn spawn_legacy_process(
|
||||
writer_rx: mpsc::Receiver<Vec<u8>>,
|
||||
logs_base_dir: Option<&Path>,
|
||||
) -> Result<LegacyProcessHandles> {
|
||||
let effective_cwd = effective_legacy_spawn_cwd(cwd, logs_base_dir);
|
||||
let (pi, output_join, writer_handle, hpc, desktop) = if tty {
|
||||
let (pi, conpty) = spawn_conpty_process_as_user(
|
||||
h_token,
|
||||
command,
|
||||
cwd,
|
||||
&effective_cwd,
|
||||
env_map,
|
||||
use_private_desktop,
|
||||
logs_base_dir,
|
||||
@@ -87,7 +89,7 @@ fn spawn_legacy_process(
|
||||
let pipe_handles = spawn_process_with_pipes(
|
||||
h_token,
|
||||
command,
|
||||
cwd,
|
||||
&effective_cwd,
|
||||
env_map,
|
||||
if stdin_open {
|
||||
StdinMode::Open
|
||||
|
||||
Reference in New Issue
Block a user