Skip to content

Commit 13c0919

Browse files
Elevated Sandbox 2 (#7792)
- DPAPI helpers for storing Sandbox user passwords securely - creation of Offline/Online sandbox users - ACL setup for sandbox users - firewall rule setup
1 parent 83aac0f commit 13c0919

File tree

13 files changed

+1681
-33
lines changed

13 files changed

+1681
-33
lines changed

codex-rs/windows-sandbox-rs/Cargo.toml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,29 @@ edition = "2021"
33
license.workspace = true
44
name = "codex-windows-sandbox"
55
version.workspace = true
6+
build = "build.rs"
67

78
[lib]
89
name = "codex_windows_sandbox"
910
path = "src/lib.rs"
1011

12+
[[bin]]
13+
name = "codex-windows-sandbox-setup"
14+
path = "src/bin/setup_main.rs"
15+
1116
[dependencies]
1217
anyhow = "1.0"
13-
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
18+
base64 = { workspace = true }
1419
dunce = "1.0"
1520
serde = { version = "1.0", features = ["derive"] }
1621
serde_json = "1.0"
22+
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
23+
windows = { version = "0.58", features = [
24+
"Win32_Foundation",
25+
"Win32_NetworkManagement_WindowsFirewall",
26+
"Win32_System_Com",
27+
"Win32_System_Variant",
28+
] }
1729
[dependencies.codex-protocol]
1830
package = "codex-protocol"
1931
path = "../protocol"
@@ -41,11 +53,18 @@ features = [
4153
"Win32_System_Console",
4254
"Win32_Storage_FileSystem",
4355
"Win32_System_Diagnostics_ToolHelp",
56+
"Win32_NetworkManagement_NetManagement",
4457
"Win32_Networking_WinSock",
4558
"Win32_System_LibraryLoader",
4659
"Win32_System_Com",
60+
"Win32_Security_Cryptography",
4761
"Win32_Security_Authentication_Identity",
62+
"Win32_UI_Shell",
63+
"Win32_System_Registry",
4864
]
4965
version = "0.52"
5066
[dev-dependencies]
5167
tempfile = "3"
68+
69+
[build-dependencies]
70+
winres = "0.1"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn main() {
2+
let mut res = winres::WindowsResource::new();
3+
res.set_manifest_file("codex-windows-sandbox-setup.manifest");
4+
let _ = res.compile();
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3+
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
4+
<security>
5+
<requestedPrivileges>
6+
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
7+
</requestedPrivileges>
8+
</security>
9+
</trustInfo>
10+
</assembly>

codex-rs/windows-sandbox-rs/src/acl.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use windows_sys::Win32::Storage::FileSystem::FILE_ALL_ACCESS;
3434
use windows_sys::Win32::Storage::FileSystem::FILE_APPEND_DATA;
3535
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
3636
use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;
37+
use windows_sys::Win32::Storage::FileSystem::FILE_DELETE_CHILD;
3738
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE;
3839
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
3940
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
@@ -45,12 +46,16 @@ use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA;
4546
use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA;
4647
use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING;
4748
use windows_sys::Win32::Storage::FileSystem::READ_CONTROL;
49+
use windows_sys::Win32::Storage::FileSystem::DELETE;
4850
const SE_KERNEL_OBJECT: u32 = 6;
4951
const INHERIT_ONLY_ACE: u8 = 0x08;
5052
const GENERIC_WRITE_MASK: u32 = 0x4000_0000;
5153
const DENY_ACCESS: i32 = 3;
5254

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

91-
/// Fast mask-based check: does any ACE for provided SIDs grant at least one desired bit? Skips inherit-only.
92-
pub unsafe fn dacl_quick_mask_allows(
96+
/// Fast mask-based check: does an ACE for provided SIDs grant the desired mask? Skips inherit-only.
97+
/// When `require_all_bits` is true, all bits in `desired_mask` must be present; otherwise any bit suffices.
98+
pub unsafe fn dacl_mask_allows(
9399
p_dacl: *mut ACL,
94100
psids: &[*mut c_void],
95101
desired_mask: u32,
102+
require_all_bits: bool,
96103
) -> bool {
97104
if p_dacl.is_null() {
98105
return false;
@@ -141,22 +148,25 @@ pub unsafe fn dacl_quick_mask_allows(
141148
let ace = &*(p_ace as *const ACCESS_ALLOWED_ACE);
142149
let mut mask = ace.Mask;
143150
MapGenericMask(&mut mask, &mapping);
144-
if (mask & desired_mask) != 0 {
151+
if (require_all_bits && (mask & desired_mask) == desired_mask)
152+
|| (!require_all_bits && (mask & desired_mask) != 0)
153+
{
145154
return true;
146155
}
147156
}
148157
false
149158
}
150159

151-
/// Path-based wrapper around the quick mask check (single DACL fetch).
152-
pub fn path_quick_mask_allows(
160+
/// Path-based wrapper around the mask check (single DACL fetch).
161+
pub fn path_mask_allows(
153162
path: &Path,
154163
psids: &[*mut c_void],
155164
desired_mask: u32,
165+
require_all_bits: bool,
156166
) -> Result<bool> {
157167
unsafe {
158168
let (p_dacl, sd) = fetch_dacl_handle(path)?;
159-
let has = dacl_quick_mask_allows(p_dacl, psids, desired_mask);
169+
let has = dacl_mask_allows(p_dacl, psids, desired_mask, require_all_bits);
160170
if !sd.is_null() {
161171
LocalFree(sd as HLOCAL);
162172
}
@@ -326,16 +336,23 @@ pub unsafe fn dacl_effective_allows_mask(
326336
}
327337

328338
#[allow(dead_code)]
329-
const WRITE_ALLOW_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE;
339+
const WRITE_ALLOW_MASK: u32 = FILE_GENERIC_READ
340+
| FILE_GENERIC_WRITE
341+
| FILE_GENERIC_EXECUTE
342+
| DELETE
343+
| FILE_DELETE_CHILD;
330344

331345
/// Ensure all provided SIDs have a write-capable allow ACE on the path.
332346
/// Returns true if any ACE was added.
347+
///
348+
/// # Safety
349+
/// Caller must pass valid SID pointers and an existing path; free the returned security descriptor with `LocalFree`.
333350
#[allow(dead_code)]
334351
pub unsafe fn ensure_allow_write_aces(path: &Path, sids: &[*mut c_void]) -> Result<bool> {
335352
let (p_dacl, p_sd) = fetch_dacl_handle(path)?;
336353
let mut entries: Vec<EXPLICIT_ACCESS_W> = Vec::new();
337354
for sid in sids {
338-
if dacl_quick_mask_allows(p_dacl, &[*sid], WRITE_ALLOW_MASK) {
355+
if dacl_mask_allows(p_dacl, &[*sid], WRITE_ALLOW_MASK, true) {
339356
continue;
340357
}
341358
entries.push(EXPLICIT_ACCESS_W {

codex-rs/windows-sandbox-rs/src/audit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::acl::add_deny_write_ace;
2-
use crate::acl::path_quick_mask_allows;
2+
use crate::acl::path_mask_allows;
33
use crate::cap::cap_sid_file;
44
use crate::cap::load_or_create_cap_sids;
55
use crate::logging::{debug_log, log_note};
@@ -84,7 +84,7 @@ unsafe fn path_has_world_write_allow(path: &Path) -> Result<bool> {
8484
let mut world = world_sid()?;
8585
let psid_world = world.as_mut_ptr() as *mut c_void;
8686
let write_mask = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES;
87-
path_quick_mask_allows(path, &[psid_world], write_mask)
87+
path_mask_allows(path, &[psid_world], write_mask, false)
8888
}
8989

9090
pub fn audit_everyone_writable(
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#[path = "../setup_main_win.rs"]
2+
mod win;
3+
4+
#[cfg(target_os = "windows")]
5+
fn main() -> anyhow::Result<()> {
6+
win::main()
7+
}
8+
9+
#[cfg(not(target_os = "windows"))]
10+
fn main() {
11+
panic!("codex-windows-sandbox-setup is Windows-only");
12+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use anyhow::anyhow;
2+
use anyhow::Result;
3+
use windows_sys::Win32::Foundation::GetLastError;
4+
use windows_sys::Win32::Foundation::HLOCAL;
5+
use windows_sys::Win32::Foundation::LocalFree;
6+
use windows_sys::Win32::Security::Cryptography::CryptProtectData;
7+
use windows_sys::Win32::Security::Cryptography::CryptUnprotectData;
8+
use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB;
9+
use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_UI_FORBIDDEN;
10+
11+
fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB {
12+
CRYPT_INTEGER_BLOB {
13+
cbData: data.len() as u32,
14+
pbData: data.as_ptr() as *mut u8,
15+
}
16+
}
17+
18+
#[allow(clippy::unnecessary_mut_passed)]
19+
pub fn protect(data: &[u8]) -> Result<Vec<u8>> {
20+
let mut in_blob = make_blob(data);
21+
let mut out_blob = CRYPT_INTEGER_BLOB {
22+
cbData: 0,
23+
pbData: std::ptr::null_mut(),
24+
};
25+
let ok = unsafe {
26+
CryptProtectData(
27+
&mut in_blob,
28+
std::ptr::null(),
29+
std::ptr::null(),
30+
std::ptr::null_mut(),
31+
std::ptr::null_mut(),
32+
CRYPTPROTECT_UI_FORBIDDEN,
33+
&mut out_blob,
34+
)
35+
};
36+
if ok == 0 {
37+
return Err(anyhow!("CryptProtectData failed: {}", unsafe { GetLastError() }));
38+
}
39+
let slice =
40+
unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec();
41+
unsafe {
42+
if !out_blob.pbData.is_null() {
43+
LocalFree(out_blob.pbData as HLOCAL);
44+
}
45+
}
46+
Ok(slice)
47+
}
48+
49+
#[allow(clippy::unnecessary_mut_passed)]
50+
pub fn unprotect(blob: &[u8]) -> Result<Vec<u8>> {
51+
let mut in_blob = make_blob(blob);
52+
let mut out_blob = CRYPT_INTEGER_BLOB {
53+
cbData: 0,
54+
pbData: std::ptr::null_mut(),
55+
};
56+
let ok = unsafe {
57+
CryptUnprotectData(
58+
&mut in_blob,
59+
std::ptr::null_mut(),
60+
std::ptr::null(),
61+
std::ptr::null_mut(),
62+
std::ptr::null_mut(),
63+
CRYPTPROTECT_UI_FORBIDDEN,
64+
&mut out_blob,
65+
)
66+
};
67+
if ok == 0 {
68+
return Err(anyhow!(
69+
"CryptUnprotectData failed: {}",
70+
unsafe { GetLastError() }
71+
));
72+
}
73+
let slice =
74+
unsafe { std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize) }.to_vec();
75+
unsafe {
76+
if !out_blob.pbData.is_null() {
77+
LocalFree(out_blob.pbData as HLOCAL);
78+
}
79+
}
80+
Ok(slice)
81+
}

0 commit comments

Comments
 (0)