Refactor Windows sandbox setup and service helpers (#45455)

## What changed

- Extract helper copying, token-user SID queries, provisioning pipe ownership, and service runtime lifecycle into dedicated modules.
- Simplify command-runner resolution and extract setup configuration loading, payload execution, provisioning request exchange, and response handling into helpers.
- Parameterize installation-record registry access and return the saved installation record from authenticated user registration.

## Testing

Add tests for explicit setup `cwd` selection and effective workspace roots, plus valid and invalid token-user SID queries. Move existing helper-copy and freshness tests alongside the extracted copy implementation.

GitOrigin-RevId: ffb39adae7611baa95e85c89f9a31ef7a779e217
This commit is contained in:
zm-oai
2026-09-14 15:55:54 +00:00
committed by copyberry
parent 7a48b95c6c
commit 1a02867bd1
19 changed files with 640 additions and 486 deletions

View File

@@ -43,22 +43,13 @@ impl WindowsSandboxRequestProcessor {
) -> Result<(), JSONRPCErrorError> {
// Validate requirements before acknowledging setup so callers do not get a
// `started` response for a Windows sandbox mode that cannot be persisted.
let command_cwd = params
.cwd
.map(PathBuf::from)
.unwrap_or_else(|| self.config.cwd.to_path_buf());
let config = self
.config_manager
.load_for_cwd(
/*request_overrides*/ None,
ConfigOverrides {
cwd: Some(command_cwd.clone()),
..Default::default()
},
Some(command_cwd.clone()),
)
.await
.map_err(|err| config_load_error(&err))?;
let (config, command_cwd) = load_setup_config(
&self.config_manager,
self.config.cwd.as_path(),
params.cwd.map(PathBuf::from),
)
.await
.map_err(|err| config_load_error(&err))?;
let setup_mode = resolve_allowed_windows_sandbox_setup_mode(
config.config_layer_stack.requirements(),
params.mode,
@@ -181,6 +172,29 @@ impl WindowsSandboxRequestProcessor {
}
}
async fn load_setup_config(
manager: &ConfigManager,
fallback_cwd: &std::path::Path,
requested_cwd: Option<PathBuf>,
) -> std::io::Result<(Config, PathBuf)> {
let cwd = requested_cwd.unwrap_or_else(|| fallback_cwd.to_path_buf());
let config = manager
.load_for_cwd(
/*request_overrides*/ None,
ConfigOverrides {
cwd: Some(cwd.clone()),
..Default::default()
},
Some(cwd.clone()),
)
.await?;
Ok((config, cwd))
}
#[cfg(test)]
#[path = "windows_sandbox_setup_config_tests.rs"]
mod setup_config_tests;
/// Resolves the requested API mode after checking that managed requirements allow it.
fn resolve_allowed_windows_sandbox_setup_mode(
requirements: &codex_config::ConfigRequirements,

View File

@@ -0,0 +1,23 @@
//! Exercise the setup RPC's real configuration loading without changing Windows accounts or ACLs.
use super::ConfigManager;
use super::load_setup_config;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
#[tokio::test]
async fn explicit_cwd_remains_the_setup_workspace() -> anyhow::Result<()> {
let home = tempfile::tempdir()?;
let install = tempfile::tempdir()?;
let project = tempfile::tempdir()?;
let manager = ConfigManager::without_managed_config_for_tests(home.path().to_path_buf());
let (config, command_cwd) =
load_setup_config(&manager, install.path(), Some(project.path().to_path_buf())).await?;
let expected = AbsolutePathBuf::from_absolute_path(project.path())?;
assert_eq!(
(command_cwd, config.effective_workspace_roots()),
(project.path().to_path_buf(), vec![expected]),
);
Ok(())
}

View File

@@ -7,8 +7,7 @@
//! and elevated capture. The legacy restrictedtoken path spawns the child directly
//! and does not use these helpers.
use crate::helper_materialization::HelperExecutable;
use crate::helper_materialization::resolve_helper_for_launch;
use crate::helper_materialization::resolve_command_runner;
use crate::winutil::resolve_sid;
use crate::winutil::string_from_sid_bytes;
use crate::winutil::to_wide;
@@ -41,7 +40,7 @@ pub const PIPE_ACCESS_OUTBOUND: u32 = 0x0000_0002;
/// Resolves the elevated command runner path, preferring the copied helper under
/// `.sandbox-bin` and falling back to the legacy sibling lookup when needed.
pub fn find_runner_exe(codex_home: &Path, log_dir: Option<&Path>) -> PathBuf {
resolve_helper_for_launch(HelperExecutable::CommandRunner, codex_home, log_dir)
resolve_command_runner(codex_home, log_dir)
}
/// Generates a unique named-pipe path used to communicate with the runner process.

View File

@@ -1,75 +1,51 @@
//! Selects sandbox helper paths; legacy file copying lives in the copy module.
mod copy;
use copy::CopyOutcome;
use copy::copy_from_source_if_needed;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::time::UNIX_EPOCH;
use tempfile::NamedTempFile;
use crate::logging::log_note;
use crate::sandbox_bin_dir;
const DEV_BUILD_VERSION_SENTINEL: &str = "0.0.0";
const COMMAND_RUNNER_EXE: &str = "codex-command-runner.exe";
pub(crate) const BIN_DIRNAME: &str = "bin";
pub(crate) const RESOURCES_DIRNAME: &str = "codex-resources";
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum HelperExecutable {
CommandRunner,
}
impl HelperExecutable {
fn file_name(self) -> &'static str {
match self {
Self::CommandRunner => "codex-command-runner.exe",
}
}
fn label(self) -> &'static str {
match self {
Self::CommandRunner => "command-runner",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CopyOutcome {
Reused,
ReCopied,
}
static HELPER_PATH_CACHE: OnceLock<Mutex<HashMap<String, PathBuf>>> = OnceLock::new();
pub(crate) fn helper_bin_dir(codex_home: &Path) -> PathBuf {
sandbox_bin_dir(codex_home)
}
pub(crate) fn legacy_lookup(kind: HelperExecutable) -> PathBuf {
pub(crate) fn legacy_lookup() -> PathBuf {
if let Ok(exe) = std::env::current_exe()
&& let Some(candidate) = bundled_executable_path_for_exe(&exe, kind.file_name())
&& let Some(candidate) = bundled_executable_path_for_exe(&exe, COMMAND_RUNNER_EXE)
{
return candidate;
}
PathBuf::from(kind.file_name())
PathBuf::from(COMMAND_RUNNER_EXE)
}
pub(crate) fn resolve_helper_for_launch(
kind: HelperExecutable,
codex_home: &Path,
log_dir: Option<&Path>,
) -> PathBuf {
match copy_helper_if_needed(kind, codex_home, log_dir) {
pub(crate) fn resolve_command_runner(codex_home: &Path, log_dir: Option<&Path>) -> PathBuf {
match copy_runner_if_needed(codex_home, log_dir) {
Ok(path) => {
log_note(
&format!(
"helper launch resolution: using copied {} path {}",
kind.label(),
"helper launch resolution: using copied command-runner path {}",
path.display()
),
log_dir,
@@ -77,11 +53,10 @@ pub(crate) fn resolve_helper_for_launch(
path
}
Err(err) => {
let fallback = legacy_lookup(kind);
let fallback = legacy_lookup();
log_note(
&format!(
"helper copy failed for {}: {err:#}; falling back to legacy path {}",
kind.label(),
"helper copy failed for command-runner: {err:#}; falling back to legacy path {}",
fallback.display()
),
log_dir,
@@ -120,17 +95,12 @@ pub fn resolve_exe_for_launch(source: &Path, codex_home: &Path) -> PathBuf {
}
}
pub(crate) fn copy_helper_if_needed(
kind: HelperExecutable,
codex_home: &Path,
log_dir: Option<&Path>,
) -> Result<PathBuf> {
let cache_key = format!("{}|{}", kind.file_name(), codex_home.display());
fn copy_runner_if_needed(codex_home: &Path, log_dir: Option<&Path>) -> Result<PathBuf> {
let cache_key = format!("{}|{}", COMMAND_RUNNER_EXE, codex_home.display());
if let Some(path) = cached_helper_path(&cache_key) {
log_note(
&format!(
"helper copy: using in-memory cache for {} -> {}",
kind.label(),
"helper copy: using in-memory cache for command-runner -> {}",
path.display()
),
log_dir,
@@ -138,12 +108,11 @@ pub(crate) fn copy_helper_if_needed(
return Ok(path);
}
let source = sibling_source_path(kind)?;
let destination = helper_destination_for_source(kind, codex_home, &source)?;
let source = sibling_source_path()?;
let destination = helper_destination_for_source(codex_home, &source)?;
log_note(
&format!(
"helper copy: validating {} source={} destination={}",
kind.label(),
"helper copy: validating command-runner source={} destination={}",
source.display(),
destination.display()
),
@@ -156,9 +125,8 @@ pub(crate) fn copy_helper_if_needed(
};
log_note(
&format!(
"helper copy: {} {} source={} destination={}",
"helper copy: {} command-runner source={} destination={}",
action,
kind.label(),
source.display(),
destination.display()
),
@@ -181,9 +149,9 @@ fn store_helper_path(cache_key: String, path: PathBuf) {
}
}
fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
fn sibling_source_path() -> Result<PathBuf> {
let exe = std::env::current_exe().context("resolve current executable for helper lookup")?;
bundled_executable_path_for_exe(&exe, kind.file_name()).ok_or_else(|| {
bundled_executable_path_for_exe(&exe, COMMAND_RUNNER_EXE).ok_or_else(|| {
anyhow!(
"helper not found next to current executable or under {RESOURCES_DIRNAME}: {}",
exe.display()
@@ -216,29 +184,13 @@ pub(crate) fn bundled_executable_path_for_exe(exe: &Path, file_name: &str) -> Op
find(exe).or_else(|| find(&dunce::canonicalize(exe).ok()?))
}
fn helper_destination_for_source(
kind: HelperExecutable,
codex_home: &Path,
source: &Path,
) -> Result<PathBuf> {
fn helper_destination_for_source(codex_home: &Path, source: &Path) -> Result<PathBuf> {
let suffix = helper_version_suffix(source)?;
let file_name = materialized_file_name(kind, &suffix);
Ok(helper_bin_dir(codex_home).join(file_name))
Ok(helper_bin_dir(codex_home).join(materialized_file_name(&suffix)))
}
fn materialized_file_name(kind: HelperExecutable, suffix: &str) -> String {
let source_name = kind.file_name();
let path = Path::new(source_name);
let stem = path
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or(source_name);
let extension = path
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| format!(".{ext}"))
.unwrap_or_default();
format!("{stem}-{suffix}{extension}")
fn materialized_file_name(suffix: &str) -> String {
format!("codex-command-runner-{suffix}.exe")
}
fn helper_version_suffix(source: &Path) -> Result<String> {
@@ -262,117 +214,16 @@ fn dev_build_suffix(source: &Path) -> Result<String> {
Ok(format!("{}-{:x}", metadata.len(), duration.as_secs(),))
}
fn copy_from_source_if_needed(source: &Path, destination: &Path) -> Result<CopyOutcome> {
if destination_is_fresh(source, destination)? {
return Ok(CopyOutcome::Reused);
}
let destination_dir = destination.parent().ok_or_else(|| {
anyhow!(
"helper destination has no parent: {}",
destination.display()
)
})?;
fs::create_dir_all(destination_dir).with_context(|| {
format!(
"create helper destination directory {}",
destination_dir.display()
)
})?;
let temp_path = NamedTempFile::new_in(destination_dir)
.with_context(|| {
format!(
"create temporary helper file in {}",
destination_dir.display()
)
})?
.into_temp_path();
let temp_path_buf = temp_path.to_path_buf();
let mut source_file = fs::File::open(source)
.with_context(|| format!("open helper source for read {}", source.display()))?;
let mut temp_file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&temp_path_buf)
.with_context(|| format!("open temporary helper file {}", temp_path_buf.display()))?;
// Write into a temp file created inside `.sandbox-bin` so the copied helper keeps the
// destination directory's inherited ACLs instead of reusing the source file's descriptor.
std::io::copy(&mut source_file, &mut temp_file).with_context(|| {
format!(
"copy helper from {} to {}",
source.display(),
temp_path_buf.display()
)
})?;
temp_file
.flush()
.with_context(|| format!("flush temporary helper file {}", temp_path_buf.display()))?;
drop(temp_file);
if destination.exists() {
fs::remove_file(destination).with_context(|| {
format!("remove stale helper destination {}", destination.display())
})?;
}
match fs::rename(&temp_path_buf, destination) {
Ok(()) => Ok(CopyOutcome::ReCopied),
Err(rename_err) => {
if destination_is_fresh(source, destination)? {
Ok(CopyOutcome::Reused)
} else {
Err(rename_err).with_context(|| {
format!(
"rename helper temp file {} to {}",
temp_path_buf.display(),
destination.display()
)
})
}
}
}
}
fn destination_is_fresh(source: &Path, destination: &Path) -> Result<bool> {
let source_meta = fs::metadata(source)
.with_context(|| format!("read helper source metadata {}", source.display()))?;
let destination_meta = match fs::metadata(destination) {
Ok(meta) => meta,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(err) => {
return Err(err).with_context(|| {
format!("read helper destination metadata {}", destination.display())
});
}
};
if source_meta.len() != destination_meta.len() {
return Ok(false);
}
let source_modified = source_meta
.modified()
.with_context(|| format!("read helper source mtime {}", source.display()))?;
let destination_modified = destination_meta
.modified()
.with_context(|| format!("read helper destination mtime {}", destination.display()))?;
Ok(destination_modified >= source_modified)
}
#[cfg(test)]
mod tests {
use super::BIN_DIRNAME;
use super::CopyOutcome;
use super::DEV_BUILD_VERSION_SENTINEL;
use super::HelperExecutable;
use super::RESOURCES_DIRNAME;
use super::bundled_executable_path_for_exe;
use super::copy_from_source_if_needed;
use super::destination_is_fresh;
use super::dev_build_suffix;
use super::helper_bin_dir;
use super::helper_version_suffix;
@@ -383,56 +234,6 @@ mod tests {
use std::path::PathBuf;
use tempfile::TempDir;
#[test]
fn copy_from_source_if_needed_copies_missing_destination() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("bin").join("helper.exe");
fs::write(&source, b"runner-v1").expect("write source");
let outcome = copy_from_source_if_needed(&source, &destination).expect("copy helper");
assert_eq!(CopyOutcome::ReCopied, outcome);
assert_eq!(
b"runner-v1".as_slice(),
fs::read(&destination).expect("read destination")
);
}
#[test]
fn destination_is_fresh_uses_size_and_mtime() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("destination.exe");
fs::write(&destination, b"same-size").expect("write destination");
std::thread::sleep(std::time::Duration::from_secs(1));
fs::write(&source, b"same-size").expect("write source");
assert!(!destination_is_fresh(&source, &destination).expect("stale metadata"));
fs::write(&destination, b"same-size").expect("rewrite destination");
assert!(destination_is_fresh(&source, &destination).expect("fresh metadata"));
}
#[test]
fn copy_from_source_if_needed_reuses_fresh_destination() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("bin").join("helper.exe");
fs::write(&source, b"runner-v1").expect("write source");
copy_from_source_if_needed(&source, &destination).expect("initial copy");
let outcome = copy_from_source_if_needed(&source, &destination).expect("revalidate helper");
assert_eq!(CopyOutcome::Reused, outcome);
assert_eq!(
b"runner-v1".as_slice(),
fs::read(&destination).expect("read destination")
);
}
#[test]
fn helper_bin_dir_is_under_sandbox_bin() {
let codex_home = Path::new(r"C:\Users\example\.codex");
@@ -452,10 +253,8 @@ mod tests {
let runner_source = source_dir.join("codex-command-runner.exe");
fs::write(&runner_source, b"runner").expect("runner");
let runner_suffix = helper_version_suffix(&runner_source).expect("runner suffix");
let runner_destination = helper_bin_dir(&codex_home).join(materialized_file_name(
HelperExecutable::CommandRunner,
&runner_suffix,
));
let runner_destination =
helper_bin_dir(&codex_home).join(materialized_file_name(&runner_suffix));
let runner_outcome =
copy_from_source_if_needed(&runner_source, &runner_destination).expect("runner copy");
@@ -596,7 +395,7 @@ mod tests {
#[test]
fn materialized_file_name_adds_suffix_before_extension() {
let file_name = materialized_file_name(HelperExecutable::CommandRunner, "test-suffix");
let file_name = materialized_file_name("test-suffix");
assert_eq!(file_name, "codex-command-runner-test-suffix.exe");
}

View File

@@ -0,0 +1,120 @@
//! Copies legacy helpers using destination-inherited ACLs and the existing freshness check.
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use std::fs;
use std::io::Write;
use std::path::Path;
use tempfile::NamedTempFile;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum CopyOutcome {
Reused,
ReCopied,
}
pub(super) fn copy_from_source_if_needed(source: &Path, destination: &Path) -> Result<CopyOutcome> {
if destination_is_fresh(source, destination)? {
return Ok(CopyOutcome::Reused);
}
let destination_dir = destination.parent().ok_or_else(|| {
anyhow!(
"helper destination has no parent: {}",
destination.display()
)
})?;
fs::create_dir_all(destination_dir).with_context(|| {
format!(
"create helper destination directory {}",
destination_dir.display()
)
})?;
let temp_path = NamedTempFile::new_in(destination_dir)
.with_context(|| {
format!(
"create temporary helper file in {}",
destination_dir.display()
)
})?
.into_temp_path();
let temp_path_buf = temp_path.to_path_buf();
let mut source_file = fs::File::open(source)
.with_context(|| format!("open helper source for read {}", source.display()))?;
let mut temp_file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&temp_path_buf)
.with_context(|| format!("open temporary helper file {}", temp_path_buf.display()))?;
// Write into a temp file created inside `.sandbox-bin` so the copied helper keeps the
// destination directory's inherited ACLs instead of reusing the source file's descriptor.
std::io::copy(&mut source_file, &mut temp_file).with_context(|| {
format!(
"copy helper from {} to {}",
source.display(),
temp_path_buf.display()
)
})?;
temp_file
.flush()
.with_context(|| format!("flush temporary helper file {}", temp_path_buf.display()))?;
drop(temp_file);
if destination.exists() {
fs::remove_file(destination).with_context(|| {
format!("remove stale helper destination {}", destination.display())
})?;
}
match fs::rename(&temp_path_buf, destination) {
Ok(()) => Ok(CopyOutcome::ReCopied),
Err(rename_err) => {
if destination_is_fresh(source, destination)? {
Ok(CopyOutcome::Reused)
} else {
Err(rename_err).with_context(|| {
format!(
"rename helper temp file {} to {}",
temp_path_buf.display(),
destination.display()
)
})
}
}
}
}
fn destination_is_fresh(source: &Path, destination: &Path) -> Result<bool> {
let source_meta = fs::metadata(source)
.with_context(|| format!("read helper source metadata {}", source.display()))?;
let destination_meta = match fs::metadata(destination) {
Ok(meta) => meta,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(err) => {
return Err(err).with_context(|| {
format!("read helper destination metadata {}", destination.display())
});
}
};
if source_meta.len() != destination_meta.len() {
return Ok(false);
}
let source_modified = source_meta
.modified()
.with_context(|| format!("read helper source mtime {}", source.display()))?;
let destination_modified = destination_meta
.modified()
.with_context(|| format!("read helper destination mtime {}", destination.display()))?;
Ok(destination_modified >= source_modified)
}
#[cfg(test)]
#[path = "copy_tests.rs"]
mod tests;

View File

@@ -0,0 +1,58 @@
//! Tests the legacy helper copy and freshness contract.
use super::CopyOutcome;
use super::copy_from_source_if_needed;
use super::destination_is_fresh;
use pretty_assertions::assert_eq;
use std::fs;
use tempfile::TempDir;
#[test]
fn copy_from_source_if_needed_copies_missing_destination() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("bin").join("helper.exe");
fs::write(&source, b"runner-v1").expect("write source");
let outcome = copy_from_source_if_needed(&source, &destination).expect("copy helper");
assert_eq!(CopyOutcome::ReCopied, outcome);
assert_eq!(
b"runner-v1".as_slice(),
fs::read(&destination).expect("read destination")
);
}
#[test]
fn destination_is_fresh_uses_size_and_mtime() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("destination.exe");
fs::write(&destination, b"same-size").expect("write destination");
std::thread::sleep(std::time::Duration::from_secs(1));
fs::write(&source, b"same-size").expect("write source");
assert!(!destination_is_fresh(&source, &destination).expect("stale metadata"));
fs::write(&destination, b"same-size").expect("rewrite destination");
assert!(destination_is_fresh(&source, &destination).expect("fresh metadata"));
}
#[test]
fn copy_from_source_if_needed_reuses_fresh_destination() {
let tmp = TempDir::new().expect("tempdir");
let source = tmp.path().join("source.exe");
let destination = tmp.path().join("bin").join("helper.exe");
fs::write(&source, b"runner-v1").expect("write source");
copy_from_source_if_needed(&source, &destination).expect("initial copy");
let outcome = copy_from_source_if_needed(&source, &destination).expect("revalidate helper");
assert_eq!(CopyOutcome::Reused, outcome);
assert_eq!(
b"runner-v1".as_slice(),
fs::read(&destination).expect("read destination")
);
}

View File

@@ -37,12 +37,16 @@ pub struct InstallationRecord {
}
pub fn load() -> Result<Option<InstallationRecord>> {
load_from(INSTALLATION_KEY)
}
pub(crate) fn load_from(key: &str) -> 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(key).as_ptr(),
to_wide(INSTALLATION_VALUE).as_ptr(),
registry::RRF_RT_REG_SZ,
ptr::null_mut(),
@@ -72,6 +76,10 @@ pub fn load() -> Result<Option<InstallationRecord>> {
}
pub fn save(record: &InstallationRecord) -> Result<()> {
save_to(INSTALLATION_KEY, record)
}
pub(crate) fn save_to(key: &str, record: &InstallationRecord) -> Result<()> {
let value = to_wide(
serde_json::to_string(record).context("serialize protected sandbox installation record")?,
);
@@ -82,7 +90,7 @@ pub fn save(record: &InstallationRecord) -> Result<()> {
let status = unsafe {
registry::RegSetKeyValueW(
registry::HKEY_LOCAL_MACHINE,
to_wide(INSTALLATION_KEY).as_ptr(),
to_wide(key).as_ptr(),
to_wide(INSTALLATION_VALUE).as_ptr(),
registry::REG_SZ,
value.as_ptr().cast(),

View File

@@ -97,6 +97,8 @@ mod resolved_permissions;
#[cfg(target_os = "windows")]
mod token;
#[cfg(target_os = "windows")]
mod token_user;
#[cfg(target_os = "windows")]
mod wfp;
#[cfg(target_os = "windows")]
mod wfp_setup;

View File

@@ -119,7 +119,7 @@ pub fn provision_windows_sandbox_via_service(
},
};
match send_service_request(request, PROVISIONING_TIMEOUT)? {
match send_service_request(&request, PROVISIONING_TIMEOUT)? {
crate::SandboxProvisioningResponse::Ok => {
Ok(WindowsSandboxProvisioningOutcome::Provisioned)
}
@@ -141,7 +141,7 @@ pub fn register_desktop_installation(codex_home: &Path) -> anyhow::Result<()> {
.to_owned(),
},
};
match send_service_request(request, Duration::from_secs(5))? {
match send_service_request(&request, Duration::from_secs(5))? {
crate::SandboxProvisioningResponse::Ok => Ok(()),
crate::SandboxProvisioningResponse::Unavailable => {
bail!("desktop uninstall registration service is unavailable")
@@ -151,31 +151,14 @@ pub fn register_desktop_installation(codex_home: &Path) -> anyhow::Result<()> {
}
fn send_service_request(
request: crate::FramedProvisioningMessage,
request: &crate::FramedProvisioningMessage,
timeout: Duration,
) -> anyhow::Result<crate::SandboxProvisioningResponse> {
let deadline = Instant::now() + timeout;
let Some(mut pipe) = connect(deadline)? else {
return Ok(crate::SandboxProvisioningResponse::Unavailable);
};
let response = (|| -> anyhow::Result<crate::FramedProvisioningMessage> {
verify_server(pipe.as_raw_handle() as HANDLE)
.context("authenticate provisioning pipe server")?;
crate::write_provisioning_frame(&mut pipe, &request)
.context("send sandbox provisioning request")?;
crate::framed_io::wait_for_complete_frame(&pipe, deadline)
.context("wait for sandbox provisioning response")?;
crate::read_provisioning_frame(&mut pipe)
.context("read sandbox provisioning response")?
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::UnexpectedEof,
"sandbox provisioning service closed the pipe without a response",
)
})
.context("read sandbox provisioning response")
})();
let response = match response {
let response = match exchange_request(&mut pipe, request, deadline) {
Ok(response) => response,
Err(error)
if error.downcast_ref::<io::Error>().is_some_and(|error| {
@@ -195,7 +178,29 @@ fn send_service_request(
}
Err(error) => return Err(error),
};
Ok(response)
}
fn exchange_request(
pipe: &mut File,
request: &crate::FramedProvisioningMessage,
deadline: Instant,
) -> anyhow::Result<crate::SandboxProvisioningResponse> {
verify_server(pipe.as_raw_handle() as HANDLE)
.context("authenticate provisioning pipe server")?;
crate::write_provisioning_frame(&mut *pipe, request)
.context("send sandbox provisioning request")?;
crate::framed_io::wait_for_complete_frame(pipe, deadline)
.context("wait for sandbox provisioning response")?;
let response = crate::read_provisioning_frame(pipe)
.context("read sandbox provisioning response")?
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::UnexpectedEof,
"sandbox provisioning service closed the pipe without a response",
)
})
.context("read sandbox provisioning response")?;
if response.version != crate::PROVISIONING_PROTOCOL_VERSION {
return Ok(crate::SandboxProvisioningResponse::Unavailable);
}

View File

@@ -519,6 +519,10 @@ fn real_main(setup_mode: &mut Option<SetupMode>) -> Result<()> {
),
)));
}
run_payload(&payload)
}
fn run_payload(payload: &Payload) -> Result<()> {
let sbx_dir = sandbox_dir(&payload.codex_home);
std::fs::create_dir_all(&sbx_dir).map_err(|err| {
anyhow::Error::new(SetupFailure::new(
@@ -532,7 +536,7 @@ fn real_main(setup_mode: &mut Option<SetupMode>) -> Result<()> {
format!("open log in {} failed: {err}", sbx_dir.display()),
))
})?;
let result = run_setup(&payload, &mut log, &sbx_dir);
let result = run_setup(payload, &mut log, &sbx_dir);
if let Err(err) = &result {
let _ = log_line(&mut log, &format!("setup error: {err:?}"));
log_note(&format!("setup error: {err:?}"), Some(sbx_dir.as_path()));

View File

@@ -20,7 +20,6 @@ use windows_sys::Win32::Security::Authorization::TRUSTEE_W;
use windows_sys::Win32::Security::CopySid;
use windows_sys::Win32::Security::CreateRestrictedToken;
use windows_sys::Win32::Security::CreateWellKnownSid;
use windows_sys::Win32::Security::GetLengthSid;
use windows_sys::Win32::Security::GetTokenInformation;
use windows_sys::Win32::Security::IsValidSid;
use windows_sys::Win32::Security::LookupPrivilegeValueW;
@@ -36,10 +35,8 @@ use windows_sys::Win32::Security::TOKEN_DUPLICATE;
use windows_sys::Win32::Security::TOKEN_GROUPS;
use windows_sys::Win32::Security::TOKEN_PRIVILEGES;
use windows_sys::Win32::Security::TOKEN_QUERY;
use windows_sys::Win32::Security::TOKEN_USER;
use windows_sys::Win32::Security::TokenDefaultDacl;
use windows_sys::Win32::Security::TokenGroups;
use windows_sys::Win32::Security::TokenUser;
use windows_sys::Win32::System::Threading::GetCurrentProcess;
const DISABLE_MAX_PRIVILEGE: u32 = 0x01;
@@ -332,45 +329,7 @@ pub unsafe fn get_logon_sid_bytes(h_token: HANDLE) -> Result<Vec<u8>> {
Err(anyhow!("Logon SID not present on token"))
}
pub(crate) unsafe fn get_user_sid_bytes(h_token: HANDLE) -> Result<Vec<u8>> {
let mut needed: u32 = 0;
GetTokenInformation(h_token, TokenUser, std::ptr::null_mut(), 0, &mut needed);
if needed == 0 {
return Err(anyhow!("TokenUser size query returned 0"));
}
let mut user_buf: Vec<u8> = vec![0u8; needed as usize];
let ok = GetTokenInformation(
h_token,
TokenUser,
user_buf.as_mut_ptr() as *mut c_void,
needed,
&mut needed,
);
if ok == 0 || (needed as usize) < std::mem::size_of::<TOKEN_USER>() {
return Err(anyhow!(
"GetTokenInformation(TokenUser) failed: {}",
GetLastError()
));
}
let token_user: TOKEN_USER = std::ptr::read_unaligned(user_buf.as_ptr() as *const TOKEN_USER);
let sid_len = GetLengthSid(token_user.User.Sid);
if sid_len == 0 {
return Err(anyhow!(
"GetLengthSid(TokenUser) failed: {}",
GetLastError()
));
}
let mut user_sid_bytes = vec![0u8; sid_len as usize];
if CopySid(
sid_len,
user_sid_bytes.as_mut_ptr() as *mut c_void,
token_user.User.Sid,
) == 0
{
return Err(anyhow!("CopySid(TokenUser) failed: {}", GetLastError()));
}
Ok(user_sid_bytes)
}
pub(crate) use crate::token_user::get_user_sid_bytes;
unsafe fn enable_single_privilege(h_token: HANDLE, name: &str) -> Result<()> {
let mut luid = LUID {

View File

@@ -0,0 +1,56 @@
//! Owns the token-user SID query used by sandbox token construction.
use anyhow::Result;
use anyhow::anyhow;
use std::ffi::c_void;
use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Security::CopySid;
use windows_sys::Win32::Security::GetLengthSid;
use windows_sys::Win32::Security::GetTokenInformation;
use windows_sys::Win32::Security::TOKEN_USER;
use windows_sys::Win32::Security::TokenUser;
pub(crate) unsafe fn get_user_sid_bytes(h_token: HANDLE) -> Result<Vec<u8>> {
let mut needed: u32 = 0;
GetTokenInformation(h_token, TokenUser, std::ptr::null_mut(), 0, &mut needed);
if needed == 0 {
return Err(anyhow!("TokenUser size query returned 0"));
}
let mut user_buf: Vec<u8> = vec![0u8; needed as usize];
let ok = GetTokenInformation(
h_token,
TokenUser,
user_buf.as_mut_ptr() as *mut c_void,
needed,
&mut needed,
);
if ok == 0 || (needed as usize) < std::mem::size_of::<TOKEN_USER>() {
return Err(anyhow!(
"GetTokenInformation(TokenUser) failed: {}",
GetLastError()
));
}
let token_user: TOKEN_USER = std::ptr::read_unaligned(user_buf.as_ptr() as *const TOKEN_USER);
let sid_len = GetLengthSid(token_user.User.Sid);
if sid_len == 0 {
return Err(anyhow!(
"GetLengthSid(TokenUser) failed: {}",
GetLastError()
));
}
let mut user_sid_bytes = vec![0u8; sid_len as usize];
if CopySid(
sid_len,
user_sid_bytes.as_mut_ptr() as *mut c_void,
token_user.User.Sid,
) == 0
{
return Err(anyhow!("CopySid(TokenUser) failed: {}", GetLastError()));
}
Ok(user_sid_bytes)
}
#[cfg(test)]
#[path = "token_user_tests.rs"]
mod tests;

View File

@@ -0,0 +1,22 @@
//! Checks the existing token-user query against real process and invalid handles.
use super::get_user_sid_bytes;
use anyhow::Result;
use anyhow::ensure;
use std::os::windows::io::FromRawHandle;
use std::os::windows::io::OwnedHandle;
use windows_sys::Win32::Security::IsValidSid;
use windows_sys::Win32::Security::TOKEN_QUERY;
use windows_sys::Win32::System::Threading::GetCurrentProcess;
use windows_sys::Win32::System::Threading::OpenProcessToken;
#[test]
fn queries_current_user_and_rejects_invalid_token() -> Result<()> {
let mut raw = 0;
ensure!(unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut raw) } != 0);
let _token = unsafe { OwnedHandle::from_raw_handle(raw as _) };
let user = unsafe { get_user_sid_bytes(raw) }?;
assert!(unsafe { IsValidSid(user.as_ptr() as _) } != 0);
assert!(unsafe { get_user_sid_bytes(0) }.is_err());
Ok(())
}

View File

@@ -5,6 +5,7 @@
mod authentication;
mod home;
mod listener;
mod request;
use anyhow::Context;
@@ -16,8 +17,6 @@ use codex_windows_sandbox::FramedProvisioningMessage;
use codex_windows_sandbox::PROVISIONING_PROTOCOL_VERSION;
use codex_windows_sandbox::ProvisioningMessage;
use codex_windows_sandbox::SandboxProvisioningResponse;
use codex_windows_sandbox::ensure_sandbox_users_group;
use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::write_provisioning_frame;
pub(crate) use home::OwnedHandle;
@@ -36,8 +35,6 @@ use std::time::Duration;
use std::time::Instant;
use windows_sys::Win32::Foundation as foundation;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Security as security;
use windows_sys::Win32::Security::Authorization as authorization;
use windows_sys::Win32::Storage::FileSystem as filesystem;
use windows_sys::Win32::System::Pipes as pipes;
@@ -62,14 +59,6 @@ impl std::fmt::Display for ServiceUnavailable {
impl std::error::Error for ServiceUnavailable {}
struct SecurityDescriptor(security::PSECURITY_DESCRIPTOR);
impl Drop for SecurityDescriptor {
fn drop(&mut self) {
unsafe { foundation::LocalFree(self.0 as foundation::HLOCAL) };
}
}
#[derive(Debug, Eq, PartialEq)]
enum PipeConnection {
Connected,
@@ -79,54 +68,14 @@ enum PipeConnection {
pub(crate) fn run(
shutdown: Arc<AtomicBool>,
on_ready: impl FnOnce() -> Result<()>,
register_installation: impl Fn(InstallationRecord, OwnedHandle) -> Result<()>,
register_installation: impl Fn(InstallationRecord, OwnedHandle) -> Result<InstallationRecord>,
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);
let mut descriptor: security::PSECURITY_DESCRIPTOR = ptr::null_mut();
if unsafe {
authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW(
to_wide(sddl).as_ptr(),
authorization::SDDL_REVISION_1,
&mut descriptor,
ptr::null_mut(),
)
} == 0
{
return Err(std::io::Error::last_os_error()).context("create provisioning pipe DACL");
}
let descriptor = SecurityDescriptor(descriptor);
let attributes = security::SECURITY_ATTRIBUTES {
nLength: size_of::<security::SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor.0,
bInheritHandle: 0,
};
let pipe = unsafe {
pipes::CreateNamedPipeW(
to_wide(PIPE_NAME).as_ptr(),
filesystem::PIPE_ACCESS_DUPLEX | filesystem::FILE_FLAG_FIRST_PIPE_INSTANCE,
pipes::PIPE_TYPE_BYTE
| pipes::PIPE_READMODE_BYTE
| pipes::PIPE_WAIT
| pipes::PIPE_REJECT_REMOTE_CLIENTS,
1,
1024,
MAX_REQUEST_BYTES as u32,
0,
&attributes,
)
};
if pipe == foundation::INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error()).context("create provisioning pipe");
}
let pipe = OwnedHandle(pipe);
let listener = listener::ProvisioningListener::open()?;
on_ready().context("publish provisioning listener readiness")?;
while !shutdown.load(Ordering::Acquire) {
let connection = accept_pipe_connection(pipe.0)?;
let connection = accept_pipe_connection(listener.pipe.0)?;
if shutdown.load(Ordering::Acquire) {
break;
}
@@ -136,17 +85,18 @@ pub(crate) fn run(
continue;
}
let authorized_process = match crate::package_identity::authorize_client_process(pipe.0) {
Ok(process) => process,
Err(_) => {
unsafe { pipes::DisconnectNamedPipe(pipe.0) };
continue;
}
};
let authorized_process =
match crate::package_identity::authorize_client_process(listener.pipe.0) {
Ok(process) => process,
Err(_) => {
unsafe { pipes::DisconnectNamedPipe(listener.pipe.0) };
continue;
}
};
let result = handle_request(
pipe.0,
listener.pipe.0,
&authorized_process,
&sandbox_sid,
&listener.sandbox_sid,
&shutdown,
&register_installation,
);
@@ -158,62 +108,78 @@ pub(crate) fn run(
}
Err(error) => {
eprintln!("sandbox provisioning request failed: {error}");
let mut message = String::new();
for character in error.to_string().chars() {
let character = if character.is_control() {
' '
} else {
character
};
if message.len() + character.len_utf8() > MAX_RESPONSE_MESSAGE_BYTES {
break;
}
message.push(character);
SandboxProvisioningResponse::Error {
message: response_error_message(&error),
}
SandboxProvisioningResponse::Error { message }
}
};
let response = FramedProvisioningMessage {
version: PROVISIONING_PROTOCOL_VERSION,
message: ProvisioningMessage::ProvisionSandboxResponse { payload: response },
};
let mut frame = Vec::new();
write_provisioning_frame(&mut frame, &response)
.context("serialize sandbox provisioning response")?;
let mut written = 0;
let sent = unsafe {
filesystem::WriteFile(
pipe.0,
frame.as_ptr(),
frame.len() as u32,
&mut written,
ptr::null_mut(),
)
};
if sent != 0 {
let deadline = Instant::now() + Duration::from_secs(1);
while !shutdown.load(Ordering::Acquire) && Instant::now() < deadline {
if unsafe {
pipes::PeekNamedPipe(
pipe.0,
ptr::null_mut(),
0,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
} == 0
{
break;
}
std::thread::sleep(Duration::from_millis(10));
}
}
unsafe { pipes::DisconnectNamedPipe(pipe.0) };
write_response(&listener.pipe, &response, &shutdown)?;
unsafe { pipes::DisconnectNamedPipe(listener.pipe.0) };
}
Ok(())
}
/// Sends one frame, then waits briefly for the client to close before disconnecting.
fn write_response(
pipe: &OwnedHandle,
response: &FramedProvisioningMessage,
shutdown: &AtomicBool,
) -> Result<()> {
let mut frame = Vec::new();
write_provisioning_frame(&mut frame, response)
.context("serialize sandbox provisioning response")?;
let mut written = 0;
let sent = unsafe {
filesystem::WriteFile(
pipe.0,
frame.as_ptr(),
frame.len() as u32,
&mut written,
ptr::null_mut(),
)
};
if sent != 0 {
let deadline = Instant::now() + Duration::from_secs(1);
while !shutdown.load(Ordering::Acquire) && Instant::now() < deadline {
if unsafe {
pipes::PeekNamedPipe(
pipe.0,
ptr::null_mut(),
0,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
} == 0
{
break;
}
std::thread::sleep(Duration::from_millis(10));
}
}
Ok(())
}
fn response_error_message(error: &anyhow::Error) -> String {
let mut message = String::new();
for character in error.to_string().chars() {
let character = if character.is_control() {
' '
} else {
character
};
if message.len() + character.len_utf8() > MAX_RESPONSE_MESSAGE_BYTES {
break;
}
message.push(character);
}
message
}
fn accept_pipe_connection(pipe: HANDLE) -> Result<PipeConnection> {
if unsafe { pipes::ConnectNamedPipe(pipe, ptr::null_mut()) } != 0 {
return Ok(PipeConnection::Connected);
@@ -268,7 +234,7 @@ fn handle_request(
authorized_process: &crate::package_identity::AuthorizedClientProcess,
sandbox_sid: &[u8],
shutdown: &AtomicBool,
register_installation: &dyn Fn(InstallationRecord, OwnedHandle) -> Result<()>,
register_installation: &dyn Fn(InstallationRecord, OwnedHandle) -> Result<InstallationRecord>,
) -> Result<SandboxProvisioningResponse> {
let deadline = Instant::now() + REQUEST_IDLE_TIMEOUT;
let mut request = [0_u8; MAX_REQUEST_BYTES];

View File

@@ -0,0 +1,91 @@
//! Owns the provisioning pipe and its security descriptor for the listener lifetime.
use anyhow::Context;
use anyhow::Result;
use codex_windows_sandbox::ensure_sandbox_users_group;
use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide;
use std::mem::size_of;
use std::ptr;
use windows_sys::Win32::Foundation as foundation;
use windows_sys::Win32::Security as security;
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 super::MAX_REQUEST_BYTES;
use super::OwnedHandle;
use super::pipe_security_descriptor;
pub(super) struct ProvisioningListener {
pub(super) pipe: OwnedHandle,
pub(super) sandbox_sid: Vec<u8>,
_descriptor: SecurityDescriptor,
}
impl ProvisioningListener {
pub(super) fn open() -> Result<Self> {
let sandbox_sid = ensure_sandbox_users_group()?;
let (descriptor, pipe) = create_provisioning_pipe(super::PIPE_NAME, &sandbox_sid)?;
Ok(Self {
pipe,
sandbox_sid,
_descriptor: descriptor,
})
}
}
struct SecurityDescriptor(security::PSECURITY_DESCRIPTOR);
impl Drop for SecurityDescriptor {
fn drop(&mut self) {
unsafe { foundation::LocalFree(self.0 as foundation::HLOCAL) };
}
}
/// Preserves the existing listener and descriptor lifetimes.
fn create_provisioning_pipe(
pipe_name: &str,
sandbox_sid: &[u8],
) -> Result<(SecurityDescriptor, OwnedHandle)> {
let sid_string = string_from_sid_bytes(sandbox_sid).map_err(anyhow::Error::msg)?;
let sddl = pipe_security_descriptor(&sid_string);
let mut descriptor: security::PSECURITY_DESCRIPTOR = ptr::null_mut();
if unsafe {
authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW(
to_wide(sddl).as_ptr(),
authorization::SDDL_REVISION_1,
&mut descriptor,
ptr::null_mut(),
)
} == 0
{
return Err(std::io::Error::last_os_error()).context("create provisioning pipe DACL");
}
let descriptor = SecurityDescriptor(descriptor);
let attributes = security::SECURITY_ATTRIBUTES {
nLength: size_of::<security::SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor.0,
bInheritHandle: 0,
};
let pipe = unsafe {
pipes::CreateNamedPipeW(
to_wide(pipe_name).as_ptr(),
filesystem::PIPE_ACCESS_DUPLEX | filesystem::FILE_FLAG_FIRST_PIPE_INSTANCE,
pipes::PIPE_TYPE_BYTE
| pipes::PIPE_READMODE_BYTE
| pipes::PIPE_WAIT
| pipes::PIPE_REJECT_REMOTE_CLIENTS,
1,
1024,
MAX_REQUEST_BYTES as u32,
0,
&attributes,
)
};
if pipe == foundation::INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error()).context("create provisioning pipe");
}
Ok((descriptor, OwnedHandle(pipe)))
}

View File

@@ -68,7 +68,7 @@ impl PackageLifecycle {
&self,
mut record: InstallationRecord,
user_token: OwnedHandle,
) -> Result<()> {
) -> Result<InstallationRecord> {
let mut active = self.installation.borrow_mut();
let previous = match active.as_ref() {
Some(installation) => Some(installation.record.clone()),
@@ -91,10 +91,11 @@ impl PackageLifecycle {
&& installation.codex_home.is_some()
{
// A restored watcher must immediately use newly registered desktop ownership.
installation.record = record;
return Ok(());
installation.record = record.clone();
return Ok(record);
}
let saved_record = record.clone();
with_owner_impersonation(user_token.0, || {
let mut directory_handles = Vec::new();
let codex_home = match crate::ipc::pin_existing_ancestors(
@@ -163,7 +164,8 @@ impl PackageLifecycle {
token,
});
Ok(())
})
})?;
Ok(saved_record)
}
pub(crate) fn restore_logged_in_owner(&self, recorded_session_id: u32) -> Result<()> {
@@ -231,6 +233,7 @@ impl PackageLifecycle {
},
token,
)
.map(|_| ())
}
pub(crate) fn clean_up(&self) -> Result<()> {

View File

@@ -20,7 +20,7 @@ use crate::ipc::ServiceRequest;
pub(crate) fn run(
identity: ClientIdentity,
request: ServiceRequest,
register_installation: &dyn Fn(InstallationRecord, OwnedHandle) -> Result<()>,
register_installation: &dyn Fn(InstallationRecord, OwnedHandle) -> Result<InstallationRecord>,
) -> Result<SandboxProvisioningResponse> {
// A policy-rejected request must not choose the uninstall owner. Use the
// token already authenticated above instead of impersonating the pipe again.

View File

@@ -40,6 +40,8 @@ use windows_sys::Win32::System::Services::SERVICE_WIN32_OWN_PROCESS;
use windows_sys::Win32::System::Services::SetServiceStatus;
use windows_sys::Win32::System::Services::StartServiceCtrlDispatcherW;
mod runtime_lifecycle;
pub(crate) const SERVICE_NAME: &str = "CodexSandboxService";
const EVENT_SERVICE_STARTED: u32 = 1000;
const EVENT_SERVICE_STOP_REQUESTED: u32 = 1001;
@@ -109,7 +111,7 @@ pub(crate) fn run_foreground() -> Result<()> {
eprintln!("{SERVICE_NAME} listening on {}", crate::ipc::PIPE_NAME);
Ok(())
},
|_, _| Ok(()),
|installation, _| Ok(installation),
|| Ok(()),
)
}
@@ -156,46 +158,7 @@ fn service_main_inner(state: &ServiceState) -> Result<()> {
state.report_status(SERVICE_START_PENDING, NO_ERROR)?;
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| {
package_lifecycle.register_authenticated_user(installation, user_token)
},
|| {
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()?;
}
runtime_lifecycle::run(state, &package_lifecycle)?;
log_information(
EVENT_SERVICE_STOPPED,
"The Codex sandbox service has stopped.",
@@ -226,11 +189,7 @@ unsafe extern "system" fn service_control_handler(
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
});
});
wake_listener();
}
NO_ERROR
}
@@ -252,11 +211,7 @@ unsafe extern "system" fn service_control_handler(
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
});
});
wake_listener();
}
}
NO_ERROR
@@ -265,6 +220,16 @@ unsafe extern "system" fn service_control_handler(
}
}
fn wake_listener() {
if let Some(state) = SERVICE_STATE.get() {
std::thread::spawn(move || {
crate::ipc::wake(crate::ipc::PIPE_NAME, || {
state.current_status.load(Ordering::Acquire) == SERVICE_STOPPED
});
});
}
}
pub(crate) fn log_information(event_id: u32, message: &str) {
log_event(EVENTLOG_INFORMATION_TYPE, event_id, message);
}

View File

@@ -0,0 +1,60 @@
//! Connects the provisioning broker to the existing package owner and uninstall listener.
use std::sync::Arc;
use std::sync::atomic::Ordering;
use anyhow::Context;
use anyhow::Result;
use windows_sys::Win32::Foundation::NO_ERROR;
use windows_sys::Win32::System::Services::SERVICE_RUNNING;
use super::EVENT_SERVICE_FAILED;
use super::EVENT_SERVICE_STARTED;
use super::ServiceState;
use super::log_error;
use super::log_information;
use crate::package_lifecycle::PackageLifecycle;
pub(super) fn run(state: &ServiceState, package_lifecycle: &PackageLifecycle) -> Result<()> {
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| {
package_lifecycle.register_authenticated_user(installation, user_token)
},
|| {
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()?;
}
Ok(())
}