Compare commits

...

2 Commits

Author SHA1 Message Date
David Wiesen
3a3e2c53cd Fix Windows sandbox real-user ACL import 2026-04-09 08:57:02 -07:00
David Wiesen
03e5039d34 Keep real user in control of Windows sandbox write roots 2026-04-09 08:56:26 -07:00

View File

@@ -63,6 +63,7 @@ use windows_sys::Win32::Storage::FileSystem::FILE_DELETE_CHILD;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
use windows_sys::Win32::Storage::FileSystem::WRITE_DAC;
const DENY_ACCESS: i32 = 3;
@@ -554,6 +555,21 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
format!("convert sandbox users group SID to PSID failed: {err}"),
))
})?;
let real_user_sid = resolve_sid(&payload.real_user).map_err(|err| {
anyhow::Error::new(SetupFailure::new(
SetupErrorCode::HelperSidResolveFailed,
format!(
"resolve SID for real user {} failed: {err}",
payload.real_user
),
))
})?;
let real_user_psid = sid_bytes_to_psid(&real_user_sid).map_err(|err| {
anyhow::Error::new(SetupFailure::new(
SetupErrorCode::HelperSidResolveFailed,
format!("convert real user SID to PSID failed: {err}"),
))
})?;
let caps = load_or_create_cap_sids(&payload.codex_home).map_err(|err| {
anyhow::Error::new(SetupFailure::new(
@@ -638,8 +654,12 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
let cap_sid_str = caps.workspace;
let sandbox_group_sid_str =
string_from_sid_bytes(&sandbox_group_sid).map_err(anyhow::Error::msg)?;
let real_user_sid_str = string_from_sid_bytes(&real_user_sid).map_err(anyhow::Error::msg)?;
let write_mask =
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD;
// Children created by the sandbox user may be owned by that user. Keep the
// interactive user able to refresh those ACLs on the next setup pass.
let real_user_write_mask = write_mask | WRITE_DAC;
let mut grant_tasks: Vec<PathBuf> = Vec::new();
let mut seen_write_roots: HashSet<PathBuf> = HashSet::new();
@@ -668,30 +688,30 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
} else {
cap_psid
};
for (label, psid) in [
("sandbox_group", sandbox_group_psid),
(cap_label, cap_psid_for_root),
for (label, psid, mask) in [
("sandbox_group", sandbox_group_psid, write_mask),
(cap_label, cap_psid_for_root, write_mask),
("real_user", real_user_psid, real_user_write_mask),
] {
let has =
match path_mask_allows(root, &[psid], write_mask, /*require_all_bits*/ true) {
Ok(h) => h,
Err(e) => {
refresh_errors.push(format!(
"write mask check failed on {} for {label}: {}",
let has = match path_mask_allows(root, &[psid], mask, /*require_all_bits*/ true) {
Ok(h) => h,
Err(e) => {
refresh_errors.push(format!(
"write mask check failed on {} for {label}: {}",
root.display(),
e
));
log_line(
log,
&format!(
"write mask check failed on {} for {label}: {}; continuing",
root.display(),
e
));
log_line(
log,
&format!(
"write mask check failed on {} for {label}: {}; continuing",
root.display(),
e
),
)?;
false
}
};
),
)?;
false
}
};
if !has {
need_grant = true;
}
@@ -700,7 +720,7 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
log_line(
log,
&format!(
"granting write ACE to {} for sandbox group and capability SID",
"granting write ACE to {} for sandbox group, capability SID, and real user",
root.display()
),
)?;
@@ -717,6 +737,7 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
} else {
vec![sandbox_group_sid_str.clone(), cap_sid_str.clone()]
};
let real_user_sid_str = real_user_sid_str.clone();
let tx = tx.clone();
scope.spawn(move || {
// Convert SID strings to psids locally in this thread.
@@ -730,7 +751,25 @@ 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(&root, &psids) }.and_then(|added| {
let Some(real_user_psid) =
(unsafe { convert_string_sid_to_sid(&real_user_sid_str) })
else {
return Err(anyhow::anyhow!("convert real user SID failed"));
};
let real_user_res = unsafe {
ensure_allow_mask_aces_with_inheritance(
&root,
&[real_user_psid],
real_user_write_mask,
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
)
};
unsafe {
LocalFree(real_user_psid as HLOCAL);
}
real_user_res.map(|real_user_added| added || real_user_added)
});
for psid in psids {
unsafe {
@@ -889,6 +928,9 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
if !workspace_psid.is_null() {
LocalFree(workspace_psid as HLOCAL);
}
if !real_user_psid.is_null() {
LocalFree(real_user_psid as HLOCAL);
}
}
if refresh_only && !refresh_errors.is_empty() {
log_line(