Compare commits

..

1 Commits

Author SHA1 Message Date
David Wiesen
7aeae2632e Repair existing Windows sandbox write ACLs 2026-04-19 00:04:19 -07:00
4 changed files with 74 additions and 78 deletions

View File

@@ -34,10 +34,6 @@ pub const APPLY_PATCH_TOOL_INSTRUCTIONS: &str = include_str!("../apply_patch_too
/// dispatcher.
pub const CODEX_CORE_APPLY_PATCH_ARG1: &str = "--codex-run-as-apply-patch";
/// Like [`CODEX_CORE_APPLY_PATCH_ARG1`], but argv[2] is a UTF-8 file path
/// containing the patch body instead of the patch body itself.
pub const CODEX_CORE_APPLY_PATCH_FILE_ARG1: &str = "--codex-run-as-apply-patch-file";
#[derive(Debug, Error, PartialEq)]
pub enum ApplyPatchError {
#[error(transparent)]

View File

@@ -4,7 +4,6 @@ use std::path::Path;
use std::path::PathBuf;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_FILE_ARG1;
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
use codex_utils_home_dir::find_codex_home;
#[cfg(unix)]
@@ -94,32 +93,19 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
}
let argv1 = args.next().unwrap_or_default();
if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 || argv1 == CODEX_CORE_APPLY_PATCH_FILE_ARG1 {
if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 {
let patch_arg = args.next().and_then(|s| s.to_str().map(str::to_owned));
let exit_code = match patch_arg {
Some(patch_arg) => {
let patch = if argv1 == CODEX_CORE_APPLY_PATCH_FILE_ARG1 {
match std::fs::read_to_string(&patch_arg) {
Ok(patch) => patch,
Err(err) => {
eprintln!(
"Error: failed to read apply_patch file {patch_arg:?}: {err}"
);
std::process::exit(1);
}
}
} else {
patch_arg
};
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
match codex_apply_patch::apply_patch(&patch, &mut stdout, &mut stderr) {
match codex_apply_patch::apply_patch(&patch_arg, &mut stdout, &mut stderr) {
Ok(()) => 0,
Err(_) => 1,
}
}
None => {
eprintln!("Error: {argv1:?} requires a UTF-8 argument.");
eprintln!("Error: {CODEX_CORE_APPLY_PATCH_ARG1} requires a UTF-8 PATCH argument.");
1
}
};

View File

@@ -22,8 +22,6 @@ use crate::tools::sandboxing::ToolRuntime;
use crate::tools::sandboxing::with_cached_approval;
use codex_apply_patch::ApplyPatchAction;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
#[cfg(target_os = "windows")]
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_FILE_ARG1;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::FileChange;
@@ -34,8 +32,6 @@ use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(target_os = "windows")]
use uuid::Uuid;
#[derive(Debug)]
pub struct ApplyPatchRequest {
@@ -73,15 +69,11 @@ impl ApplyPatchRuntime {
fn build_sandbox_command(
req: &ApplyPatchRequest,
codex_home: &std::path::Path,
) -> Result<(SandboxCommand, PathBuf), ToolError> {
let patch_file = Self::write_patch_file(req, codex_home)?;
let command = Self::build_sandbox_command_with_program(
) -> Result<SandboxCommand, ToolError> {
Ok(Self::build_sandbox_command_with_program(
req,
codex_windows_sandbox::resolve_current_exe_for_launch(codex_home, "codex.exe"),
CODEX_CORE_APPLY_PATCH_FILE_ARG1.to_string(),
patch_file.to_string_lossy().to_string(),
);
Ok((command, patch_file))
))
}
#[cfg(not(target_os = "windows"))]
@@ -90,12 +82,7 @@ impl ApplyPatchRuntime {
codex_self_exe: Option<&PathBuf>,
) -> Result<SandboxCommand, ToolError> {
let exe = Self::resolve_apply_patch_program(codex_self_exe)?;
Ok(Self::build_sandbox_command_with_program(
req,
exe,
CODEX_CORE_APPLY_PATCH_ARG1.to_string(),
req.action.patch.clone(),
))
Ok(Self::build_sandbox_command_with_program(req, exe))
}
#[cfg(not(target_os = "windows"))]
@@ -108,32 +95,13 @@ impl ApplyPatchRuntime {
.map_err(|e| ToolError::Rejected(format!("failed to determine codex exe: {e}")))
}
#[cfg(target_os = "windows")]
fn write_patch_file(
req: &ApplyPatchRequest,
codex_home: &std::path::Path,
) -> Result<PathBuf, ToolError> {
let dir = codex_home.join("tmp").join("apply-patch");
std::fs::create_dir_all(&dir).map_err(|err| {
ToolError::Rejected(format!("failed to create apply_patch temp dir: {err}"))
})?;
let path = dir.join(format!("patch-{}.txt", Uuid::new_v4()));
std::fs::write(&path, req.action.patch.as_bytes()).map_err(|err| {
ToolError::Rejected(format!("failed to write apply_patch temp file: {err}"))
})?;
Ok(path)
}
fn build_sandbox_command_with_program(
req: &ApplyPatchRequest,
exe: PathBuf,
dispatch_arg: String,
patch_arg: String,
) -> SandboxCommand {
fn build_sandbox_command_with_program(req: &ApplyPatchRequest, exe: PathBuf) -> SandboxCommand {
SandboxCommand {
program: exe.into_os_string(),
args: vec![dispatch_arg, patch_arg],
args: vec![
CODEX_CORE_APPLY_PATCH_ARG1.to_string(),
req.action.patch.clone(),
],
cwd: req.action.cwd.clone(),
// Run apply_patch with a minimal environment for determinism and to avoid leaks.
env: HashMap::new(),
@@ -245,15 +213,9 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
ctx: &ToolCtx,
) -> Result<ExecToolCallOutput, ToolError> {
#[cfg(target_os = "windows")]
let (command, patch_file) = {
let (command, patch_file) =
Self::build_sandbox_command(req, &ctx.turn.config.codex_home)?;
(command, Some(patch_file))
};
let command = Self::build_sandbox_command(req, &ctx.turn.config.codex_home)?;
#[cfg(not(target_os = "windows"))]
let command = Self::build_sandbox_command(req, ctx.turn.codex_self_exe.as_ref())?;
#[cfg(not(target_os = "windows"))]
let patch_file: Option<PathBuf> = None;
let options = ExecOptions {
expiration: req.timeout_ms.into(),
capture_policy: ExecCapturePolicy::ShellTool,
@@ -263,11 +225,8 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
.map_err(|err| ToolError::Codex(err.into()))?;
let out = execute_env(env, Self::stdout_stream(ctx))
.await
.map_err(ToolError::Codex);
if let Some(patch_file) = patch_file {
let _ = std::fs::remove_file(patch_file);
}
Ok(out?)
.map_err(ToolError::Codex)?;
Ok(out)
}
}

View File

@@ -245,6 +245,42 @@ fn read_mask_allows_or_log(
}
}
fn collect_existing_descendants(root: &Path) -> Result<Vec<PathBuf>> {
let mut paths = vec![root.to_path_buf()];
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = std::fs::read_dir(&dir)
.with_context(|| format!("read_dir failed for {}", dir.display()))?;
for entry in entries {
let entry =
entry.with_context(|| format!("read_dir entry failed for {}", dir.display()))?;
let file_type = entry
.file_type()
.with_context(|| format!("file_type failed for {}", entry.path().display()))?;
let path = entry.path();
paths.push(path.clone());
if file_type.is_dir() && !file_type.is_symlink() {
stack.push(path);
}
}
}
Ok(paths)
}
unsafe fn ensure_allow_write_aces_recursively(path: &Path, sids: &[*mut c_void]) -> Result<usize> {
let mut repaired = 0usize;
for descendant in collect_existing_descendants(path)? {
if ensure_allow_write_aces(&descendant, sids)
.with_context(|| format!("repair write ACEs on {}", descendant.display()))?
{
repaired += 1;
}
}
Ok(repaired)
}
fn lock_sandbox_dir(
dir: &Path,
real_user: &str,
@@ -706,11 +742,19 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
root.display()
),
)?;
grant_tasks.push(root.clone());
} else {
log_line(
log,
&format!(
"verifying existing descendant write ACEs under {}",
root.display()
),
)?;
}
grant_tasks.push(root.clone());
}
let (tx, rx) = mpsc::channel::<(PathBuf, Result<bool>)>();
let (tx, rx) = mpsc::channel::<(PathBuf, Result<usize>)>();
std::thread::scope(|scope| {
for root in grant_tasks {
let is_command_cwd = is_command_cwd_root(&root, &canonical_command_cwd);
@@ -732,7 +776,7 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
}
}
let res = unsafe { ensure_allow_write_aces(&root, &psids) };
let res = unsafe { ensure_allow_write_aces_recursively(&root, &psids) };
for psid in psids {
unsafe {
@@ -745,7 +789,18 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
drop(tx);
for (root, res) in rx {
match res {
Ok(_) => {}
Ok(repaired) => {
if repaired > 0 {
let _ = log_line(
log,
&format!(
"repaired write ACEs on {} existing paths under {}",
repaired,
root.display()
),
);
}
}
Err(e) => {
refresh_errors.push(format!("write ACE failed on {}: {}", root.display(), e));
if log_line(