Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
bb4ea552bd Repair inherited ACLs for existing Windows sandbox files 2026-04-17 12:06:31 -07:00
3 changed files with 62 additions and 1 deletions

View File

@@ -11,10 +11,14 @@ use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Security::AclSizeInformation;
use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW;
use windows_sys::Win32::Security::Authorization::GetSecurityInfo;
use windows_sys::Win32::Security::Authorization::ProgressInvokeNever;
use windows_sys::Win32::Security::Authorization::SetEntriesInAclW;
use windows_sys::Win32::Security::Authorization::SetNamedSecurityInfoW;
use windows_sys::Win32::Security::Authorization::SetSecurityInfo;
use windows_sys::Win32::Security::Authorization::TREE_SEC_INFO_RESET_KEEP_EXPLICIT;
use windows_sys::Win32::Security::Authorization::TreeSetNamedSecurityInfoW;
use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W;
use windows_sys::Win32::Security::Authorization::SE_FILE_OBJECT;
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID;
use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN;
use windows_sys::Win32::Security::Authorization::TRUSTEE_W;
@@ -383,6 +387,48 @@ pub unsafe fn ensure_allow_write_aces(path: &Path, sids: &[*mut c_void]) -> Resu
ensure_allow_mask_aces(path, sids, WRITE_ALLOW_MASK)
}
/// Ensure the directory and its existing descendants inherit write-capable ACEs for the provided
/// SIDs while preserving explicit child ACEs that were already present.
///
/// # Safety
/// Caller must pass valid SID pointers and an existing path; free the returned security
/// descriptor with `LocalFree`.
pub unsafe fn ensure_allow_write_aces_recursively(
path: &Path,
sids: &[*mut c_void],
) -> Result<bool> {
let added = ensure_allow_write_aces(path, sids)?;
if !path.is_dir() {
return Ok(added);
}
let (p_dacl, p_sd) = fetch_dacl_handle(path)?;
let code = TreeSetNamedSecurityInfoW(
to_wide(path).as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
std::ptr::null_mut(),
std::ptr::null_mut(),
p_dacl,
std::ptr::null(),
TREE_SEC_INFO_RESET_KEEP_EXPLICIT,
None,
ProgressInvokeNever,
std::ptr::null(),
);
if !p_sd.is_null() {
LocalFree(p_sd as HLOCAL);
}
if code != ERROR_SUCCESS {
return Err(anyhow!(
"TreeSetNamedSecurityInfoW failed for {}: {}",
path.display(),
code
));
}
Ok(added)
}
/// Adds an allow ACE granting read/write/execute to the given SID on the target path.
///
/// # Safety

View File

@@ -58,6 +58,8 @@ pub use acl::ensure_allow_mask_aces_with_inheritance;
#[cfg(target_os = "windows")]
pub use acl::ensure_allow_write_aces;
#[cfg(target_os = "windows")]
pub use acl::ensure_allow_write_aces_recursively;
#[cfg(target_os = "windows")]
pub use acl::fetch_dacl_handle;
#[cfg(target_os = "windows")]
pub use acl::path_mask_allows;

View File

@@ -16,6 +16,7 @@ use codex_windows_sandbox::canonicalize_path;
use codex_windows_sandbox::convert_string_sid_to_sid;
use codex_windows_sandbox::ensure_allow_mask_aces_with_inheritance;
use codex_windows_sandbox::ensure_allow_write_aces;
use codex_windows_sandbox::ensure_allow_write_aces_recursively;
use codex_windows_sandbox::extract_setup_failure;
use codex_windows_sandbox::hide_newly_created_users;
use codex_windows_sandbox::is_command_cwd_root;
@@ -705,6 +706,8 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
root.display()
),
)?;
}
if need_grant || root.is_dir() {
grant_tasks.push(root.clone());
}
}
@@ -712,6 +715,7 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
let (tx, rx) = mpsc::channel::<(PathBuf, Result<bool>)>();
std::thread::scope(|scope| {
for root in grant_tasks {
let recurse_existing_descendants = root.is_dir();
let is_command_cwd = is_command_cwd_root(&root, &canonical_command_cwd);
let sid_strings = if is_command_cwd {
vec![sandbox_group_sid_str.clone(), workspace_sid_str.clone()]
@@ -731,7 +735,16 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
}
}
let res = unsafe { ensure_allow_write_aces(&root, &psids) };
// Existing descendants may have missed inherited ACEs from earlier setups,
// so directory write roots need a recursive DACL refresh in addition to the
// top-level grant.
let res = unsafe {
if recurse_existing_descendants {
ensure_allow_write_aces_recursively(&root, &psids)
} else {
ensure_allow_write_aces(&root, &psids)
}
};
for psid in psids {
unsafe {