diff --git a/codex-rs/windows-sandbox-rs/src/acl.rs b/codex-rs/windows-sandbox-rs/src/acl.rs index f351dba190..0c7084c6e8 100644 --- a/codex-rs/windows-sandbox-rs/src/acl.rs +++ b/codex-rs/windows-sandbox-rs/src/acl.rs @@ -1,55 +1,60 @@ use crate::winutil::to_wide; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use std::ffi::c_void; use std::path::Path; use windows_sys::Win32::Foundation::CloseHandle; -use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::ERROR_SUCCESS; +use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; +use windows_sys::Win32::Foundation::LocalFree; +use windows_sys::Win32::Security::ACCESS_ALLOWED_ACE; +use windows_sys::Win32::Security::ACE_HEADER; +use windows_sys::Win32::Security::ACL; +use windows_sys::Win32::Security::ACL_SIZE_INFORMATION; use windows_sys::Win32::Security::AclSizeInformation; +use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W; use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW; use windows_sys::Win32::Security::Authorization::GetSecurityInfo; use windows_sys::Win32::Security::Authorization::SetEntriesInAclW; use windows_sys::Win32::Security::Authorization::SetNamedSecurityInfoW; use windows_sys::Win32::Security::Authorization::SetSecurityInfo; -use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W; use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID; use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN; use windows_sys::Win32::Security::Authorization::TRUSTEE_W; +use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION; use windows_sys::Win32::Security::EqualSid; +use windows_sys::Win32::Security::GENERIC_MAPPING; use windows_sys::Win32::Security::GetAce; use windows_sys::Win32::Security::GetAclInformation; use windows_sys::Win32::Security::MapGenericMask; -use windows_sys::Win32::Security::ACCESS_ALLOWED_ACE; -use windows_sys::Win32::Security::ACE_HEADER; -use windows_sys::Win32::Security::ACL; -use windows_sys::Win32::Security::ACL_SIZE_INFORMATION; -use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION; -use windows_sys::Win32::Security::GENERIC_MAPPING; use windows_sys::Win32::Storage::FileSystem::CreateFileW; +use windows_sys::Win32::Storage::FileSystem::DELETE; use windows_sys::Win32::Storage::FileSystem::FILE_ALL_ACCESS; use windows_sys::Win32::Storage::FileSystem::FILE_APPEND_DATA; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL; -use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; use windows_sys::Win32::Storage::FileSystem::FILE_DELETE_CHILD; +use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; 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::FILE_PERSISTENT_ACLS; use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_DELETE; use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ; use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE; use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_ATTRIBUTES; use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA; use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA; +use windows_sys::Win32::Storage::FileSystem::GetVolumeInformationW; +use windows_sys::Win32::Storage::FileSystem::GetVolumePathNameW; use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING; use windows_sys::Win32::Storage::FileSystem::READ_CONTROL; -use windows_sys::Win32::Storage::FileSystem::DELETE; const SE_KERNEL_OBJECT: u32 = 6; const INHERIT_ONLY_ACE: u8 = 0x08; const GENERIC_WRITE_MASK: u32 = 0x4000_0000; const DENY_ACCESS: i32 = 3; +const VOLUME_PATH_BUFFER_LEN: usize = 1024; /// Fetch DACL via handle-based query; caller must LocalFree the returned SD. /// @@ -92,6 +97,50 @@ pub unsafe fn fetch_dacl_handle(path: &Path) -> Result<(*mut ACL, *mut c_void)> Ok((p_dacl, p_sd)) } +pub fn path_supports_persistent_acls(path: &Path) -> Result { + let wpath = to_wide(path); + let mut volume_path = vec![0u16; VOLUME_PATH_BUFFER_LEN]; + let ok = unsafe { + GetVolumePathNameW( + wpath.as_ptr(), + volume_path.as_mut_ptr(), + volume_path.len() as u32, + ) + }; + if ok == 0 { + let err = unsafe { GetLastError() }; + return Err(anyhow!( + "GetVolumePathNameW failed for {}: {}", + path.display(), + err + )); + } + + let mut file_system_flags = 0u32; + let ok = unsafe { + GetVolumeInformationW( + volume_path.as_ptr(), + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut file_system_flags, + std::ptr::null_mut(), + 0, + ) + }; + if ok == 0 { + let err = unsafe { GetLastError() }; + return Err(anyhow!( + "GetVolumeInformationW failed for {}: {}", + path.display(), + err + )); + } + + Ok((file_system_flags & FILE_PERSISTENT_ACLS) != 0) +} + /// Fast mask-based check: does an ACE for provided SIDs grant the desired mask? Skips inherit-only. /// When `require_all_bits` is true, all bits in `desired_mask` must be present; otherwise any bit suffices. pub unsafe fn dacl_mask_allows( @@ -259,12 +308,8 @@ pub unsafe fn dacl_has_write_deny_for_sid(p_dacl: *mut ACL, psid: *mut c_void) - false } -const WRITE_ALLOW_MASK: u32 = FILE_GENERIC_READ - | FILE_GENERIC_WRITE - | FILE_GENERIC_EXECUTE - | DELETE - | FILE_DELETE_CHILD; - +const WRITE_ALLOW_MASK: u32 = + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD; unsafe fn ensure_allow_mask_aces_with_inheritance_impl( path: &Path, @@ -275,12 +320,7 @@ unsafe fn ensure_allow_mask_aces_with_inheritance_impl( let (p_dacl, p_sd) = fetch_dacl_handle(path)?; let mut entries: Vec = Vec::new(); for sid in sids { - if dacl_mask_allows( - p_dacl, - &[*sid], - allow_mask, - /*require_all_bits*/ true, - ) { + if dacl_mask_allows(p_dacl, &[*sid], allow_mask, /*require_all_bits*/ true) { continue; } entries.push(EXPLICIT_ACCESS_W { diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index 522f8926d5..a904a0d1d0 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -91,6 +91,8 @@ pub use acl::fetch_dacl_handle; #[cfg(target_os = "windows")] pub use acl::path_mask_allows; #[cfg(target_os = "windows")] +pub use acl::path_supports_persistent_acls; +#[cfg(target_os = "windows")] pub use audit::apply_world_writable_scan_and_denies; #[cfg(target_os = "windows")] pub use cap::load_or_create_cap_sids; diff --git a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs index ca3fc1e444..aac82c5302 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_main_win.rs @@ -24,6 +24,7 @@ use codex_windows_sandbox::is_command_cwd_root; 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::path_supports_persistent_acls; use codex_windows_sandbox::sandbox_bin_dir; use codex_windows_sandbox::sandbox_dir; use codex_windows_sandbox::sandbox_secrets_dir; @@ -158,6 +159,35 @@ fn apply_read_acls( )?; continue; } + match path_supports_persistent_acls(root) { + Ok(true) => {} + Ok(false) => { + log_line( + log, + &format!( + "{access_label} root {} is on a filesystem without persistent ACL support; skipping sandbox ACL grant", + root.display() + ), + )?; + continue; + } + Err(err) => { + refresh_errors.push(format!( + "{access_label} ACL capability check failed on {}: {}", + root.display(), + err + )); + log_line( + log, + &format!( + "{access_label} ACL capability check failed on {}: {}; continuing", + root.display(), + err + ), + )?; + continue; + } + } let builtin_has = read_mask_allows_or_log( root, subjects.rx_psids,