Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion codex-rs/windows-sandbox-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@ edition = "2021"
license.workspace = true
name = "codex-windows-sandbox"
version.workspace = true
build = "build.rs"

[lib]
name = "codex_windows_sandbox"
path = "src/lib.rs"

[[bin]]
name = "codex-windows-sandbox-setup"
path = "src/bin/setup_main.rs"

[dependencies]
anyhow = "1.0"
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
base64 = { workspace = true }
dunce = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_NetworkManagement_WindowsFirewall",
"Win32_System_Com",
"Win32_System_Variant",
] }
[dependencies.codex-protocol]
package = "codex-protocol"
path = "../protocol"
Expand Down Expand Up @@ -41,11 +53,18 @@ features = [
"Win32_System_Console",
"Win32_Storage_FileSystem",
"Win32_System_Diagnostics_ToolHelp",
"Win32_NetworkManagement_NetManagement",
"Win32_Networking_WinSock",
"Win32_System_LibraryLoader",
"Win32_System_Com",
"Win32_Security_Cryptography",
"Win32_Security_Authentication_Identity",
"Win32_UI_Shell",
"Win32_System_Registry",
]
version = "0.52"
[dev-dependencies]
tempfile = "3"

[build-dependencies]
winres = "0.1"
5 changes: 5 additions & 0 deletions codex-rs/windows-sandbox-rs/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() {
let mut res = winres::WindowsResource::new();
res.set_manifest_file("codex-windows-sandbox-setup.manifest");
let _ = res.compile();
}
10 changes: 10 additions & 0 deletions codex-rs/windows-sandbox-rs/codex-windows-sandbox-setup.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
33 changes: 25 additions & 8 deletions codex-rs/windows-sandbox-rs/src/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ 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_GENERIC_EXECUTE;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
Expand All @@ -45,12 +46,16 @@ use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA;
use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA;
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;

/// Fetch DACL via handle-based query; caller must LocalFree the returned SD.
///
/// # Safety
/// Caller must free the returned security descriptor with `LocalFree` and pass an existing path.
pub unsafe fn fetch_dacl_handle(path: &Path) -> Result<(*mut ACL, *mut c_void)> {
let wpath = to_wide(path);
let h = CreateFileW(
Expand Down Expand Up @@ -88,11 +93,13 @@ pub unsafe fn fetch_dacl_handle(path: &Path) -> Result<(*mut ACL, *mut c_void)>
Ok((p_dacl, p_sd))
}

/// Fast mask-based check: does any ACE for provided SIDs grant at least one desired bit? Skips inherit-only.
pub unsafe fn dacl_quick_mask_allows(
/// 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(
p_dacl: *mut ACL,
psids: &[*mut c_void],
desired_mask: u32,
require_all_bits: bool,
) -> bool {
if p_dacl.is_null() {
return false;
Expand Down Expand Up @@ -141,22 +148,25 @@ pub unsafe fn dacl_quick_mask_allows(
let ace = &*(p_ace as *const ACCESS_ALLOWED_ACE);
let mut mask = ace.Mask;
MapGenericMask(&mut mask, &mapping);
if (mask & desired_mask) != 0 {
if (require_all_bits && (mask & desired_mask) == desired_mask)
|| (!require_all_bits && (mask & desired_mask) != 0)
{
return true;
}
}
false
}

/// Path-based wrapper around the quick mask check (single DACL fetch).
pub fn path_quick_mask_allows(
/// Path-based wrapper around the mask check (single DACL fetch).
pub fn path_mask_allows(
path: &Path,
psids: &[*mut c_void],
desired_mask: u32,
require_all_bits: bool,
) -> Result<bool> {
unsafe {
let (p_dacl, sd) = fetch_dacl_handle(path)?;
let has = dacl_quick_mask_allows(p_dacl, psids, desired_mask);
let has = dacl_mask_allows(p_dacl, psids, desired_mask, require_all_bits);
if !sd.is_null() {
LocalFree(sd as HLOCAL);
}
Expand Down Expand Up @@ -326,16 +336,23 @@ pub unsafe fn dacl_effective_allows_mask(
}

#[allow(dead_code)]
const WRITE_ALLOW_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE;
const WRITE_ALLOW_MASK: u32 = FILE_GENERIC_READ
| FILE_GENERIC_WRITE
| FILE_GENERIC_EXECUTE
| DELETE
| FILE_DELETE_CHILD;

/// Ensure all provided SIDs have a write-capable allow ACE on the path.
/// Returns true if any ACE was added.
///
/// # Safety
/// Caller must pass valid SID pointers and an existing path; free the returned security descriptor with `LocalFree`.
#[allow(dead_code)]
pub unsafe fn ensure_allow_write_aces(path: &Path, sids: &[*mut c_void]) -> Result<bool> {
let (p_dacl, p_sd) = fetch_dacl_handle(path)?;
let mut entries: Vec<EXPLICIT_ACCESS_W> = Vec::new();
for sid in sids {
if dacl_quick_mask_allows(p_dacl, &[*sid], WRITE_ALLOW_MASK) {
if dacl_mask_allows(p_dacl, &[*sid], WRITE_ALLOW_MASK, true) {
continue;
}
entries.push(EXPLICIT_ACCESS_W {
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/windows-sandbox-rs/src/audit.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::acl::add_deny_write_ace;
use crate::acl::path_quick_mask_allows;
use crate::acl::path_mask_allows;
use crate::cap::cap_sid_file;
use crate::cap::load_or_create_cap_sids;
use crate::logging::{debug_log, log_note};
Expand Down Expand Up @@ -84,7 +84,7 @@ unsafe fn path_has_world_write_allow(path: &Path) -> Result<bool> {
let mut world = world_sid()?;
let psid_world = world.as_mut_ptr() as *mut c_void;
let write_mask = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES;
path_quick_mask_allows(path, &[psid_world], write_mask)
path_mask_allows(path, &[psid_world], write_mask, false)
}

pub fn audit_everyone_writable(
Expand Down
12 changes: 12 additions & 0 deletions codex-rs/windows-sandbox-rs/src/bin/setup_main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[path = "../setup_main_win.rs"]
mod win;

#[cfg(target_os = "windows")]
fn main() -> anyhow::Result<()> {
win::main()
}

#[cfg(not(target_os = "windows"))]
fn main() {
panic!("codex-windows-sandbox-setup is Windows-only");
}
81 changes: 81 additions & 0 deletions codex-rs/windows-sandbox-rs/src/dpapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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::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_UI_FORBIDDEN;

fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB {
CRYPT_INTEGER_BLOB {
cbData: data.len() as u32,
pbData: data.as_ptr() as *mut u8,
}
}

#[allow(clippy::unnecessary_mut_passed)]
pub fn protect(data: &[u8]) -> Result<Vec<u8>> {
let mut in_blob = make_blob(data);
let mut out_blob = CRYPT_INTEGER_BLOB {
cbData: 0,
pbData: std::ptr::null_mut(),
};
let ok = unsafe {
CryptProtectData(
&mut in_blob,
std::ptr::null(),
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
CRYPTPROTECT_UI_FORBIDDEN,
&mut out_blob,
)
};
if ok == 0 {
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();
unsafe {
if !out_blob.pbData.is_null() {
LocalFree(out_blob.pbData as HLOCAL);
}
}
Ok(slice)
}

#[allow(clippy::unnecessary_mut_passed)]
pub fn unprotect(blob: &[u8]) -> Result<Vec<u8>> {
let mut in_blob = make_blob(blob);
let mut out_blob = CRYPT_INTEGER_BLOB {
cbData: 0,
pbData: std::ptr::null_mut(),
};
let ok = unsafe {
CryptUnprotectData(
&mut in_blob,
std::ptr::null_mut(),
std::ptr::null(),
std::ptr::null_mut(),
std::ptr::null_mut(),
CRYPTPROTECT_UI_FORBIDDEN,
&mut out_blob,
)
};
if ok == 0 {
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();
unsafe {
if !out_blob.pbData.is_null() {
LocalFree(out_blob.pbData as HLOCAL);
}
}
Ok(slice)
}
Loading
Loading