diff --git a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs b/codex-rs/windows-sandbox-rs/src/sandbox_users.rs index 13460b9444..fe47de056c 100644 --- a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs +++ b/codex-rs/windows-sandbox-rs/src/sandbox_users.rs @@ -12,9 +12,12 @@ 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; +use windows_sys::Win32::Foundation::WAIT_OBJECT_0; +use windows_sys::Win32::Foundation::WAIT_TIMEOUT; 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::NERR_Success; @@ -33,6 +36,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; @@ -76,6 +87,8 @@ pub fn provision_sandbox_users( let online_password = random_password(); ensure_sandbox_user(offline_username, &offline_password, log)?; ensure_sandbox_user(online_username, &online_password, log)?; + initialize_user_profile(offline_username, &offline_password, log)?; + initialize_user_profile(online_username, &online_password, log)?; write_secrets( codex_home, offline_username, @@ -88,6 +101,107 @@ pub fn provision_sandbox_users( Ok(()) } +fn initialize_user_profile(username: &str, password: &str, log: &mut File) -> Result<()> { + let profile_dir = PathBuf::from(format!(r"C:\Users\{username}")); + if profile_dir.exists() { + super::log_line( + log, + &format!("sandbox user profile already initialized for {username}"), + )?; + return Ok(()); + } + + let exe = r"C:\Windows\System32\cmd.exe"; + let command = format!(r#""{exe}" /c exit 0"#); + let user_w = to_wide(OsStr::new(username)); + let domain_w = to_wide("."); + let password_w = to_wide(OsStr::new(password)); + let exe_w = to_wide(OsStr::new(exe)); + let mut command_w = to_wide(command.as_str()); + let mut startup_info: STARTUPINFOW = unsafe { std::mem::zeroed() }; + startup_info.cb = std::mem::size_of::() as u32; + let mut process_info: PROCESS_INFORMATION = unsafe { std::mem::zeroed() }; + + super::log_line( + log, + &format!("initializing sandbox user profile for {username}"), + )?; + + let launched = unsafe { + CreateProcessWithLogonW( + user_w.as_ptr(), + domain_w.as_ptr(), + password_w.as_ptr(), + LOGON_WITH_PROFILE, + exe_w.as_ptr(), + command_w.as_mut_ptr(), + CREATE_NO_WINDOW, + std::ptr::null(), + std::ptr::null(), + &startup_info, + &mut process_info, + ) + }; + if launched == 0 { + return Err(anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperUserProfileInitFailed, + format!( + "CreateProcessWithLogonW failed for {username}: {}", + unsafe { GetLastError() } + ), + ))); + } + + let wait_result = unsafe { + if process_info.hThread != 0 { + CloseHandle(process_info.hThread); + } + WaitForSingleObject(process_info.hProcess, INFINITE) + }; + + let mut exit_code: u32 = 1; + let got_exit_code = unsafe { GetExitCodeProcess(process_info.hProcess, &mut exit_code) }; + unsafe { + if process_info.hProcess != 0 { + CloseHandle(process_info.hProcess); + } + } + if wait_result == WAIT_TIMEOUT { + return Err(anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperUserProfileInitFailed, + format!("profile bootstrap timed out for {username}"), + ))); + } + if wait_result != WAIT_OBJECT_0 { + return Err(anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperUserProfileInitFailed, + format!("WaitForSingleObject failed for {username}: {wait_result}"), + ))); + } + if got_exit_code == 0 { + return Err(anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperUserProfileInitFailed, + format!("GetExitCodeProcess failed for {username}: {}", unsafe { + GetLastError() + }), + ))); + } + if exit_code != 0 { + return Err(anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperUserProfileInitFailed, + format!("profile bootstrap command exited with {exit_code} for {username}"), + ))); + } + if !profile_dir.exists() { + return Err(anyhow::Error::new(SetupFailure::new( + SetupErrorCode::HelperUserProfileInitFailed, + format!("profile directory was not created for {username}"), + ))); + } + + 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)?; diff --git a/codex-rs/windows-sandbox-rs/src/setup_error.rs b/codex-rs/windows-sandbox-rs/src/setup_error.rs index 64e618ace1..8414f8e5fb 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_error.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_error.rs @@ -42,6 +42,8 @@ pub enum SetupErrorCode { HelperUsersGroupCreateFailed, /// Helper failed to create or update a sandbox user account. HelperUserCreateOrUpdateFailed, + /// Helper failed to initialize a sandbox user profile. + HelperUserProfileInitFailed, /// Helper failed to protect user passwords with DPAPI. HelperDpapiProtectFailed, /// Helper failed to write the sandbox users secrets file. @@ -84,6 +86,7 @@ impl SetupErrorCode { Self::HelperUserProvisionFailed => "helper_user_provision_failed", Self::HelperUsersGroupCreateFailed => "helper_users_group_create_failed", Self::HelperUserCreateOrUpdateFailed => "helper_user_create_or_update_failed", + Self::HelperUserProfileInitFailed => "helper_user_profile_init_failed", Self::HelperDpapiProtectFailed => "helper_dpapi_protect_failed", Self::HelperUsersFileWriteFailed => "helper_users_file_write_failed", Self::HelperSetupMarkerWriteFailed => "helper_setup_marker_write_failed", diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs index 01d9e34f4a..7994cccd99 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs @@ -34,7 +34,7 @@ use windows_sys::Win32::Security::CheckTokenMembership; use windows_sys::Win32::Security::FreeSid; use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY; -pub const SETUP_VERSION: u32 = 5; +pub const SETUP_VERSION: u32 = 6; pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline"; pub const ONLINE_USERNAME: &str = "CodexSandboxOnline"; const ERROR_CANCELLED: u32 = 1223;