Add a Windows sandbox provisioning protocol (#42334)

## What changed

- Define a versioned, length-prefixed JSON request/response protocol for the
  machine-wide Windows sandbox provisioning service, including its named pipe
  and network setup parameters.
- Add a readiness check that compares the recorded proxy ports and local-binding
  policy with the requested provisioning settings.
- Share framed JSON I/O, sandbox-group creation, and SID resolution between the
  existing elevated setup path and provisioning clients.

GitOrigin-RevId: d6250542d06f7646ffeb92150472e7d7fa1e5e66
This commit is contained in:
johnl-oai authored and copyberry committed 2026-09-02 19:23:27 +00:00
1 parent dc0dc4f15d
commit 301a7c5e01
8 files changed
+199 -156

No files matched your search

@@ -22,6 +22,7 @@ use codex_windows_sandbox::log_note;
use codex_windows_sandbox::log_writer; use codex_windows_sandbox::log_writer;
use codex_windows_sandbox::path_mask_allows; use codex_windows_sandbox::path_mask_allows;
use codex_windows_sandbox::path_write_aces_need_refresh; use codex_windows_sandbox::path_write_aces_need_refresh;
use codex_windows_sandbox::resolve_sid;
use codex_windows_sandbox::sandbox_bin_dir; use codex_windows_sandbox::sandbox_bin_dir;
use codex_windows_sandbox::sandbox_dir; use codex_windows_sandbox::sandbox_dir;
use codex_windows_sandbox::sandbox_secrets_dir; use codex_windows_sandbox::sandbox_secrets_dir;
@@ -80,7 +81,6 @@ use sandbox_users::commit_setup_marker;
use sandbox_users::prepare_setup_marker; use sandbox_users::prepare_setup_marker;
use sandbox_users::provision_sandbox_users; use sandbox_users::provision_sandbox_users;
use sandbox_users::resolve_sandbox_users_group_sid; use sandbox_users::resolve_sandbox_users_group_sid;
use sandbox_users::resolve_sid;
use sandbox_users::sid_bytes_to_psid; use sandbox_users::sid_bytes_to_psid;
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
@@ -16,10 +16,8 @@ use windows_sys::Win32::Foundation::GENERIC_WRITE;
use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::LocalFree;
use windows_sys::Win32::NetworkManagement::NetManagement::LOCALGROUP_INFO_1;
use windows_sys::Win32::NetworkManagement::NetManagement::LOCALGROUP_MEMBERS_INFO_3; use windows_sys::Win32::NetworkManagement::NetManagement::LOCALGROUP_MEMBERS_INFO_3;
use windows_sys::Win32::NetworkManagement::NetManagement::NERR_Success; use windows_sys::Win32::NetworkManagement::NetManagement::NERR_Success;
use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAdd;
use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAddMembers; use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAddMembers;
use windows_sys::Win32::NetworkManagement::NetManagement::NetUserAdd; use windows_sys::Win32::NetworkManagement::NetManagement::NetUserAdd;
use windows_sys::Win32::NetworkManagement::NetManagement::NetUserSetInfo; use windows_sys::Win32::NetworkManagement::NetManagement::NetUserSetInfo;
@@ -31,9 +29,6 @@ use windows_sys::Win32::NetworkManagement::NetManagement::USER_PRIV_USER;
use windows_sys::Win32::Security::Authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW; use windows_sys::Win32::Security::Authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW;
use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW; use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW;
use windows_sys::Win32::Security::Authorization::SDDL_REVISION_1; use windows_sys::Win32::Security::Authorization::SDDL_REVISION_1;
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::LookupAccountSidW;
use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR; use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR;
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
@@ -42,26 +37,19 @@ use windows_sys::Win32::Storage::FileSystem::CREATE_NEW;
use windows_sys::Win32::Storage::FileSystem::CreateFileW; use windows_sys::Win32::Storage::FileSystem::CreateFileW;
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
use codex_windows_sandbox::SANDBOX_USERS_GROUP;
use codex_windows_sandbox::SETUP_VERSION; use codex_windows_sandbox::SETUP_VERSION;
use codex_windows_sandbox::SetupErrorCode; use codex_windows_sandbox::SetupErrorCode;
use codex_windows_sandbox::SetupFailure; use codex_windows_sandbox::SetupFailure;
use codex_windows_sandbox::dpapi_protect; use codex_windows_sandbox::dpapi_protect;
use codex_windows_sandbox::ensure_sandbox_users_group;
use codex_windows_sandbox::resolve_sid;
use codex_windows_sandbox::sandbox_dir; use codex_windows_sandbox::sandbox_dir;
use codex_windows_sandbox::sandbox_secrets_dir; use codex_windows_sandbox::sandbox_secrets_dir;
use codex_windows_sandbox::string_from_sid_bytes; use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide; use codex_windows_sandbox::to_wide;
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_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 dyn Write) -> Result<()> {
ensure_local_group(SANDBOX_USERS_GROUP, SANDBOX_USERS_GROUP_COMMENT, log)
}
pub fn resolve_sandbox_users_group_sid() -> Result<Vec<u8>> { pub fn resolve_sandbox_users_group_sid() -> Result<Vec<u8>> {
resolve_sid(SANDBOX_USERS_GROUP) resolve_sid(SANDBOX_USERS_GROUP)
@@ -73,7 +61,14 @@ pub fn provision_sandbox_users(
online_username: &str, online_username: &str,
log: &mut dyn Write, log: &mut dyn Write,
) -> Result<()> { ) -> Result<()> {
ensure_sandbox_users_group(log)?; if let Err(err) = ensure_sandbox_users_group() {
let message = format!("failed to create local group {SANDBOX_USERS_GROUP}: {err}");
super::log_line(log, &message)?;
return Err(anyhow::Error::new(SetupFailure::new(
SetupErrorCode::HelperUsersGroupCreateFailed,
message,
)));
}
super::log_line( super::log_line(
log, log,
&format!("ensuring sandbox users offline={offline_username} online={online_username}"), &format!("ensuring sandbox users offline={offline_username} online={online_username}"),
@@ -162,38 +157,6 @@ pub fn ensure_local_user(name: &str, password: &str, log: &mut dyn Write) -> Res
Ok(()) Ok(())
} }
pub fn ensure_local_group(name: &str, comment: &str, log: &mut dyn Write) -> Result<()> {
const ERROR_ALIAS_EXISTS: u32 = 1379;
const NERR_GROUP_EXISTS: u32 = 2223;
let name_w = to_wide(OsStr::new(name));
let comment_w = to_wide(OsStr::new(comment));
unsafe {
let info = LOCALGROUP_INFO_1 {
lgrpi1_name: name_w.as_ptr() as *mut u16,
lgrpi1_comment: comment_w.as_ptr() as *mut u16,
};
let mut parm_err: u32 = 0;
let status = NetLocalGroupAdd(
std::ptr::null(),
1,
&info as *const _ as *mut u8,
&mut parm_err as *mut _,
);
if status != NERR_Success && status != ERROR_ALIAS_EXISTS && status != NERR_GROUP_EXISTS {
super::log_line(
log,
&format!("NetLocalGroupAdd failed for {name} code {status} parm_err={parm_err}"),
)?;
return Err(anyhow::Error::new(SetupFailure::new(
SetupErrorCode::HelperUsersGroupCreateFailed,
format!("failed to create local group {name}, code {status}"),
)));
}
}
Ok(())
}
pub fn ensure_local_group_member(group_name: &str, member_name: &str) -> Result<()> { pub fn ensure_local_group_member(group_name: &str, member_name: &str) -> Result<()> {
// If the member is already in the group, NetLocalGroupAddMembers may // If the member is already in the group, NetLocalGroupAddMembers may
// return an error code. We don't care. // return an error code. We don't care.
@@ -214,82 +177,6 @@ pub fn ensure_local_group_member(group_name: &str, member_name: &str) -> Result<
Ok(()) Ok(())
} }
pub fn resolve_sid(name: &str) -> Result<Vec<u8>> {
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;
let mut domain: Vec<u16> = Vec::new();
let mut domain_len: u32 = 0;
let mut use_type: SID_NAME_USE = 0;
loop {
let ok = unsafe {
LookupAccountNameW(
std::ptr::null(),
name_w.as_ptr(),
sid_buffer.as_mut_ptr() as *mut c_void,
&mut sid_len,
domain.as_mut_ptr(),
&mut domain_len,
&mut use_type,
)
};
if ok != 0 {
sid_buffer.truncate(sid_len as usize);
return Ok(sid_buffer);
}
let err = unsafe { GetLastError() };
if err == ERROR_INSUFFICIENT_BUFFER {
sid_buffer.resize(sid_len as usize, 0);
domain.resize(domain_len as usize, 0);
continue;
}
return Err(anyhow::anyhow!(
"LookupAccountNameW failed for {name}: {err}"
));
}
}
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<Vec<u8>> {
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<String> { fn lookup_account_name_for_sid(sid_str: &str) -> Result<String> {
let sid_w = to_wide(OsStr::new(sid_str)); let sid_w = to_wide(OsStr::new(sid_str));
let mut psid: *mut c_void = std::ptr::null_mut(); let mut psid: *mut c_void = std::ptr::null_mut();
@@ -19,12 +19,6 @@ use std::io::Read;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
/// Safety cap for a single framed message payload.
///
/// This is not a protocol requirement; it simply bounds memory use and rejects
/// obviously invalid frames.
const MAX_FRAME_LEN: usize = 8 * 1024 * 1024;
/// Protocol version shared by the parent process and elevated command runner. /// Protocol version shared by the parent process and elevated command runner.
pub const IPC_PROTOCOL_VERSION: u8 = 6; pub const IPC_PROTOCOL_VERSION: u8 = 6;
@@ -151,34 +145,13 @@ pub fn decode_bytes(data: &str) -> Result<Vec<u8>> {
} }
/// Write a length-prefixed JSON frame. /// Write a length-prefixed JSON frame.
pub fn write_frame<W: Write>(mut writer: W, msg: &FramedMessage) -> Result<()> { pub fn write_frame<W: Write>(writer: W, msg: &FramedMessage) -> Result<()> {
let payload = serde_json::to_vec(msg)?; crate::framed_io::write_frame(writer, msg)
if payload.len() > MAX_FRAME_LEN {
anyhow::bail!("frame too large: {}", payload.len());
}
let len = payload.len() as u32;
writer.write_all(&len.to_le_bytes())?;
writer.write_all(&payload)?;
writer.flush()?;
Ok(())
} }
/// Read a length-prefixed JSON frame; returns `Ok(None)` on EOF. /// Read a length-prefixed JSON frame; returns `Ok(None)` on EOF.
pub fn read_frame<R: Read>(mut reader: R) -> Result<Option<FramedMessage>> { pub fn read_frame<R: Read>(reader: R) -> Result<Option<FramedMessage>> {
let mut len_buf = [0u8; 4]; crate::framed_io::read_frame(reader)
match reader.read_exact(&mut len_buf) {
Ok(()) => {}
Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
Err(err) => return Err(err.into()),
}
let len = u32::from_le_bytes(len_buf) as usize;
if len > MAX_FRAME_LEN {
anyhow::bail!("frame too large: {len}");
}
let mut payload = vec![0u8; len];
reader.read_exact(&mut payload)?;
let msg: FramedMessage = serde_json::from_slice(&payload)?;
Ok(Some(msg))
} }
#[cfg(test)] #[cfg(test)]
@@ -0,0 +1,39 @@
//! Shared length-prefixed JSON framing for independent Windows sandbox IPC protocols.
use anyhow::Result;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::io::Read;
use std::io::Write;
/// Bound the memory used by an individual untrusted IPC frame.
const MAX_FRAME_LEN: usize = 8 * 1024 * 1024;
pub(crate) fn write_frame<W: Write, T: Serialize>(mut writer: W, message: &T) -> Result<()> {
let payload = serde_json::to_vec(message)?;
if payload.len() > MAX_FRAME_LEN {
anyhow::bail!("frame too large: {}", payload.len());
}
let len = payload.len() as u32;
writer.write_all(&len.to_le_bytes())?;
writer.write_all(&payload)?;
writer.flush()?;
Ok(())
}
pub(crate) fn read_frame<R: Read, T: DeserializeOwned>(mut reader: R) -> Result<Option<T>> {
let mut len_buf = [0u8; 4];
match reader.read_exact(&mut len_buf) {
Ok(()) => {}
Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
Err(err) => return Err(err.into()),
}
let len = u32::from_le_bytes(len_buf) as usize;
if len > MAX_FRAME_LEN {
anyhow::bail!("frame too large: {len}");
}
let mut payload = vec![0u8; len];
reader.read_exact(&mut payload)?;
let message = serde_json::from_slice(&payload)?;
Ok(Some(message))
}
@@ -47,6 +47,24 @@ pub fn sandbox_setup_is_complete(codex_home: &Path) -> bool {
matches!(load_users(codex_home), Ok(Some(users)) if users.version_matches()) matches!(load_users(codex_home), Ok(Some(users)) if users.version_matches())
} }
/// Returns true when setup artifacts and provisioned network settings match.
pub fn sandbox_setup_is_complete_with_settings(
codex_home: &Path,
settings: &crate::WindowsSandboxProvisioningSettings,
) -> bool {
let Ok(Some(mut marker)) = load_marker(codex_home) else {
return false;
};
marker.proxy_ports.sort_unstable();
let mut proxy_ports = settings.proxy_ports.clone();
proxy_ports.sort_unstable();
marker.version_matches()
&& marker.proxy_ports == proxy_ports
&& marker.allow_local_binding == settings.allow_local_binding
&& matches!(load_users(codex_home), Ok(Some(users)) if users.version_matches())
}
fn load_marker(codex_home: &Path) -> Result<Option<SetupMarker>> { fn load_marker(codex_home: &Path) -> Result<Option<SetupMarker>> {
let path = setup_marker_path(codex_home); let path = setup_marker_path(codex_home);
let marker = match fs::read_to_string(&path) { let marker = match fs::read_to_string(&path) {
+28
View File
@@ -65,6 +65,8 @@ mod dpapi;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod env; mod env;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod framed_io;
#[cfg(target_os = "windows")]
mod helper_materialization; mod helper_materialization;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod hide_users; mod hide_users;
@@ -77,6 +79,8 @@ mod path_normalization;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod process; mod process;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod provisioning_protocol;
#[cfg(target_os = "windows")]
mod resolved_permissions; mod resolved_permissions;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod token; mod token;
@@ -197,6 +201,8 @@ pub use identity::require_logon_sandbox_creds;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use identity::sandbox_setup_is_complete; pub use identity::sandbox_setup_is_complete;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use identity::sandbox_setup_is_complete_with_settings;
#[cfg(target_os = "windows")]
pub use ipc_framed::ErrorPayload; pub use ipc_framed::ErrorPayload;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use ipc_framed::ErrorStage; pub use ipc_framed::ErrorStage;
@@ -253,6 +259,22 @@ pub use process::read_handle_loop;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use process::spawn_process_with_pipes; pub use process::spawn_process_with_pipes;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use provisioning_protocol::FramedProvisioningMessage;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::PROVISIONING_PROTOCOL_VERSION;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::ProvisioningMessage;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::SANDBOX_PROVISIONING_PIPE_NAME;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::SandboxProvisioningRequest;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::SandboxProvisioningResponse;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::read_provisioning_frame;
#[cfg(target_os = "windows")]
pub use provisioning_protocol::write_provisioning_frame;
#[cfg(target_os = "windows")]
pub use resolved_permissions::ResolvedWindowsSandboxPermissions; pub use resolved_permissions::ResolvedWindowsSandboxPermissions;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use resolved_permissions::WindowsSandboxTokenMode; pub use resolved_permissions::WindowsSandboxTokenMode;
@@ -338,8 +360,14 @@ pub use windows_impl::run_windows_sandbox_capture_with_filesystem_overrides;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use windows_impl::run_windows_sandbox_legacy_preflight; pub use windows_impl::run_windows_sandbox_legacy_preflight;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use winutil::SANDBOX_USERS_GROUP;
#[cfg(target_os = "windows")]
pub use winutil::ensure_sandbox_users_group;
#[cfg(target_os = "windows")]
pub use winutil::quote_windows_arg; pub use winutil::quote_windows_arg;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use winutil::resolve_sid;
#[cfg(target_os = "windows")]
pub use winutil::string_from_sid_bytes; pub use winutil::string_from_sid_bytes;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use winutil::to_wide; pub use winutil::to_wide;
@@ -0,0 +1,64 @@
//! Dedicated protocol exchanged with the Windows sandbox provisioning service.
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use std::io::Read;
use std::io::Write;
/// Protocol version shared by provisioning clients and the Windows service.
pub const PROVISIONING_PROTOCOL_VERSION: u8 = 1;
/// Named pipe used by the machine-wide Windows sandbox provisioning service.
pub const SANDBOX_PROVISIONING_PIPE_NAME: &str = r"\\.\pipe\OpenAI.CodexSandbox";
/// Versioned provisioning-service message carried in a length-prefixed JSON frame.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FramedProvisioningMessage {
pub version: u8,
#[serde(flatten)]
pub message: ProvisioningMessage,
}
/// Request and response messages exchanged with the provisioning service.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ProvisioningMessage {
ProvisionSandboxRequest {
payload: SandboxProvisioningRequest,
},
ProvisionSandboxResponse {
payload: SandboxProvisioningResponse,
},
}
/// Sandbox setup parameters sent to the provisioning service.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct SandboxProvisioningRequest {
pub codex_home: String,
pub http_port: Option<u16>,
pub socks_port: Option<u16>,
pub allow_local_binding: bool,
}
/// Result returned by the sandbox provisioning service.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum SandboxProvisioningResponse {
Ok,
Error { message: String },
}
/// Write a length-prefixed provisioning-service message.
pub fn write_provisioning_frame<W: Write>(
writer: W,
message: &FramedProvisioningMessage,
) -> Result<()> {
crate::framed_io::write_frame(writer, message)
}
/// Read a length-prefixed provisioning-service message, returning `None` on EOF.
pub fn read_provisioning_frame<R: Read>(reader: R) -> Result<Option<FramedProvisioningMessage>> {
crate::framed_io::read_frame(reader)
}
@@ -5,6 +5,9 @@ use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Foundation::HLOCAL;
use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::LocalFree;
use windows_sys::Win32::NetworkManagement::NetManagement::LOCALGROUP_INFO_1;
use windows_sys::Win32::NetworkManagement::NetManagement::NERR_Success;
use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAdd;
use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW; use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW;
use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW; use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW;
use windows_sys::Win32::Security::CopySid; use windows_sys::Win32::Security::CopySid;
@@ -16,6 +19,9 @@ use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_FROM_SYSTEM;
use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_IGNORE_INSERTS; use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_IGNORE_INSERTS;
use windows_sys::Win32::System::Diagnostics::Debug::FormatMessageW; use windows_sys::Win32::System::Diagnostics::Debug::FormatMessageW;
pub const SANDBOX_USERS_GROUP: &str = "CodexSandboxUsers";
const SANDBOX_USERS_GROUP_COMMENT: &str = "Codex sandbox internal group (managed)";
pub fn to_wide<S: AsRef<OsStr>>(s: S) -> Vec<u16> { pub fn to_wide<S: AsRef<OsStr>>(s: S) -> Vec<u16> {
let mut v: Vec<u16> = s.as_ref().encode_wide().collect(); let mut v: Vec<u16> = s.as_ref().encode_wide().collect();
v.push(0); v.push(0);
@@ -129,6 +135,34 @@ const SID_AUTHENTICATED_USERS: &str = "S-1-5-11";
const SID_EVERYONE: &str = "S-1-1-0"; const SID_EVERYONE: &str = "S-1-1-0";
const SID_SYSTEM: &str = "S-1-5-18"; const SID_SYSTEM: &str = "S-1-5-18";
pub fn ensure_sandbox_users_group() -> Result<Vec<u8>> {
const ERROR_ALIAS_EXISTS: u32 = 1379;
const NERR_GROUP_EXISTS: u32 = 2223;
let name = to_wide(SANDBOX_USERS_GROUP);
let comment = to_wide(SANDBOX_USERS_GROUP_COMMENT);
let info = LOCALGROUP_INFO_1 {
lgrpi1_name: name.as_ptr() as *mut u16,
lgrpi1_comment: comment.as_ptr() as *mut u16,
};
let mut parameter_error = 0;
let status = unsafe {
NetLocalGroupAdd(
std::ptr::null(),
1,
(&raw const info).cast(),
&raw mut parameter_error,
)
};
if status != NERR_Success && status != ERROR_ALIAS_EXISTS && status != NERR_GROUP_EXISTS {
return Err(anyhow::anyhow!(
"NetLocalGroupAdd failed for {SANDBOX_USERS_GROUP} code {status} parm_err={parameter_error}"
));
}
resolve_sid(SANDBOX_USERS_GROUP)
}
pub fn resolve_sid(name: &str) -> Result<Vec<u8>> { pub fn resolve_sid(name: &str) -> Result<Vec<u8>> {
if let Some(sid_str) = well_known_sid_str(name) { if let Some(sid_str) = well_known_sid_str(name) {
return sid_bytes_from_string(sid_str); return sid_bytes_from_string(sid_str);