mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
fix(windows-sandbox): bootstrap sandbox user profiles
This commit is contained in:
@@ -22,6 +22,7 @@ use codex_windows_sandbox::create_readonly_token_with_caps_from;
|
||||
use codex_windows_sandbox::create_workspace_write_token_with_caps_from;
|
||||
use codex_windows_sandbox::get_current_token_for_restriction;
|
||||
use codex_windows_sandbox::hide_current_user_profile_dir;
|
||||
use codex_windows_sandbox::inherit_path_env;
|
||||
use codex_windows_sandbox::ipc_framed::ErrorPayload;
|
||||
use codex_windows_sandbox::ipc_framed::ExitPayload;
|
||||
use codex_windows_sandbox::ipc_framed::FramedMessage;
|
||||
@@ -33,6 +34,7 @@ use codex_windows_sandbox::ipc_framed::encode_bytes;
|
||||
use codex_windows_sandbox::ipc_framed::read_frame;
|
||||
use codex_windows_sandbox::ipc_framed::write_frame;
|
||||
use codex_windows_sandbox::log_note;
|
||||
use codex_windows_sandbox::overlay_current_user_profile_env;
|
||||
use codex_windows_sandbox::parse_policy;
|
||||
use codex_windows_sandbox::read_handle_loop;
|
||||
use codex_windows_sandbox::spawn_process_with_pipes;
|
||||
@@ -251,13 +253,17 @@ fn spawn_ipc_process(
|
||||
Some(log_dir.as_path()),
|
||||
);
|
||||
|
||||
let mut child_env = req.env.clone();
|
||||
inherit_path_env(&mut child_env);
|
||||
overlay_current_user_profile_env(&mut child_env);
|
||||
|
||||
let mut hpc_handle: Option<HANDLE> = None;
|
||||
let (pi, stdout_handle, stderr_handle, stdin_handle) = if req.tty {
|
||||
let (pi, conpty) = codex_windows_sandbox::spawn_conpty_process_as_user(
|
||||
h_token,
|
||||
&req.command,
|
||||
&effective_cwd,
|
||||
&req.env,
|
||||
&child_env,
|
||||
req.use_private_desktop,
|
||||
Some(log_dir.as_path()),
|
||||
)?;
|
||||
@@ -287,7 +293,7 @@ fn spawn_ipc_process(
|
||||
h_token,
|
||||
&req.command,
|
||||
&effective_cwd,
|
||||
&req.env,
|
||||
&child_env,
|
||||
stdin_mode,
|
||||
StderrMode::Separate,
|
||||
/*use_private_desktop*/ false,
|
||||
|
||||
@@ -21,6 +21,7 @@ mod windows_impl {
|
||||
use crate::cap::load_or_create_cap_sids;
|
||||
use crate::env::ensure_non_interactive_pager;
|
||||
use crate::env::inherit_path_env;
|
||||
use crate::env::is_profile_env_key;
|
||||
use crate::env::normalize_null_device_env;
|
||||
use crate::helper_materialization::HelperExecutable;
|
||||
use crate::helper_materialization::resolve_helper_for_launch;
|
||||
@@ -58,10 +59,13 @@ mod windows_impl {
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use windows_sys::Win32::Security::Authentication::Identity::LogonUserW;
|
||||
use windows_sys::Win32::Security::Authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW;
|
||||
use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR;
|
||||
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
|
||||
use windows_sys::Win32::System::Diagnostics::Debug::SetErrorMode;
|
||||
use windows_sys::Win32::System::Environment::CreateEnvironmentBlock;
|
||||
use windows_sys::Win32::System::Environment::DestroyEnvironmentBlock;
|
||||
use windows_sys::Win32::System::Pipes::ConnectNamedPipe;
|
||||
use windows_sys::Win32::System::Pipes::CreateNamedPipeW;
|
||||
const PIPE_ACCESS_INBOUND: u32 = 0x0000_0001;
|
||||
@@ -208,6 +212,93 @@ mod windows_impl {
|
||||
|
||||
pub use crate::windows_impl::CaptureResult;
|
||||
|
||||
fn make_env_block(env_map: &HashMap<String, String>) -> Vec<u16> {
|
||||
let mut items: Vec<(String, String)> = env_map
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
items.sort_by(|a, b| {
|
||||
a.0.to_uppercase()
|
||||
.cmp(&b.0.to_uppercase())
|
||||
.then(a.0.cmp(&b.0))
|
||||
});
|
||||
let mut wide: Vec<u16> = Vec::new();
|
||||
for (key, value) in items {
|
||||
let mut pair = to_wide(format!("{key}={value}"));
|
||||
pair.pop();
|
||||
wide.extend_from_slice(&pair);
|
||||
wide.push(0);
|
||||
}
|
||||
wide.push(0);
|
||||
wide
|
||||
}
|
||||
|
||||
fn build_logon_environment_block(
|
||||
username: &[u16],
|
||||
domain: &[u16],
|
||||
password: &[u16],
|
||||
extra_env: &HashMap<String, String>,
|
||||
) -> Result<Vec<u16>> {
|
||||
let mut logon_token: HANDLE = 0;
|
||||
let logon_ok = unsafe {
|
||||
LogonUserW(
|
||||
username.as_ptr(),
|
||||
domain.as_ptr(),
|
||||
password.as_ptr(),
|
||||
2, // LOGON32_LOGON_INTERACTIVE
|
||||
0, // LOGON32_PROVIDER_DEFAULT
|
||||
&mut logon_token,
|
||||
)
|
||||
};
|
||||
if logon_ok == 0 {
|
||||
return Err(anyhow::anyhow!("LogonUserW failed: {}", unsafe {
|
||||
GetLastError()
|
||||
}));
|
||||
}
|
||||
|
||||
let mut raw_block: *mut c_void = ptr::null_mut();
|
||||
let ok = unsafe { CreateEnvironmentBlock(&mut raw_block, logon_token, 0) };
|
||||
if ok == 0 {
|
||||
unsafe {
|
||||
CloseHandle(logon_token);
|
||||
}
|
||||
return Err(anyhow::anyhow!(
|
||||
"CreateEnvironmentBlock failed: {}",
|
||||
unsafe { GetLastError() }
|
||||
));
|
||||
}
|
||||
|
||||
let mut env_map: HashMap<String, String> = HashMap::new();
|
||||
unsafe {
|
||||
let mut cursor = raw_block as *const u16;
|
||||
loop {
|
||||
let mut len = 0usize;
|
||||
while *cursor.add(len) != 0 {
|
||||
len += 1;
|
||||
}
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
let entry = String::from_utf16_lossy(std::slice::from_raw_parts(cursor, len));
|
||||
if let Some((key, value)) = entry.split_once('=') {
|
||||
env_map.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
cursor = cursor.add(len + 1);
|
||||
}
|
||||
DestroyEnvironmentBlock(raw_block);
|
||||
CloseHandle(logon_token);
|
||||
}
|
||||
|
||||
for (key, value) in extra_env {
|
||||
if is_profile_env_key(key) && env_map.contains_key(key) {
|
||||
continue;
|
||||
}
|
||||
env_map.insert(key.clone(), value.clone());
|
||||
}
|
||||
|
||||
Ok(make_env_block(&env_map))
|
||||
}
|
||||
|
||||
fn read_spawn_ready(pipe_read: &mut File) -> Result<()> {
|
||||
let msg = read_frame(pipe_read)?
|
||||
.ok_or_else(|| anyhow::anyhow!("runner pipe closed before spawn_ready"))?;
|
||||
@@ -315,7 +406,12 @@ mod windows_impl {
|
||||
let cwd_w: Vec<u16> = to_wide(cwd);
|
||||
|
||||
// Minimal CPWL launch: inherit env, no desktop override, no handle inheritance.
|
||||
let env_block: Option<Vec<u16>> = None;
|
||||
let env_block = Some(build_logon_environment_block(
|
||||
&user_w,
|
||||
&domain_w,
|
||||
&password_w,
|
||||
&env_map,
|
||||
)?);
|
||||
let mut si: STARTUPINFOW = unsafe { std::mem::zeroed() };
|
||||
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||
let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow};
|
||||
use dirs_next::home_dir;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
@@ -6,6 +6,24 @@ use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const PROFILE_ENV_KEYS: &[&str] = &[
|
||||
"APPDATA",
|
||||
"HOMEDRIVE",
|
||||
"HOMEPATH",
|
||||
"HOME",
|
||||
"LOCALAPPDATA",
|
||||
"TEMP",
|
||||
"TMP",
|
||||
"USERNAME",
|
||||
"USERPROFILE",
|
||||
];
|
||||
|
||||
pub fn is_profile_env_key(key: &str) -> bool {
|
||||
PROFILE_ENV_KEYS
|
||||
.iter()
|
||||
.any(|candidate| candidate.eq_ignore_ascii_case(key))
|
||||
}
|
||||
|
||||
pub fn normalize_null_device_env(env_map: &mut HashMap<String, String>) {
|
||||
let keys: Vec<String> = env_map.keys().cloned().collect();
|
||||
for k in keys {
|
||||
@@ -42,6 +60,14 @@ pub fn inherit_path_env(env_map: &mut HashMap<String, String>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlay_current_user_profile_env(env_map: &mut HashMap<String, String>) {
|
||||
for key in PROFILE_ENV_KEYS {
|
||||
if let Ok(value) = env::var(key) {
|
||||
env_map.insert((*key).to_string(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepend_path(env_map: &mut HashMap<String, String>, prefix: &str) {
|
||||
let existing = env_map
|
||||
.get("PATH")
|
||||
|
||||
@@ -78,6 +78,10 @@ pub use elevated_impl::ElevatedSandboxCaptureRequest;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use elevated_impl::run_windows_sandbox_capture as run_windows_sandbox_capture_elevated;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use env::inherit_path_env;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use env::overlay_current_user_profile_env;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use helper_materialization::resolve_current_exe_for_launch;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use hide_users::hide_current_user_profile_dir;
|
||||
|
||||
@@ -12,6 +12,7 @@ use std::ffi::c_void;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::Foundation::LocalFree;
|
||||
@@ -33,6 +34,14 @@ 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 windows_sys::Win32::System::Threading::CREATE_NO_WINDOW;
|
||||
use windows_sys::Win32::System::Threading::CreateProcessWithLogonW;
|
||||
use windows_sys::Win32::System::Threading::GetExitCodeProcess;
|
||||
use windows_sys::Win32::System::Threading::INFINITE;
|
||||
use windows_sys::Win32::System::Threading::LOGON_WITH_PROFILE;
|
||||
use windows_sys::Win32::System::Threading::PROCESS_INFORMATION;
|
||||
use windows_sys::Win32::System::Threading::STARTUPINFOW;
|
||||
use windows_sys::Win32::System::Threading::WaitForSingleObject;
|
||||
|
||||
use codex_windows_sandbox::SETUP_VERSION;
|
||||
use codex_windows_sandbox::SetupErrorCode;
|
||||
@@ -91,6 +100,67 @@ pub fn provision_sandbox_users(
|
||||
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)?;
|
||||
ensure_user_profile_initialized(username, password, log)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_user_profile_initialized(username: &str, password: &str, log: &mut File) -> Result<()> {
|
||||
let username_w = to_wide(OsStr::new(username));
|
||||
let domain_w = to_wide(".");
|
||||
let password_w = to_wide(OsStr::new(password));
|
||||
let application = to_wide(r"C:\Windows\System32\cmd.exe");
|
||||
let mut command_line = to_wide(r#"cmd.exe /d /c exit 0"#);
|
||||
let mut startup_info: STARTUPINFOW = unsafe { std::mem::zeroed() };
|
||||
startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||
let mut process_info: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
|
||||
|
||||
let ok = unsafe {
|
||||
CreateProcessWithLogonW(
|
||||
username_w.as_ptr(),
|
||||
domain_w.as_ptr(),
|
||||
password_w.as_ptr(),
|
||||
LOGON_WITH_PROFILE,
|
||||
application.as_ptr(),
|
||||
command_line.as_mut_ptr(),
|
||||
CREATE_NO_WINDOW,
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&startup_info,
|
||||
&mut process_info,
|
||||
)
|
||||
};
|
||||
if ok == 0 {
|
||||
let err = unsafe { GetLastError() };
|
||||
super::log_line(
|
||||
log,
|
||||
&format!("CreateProcessWithLogonW failed for {username}: {err}"),
|
||||
)?;
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperUserCreateOrUpdateFailed,
|
||||
format!("failed to initialize sandbox user profile for {username}: {err}"),
|
||||
)));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
WaitForSingleObject(process_info.hProcess, INFINITE);
|
||||
}
|
||||
let mut exit_code = 1u32;
|
||||
unsafe {
|
||||
let _ = GetExitCodeProcess(process_info.hProcess, &mut exit_code);
|
||||
CloseHandle(process_info.hThread);
|
||||
CloseHandle(process_info.hProcess);
|
||||
}
|
||||
if exit_code != 0 {
|
||||
super::log_line(
|
||||
log,
|
||||
&format!("profile bootstrap exited with code {exit_code} for {username}"),
|
||||
)?;
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperUserCreateOrUpdateFailed,
|
||||
format!("sandbox user profile bootstrap exited with code {exit_code} for {username}"),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user