use machine scope instead of user scope for dpapi. (#9713)

This fixes a bug where the elevated sandbox setup encrypts sandbox user
passwords as an admin user, but normal command execution attempts to
decrypt them as a different user.

Machine scope allows all users to encyrpt/decrypt

this PR also moves the encrypted file to a different location
.codex/.sandbox-secrets which the sandbox users cannot read.
This commit is contained in:
iceweasel-oai
2026-01-22 16:40:13 -08:00
committed by GitHub
parent 0e79d239ed
commit 0e4adcd760
5 changed files with 58 additions and 19 deletions

View File

@@ -1,12 +1,13 @@
use anyhow::anyhow;
use anyhow::Result;
use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::HLOCAL;
use windows_sys::Win32::Foundation::LocalFree;
use windows_sys::Win32::Foundation::HLOCAL;
use windows_sys::Win32::Security::Cryptography::CryptProtectData;
use windows_sys::Win32::Security::Cryptography::CryptUnprotectData;
use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB;
use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_LOCAL_MACHINE;
use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_UI_FORBIDDEN;
use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB;
fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB {
CRYPT_INTEGER_BLOB {
@@ -29,12 +30,15 @@ pub fn protect(data: &[u8]) -> Result<Vec<u8>> {
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
CRYPTPROTECT_UI_FORBIDDEN,
// Use machine scope so elevated and non-elevated processes can decrypt.
CRYPTPROTECT_UI_FORBIDDEN | CRYPTPROTECT_LOCAL_MACHINE,
&mut out_blob,
)
};
if ok == 0 {
return Err(anyhow!("CryptProtectData failed: {}", unsafe { GetLastError() }));
return Err(anyhow!("CryptProtectData failed: {}", unsafe {
GetLastError()
}));
}
let slice =
unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec();
@@ -60,15 +64,15 @@ pub fn unprotect(blob: &[u8]) -> Result<Vec<u8>> {
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
CRYPTPROTECT_UI_FORBIDDEN,
// Use machine scope so elevated and non-elevated processes can decrypt.
CRYPTPROTECT_UI_FORBIDDEN | CRYPTPROTECT_LOCAL_MACHINE,
&mut out_blob,
)
};
if ok == 0 {
return Err(anyhow!(
"CryptUnprotectData failed: {}",
unsafe { GetLastError() }
));
return Err(anyhow!("CryptUnprotectData failed: {}", unsafe {
GetLastError()
}));
}
let slice =
unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec();

View File

@@ -63,6 +63,8 @@ pub use setup::run_setup_refresh;
#[cfg(target_os = "windows")]
pub use setup::sandbox_dir;
#[cfg(target_os = "windows")]
pub use setup::sandbox_secrets_dir;
#[cfg(target_os = "windows")]
pub use setup::SETUP_VERSION;
#[cfg(target_os = "windows")]
pub use token::convert_string_sid_to_sid;

View File

@@ -36,6 +36,7 @@ use windows_sys::Win32::Security::SID_NAME_USE;
use codex_windows_sandbox::dpapi_protect;
use codex_windows_sandbox::sandbox_dir;
use codex_windows_sandbox::sandbox_secrets_dir;
use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::SETUP_VERSION;
@@ -394,6 +395,8 @@ fn write_secrets(
) -> Result<()> {
let sandbox_dir = sandbox_dir(codex_home);
std::fs::create_dir_all(&sandbox_dir)?;
let secrets_dir = sandbox_secrets_dir(codex_home);
std::fs::create_dir_all(&secrets_dir)?;
let offline_blob = dpapi_protect(offline_pwd.as_bytes())?;
let online_blob = dpapi_protect(online_pwd.as_bytes())?;
let users = SandboxUsersFile {
@@ -415,7 +418,7 @@ fn write_secrets(
read_roots: Vec::new(),
write_roots: Vec::new(),
};
let users_path = sandbox_dir.join("sandbox_users.json");
let users_path = secrets_dir.join("sandbox_users.json");
let marker_path = sandbox_dir.join("setup_marker.json");
std::fs::write(users_path, serde_json::to_vec_pretty(&users)?)?;
std::fs::write(marker_path, serde_json::to_vec_pretty(&marker)?)?;

View File

@@ -14,6 +14,7 @@ use codex_windows_sandbox::load_or_create_cap_sids;
use codex_windows_sandbox::log_note;
use codex_windows_sandbox::path_mask_allows;
use codex_windows_sandbox::sandbox_dir;
use codex_windows_sandbox::sandbox_secrets_dir;
use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::LOG_FILE_NAME;
@@ -52,6 +53,8 @@ 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;
const DENY_ACCESS: i32 = 3;
mod read_acl_mutex;
mod sandbox_users;
use read_acl_mutex::acquire_read_acl_mutex;
@@ -225,6 +228,7 @@ fn lock_sandbox_dir(
dir: &Path,
real_user: &str,
sandbox_group_sid: &[u8],
sandbox_group_access_mode: i32,
_log: &mut File,
) -> Result<()> {
std::fs::create_dir_all(dir)?;
@@ -232,27 +236,31 @@ fn lock_sandbox_dir(
let admins_sid = resolve_sid("Administrators")?;
let real_sid = resolve_sid(real_user)?;
let entries = [
(
sandbox_group_sid.to_vec(),
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
sandbox_group_access_mode,
),
(
system_sid,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
GRANT_ACCESS,
),
(
admins_sid,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
GRANT_ACCESS,
),
(
real_sid,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE,
),
(
sandbox_group_sid.to_vec(),
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
GRANT_ACCESS,
),
];
unsafe {
let mut eas: Vec<EXPLICIT_ACCESS_W> = Vec::new();
let mut sids: Vec<*mut c_void> = Vec::new();
for (sid_bytes, mask) in entries.iter().map(|(s, m)| (s, *m)) {
for (sid_bytes, mask, access_mode) in entries.iter().map(|(s, m, a)| (s, *m, *a)) {
let sid_str = string_from_sid_bytes(sid_bytes).map_err(anyhow::Error::msg)?;
let sid_w = to_wide(OsStr::new(&sid_str));
let mut psid: *mut c_void = std::ptr::null_mut();
@@ -265,7 +273,7 @@ fn lock_sandbox_dir(
sids.push(psid);
eas.push(EXPLICIT_ACCESS_W {
grfAccessPermissions: mask,
grfAccessMode: GRANT_ACCESS,
grfAccessMode: access_mode,
grfInheritance: OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
Trustee: TRUSTEE_W {
pMultipleTrustee: std::ptr::null_mut(),
@@ -605,8 +613,20 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
&sandbox_dir(&payload.codex_home),
&payload.real_user,
&sandbox_group_sid,
GRANT_ACCESS,
log,
)?;
lock_sandbox_dir(
&sandbox_secrets_dir(&payload.codex_home),
&payload.real_user,
&sandbox_group_sid,
DENY_ACCESS,
log,
)?;
let legacy_users = sandbox_dir(&payload.codex_home).join("sandbox_users.json");
if legacy_users.exists() {
let _ = std::fs::remove_file(&legacy_users);
}
}
unsafe {
if !sandbox_group_psid.is_null() {

View File

@@ -26,7 +26,7 @@ use windows_sys::Win32::Security::CheckTokenMembership;
use windows_sys::Win32::Security::FreeSid;
use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY;
pub const SETUP_VERSION: u32 = 4;
pub const SETUP_VERSION: u32 = 5;
pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline";
pub const ONLINE_USERNAME: &str = "CodexSandboxOnline";
const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020;
@@ -36,12 +36,16 @@ pub fn sandbox_dir(codex_home: &Path) -> PathBuf {
codex_home.join(".sandbox")
}
pub fn sandbox_secrets_dir(codex_home: &Path) -> PathBuf {
codex_home.join(".sandbox-secrets")
}
pub fn setup_marker_path(codex_home: &Path) -> PathBuf {
sandbox_dir(codex_home).join("setup_marker.json")
}
pub fn sandbox_users_path(codex_home: &Path) -> PathBuf {
sandbox_dir(codex_home).join("sandbox_users.json")
sandbox_secrets_dir(codex_home).join("sandbox_users.json")
}
pub fn run_setup_refresh(
@@ -430,10 +434,16 @@ fn filter_sensitive_write_roots(mut roots: Vec<PathBuf>, codex_home: &Path) -> V
let codex_home_key = crate::audit::normalize_path_key(codex_home);
let sbx_dir_key = crate::audit::normalize_path_key(&sandbox_dir(codex_home));
let sbx_dir_prefix = format!("{}/", sbx_dir_key.trim_end_matches('/'));
let secrets_dir_key = crate::audit::normalize_path_key(&sandbox_secrets_dir(codex_home));
let secrets_dir_prefix = format!("{}/", secrets_dir_key.trim_end_matches('/'));
roots.retain(|root| {
let key = crate::audit::normalize_path_key(root);
key != codex_home_key && key != sbx_dir_key && !key.starts_with(&sbx_dir_prefix)
key != codex_home_key
&& key != sbx_dir_key
&& !key.starts_with(&sbx_dir_prefix)
&& key != secrets_dir_key
&& !key.starts_with(&secrets_dir_prefix)
});
roots
}