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::anyhow;
use anyhow::Result; use anyhow::Result;
use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::HLOCAL;
use windows_sys::Win32::Foundation::LocalFree; 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::CryptProtectData;
use windows_sys::Win32::Security::Cryptography::CryptUnprotectData; 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::CRYPTPROTECT_UI_FORBIDDEN;
use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB;
fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB { fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB {
CRYPT_INTEGER_BLOB { CRYPT_INTEGER_BLOB {
@@ -29,12 +30,15 @@ pub fn protect(data: &[u8]) -> Result<Vec<u8>> {
std::ptr::null(), std::ptr::null(),
std::ptr::null_mut(), std::ptr::null_mut(),
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, &mut out_blob,
) )
}; };
if ok == 0 { if ok == 0 {
return Err(anyhow!("CryptProtectData failed: {}", unsafe { GetLastError() })); return Err(anyhow!("CryptProtectData failed: {}", unsafe {
GetLastError()
}));
} }
let slice = let slice =
unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec(); 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(),
std::ptr::null_mut(), std::ptr::null_mut(),
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, &mut out_blob,
) )
}; };
if ok == 0 { if ok == 0 {
return Err(anyhow!( return Err(anyhow!("CryptUnprotectData failed: {}", unsafe {
"CryptUnprotectData failed: {}", GetLastError()
unsafe { GetLastError() } }));
));
} }
let slice = let slice =
unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec(); 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")] #[cfg(target_os = "windows")]
pub use setup::sandbox_dir; pub use setup::sandbox_dir;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use setup::sandbox_secrets_dir;
#[cfg(target_os = "windows")]
pub use setup::SETUP_VERSION; pub use setup::SETUP_VERSION;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use token::convert_string_sid_to_sid; 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::dpapi_protect;
use codex_windows_sandbox::sandbox_dir; 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::string_from_sid_bytes;
use codex_windows_sandbox::to_wide; use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::SETUP_VERSION; use codex_windows_sandbox::SETUP_VERSION;
@@ -394,6 +395,8 @@ fn write_secrets(
) -> Result<()> { ) -> Result<()> {
let sandbox_dir = sandbox_dir(codex_home); let sandbox_dir = sandbox_dir(codex_home);
std::fs::create_dir_all(&sandbox_dir)?; 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 offline_blob = dpapi_protect(offline_pwd.as_bytes())?;
let online_blob = dpapi_protect(online_pwd.as_bytes())?; let online_blob = dpapi_protect(online_pwd.as_bytes())?;
let users = SandboxUsersFile { let users = SandboxUsersFile {
@@ -415,7 +418,7 @@ fn write_secrets(
read_roots: Vec::new(), read_roots: Vec::new(),
write_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"); let marker_path = sandbox_dir.join("setup_marker.json");
std::fs::write(users_path, serde_json::to_vec_pretty(&users)?)?; std::fs::write(users_path, serde_json::to_vec_pretty(&users)?)?;
std::fs::write(marker_path, serde_json::to_vec_pretty(&marker)?)?; 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::log_note;
use codex_windows_sandbox::path_mask_allows; use codex_windows_sandbox::path_mask_allows;
use codex_windows_sandbox::sandbox_dir; 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::string_from_sid_bytes;
use codex_windows_sandbox::to_wide; use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::LOG_FILE_NAME; 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_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
const DENY_ACCESS: i32 = 3;
mod read_acl_mutex; mod read_acl_mutex;
mod sandbox_users; mod sandbox_users;
use read_acl_mutex::acquire_read_acl_mutex; use read_acl_mutex::acquire_read_acl_mutex;
@@ -225,6 +228,7 @@ fn lock_sandbox_dir(
dir: &Path, dir: &Path,
real_user: &str, real_user: &str,
sandbox_group_sid: &[u8], sandbox_group_sid: &[u8],
sandbox_group_access_mode: i32,
_log: &mut File, _log: &mut File,
) -> Result<()> { ) -> Result<()> {
std::fs::create_dir_all(dir)?; std::fs::create_dir_all(dir)?;
@@ -232,27 +236,31 @@ fn lock_sandbox_dir(
let admins_sid = resolve_sid("Administrators")?; let admins_sid = resolve_sid("Administrators")?;
let real_sid = resolve_sid(real_user)?; let real_sid = resolve_sid(real_user)?;
let entries = [ let entries = [
(
sandbox_group_sid.to_vec(),
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
sandbox_group_access_mode,
),
( (
system_sid, system_sid,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
GRANT_ACCESS,
), ),
( (
admins_sid, admins_sid,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
GRANT_ACCESS,
), ),
( (
real_sid, real_sid,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE,
), GRANT_ACCESS,
(
sandbox_group_sid.to_vec(),
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
), ),
]; ];
unsafe { unsafe {
let mut eas: Vec<EXPLICIT_ACCESS_W> = Vec::new(); let mut eas: Vec<EXPLICIT_ACCESS_W> = Vec::new();
let mut sids: Vec<*mut c_void> = 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_str = string_from_sid_bytes(sid_bytes).map_err(anyhow::Error::msg)?;
let sid_w = to_wide(OsStr::new(&sid_str)); let sid_w = to_wide(OsStr::new(&sid_str));
let mut psid: *mut c_void = std::ptr::null_mut(); let mut psid: *mut c_void = std::ptr::null_mut();
@@ -265,7 +273,7 @@ fn lock_sandbox_dir(
sids.push(psid); sids.push(psid);
eas.push(EXPLICIT_ACCESS_W { eas.push(EXPLICIT_ACCESS_W {
grfAccessPermissions: mask, grfAccessPermissions: mask,
grfAccessMode: GRANT_ACCESS, grfAccessMode: access_mode,
grfInheritance: OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, grfInheritance: OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
Trustee: TRUSTEE_W { Trustee: TRUSTEE_W {
pMultipleTrustee: std::ptr::null_mut(), 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), &sandbox_dir(&payload.codex_home),
&payload.real_user, &payload.real_user,
&sandbox_group_sid, &sandbox_group_sid,
GRANT_ACCESS,
log, 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 { unsafe {
if !sandbox_group_psid.is_null() { 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::FreeSid;
use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY; 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 OFFLINE_USERNAME: &str = "CodexSandboxOffline";
pub const ONLINE_USERNAME: &str = "CodexSandboxOnline"; pub const ONLINE_USERNAME: &str = "CodexSandboxOnline";
const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020; const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020;
@@ -36,12 +36,16 @@ pub fn sandbox_dir(codex_home: &Path) -> PathBuf {
codex_home.join(".sandbox") 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 { pub fn setup_marker_path(codex_home: &Path) -> PathBuf {
sandbox_dir(codex_home).join("setup_marker.json") sandbox_dir(codex_home).join("setup_marker.json")
} }
pub fn sandbox_users_path(codex_home: &Path) -> PathBuf { 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( 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 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_key = crate::audit::normalize_path_key(&sandbox_dir(codex_home));
let sbx_dir_prefix = format!("{}/", sbx_dir_key.trim_end_matches('/')); 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| { roots.retain(|root| {
let key = crate::audit::normalize_path_key(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 roots
} }