- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
287 lines
10 KiB
Rust
287 lines
10 KiB
Rust
use crate::winutil::to_wide;
|
|
use anyhow::anyhow;
|
|
use anyhow::Result;
|
|
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::HLOCAL;
|
|
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
|
use windows_sys::Win32::Security::AclSizeInformation;
|
|
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::EqualSid;
|
|
use windows_sys::Win32::Security::GetAce;
|
|
use windows_sys::Win32::Security::GetAclInformation;
|
|
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::Storage::FileSystem::CreateFileW;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
|
|
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_SHARE_READ;
|
|
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE;
|
|
use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING;
|
|
const SE_KERNEL_OBJECT: u32 = 6;
|
|
const INHERIT_ONLY_ACE: u8 = 0x08;
|
|
|
|
pub unsafe fn dacl_has_write_allow_for_sid(p_dacl: *mut ACL, psid: *mut c_void) -> bool {
|
|
if p_dacl.is_null() {
|
|
return false;
|
|
}
|
|
let mut info: ACL_SIZE_INFORMATION = std::mem::zeroed();
|
|
let ok = GetAclInformation(
|
|
p_dacl as *const ACL,
|
|
&mut info as *mut _ as *mut c_void,
|
|
std::mem::size_of::<ACL_SIZE_INFORMATION>() as u32,
|
|
AclSizeInformation,
|
|
);
|
|
if ok == 0 {
|
|
return false;
|
|
}
|
|
let count = info.AceCount as usize;
|
|
for i in 0..count {
|
|
let mut p_ace: *mut c_void = std::ptr::null_mut();
|
|
if GetAce(p_dacl as *const ACL, i as u32, &mut p_ace) == 0 {
|
|
continue;
|
|
}
|
|
let hdr = &*(p_ace as *const ACE_HEADER);
|
|
if hdr.AceType != 0 {
|
|
continue; // ACCESS_ALLOWED_ACE_TYPE
|
|
}
|
|
// Ignore ACEs that are inherit-only (do not apply to the current object)
|
|
if (hdr.AceFlags & INHERIT_ONLY_ACE) != 0 {
|
|
continue;
|
|
}
|
|
let ace = &*(p_ace as *const ACCESS_ALLOWED_ACE);
|
|
let mask = ace.Mask;
|
|
let base = p_ace as usize;
|
|
let sid_ptr =
|
|
(base + std::mem::size_of::<ACE_HEADER>() + std::mem::size_of::<u32>()) as *mut c_void;
|
|
let eq = EqualSid(sid_ptr, psid);
|
|
if eq != 0 && (mask & FILE_GENERIC_WRITE) != 0 {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
// Compute effective rights for a trustee SID against a DACL and decide if write is effectively allowed.
|
|
// This accounts for deny ACEs and ordering; falls back to a conservative per-ACE scan if the API fails.
|
|
#[allow(dead_code)]
|
|
pub unsafe fn dacl_effective_allows_write(p_dacl: *mut ACL, psid: *mut c_void) -> bool {
|
|
if p_dacl.is_null() {
|
|
return false;
|
|
}
|
|
use windows_sys::Win32::Security::Authorization::GetEffectiveRightsFromAclW;
|
|
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;
|
|
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut access: u32 = 0;
|
|
let ok = GetEffectiveRightsFromAclW(p_dacl, &trustee, &mut access);
|
|
if ok != 0 {
|
|
// Check for generic or specific write bits
|
|
let write_bits = FILE_GENERIC_WRITE
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_APPEND_DATA
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA
|
|
| windows_sys::Win32::Storage::FileSystem::FILE_WRITE_ATTRIBUTES;
|
|
return (access & write_bits) != 0;
|
|
}
|
|
// Fallback: simple allow ACE scan (already ignores inherit-only)
|
|
dacl_has_write_allow_for_sid(p_dacl, psid)
|
|
}
|
|
pub unsafe fn add_allow_ace(path: &Path, psid: *mut c_void) -> Result<bool> {
|
|
let mut p_sd: *mut c_void = std::ptr::null_mut();
|
|
let mut p_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code = GetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr(),
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
&mut p_dacl,
|
|
std::ptr::null_mut(),
|
|
&mut p_sd,
|
|
);
|
|
if code != ERROR_SUCCESS {
|
|
return Err(anyhow!("GetNamedSecurityInfoW failed: {}", code));
|
|
}
|
|
let mut added = false;
|
|
if !dacl_has_write_allow_for_sid(p_dacl, psid) {
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed();
|
|
explicit.grfAccessPermissions =
|
|
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE;
|
|
explicit.grfAccessMode = 2; // SET_ACCESS
|
|
explicit.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
|
|
explicit.Trustee = trustee;
|
|
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code2 = SetEntriesInAclW(1, &explicit, p_dacl, &mut p_new_dacl);
|
|
if code2 == ERROR_SUCCESS {
|
|
let code3 = SetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr() as *mut u16,
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
p_new_dacl,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if code3 == ERROR_SUCCESS {
|
|
added = true;
|
|
}
|
|
if !p_new_dacl.is_null() {
|
|
LocalFree(p_new_dacl as HLOCAL);
|
|
}
|
|
}
|
|
}
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
Ok(added)
|
|
}
|
|
|
|
pub unsafe fn revoke_ace(path: &Path, psid: *mut c_void) {
|
|
let mut p_sd: *mut c_void = std::ptr::null_mut();
|
|
let mut p_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code = GetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr(),
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
&mut p_dacl,
|
|
std::ptr::null_mut(),
|
|
&mut p_sd,
|
|
);
|
|
if code != ERROR_SUCCESS {
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
return;
|
|
}
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed();
|
|
explicit.grfAccessPermissions = 0;
|
|
explicit.grfAccessMode = 4; // REVOKE_ACCESS
|
|
explicit.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
|
|
explicit.Trustee = trustee;
|
|
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code2 = SetEntriesInAclW(1, &explicit, p_dacl, &mut p_new_dacl);
|
|
if code2 == ERROR_SUCCESS {
|
|
let _ = SetNamedSecurityInfoW(
|
|
to_wide(path).as_ptr() as *mut u16,
|
|
1,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
p_new_dacl,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if !p_new_dacl.is_null() {
|
|
LocalFree(p_new_dacl as HLOCAL);
|
|
}
|
|
}
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
}
|
|
|
|
pub unsafe fn allow_null_device(psid: *mut c_void) {
|
|
let desired = 0x00020000 | 0x00040000; // READ_CONTROL | WRITE_DAC
|
|
let h = CreateFileW(
|
|
to_wide(r"\\\\.\\NUL").as_ptr(),
|
|
desired,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
std::ptr::null_mut(),
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0,
|
|
);
|
|
if h == 0 || h == INVALID_HANDLE_VALUE {
|
|
return;
|
|
}
|
|
let mut p_sd: *mut c_void = std::ptr::null_mut();
|
|
let mut p_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code = GetSecurityInfo(
|
|
h,
|
|
SE_KERNEL_OBJECT as i32,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
&mut p_dacl,
|
|
std::ptr::null_mut(),
|
|
&mut p_sd,
|
|
);
|
|
if code == ERROR_SUCCESS {
|
|
let trustee = TRUSTEE_W {
|
|
pMultipleTrustee: std::ptr::null_mut(),
|
|
MultipleTrusteeOperation: 0,
|
|
TrusteeForm: TRUSTEE_IS_SID,
|
|
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
|
ptstrName: psid as *mut u16,
|
|
};
|
|
let mut explicit: EXPLICIT_ACCESS_W = std::mem::zeroed();
|
|
explicit.grfAccessPermissions =
|
|
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE;
|
|
explicit.grfAccessMode = 2; // SET_ACCESS
|
|
explicit.grfInheritance = 0;
|
|
explicit.Trustee = trustee;
|
|
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
|
|
let code2 = SetEntriesInAclW(1, &explicit, p_dacl, &mut p_new_dacl);
|
|
if code2 == ERROR_SUCCESS {
|
|
let _ = SetSecurityInfo(
|
|
h,
|
|
SE_KERNEL_OBJECT as i32,
|
|
DACL_SECURITY_INFORMATION,
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
p_new_dacl,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if !p_new_dacl.is_null() {
|
|
LocalFree(p_new_dacl as HLOCAL);
|
|
}
|
|
}
|
|
}
|
|
if !p_sd.is_null() {
|
|
LocalFree(p_sd as HLOCAL);
|
|
}
|
|
CloseHandle(h);
|
|
}
|
|
const CONTAINER_INHERIT_ACE: u32 = 0x2;
|
|
const OBJECT_INHERIT_ACE: u32 = 0x1;
|