Clean up Windows sandbox resources on app uninstall (#42375)

## What changed

- Record the authenticated sandbox owner and observe package uninstall events across service restarts and session changes.
- During uninstall, serialize setup and cleanup, disable sandbox accounts, stop their processes, and remove sandbox directories, firewall and WFP rules, hidden-user entries, accounts, and the sandbox group.
- Restrict desktop-owned directory cleanup to pinned paths and perform it while impersonating the authenticated owner.
- Detect missing or disabled sandbox accounts after interrupted cleanup and reprovision them before restoring network access.

GitOrigin-RevId: 7d63fff7ddcff3eb01d018653146df12044bd277
This commit is contained in:
chess authored and copyberry committed 2026-09-02 22:19:51 +00:00
1 parent a14ef02e1c
commit 665e5f45ab
25 files changed
+1349 -97

No files matched your search

@@ -22,9 +22,18 @@ codex-cloud-config = { workspace = true }
codex-config = { workspace = true }
codex-core = { workspace = true }
codex-windows-sandbox = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] }
toml = { workspace = true }
[target.'cfg(windows)'.dependencies.windows]
version = "0.58"
features = [
"ApplicationModel",
"Win32_System_WinRT",
]
[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.52"
features = [
@@ -40,6 +49,8 @@ features = [
"Win32_System_IO",
"Win32_System_Memory",
"Win32_System_Pipes",
"Win32_System_Registry",
"Win32_System_RemoteDesktop",
"Win32_System_Services",
"Win32_System_SystemServices",
"Win32_System_Threading",
@@ -0,0 +1,139 @@
//! Persists the authenticated sandbox owner across service restarts and package updates.
use std::ffi::OsString;
use std::io;
use std::mem::size_of;
use std::os::windows::ffi::OsStringExt;
use std::path::Path;
use std::path::PathBuf;
use std::ptr;
use anyhow::Context;
use anyhow::Result;
use anyhow::ensure;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::validate_local_directory_path;
use serde::Deserialize;
use serde::Serialize;
use windows_sys::Win32::Foundation as foundation;
use windows_sys::Win32::System::Registry as registry;
use windows_sys::Win32::UI::Shell::GetUserProfileDirectoryW;
// Package updates can replace the service key, so keep this record outside it.
const INSTALLATION_KEY: &str = r"SOFTWARE\OpenAI\Codex\WindowsSandboxService";
const INSTALLATION_VALUE: &str = "ProvisionedInstallation";
const MAX_VALUE_UNITS: usize = 4096;
const DESKTOP_INSTALLATION_MARKER: &str = ".desktop-created";
#[derive(Clone, Deserialize, Serialize)]
pub(crate) struct DesktopInstallation {
pub(crate) created_codex_home: bool,
pub(crate) cache_home: PathBuf,
}
#[derive(Clone, Deserialize, Serialize)]
pub(crate) struct InstallationRecord {
pub(crate) user_sid: String,
pub(crate) codex_home: PathBuf,
pub(crate) session_id: u32,
#[serde(default)]
pub(crate) desktop_installation: Option<DesktopInstallation>,
}
// Read this app-owned marker while impersonating the authenticated user. The
// desktop writes it only when creating the home. Cache cleanup does not need ownership.
pub(crate) fn read_desktop_installation(
home: &Path,
user_token: foundation::HANDLE,
) -> Result<DesktopInstallation> {
let mut length = 0;
unsafe { GetUserProfileDirectoryW(user_token, ptr::null_mut(), &mut length) };
let mut profile = vec![0_u16; length as usize];
if unsafe { GetUserProfileDirectoryW(user_token, profile.as_mut_ptr(), &mut length) } == 0 {
return Err(io::Error::last_os_error()).context("read sandbox owner's profile directory");
}
let cache_home =
PathBuf::from(OsString::from_wide(&profile[..length as usize - 1])).join(".cache");
validate_local_directory_path(&cache_home)?;
Ok(DesktopInstallation {
created_codex_home: home.join(DESKTOP_INSTALLATION_MARKER).is_file(),
cache_home,
})
}
pub(crate) fn load() -> Result<Option<InstallationRecord>> {
let mut value = [0_u16; MAX_VALUE_UNITS];
let mut value_length = std::mem::size_of_val(&value) as u32;
let status = unsafe {
registry::RegGetValueW(
registry::HKEY_LOCAL_MACHINE,
to_wide(INSTALLATION_KEY).as_ptr(),
to_wide(INSTALLATION_VALUE).as_ptr(),
registry::RRF_RT_REG_SZ,
ptr::null_mut(),
value.as_mut_ptr().cast(),
&mut value_length,
)
};
match status {
foundation::ERROR_FILE_NOT_FOUND | foundation::ERROR_PATH_NOT_FOUND => return Ok(None),
foundation::ERROR_SUCCESS => {}
status => {
return Err(io::Error::from_raw_os_error(status as i32))
.context("read protected sandbox installation record");
}
}
ensure!(
value_length as usize <= std::mem::size_of_val(&value)
&& value_length.is_multiple_of(size_of::<u16>() as u32),
"sandbox installation record has an invalid length"
);
let value = value[..value_length as usize / size_of::<u16>()]
.strip_suffix(&[0])
.context("sandbox installation record is not null-terminated")?;
let value = String::from_utf16(value).context("decode sandbox installation record")?;
let record = serde_json::from_str(&value).context("parse sandbox installation record")?;
Ok(Some(record))
}
pub(crate) fn save(record: &InstallationRecord) -> Result<()> {
let value = to_wide(
serde_json::to_string(record).context("serialize protected sandbox installation record")?,
);
ensure!(
value.len() <= MAX_VALUE_UNITS,
"sandbox installation record exceeds its size limit"
);
let status = unsafe {
registry::RegSetKeyValueW(
registry::HKEY_LOCAL_MACHINE,
to_wide(INSTALLATION_KEY).as_ptr(),
to_wide(INSTALLATION_VALUE).as_ptr(),
registry::REG_SZ,
value.as_ptr().cast(),
(value.len() * size_of::<u16>()) as u32,
)
};
if status == foundation::ERROR_SUCCESS {
Ok(())
} else {
Err(io::Error::from_raw_os_error(status as i32))
.context("persist protected sandbox installation record")
}
}
pub(crate) fn remove() -> Result<()> {
let status = unsafe {
registry::RegDeleteKeyW(
registry::HKEY_LOCAL_MACHINE,
to_wide(INSTALLATION_KEY).as_ptr(),
)
};
match status {
foundation::ERROR_SUCCESS
| foundation::ERROR_FILE_NOT_FOUND
| foundation::ERROR_PATH_NOT_FOUND => Ok(()),
status => Err(io::Error::from_raw_os_error(status as i32))
.context("remove protected sandbox installation record"),
}
}
+40 -8
View File
@@ -21,9 +21,8 @@ use codex_windows_sandbox::sandbox_setup_is_complete_with_settings;
use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::write_provisioning_frame;
use home::OwnedHandle;
#[cfg(test)]
use home::pin_existing_ancestors;
pub(crate) use home::OwnedHandle;
pub(crate) use home::pin_existing_ancestors;
#[cfg(test)]
use request::ProvisioningRequest;
use request::validate_request;
@@ -43,6 +42,8 @@ use windows_sys::Win32::Security::Authorization as authorization;
use windows_sys::Win32::Storage::FileSystem as filesystem;
use windows_sys::Win32::System::Pipes as pipes;
use crate::installation_record::InstallationRecord;
pub(crate) const PIPE_NAME: &str = codex_windows_sandbox::SANDBOX_PROVISIONING_PIPE_NAME;
const MAX_REQUEST_BYTES: usize = 4096;
@@ -64,7 +65,12 @@ enum PipeConnection {
Disconnected,
}
pub(crate) fn run(shutdown: Arc<AtomicBool>, on_ready: impl FnOnce() -> Result<()>) -> Result<()> {
pub(crate) fn run(
shutdown: Arc<AtomicBool>,
on_ready: impl FnOnce() -> Result<()>,
on_authenticated_user: impl Fn(&InstallationRecord, OwnedHandle) -> Result<()>,
on_session_change: impl Fn() -> Result<()>,
) -> Result<()> {
let sandbox_sid = ensure_sandbox_users_group()?;
let sid_string = string_from_sid_bytes(&sandbox_sid).map_err(anyhow::Error::msg)?;
let sddl = pipe_security_descriptor(&sid_string);
@@ -109,12 +115,15 @@ pub(crate) fn run(shutdown: Arc<AtomicBool>, on_ready: impl FnOnce() -> Result<(
on_ready().context("publish provisioning listener readiness")?;
while !shutdown.load(Ordering::Acquire) {
if accept_pipe_connection(pipe.0)? == PipeConnection::Disconnected {
continue;
}
let connection = accept_pipe_connection(pipe.0)?;
if shutdown.load(Ordering::Acquire) {
break;
}
// Session-change wakeups close the pipe immediately and can arrive disconnected.
on_session_change().context("restore the signed-in user's uninstall listener")?;
if connection == PipeConnection::Disconnected {
continue;
}
let authorized_process = match crate::package_identity::authorize_client_process(pipe.0) {
Ok(process) => process,
@@ -123,7 +132,13 @@ pub(crate) fn run(shutdown: Arc<AtomicBool>, on_ready: impl FnOnce() -> Result<(
continue;
}
};
let result = handle_request(pipe.0, &authorized_process, &sandbox_sid, &shutdown);
let result = handle_request(
pipe.0,
&authorized_process,
&sandbox_sid,
&shutdown,
&on_authenticated_user,
);
let response = match result {
Ok(response) => response,
Err(error) if error.is::<home::UnsupportedHomeDrive>() => {
@@ -241,6 +256,7 @@ fn handle_request(
authorized_process: &crate::package_identity::AuthorizedClientProcess,
sandbox_sid: &[u8],
shutdown: &AtomicBool,
on_authenticated_user: &dyn Fn(&InstallationRecord, OwnedHandle) -> Result<()>,
) -> Result<SandboxProvisioningResponse> {
let deadline = Instant::now() + REQUEST_IDLE_TIMEOUT;
let mut request = [0_u8; MAX_REQUEST_BYTES];
@@ -322,7 +338,22 @@ fn handle_request(
return Err(error)
.context("requested sandbox settings violate administrator-controlled machine policy");
}
// A policy-rejected request must not choose the uninstall owner. Use the
// token already authenticated above instead of impersonating the pipe again.
let previous = crate::installation_record::load()?.filter(|record| {
record.user_sid == identity.user_sid && record.codex_home == identity.codex_home
});
let installation = InstallationRecord {
codex_home: identity.codex_home.clone(),
user_sid: identity.user_sid,
session_id: identity.session_id,
desktop_installation: previous
.and_then(|record| record.desktop_installation)
.or(identity.desktop_installation),
};
on_authenticated_user(&installation, identity.token)?;
if sandbox_setup_is_complete_with_settings(&identity.codex_home, &request.settings) {
crate::service::record_provisioned_user(&installation)?;
return Ok(SandboxProvisioningResponse::Ok);
}
let helper = std::env::current_exe()
@@ -352,6 +383,7 @@ fn handle_request(
&retained_handles,
) {
Ok(()) => {
crate::service::record_provisioned_user(&installation)?;
crate::service::log_information(
crate::service::EVENT_PROVISIONING_SUCCEEDED,
"Codex sandbox provisioning completed successfully.",
@@ -3,6 +3,7 @@
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use codex_windows_sandbox::string_from_sid_bytes;
use std::ffi::c_void;
use std::mem::size_of;
use std::path::Path;
@@ -20,6 +21,10 @@ use super::request::ProvisioningRequest;
pub(super) struct ClientIdentity {
pub(super) account: String,
pub(super) codex_home: PathBuf,
pub(super) user_sid: String,
pub(super) session_id: u32,
pub(super) token: OwnedHandle,
pub(super) desktop_installation: Option<crate::installation_record::DesktopInstallation>,
// Retained by both the service and helper throughout provisioning.
pub(super) directory_handles: Vec<OwnedHandle>,
}
@@ -38,7 +43,7 @@ pub(super) fn authenticate_client(
.context("impersonate provisioning client");
}
let (identity, token) = authenticate_impersonated_client(
let identity = authenticate_impersonated_client(
authorized_process,
sandbox_sid,
&request.codex_home,
@@ -47,7 +52,7 @@ pub(super) fn authenticate_client(
&identity.codex_home,
&request.settings,
&request.listeners,
token.0,
identity.token.0,
);
Ok((identity, policy_result))
})
@@ -60,7 +65,7 @@ fn authenticate_impersonated_client(
authorized_process: &crate::package_identity::AuthorizedClientProcess,
sandbox_sid: &[u8],
requested_home: &Path,
) -> Result<(ClientIdentity, OwnedHandle)> {
) -> Result<ClientIdentity> {
let mut raw_token = 0;
if unsafe {
threading::OpenThreadToken(
@@ -138,16 +143,34 @@ fn authenticate_impersonated_client(
let sid = unsafe { ptr::read_unaligned(user.as_ptr().cast::<security::TOKEN_USER>()) }
.User
.Sid;
let sid_length = unsafe { security::GetLengthSid(sid) };
if sid_length == 0 {
return Err(std::io::Error::last_os_error()).context("size provisioning client SID");
}
let user_sid = string_from_sid_bytes(unsafe {
std::slice::from_raw_parts(sid.cast::<u8>(), sid_length as usize)
})
.map_err(anyhow::Error::msg)?;
let account = account_name(sid)?;
let (codex_home, handles) = prepare_codex_home(requested_home)?;
Ok((
ClientIdentity {
account,
codex_home,
directory_handles: handles,
},
let desktop_installation =
crate::installation_record::read_desktop_installation(&codex_home, token.0)
.inspect_err(|_| {
crate::service::log_error(
crate::service::EVENT_SERVICE_FAILED,
"unable to read desktop directory ownership; preserving desktop directories",
);
})
.ok();
Ok(ClientIdentity {
account,
codex_home,
user_sid,
session_id: session,
token,
))
desktop_installation,
directory_handles: handles,
})
}
fn account_name(sid: *mut c_void) -> Result<String> {
@@ -33,7 +33,7 @@ impl std::fmt::Display for UnsupportedHomeDrive {
impl std::error::Error for UnsupportedHomeDrive {}
pub(super) struct OwnedHandle(pub(super) HANDLE);
pub(crate) struct OwnedHandle(pub(crate) HANDLE);
impl Drop for OwnedHandle {
fn drop(&mut self) {
@@ -138,7 +138,7 @@ pub(super) fn prepare_codex_home(requested: &Path) -> Result<(PathBuf, Vec<Owned
Ok((home, handles))
}
pub(super) fn pin_existing_ancestors(path: &Path, handles: &mut Vec<OwnedHandle>) -> Result<()> {
pub(crate) fn pin_existing_ancestors(path: &Path, handles: &mut Vec<OwnedHandle>) -> Result<()> {
let mut current = PathBuf::new();
for component in path.components() {
current.push(component.as_os_str());
@@ -1,5 +1,7 @@
use anyhow::Result;
#[cfg(windows)]
mod installation_record;
#[cfg(windows)]
mod ipc;
#[cfg(windows)]
@@ -7,6 +9,8 @@ mod machine_policy;
#[cfg(windows)]
mod package_identity;
#[cfg(windows)]
mod package_lifecycle;
#[cfg(windows)]
mod service;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -165,7 +165,7 @@ fn package_family_name(
))
}
fn token_user(token: HANDLE) -> Result<Vec<u8>> {
pub(crate) fn token_user(token: HANDLE) -> Result<Vec<u8>> {
let mut length = 0;
unsafe {
security::GetTokenInformation(token, security::TokenUser, ptr::null_mut(), 0, &mut length)
@@ -0,0 +1,279 @@
//! Restores owner-scoped uninstall notifications and ties file cleanup to pinned directories.
use std::cell::RefCell;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use anyhow::Context;
use anyhow::Result;
use anyhow::ensure;
use codex_windows_sandbox::clean_up_packaged_windows_sandbox;
use codex_windows_sandbox::string_from_sid_bytes;
use windows::ApplicationModel::Package;
use windows::ApplicationModel::PackageCatalog;
use windows::ApplicationModel::PackageUninstallingEventArgs;
use windows::Foundation::EventRegistrationToken;
use windows::Foundation::TypedEventHandler;
use windows::Win32::System::WinRT::RO_INIT_MULTITHREADED;
use windows::Win32::System::WinRT::RoInitialize;
use windows::Win32::System::WinRT::RoUninitialize;
use windows::core::HSTRING;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Security as security;
use windows_sys::Win32::System::RemoteDesktop::WTS_CURRENT_SERVER_HANDLE;
use windows_sys::Win32::System::RemoteDesktop::WTSEnumerateSessionsW;
use windows_sys::Win32::System::RemoteDesktop::WTSFreeMemory;
use windows_sys::Win32::System::RemoteDesktop::WTSQueryUserToken;
use crate::installation_record::DesktopInstallation;
use crate::installation_record::InstallationRecord;
use crate::ipc::OwnedHandle;
struct UserInstallation {
codex_home: Option<PathBuf>,
directory_handles: Vec<OwnedHandle>,
desktop_installation: Option<DesktopInstallation>,
user_token: OwnedHandle,
catalog: PackageCatalog,
token: EventRegistrationToken,
}
pub(crate) struct PackageLifecycle {
package_name: HSTRING,
uninstalling: Arc<AtomicBool>,
installation: RefCell<Option<UserInstallation>>,
}
impl PackageLifecycle {
pub(crate) fn new(uninstalling: Arc<AtomicBool>) -> Result<Self> {
unsafe { RoInitialize(RO_INIT_MULTITHREADED) }
.context("initialize the sandbox service Windows Runtime apartment")?;
Ok(Self {
package_name: Package::Current()?.Id()?.FullName()?,
uninstalling,
installation: RefCell::default(),
})
}
pub(crate) fn watch_authenticated_user(
&self,
installation: &InstallationRecord,
user_token: OwnedHandle,
) -> Result<()> {
if self.installation.borrow().is_some() {
return Ok(());
}
with_owner_impersonation(user_token.0, || {
let mut directory_handles = Vec::new();
let codex_home = match crate::ipc::pin_existing_ancestors(
&installation.codex_home,
&mut directory_handles,
) {
Ok(()) => Some(installation.codex_home.clone()),
Err(error) => {
directory_handles.clear();
crate::service::log_error(
crate::service::EVENT_SERVICE_FAILED,
&format!(
"skipping sandbox file cleanup because the home could not be pinned: {error:#}"
),
);
None
}
};
let catalog = PackageCatalog::OpenForCurrentUser()
.context("open the authenticated user's package catalog")?;
let package_name = self.package_name.clone();
let uninstalling = Arc::clone(&self.uninstalling);
let token = catalog
.PackageUninstalling(&TypedEventHandler::<
PackageCatalog,
PackageUninstallingEventArgs,
>::new(move |_, event| {
if let Some(event) = event
&& event.Package()?.Id()?.FullName()? == package_name
{
uninstalling.store(!event.IsComplete()?, Ordering::Release);
}
Ok(())
}))
.context("subscribe to authenticated package uninstall notifications")?;
self.installation.replace(Some(UserInstallation {
codex_home,
directory_handles,
desktop_installation: installation.desktop_installation.clone(),
user_token,
catalog,
token,
}));
Ok(())
})
}
pub(crate) fn restore_logged_in_owner(&self, recorded_session_id: u32) -> Result<()> {
let Err(error) = self.restore_authenticated_user(recorded_session_id) else {
return Ok(());
};
// Session IDs can change while the service is stopped; the owner SID is durable.
let mut sessions = std::ptr::null_mut();
let mut count = 0;
if unsafe {
WTSEnumerateSessionsW(
WTS_CURRENT_SERVER_HANDLE,
/*reserved*/ 0,
/*version*/ 1,
&mut sessions,
&mut count,
)
} == 0
{
return Err(io::Error::last_os_error()).context("list current Windows sessions");
}
let restored = (0..count).any(|index| {
let session_id = unsafe { (*sessions.add(index as usize)).SessionId };
session_id != recorded_session_id && self.restore_authenticated_user(session_id).is_ok()
});
unsafe { WTSFreeMemory(sessions.cast()) };
if restored { Ok(()) } else { Err(error) }
}
pub(crate) fn restore_authenticated_user(&self, session_id: u32) -> Result<()> {
if self.installation.borrow().is_some() {
return Ok(());
}
let Some(record) = crate::installation_record::load()? else {
return Ok(());
};
let mut raw_token = 0;
if unsafe { WTSQueryUserToken(session_id, &mut raw_token) } == 0 {
return Err(io::Error::last_os_error()).context("open the logged-in user's token");
}
let token = crate::ipc::OwnedHandle(raw_token);
let user = crate::package_identity::token_user(token.0)?;
let sid = unsafe { std::ptr::read_unaligned(user.as_ptr().cast::<security::TOKEN_USER>()) }
.User
.Sid;
let sid_length = unsafe { security::GetLengthSid(sid) };
if sid_length == 0 {
return Err(io::Error::last_os_error()).context("read the logged-in user's SID");
}
let user_sid = string_from_sid_bytes(unsafe {
std::slice::from_raw_parts(sid.cast::<u8>(), sid_length as usize)
})
.map_err(anyhow::Error::msg)?;
ensure!(
user_sid == record.user_sid,
"logged-in user does not match the recorded sandbox owner"
);
self.watch_authenticated_user(&record, token)?;
if session_id != record.session_id {
crate::installation_record::save(&crate::installation_record::InstallationRecord {
session_id,
..record
})?;
}
Ok(())
}
pub(crate) fn clean_up(&self) -> Result<()> {
let mut installation = self.installation.borrow_mut();
let installation = installation
.as_mut()
.context("the authenticated package installation was not recorded")?;
crate::service::log_information(
crate::service::EVENT_CLEANUP_STARTED,
"sandbox uninstall cleanup started",
);
// A partial uninstall must not let a later install inherit stale directory ownership.
crate::installation_record::remove()?;
let codex_home = installation.codex_home.clone();
clean_up_packaged_windows_sandbox(codex_home.as_deref(), || {
let Some(desktop) = &installation.desktop_installation else {
return Ok(());
};
// The marker is user-writable. It must never authorize deletion as LocalSystem.
with_owner_impersonation(installation.user_token.0, || {
let mut errors = Vec::new();
let mut record_error = |result: io::Result<()>| {
if let Err(error) = result
&& error.kind() != io::ErrorKind::NotFound
{
errors.push(error.to_string());
}
};
if let Some(home) = &codex_home
&& desktop.created_codex_home
{
// Release the home itself so it can be deleted; keep its ancestors pinned.
installation.directory_handles.pop();
record_error(std::fs::remove_dir_all(home));
}
// The cache may have been created after provisioning. Pin it only for cleanup.
let mut cache_directory_handles = Vec::new();
if desktop.cache_home.is_dir() {
match crate::ipc::pin_existing_ancestors(
&desktop.cache_home,
&mut cache_directory_handles,
) {
Ok(()) => record_error(std::fs::remove_dir_all(
desktop.cache_home.join("codex-runtimes"),
)),
Err(error) => errors.push(error.to_string()),
}
}
ensure!(
errors.is_empty(),
"remove desktop directories: {}",
errors.join("; ")
);
Ok(())
})
})
.context("remove packaged Windows sandbox resources")?;
crate::service::log_information(
crate::service::EVENT_CLEANUP_FINISHED,
"sandbox uninstall cleanup finished",
);
Ok(())
}
}
fn with_owner_impersonation(
user_token: HANDLE,
operation: impl FnOnce() -> Result<()>,
) -> Result<()> {
if unsafe { security::ImpersonateLoggedOnUser(user_token) } == 0 {
return Err(io::Error::last_os_error()).context("impersonate the sandbox owner");
}
let result = operation();
if unsafe { security::RevertToSelf() } == 0 {
crate::service::log_error(
crate::service::EVENT_SERVICE_FAILED,
&format!(
"unable to revert sandbox-owner impersonation: {}",
io::Error::last_os_error()
),
);
// Continuing as the user would make later machine cleanup unsafe.
std::process::abort();
}
result
}
impl Drop for PackageLifecycle {
fn drop(&mut self) {
if let Some(installation) = self.installation.get_mut().take() {
let _ = installation
.catalog
.RemovePackageUninstalling(installation.token);
}
unsafe { RoUninitialize() };
}
}
+103 -19
View File
@@ -2,6 +2,7 @@
use std::ffi::c_void;
use std::io;
use std::mem::size_of;
use std::ptr;
use std::sync::Arc;
use std::sync::OnceLock;
@@ -19,10 +20,13 @@ use windows_sys::Win32::System::EventLog::EVENTLOG_ERROR_TYPE;
use windows_sys::Win32::System::EventLog::EVENTLOG_INFORMATION_TYPE;
use windows_sys::Win32::System::EventLog::RegisterEventSourceW;
use windows_sys::Win32::System::EventLog::ReportEventW;
use windows_sys::Win32::System::RemoteDesktop::WTSSESSION_NOTIFICATION;
use windows_sys::Win32::System::Services::RegisterServiceCtrlHandlerExW;
use windows_sys::Win32::System::Services::SERVICE_ACCEPT_SESSIONCHANGE;
use windows_sys::Win32::System::Services::SERVICE_ACCEPT_SHUTDOWN;
use windows_sys::Win32::System::Services::SERVICE_ACCEPT_STOP;
use windows_sys::Win32::System::Services::SERVICE_CONTROL_INTERROGATE;
use windows_sys::Win32::System::Services::SERVICE_CONTROL_SESSIONCHANGE;
use windows_sys::Win32::System::Services::SERVICE_CONTROL_SHUTDOWN;
use windows_sys::Win32::System::Services::SERVICE_CONTROL_STOP;
use windows_sys::Win32::System::Services::SERVICE_RUNNING;
@@ -40,25 +44,33 @@ pub(crate) const SERVICE_NAME: &str = "CodexSandboxService";
const EVENT_SERVICE_STARTED: u32 = 1000;
const EVENT_SERVICE_STOP_REQUESTED: u32 = 1001;
const EVENT_SERVICE_STOPPED: u32 = 1002;
const EVENT_SERVICE_FAILED: u32 = 1003;
pub(crate) const EVENT_SERVICE_FAILED: u32 = 1003;
pub(crate) const EVENT_PROVISIONING_SUCCEEDED: u32 = 2000;
pub(crate) const EVENT_PROVISIONING_FAILED: u32 = 2001;
pub(crate) const EVENT_REQUEST_REJECTED: u32 = 2002;
pub(crate) const EVENT_CLEANUP_STARTED: u32 = 3002;
pub(crate) const EVENT_CLEANUP_FINISHED: u32 = 3003;
const MAX_EVENT_MESSAGE_UNITS: usize = 1024;
static SERVICE_STATE: OnceLock<ServiceState> = OnceLock::new();
struct ServiceState {
shutdown: Arc<AtomicBool>,
uninstalling: Arc<AtomicBool>,
status_handle: OnceLock<SERVICE_STATUS_HANDLE>,
current_status: AtomicU32,
changed_session: AtomicU32,
stop_requested: AtomicBool,
}
pub(crate) fn run() -> Result<()> {
let state = ServiceState {
shutdown: Arc::new(AtomicBool::new(false)),
uninstalling: Arc::new(AtomicBool::new(false)),
status_handle: OnceLock::new(),
current_status: AtomicU32::new(SERVICE_START_PENDING),
changed_session: AtomicU32::new(u32::MAX),
stop_requested: AtomicBool::new(false),
};
SERVICE_STATE
.set(state)
@@ -91,10 +103,15 @@ pub(crate) fn run() -> Result<()> {
#[cfg(debug_assertions)]
pub(crate) fn run_foreground() -> Result<()> {
crate::package_identity::enable_foreground_mode();
crate::ipc::run(Arc::new(AtomicBool::new(false)), || {
eprintln!("{SERVICE_NAME} listening on {}", crate::ipc::PIPE_NAME);
Ok(())
})
crate::ipc::run(
Arc::new(AtomicBool::new(false)),
|| {
eprintln!("{SERVICE_NAME} listening on {}", crate::ipc::PIPE_NAME);
Ok(())
},
|_, _| Ok(()),
|| Ok(()),
)
}
unsafe extern "system" fn service_main(_argument_count: u32, _arguments: *mut *mut u16) {
@@ -137,16 +154,55 @@ fn service_main_inner(state: &ServiceState) -> Result<()> {
.map_err(|_| anyhow::anyhow!("the service status handle was already registered"))?;
state.report_status(SERVICE_START_PENDING, NO_ERROR)?;
crate::ipc::run(Arc::clone(&state.shutdown), || {
state.report_status(SERVICE_RUNNING, NO_ERROR)?;
log_information(
EVENT_SERVICE_STARTED,
"The Codex sandbox service is running.",
);
Ok(())
})
let package_lifecycle =
crate::package_lifecycle::PackageLifecycle::new(Arc::clone(&state.uninstalling))?;
crate::ipc::run(
Arc::clone(&state.shutdown),
|| {
state.report_status(SERVICE_RUNNING, NO_ERROR)?;
if let Some(record) = crate::installation_record::load()?
&& let Err(error) = package_lifecycle.restore_logged_in_owner(record.session_id)
{
log_error(
EVENT_SERVICE_FAILED,
&format!("unable to restore package uninstall listener: {error:#}"),
);
}
log_information(
EVENT_SERVICE_STARTED,
"The Codex sandbox service is running.",
);
Ok(())
},
|installation, user_token| {
if let Err(error) = package_lifecycle.watch_authenticated_user(installation, user_token)
{
log_error(
EVENT_SERVICE_FAILED,
&format!("unable to observe package uninstall: {error:#}"),
);
}
Ok(())
},
|| {
let session = state.changed_session.swap(u32::MAX, Ordering::AcqRel);
if session == u32::MAX {
return Ok(());
}
if let Err(error) = package_lifecycle.restore_authenticated_user(session) {
log_error(
EVENT_SERVICE_FAILED,
&format!("unable to restore package uninstall listener: {error:#}"),
);
}
Ok(())
},
)
.context("run the sandbox provisioning broker")?;
if state.stop_requested.load(Ordering::Acquire) && state.uninstalling.load(Ordering::Acquire) {
package_lifecycle.clean_up()?;
}
log_information(
EVENT_SERVICE_STOPPED,
"The Codex sandbox service has stopped.",
@@ -157,7 +213,7 @@ fn service_main_inner(state: &ServiceState) -> Result<()> {
unsafe extern "system" fn service_control_handler(
control: u32,
_event_type: u32,
_event_data: *mut c_void,
event_data: *mut c_void,
_context: *mut c_void,
) -> u32 {
let Some(state) = SERVICE_STATE.get() else {
@@ -166,19 +222,22 @@ unsafe extern "system" fn service_control_handler(
match control {
SERVICE_CONTROL_STOP | SERVICE_CONTROL_SHUTDOWN => {
if control == SERVICE_CONTROL_STOP {
state.stop_requested.store(true, Ordering::Release);
}
if !state.shutdown.swap(true, Ordering::AcqRel) {
if let Err(error) = state.report_status(SERVICE_STOP_PENDING, NO_ERROR) {
eprintln!("unable to report service shutdown: {error:#}");
}
log_information(
EVENT_SERVICE_STOP_REQUESTED,
"The Codex sandbox service was asked to stop.",
);
std::thread::spawn(move || {
crate::ipc::wake(crate::ipc::PIPE_NAME, || {
state.current_status.load(Ordering::Acquire) == SERVICE_STOPPED
});
});
log_information(
EVENT_SERVICE_STOP_REQUESTED,
"The Codex sandbox service was asked to stop.",
);
}
NO_ERROR
}
@@ -193,6 +252,22 @@ unsafe extern "system" fn service_control_handler(
}
NO_ERROR
}
SERVICE_CONTROL_SESSIONCHANGE => {
if !event_data.is_null() {
let event = unsafe { &*event_data.cast::<WTSSESSION_NOTIFICATION>() };
if event.cbSize as usize >= size_of::<WTSSESSION_NOTIFICATION>() {
state
.changed_session
.store(event.dwSessionId, Ordering::Release);
std::thread::spawn(move || {
crate::ipc::wake(crate::ipc::PIPE_NAME, || {
state.current_status.load(Ordering::Acquire) == SERVICE_STOPPED
});
});
}
}
NO_ERROR
}
_ => ERROR_CALL_NOT_IMPLEMENTED,
}
}
@@ -201,6 +276,15 @@ pub(crate) fn log_information(event_id: u32, message: &str) {
log_event(EVENTLOG_INFORMATION_TYPE, event_id, message);
}
pub(crate) fn record_provisioned_user(
installation: &crate::installation_record::InstallationRecord,
) -> Result<()> {
if SERVICE_STATE.get().is_some() {
crate::installation_record::save(installation)?;
}
Ok(())
}
pub(crate) fn log_error(event_id: u32, message: &str) {
log_event(EVENTLOG_ERROR_TYPE, event_id, message);
}
@@ -262,7 +346,7 @@ impl ServiceState {
dwServiceType: SERVICE_WIN32_OWN_PROCESS,
dwCurrentState: current_status,
dwControlsAccepted: if current_status == SERVICE_RUNNING {
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_SESSIONCHANGE
} else {
0
},