Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
404967fe25 Keep real user on Windows sandbox-created objects 2026-04-09 08:53:19 -07:00
5 changed files with 67 additions and 5 deletions

View File

@@ -27,7 +27,7 @@ use codex_windows_sandbox::StdinMode;
use codex_windows_sandbox::allow_null_device;
use codex_windows_sandbox::convert_string_sid_to_sid;
use codex_windows_sandbox::create_readonly_token_with_caps_from;
use codex_windows_sandbox::create_workspace_write_token_with_caps_from;
use codex_windows_sandbox::create_workspace_write_token_with_caps_and_default_dacl_sids_from;
use codex_windows_sandbox::decode_bytes;
use codex_windows_sandbox::encode_bytes;
use codex_windows_sandbox::get_current_token_for_restriction;
@@ -208,8 +208,16 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
if cap_psids.is_empty() {
anyhow::bail!("runner: empty capability SID list");
}
let mut real_user_psid: Option<*mut c_void> = None;
if let Some(real_user_sid) = &req.real_user_sid {
let Some(psid) = (unsafe { convert_string_sid_to_sid(real_user_sid) }) else {
anyhow::bail!("ConvertStringSidToSidW failed for real user SID");
};
real_user_psid = Some(psid);
}
let base = unsafe { get_current_token_for_restriction()? };
let default_dacl_extra_sids: Vec<*mut c_void> = real_user_psid.iter().copied().collect();
let token_res: Result<(HANDLE, *mut c_void)> = unsafe {
match &policy {
SandboxPolicy::ReadOnly { .. } => {
@@ -217,8 +225,12 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
.map(|h_token| (h_token, cap_psids[0]))
}
SandboxPolicy::WorkspaceWrite { .. } => {
create_workspace_write_token_with_caps_from(base, &cap_psids)
.map(|h_token| (h_token, cap_psids[0]))
create_workspace_write_token_with_caps_and_default_dacl_sids_from(
base,
&cap_psids,
&default_dacl_extra_sids,
)
.map(|h_token| (h_token, cap_psids[0]))
}
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
unreachable!()
@@ -237,6 +249,11 @@ fn spawn_ipc_process(req: &SpawnRequest) -> Result<IpcSpawnedProcess> {
LocalFree(psid as HLOCAL);
}
}
if let Some(psid) = real_user_psid
&& !psid.is_null()
{
LocalFree(psid as HLOCAL);
}
}
let effective_cwd = effective_cwd(&req.cwd, Some(log_dir.as_path()));

View File

@@ -58,6 +58,8 @@ pub struct SpawnRequest {
pub codex_home: PathBuf,
pub real_codex_home: PathBuf,
pub cap_sids: Vec<String>,
#[serde(default)]
pub real_user_sid: Option<String>,
pub timeout_ms: Option<u64>,
pub tty: bool,
#[serde(default)]

View File

@@ -220,6 +220,29 @@ mod windows_impl {
}
}
fn resolve_real_user_sid_for_runner(log_dir: Option<&Path>) -> Option<String> {
let real_user = match std::env::var("USERNAME") {
Ok(username) => username,
Err(err) => {
log_note(
&format!("runner launch: USERNAME unavailable: {err}"),
log_dir,
);
return None;
}
};
match resolve_sid(&real_user).and_then(|sid| string_from_sid_bytes(&sid)) {
Ok(sid) => Some(sid),
Err(err) => {
log_note(
&format!("runner launch: resolve SID for real user {real_user} failed: {err}"),
log_dir,
);
None
}
}
}
/// Launches the command runner under the sandbox user and captures its output.
#[allow(clippy::too_many_arguments)]
pub fn run_windows_sandbox_capture(
@@ -416,6 +439,7 @@ mod windows_impl {
codex_home: sandbox_base.clone(),
real_codex_home: codex_home.to_path_buf(),
cap_sids,
real_user_sid: resolve_real_user_sid_for_runner(logs_base_dir),
timeout_ms,
tty: false,
stdin_open: false,

View File

@@ -172,6 +172,8 @@ pub use token::create_readonly_token_with_cap_from;
#[cfg(target_os = "windows")]
pub use token::create_readonly_token_with_caps_from;
#[cfg(target_os = "windows")]
pub use token::create_workspace_write_token_with_caps_and_default_dacl_sids_from;
#[cfg(target_os = "windows")]
pub use token::create_workspace_write_token_with_caps_from;
#[cfg(target_os = "windows")]
pub use token::get_current_token_for_restriction;

View File

@@ -312,7 +312,22 @@ pub unsafe fn create_workspace_write_token_with_caps_from(
base_token: HANDLE,
psid_capabilities: &[*mut c_void],
) -> Result<HANDLE> {
create_token_with_caps_from(base_token, psid_capabilities)
create_token_with_caps_from(base_token, psid_capabilities, &[])
}
/// Create a restricted token, and include extra SIDs in the token's default DACL.
///
/// The extra SIDs are not added to the token for access checks; they only keep objects created by
/// sandboxed children manageable by their expected owner (for example, the interactive user).
///
/// # Safety
/// Caller must close the returned token handle; all SID pointers must be valid for this call.
pub unsafe fn create_workspace_write_token_with_caps_and_default_dacl_sids_from(
base_token: HANDLE,
psid_capabilities: &[*mut c_void],
default_dacl_extra_sids: &[*mut c_void],
) -> Result<HANDLE> {
create_token_with_caps_from(base_token, psid_capabilities, default_dacl_extra_sids)
}
/// Create a restricted token that includes all provided capability SIDs.
@@ -323,12 +338,13 @@ pub unsafe fn create_readonly_token_with_caps_from(
base_token: HANDLE,
psid_capabilities: &[*mut c_void],
) -> Result<HANDLE> {
create_token_with_caps_from(base_token, psid_capabilities)
create_token_with_caps_from(base_token, psid_capabilities, &[])
}
unsafe fn create_token_with_caps_from(
base_token: HANDLE,
psid_capabilities: &[*mut c_void],
default_dacl_extra_sids: &[*mut c_void],
) -> Result<HANDLE> {
if psid_capabilities.is_empty() {
return Err(anyhow!("no capability SIDs provided"));
@@ -372,6 +388,7 @@ unsafe fn create_token_with_caps_from(
dacl_sids.push(psid_logon);
dacl_sids.push(psid_everyone);
dacl_sids.extend_from_slice(psid_capabilities);
dacl_sids.extend_from_slice(default_dacl_extra_sids);
set_default_dacl(new_token, &dacl_sids)?;
enable_single_privilege(new_token, "SeChangeNotifyPrivilege")?;