diff --git a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs b/codex-rs/windows-sandbox-rs/src/sandbox_users.rs index 41298b8fb..8aa67a41a 100644 --- a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs +++ b/codex-rs/windows-sandbox-rs/src/sandbox_users.rs @@ -13,6 +13,7 @@ use std::fs::File; use std::path::Path; use std::path::PathBuf; use windows_sys::Win32::Foundation::GetLastError; +use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER; use windows_sys::Win32::NetworkManagement::NetManagement::NERR_Success; use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAdd; @@ -27,7 +28,10 @@ use windows_sys::Win32::NetworkManagement::NetManagement::USER_INFO_1; use windows_sys::Win32::NetworkManagement::NetManagement::USER_INFO_1003; use windows_sys::Win32::NetworkManagement::NetManagement::USER_PRIV_USER; use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW; +use windows_sys::Win32::Security::CopySid; +use windows_sys::Win32::Security::GetLengthSid; use windows_sys::Win32::Security::LookupAccountNameW; +use windows_sys::Win32::Security::LookupAccountSidW; use windows_sys::Win32::Security::SID_NAME_USE; use codex_windows_sandbox::dpapi_protect; @@ -38,6 +42,11 @@ use codex_windows_sandbox::SETUP_VERSION; pub const SANDBOX_USERS_GROUP: &str = "CodexSandboxUsers"; const SANDBOX_USERS_GROUP_COMMENT: &str = "Codex sandbox internal group (managed)"; +const SID_ADMINISTRATORS: &str = "S-1-5-32-544"; +const SID_USERS: &str = "S-1-5-32-545"; +const SID_AUTHENTICATED_USERS: &str = "S-1-5-11"; +const SID_EVERYONE: &str = "S-1-1-0"; +const SID_SYSTEM: &str = "S-1-5-18"; pub fn ensure_sandbox_users_group(log: &mut File) -> Result<()> { ensure_local_group(SANDBOX_USERS_GROUP, SANDBOX_USERS_GROUP_COMMENT, log) @@ -119,17 +128,24 @@ pub fn ensure_local_user(name: &str, password: &str, log: &mut File) -> Result<( } // Ensure the principal is a regular local user account. - let group = to_wide(OsStr::new("Users")); - let member = LOCALGROUP_MEMBERS_INFO_3 { - lgrmi3_domainandname: name_w.as_ptr() as *mut u16, - }; - let _ = NetLocalGroupAddMembers( - std::ptr::null(), - group.as_ptr(), - 3, - &member as *const _ as *mut u8, - 1, - ); + if let Ok(group_name) = lookup_account_name_for_sid(SID_USERS) { + let group = to_wide(OsStr::new(&group_name)); + let member = LOCALGROUP_MEMBERS_INFO_3 { + lgrmi3_domainandname: name_w.as_ptr() as *mut u16, + }; + let _ = NetLocalGroupAddMembers( + std::ptr::null(), + group.as_ptr(), + 3, + &member as *const _ as *mut u8, + 1, + ); + } else { + super::log_line( + log, + "LookupAccountSidW failed for Users SID; skipping Users group membership", + )?; + } } Ok(()) } @@ -184,6 +200,9 @@ pub fn ensure_local_group_member(group_name: &str, member_name: &str) -> Result< } pub fn resolve_sid(name: &str) -> Result> { + if let Some(sid_str) = well_known_sid_str(name) { + return sid_bytes_from_string(sid_str); + } let name_w = to_wide(OsStr::new(name)); let mut sid_buffer = vec![0u8; 68]; let mut sid_len: u32 = sid_buffer.len() as u32; @@ -218,6 +237,104 @@ pub fn resolve_sid(name: &str) -> Result> { } } +fn well_known_sid_str(name: &str) -> Option<&'static str> { + match name { + "Administrators" => Some(SID_ADMINISTRATORS), + "Users" => Some(SID_USERS), + "Authenticated Users" => Some(SID_AUTHENTICATED_USERS), + "Everyone" => Some(SID_EVERYONE), + "SYSTEM" => Some(SID_SYSTEM), + _ => None, + } +} + +fn sid_bytes_from_string(sid_str: &str) -> Result> { + let sid_w = to_wide(OsStr::new(sid_str)); + let mut psid: *mut c_void = std::ptr::null_mut(); + if unsafe { ConvertStringSidToSidW(sid_w.as_ptr(), &mut psid) } == 0 { + return Err(anyhow::anyhow!( + "ConvertStringSidToSidW failed for {sid_str}: {}", + unsafe { GetLastError() } + )); + } + let sid_len = unsafe { GetLengthSid(psid) }; + if sid_len == 0 { + unsafe { + LocalFree(psid as _); + } + return Err(anyhow::anyhow!("GetLengthSid failed for {sid_str}")); + } + let mut out = vec![0u8; sid_len as usize]; + let ok = unsafe { CopySid(sid_len, out.as_mut_ptr() as *mut c_void, psid) }; + unsafe { + LocalFree(psid as _); + } + if ok == 0 { + return Err(anyhow::anyhow!("CopySid failed for {sid_str}")); + } + Ok(out) +} + +fn lookup_account_name_for_sid(sid_str: &str) -> Result { + let sid_w = to_wide(OsStr::new(sid_str)); + let mut psid: *mut c_void = std::ptr::null_mut(); + if unsafe { ConvertStringSidToSidW(sid_w.as_ptr(), &mut psid) } == 0 { + return Err(anyhow::anyhow!( + "ConvertStringSidToSidW failed for {sid_str}: {}", + unsafe { GetLastError() } + )); + } + let mut name_len: u32 = 0; + let mut domain_len: u32 = 0; + let mut use_type: SID_NAME_USE = 0; + let ok = unsafe { + LookupAccountSidW( + std::ptr::null(), + psid, + std::ptr::null_mut(), + &mut name_len, + std::ptr::null_mut(), + &mut domain_len, + &mut use_type, + ) + }; + if ok == 0 { + let err = unsafe { GetLastError() }; + if err != ERROR_INSUFFICIENT_BUFFER { + unsafe { + LocalFree(psid as _); + } + return Err(anyhow::anyhow!( + "LookupAccountSidW preflight failed for {sid_str}: {err}" + )); + } + } + let mut name_buf: Vec = vec![0u16; name_len as usize]; + let mut domain_buf: Vec = vec![0u16; domain_len as usize]; + let ok = unsafe { + LookupAccountSidW( + std::ptr::null(), + psid, + name_buf.as_mut_ptr(), + &mut name_len, + domain_buf.as_mut_ptr(), + &mut domain_len, + &mut use_type, + ) + }; + unsafe { + LocalFree(psid as _); + } + if ok == 0 { + return Err(anyhow::anyhow!( + "LookupAccountSidW failed for {sid_str}: {}", + unsafe { GetLastError() } + )); + } + let name = String::from_utf16_lossy(&name_buf); + Ok(name.trim_end_matches('\0').to_string()) +} + pub fn sid_bytes_to_psid(sid: &[u8]) -> Result<*mut c_void> { let sid_str = string_from_sid_bytes(sid).map_err(anyhow::Error::msg)?; let sid_w = to_wide(OsStr::new(&sid_str)); diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 30907de7c..7578e494c 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -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 = 3; +pub const SETUP_VERSION: u32 = 4; pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline"; pub const ONLINE_USERNAME: &str = "CodexSandboxOnline"; const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020;