mirror of
https://github.com/openai/codex.git
synced 2026-05-03 04:42:20 +03:00
487 lines
16 KiB
Rust
487 lines
16 KiB
Rust
#![cfg(target_os = "windows")]
|
|
|
|
use anyhow::Result;
|
|
use base64::engine::general_purpose::STANDARD as BASE64;
|
|
use base64::Engine;
|
|
use rand::rngs::SmallRng;
|
|
use rand::RngCore;
|
|
use rand::SeedableRng;
|
|
use serde::Serialize;
|
|
use std::ffi::c_void;
|
|
use std::ffi::OsStr;
|
|
use std::fs::File;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use windows_sys::Win32::Foundation::GetLastError;
|
|
use windows_sys::Win32::Foundation::LocalFree;
|
|
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
|
|
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::NetUserAdd;
|
|
use windows_sys::Win32::NetworkManagement::NetManagement::NetUserSetInfo;
|
|
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::UF_DONT_EXPIRE_PASSWD;
|
|
use windows_sys::Win32::NetworkManagement::NetManagement::UF_SCRIPT;
|
|
use windows_sys::Win32::NetworkManagement::NetManagement::USER_INFO_1;
|
|
use windows_sys::Win32::NetworkManagement::NetManagement::USER_INFO_1003;
|
|
use windows_sys::Win32::NetworkManagement::NetManagement::USER_PRIV_USER;
|
|
use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW;
|
|
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::SID_NAME_USE;
|
|
|
|
use codex_windows_sandbox::dpapi_protect;
|
|
use codex_windows_sandbox::sandbox_dir;
|
|
use codex_windows_sandbox::sandbox_secrets_dir;
|
|
use codex_windows_sandbox::string_from_sid_bytes;
|
|
use codex_windows_sandbox::to_wide;
|
|
use codex_windows_sandbox::SetupErrorCode;
|
|
use codex_windows_sandbox::SetupFailure;
|
|
use codex_windows_sandbox::SETUP_VERSION;
|
|
|
|
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_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 File) -> Result<()> {
|
|
ensure_local_group(SANDBOX_USERS_GROUP, SANDBOX_USERS_GROUP_COMMENT, log)
|
|
}
|
|
|
|
pub fn resolve_sandbox_users_group_sid() -> Result<Vec<u8>> {
|
|
resolve_sid(SANDBOX_USERS_GROUP)
|
|
}
|
|
|
|
pub fn provision_sandbox_users(
|
|
codex_home: &Path,
|
|
offline_username: &str,
|
|
online_username: &str,
|
|
log: &mut File,
|
|
) -> Result<()> {
|
|
ensure_sandbox_users_group(log)?;
|
|
super::log_line(
|
|
log,
|
|
&format!("ensuring sandbox users offline={offline_username} online={online_username}"),
|
|
)?;
|
|
let offline_password = random_password();
|
|
let online_password = random_password();
|
|
ensure_sandbox_user(offline_username, &offline_password, log)?;
|
|
ensure_sandbox_user(online_username, &online_password, log)?;
|
|
write_secrets(
|
|
codex_home,
|
|
offline_username,
|
|
&offline_password,
|
|
online_username,
|
|
&online_password,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn ensure_sandbox_user(username: &str, password: &str, log: &mut File) -> Result<()> {
|
|
ensure_local_user(username, password, log)?;
|
|
ensure_local_group_member(SANDBOX_USERS_GROUP, username)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn ensure_local_user(name: &str, password: &str, log: &mut File) -> Result<()> {
|
|
let name_w = to_wide(OsStr::new(name));
|
|
let pwd_w = to_wide(OsStr::new(password));
|
|
unsafe {
|
|
let info = USER_INFO_1 {
|
|
usri1_name: name_w.as_ptr() as *mut u16,
|
|
usri1_password: pwd_w.as_ptr() as *mut u16,
|
|
usri1_password_age: 0,
|
|
usri1_priv: USER_PRIV_USER,
|
|
usri1_home_dir: std::ptr::null_mut(),
|
|
usri1_comment: std::ptr::null_mut(),
|
|
usri1_flags: UF_SCRIPT | UF_DONT_EXPIRE_PASSWD,
|
|
usri1_script_path: std::ptr::null_mut(),
|
|
};
|
|
let status = NetUserAdd(
|
|
std::ptr::null(),
|
|
1,
|
|
&info as *const _ as *mut u8,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if status != NERR_Success {
|
|
// Try update password via level 1003.
|
|
let pw_info = USER_INFO_1003 {
|
|
usri1003_password: pwd_w.as_ptr() as *mut u16,
|
|
};
|
|
let upd = NetUserSetInfo(
|
|
std::ptr::null(),
|
|
name_w.as_ptr(),
|
|
1003,
|
|
&pw_info as *const _ as *mut u8,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if upd != NERR_Success {
|
|
super::log_line(log, &format!("NetUserSetInfo failed for {name} code {upd}"))?;
|
|
return Err(anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperUserCreateOrUpdateFailed,
|
|
format!("failed to create/update user {name}, code {status}/{upd}"),
|
|
)));
|
|
}
|
|
}
|
|
|
|
// Ensure the principal is a regular local user account.
|
|
if let Ok(group_name) = lookup_account_name_for_sid(SID_USERS) {
|
|
let group = to_wide(OsStr::new(&group_name));
|
|
let member = LOCALGROUP_MEMBERS_INFO_3 {
|
|
lgrmi3_domainandname: name_w.as_ptr() as *mut u16,
|
|
};
|
|
let _ = NetLocalGroupAddMembers(
|
|
std::ptr::null(),
|
|
group.as_ptr(),
|
|
3,
|
|
&member as *const _ as *mut u8,
|
|
1,
|
|
);
|
|
} else {
|
|
super::log_line(
|
|
log,
|
|
"LookupAccountSidW failed for Users SID; skipping Users group membership",
|
|
)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn ensure_local_group(name: &str, comment: &str, log: &mut File) -> 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<()> {
|
|
// If the member is already in the group, NetLocalGroupAddMembers may
|
|
// return an error code. We don't care.
|
|
let group_w = to_wide(OsStr::new(group_name));
|
|
let member_w = to_wide(OsStr::new(member_name));
|
|
unsafe {
|
|
let member = LOCALGROUP_MEMBERS_INFO_3 {
|
|
lgrmi3_domainandname: member_w.as_ptr() as *mut u16,
|
|
};
|
|
let _ = NetLocalGroupAddMembers(
|
|
std::ptr::null(),
|
|
group_w.as_ptr(),
|
|
3,
|
|
&member as *const _ as *mut u8,
|
|
1,
|
|
);
|
|
}
|
|
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> {
|
|
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 mut name_len: u32 = 0;
|
|
let mut domain_len: u32 = 0;
|
|
let mut use_type: SID_NAME_USE = 0;
|
|
let ok = unsafe {
|
|
LookupAccountSidW(
|
|
std::ptr::null(),
|
|
psid,
|
|
std::ptr::null_mut(),
|
|
&mut name_len,
|
|
std::ptr::null_mut(),
|
|
&mut domain_len,
|
|
&mut use_type,
|
|
)
|
|
};
|
|
if ok == 0 {
|
|
let err = unsafe { GetLastError() };
|
|
if err != ERROR_INSUFFICIENT_BUFFER {
|
|
unsafe {
|
|
LocalFree(psid as _);
|
|
}
|
|
return Err(anyhow::anyhow!(
|
|
"LookupAccountSidW preflight failed for {sid_str}: {err}"
|
|
));
|
|
}
|
|
}
|
|
let mut name_buf: Vec<u16> = vec![0u16; name_len as usize];
|
|
let mut domain_buf: Vec<u16> = vec![0u16; domain_len as usize];
|
|
let ok = unsafe {
|
|
LookupAccountSidW(
|
|
std::ptr::null(),
|
|
psid,
|
|
name_buf.as_mut_ptr(),
|
|
&mut name_len,
|
|
domain_buf.as_mut_ptr(),
|
|
&mut domain_len,
|
|
&mut use_type,
|
|
)
|
|
};
|
|
unsafe {
|
|
LocalFree(psid as _);
|
|
}
|
|
if ok == 0 {
|
|
return Err(anyhow::anyhow!(
|
|
"LookupAccountSidW failed for {sid_str}: {}",
|
|
unsafe { GetLastError() }
|
|
));
|
|
}
|
|
let name = String::from_utf16_lossy(&name_buf);
|
|
Ok(name.trim_end_matches('\0').to_string())
|
|
}
|
|
|
|
pub fn sid_bytes_to_psid(sid: &[u8]) -> Result<*mut c_void> {
|
|
let sid_str = string_from_sid_bytes(sid).map_err(anyhow::Error::msg)?;
|
|
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: {}",
|
|
unsafe { GetLastError() }
|
|
));
|
|
}
|
|
Ok(psid)
|
|
}
|
|
|
|
fn random_password() -> String {
|
|
const CHARS: &[u8] =
|
|
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+";
|
|
let mut rng = SmallRng::from_entropy();
|
|
let mut buf = [0u8; 24];
|
|
rng.fill_bytes(&mut buf);
|
|
buf.iter()
|
|
.map(|b| {
|
|
let idx = (*b as usize) % CHARS.len();
|
|
CHARS[idx] as char
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct SandboxUserRecord {
|
|
username: String,
|
|
password: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct SandboxUsersFile {
|
|
version: u32,
|
|
offline: SandboxUserRecord,
|
|
online: SandboxUserRecord,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct SetupMarker {
|
|
version: u32,
|
|
offline_username: String,
|
|
online_username: String,
|
|
created_at: String,
|
|
read_roots: Vec<PathBuf>,
|
|
write_roots: Vec<PathBuf>,
|
|
}
|
|
|
|
fn write_secrets(
|
|
codex_home: &Path,
|
|
offline_user: &str,
|
|
offline_pwd: &str,
|
|
online_user: &str,
|
|
online_pwd: &str,
|
|
) -> Result<()> {
|
|
let sandbox_dir = sandbox_dir(codex_home);
|
|
std::fs::create_dir_all(&sandbox_dir).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperUsersFileWriteFailed,
|
|
format!(
|
|
"failed to create sandbox dir {}: {err}",
|
|
sandbox_dir.display()
|
|
),
|
|
))
|
|
})?;
|
|
let secrets_dir = sandbox_secrets_dir(codex_home);
|
|
std::fs::create_dir_all(&secrets_dir).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperUsersFileWriteFailed,
|
|
format!(
|
|
"failed to create secrets dir {}: {err}",
|
|
secrets_dir.display()
|
|
),
|
|
))
|
|
})?;
|
|
let offline_blob = dpapi_protect(offline_pwd.as_bytes()).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperDpapiProtectFailed,
|
|
format!("dpapi protect failed for offline user: {err}"),
|
|
))
|
|
})?;
|
|
let online_blob = dpapi_protect(online_pwd.as_bytes()).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperDpapiProtectFailed,
|
|
format!("dpapi protect failed for online user: {err}"),
|
|
))
|
|
})?;
|
|
let users = SandboxUsersFile {
|
|
version: SETUP_VERSION,
|
|
offline: SandboxUserRecord {
|
|
username: offline_user.to_string(),
|
|
password: BASE64.encode(offline_blob),
|
|
},
|
|
online: SandboxUserRecord {
|
|
username: online_user.to_string(),
|
|
password: BASE64.encode(online_blob),
|
|
},
|
|
};
|
|
let marker = SetupMarker {
|
|
version: SETUP_VERSION,
|
|
offline_username: offline_user.to_string(),
|
|
online_username: online_user.to_string(),
|
|
created_at: chrono::Utc::now().to_rfc3339(),
|
|
read_roots: Vec::new(),
|
|
write_roots: Vec::new(),
|
|
};
|
|
let users_path = secrets_dir.join("sandbox_users.json");
|
|
let marker_path = sandbox_dir.join("setup_marker.json");
|
|
let users_json = serde_json::to_vec_pretty(&users).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperUsersFileWriteFailed,
|
|
format!("serialize sandbox users failed: {err}"),
|
|
))
|
|
})?;
|
|
std::fs::write(&users_path, users_json).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperUsersFileWriteFailed,
|
|
format!(
|
|
"write sandbox users file {} failed: {err}",
|
|
users_path.display()
|
|
),
|
|
))
|
|
})?;
|
|
let marker_json = serde_json::to_vec_pretty(&marker).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperSetupMarkerWriteFailed,
|
|
format!("serialize setup marker failed: {err}"),
|
|
))
|
|
})?;
|
|
std::fs::write(&marker_path, marker_json).map_err(|err| {
|
|
anyhow::Error::new(SetupFailure::new(
|
|
SetupErrorCode::HelperSetupMarkerWriteFailed,
|
|
format!(
|
|
"write setup marker file {} failed: {err}",
|
|
marker_path.display()
|
|
),
|
|
))
|
|
})?;
|
|
Ok(())
|
|
}
|