Files
ProxyWarden/src-tauri/tests/privileged_jobs_tests.rs
dokril efda8eb98f
CI / Windows baseline (push) Canceled after 0s
Release v2.0.0
2026-09-10 20:59:52 +03:00

1826 lines
60 KiB
Rust

use proxywarden_lib::component_catalog::ComponentId as CatalogComponentId;
use proxywarden_lib::component_cutover::{
ComponentCutoverUserEvidence, CUTOVER_USER_EVIDENCE_SCHEMA_VERSION,
};
use proxywarden_lib::privileged_jobs::{
cutover_recovery_probe_fingerprint, execute_privileged_job, launch_privileged_job,
parse_early_helper_arguments, validate_install_receipt, CanonicalComponentRoot,
ElevatedJobLauncher, ElevatedLaunchOutcome, ElevationProbe, EpochClock, InstallReceipt,
InstallReceiptManager, InstalledAssetReceipt, InstalledFileReceipt, InstalledPackageSource,
LaunchedPrivilegedJob, ManagedComponent, PlannedAssetFingerprint, PlannedGithubOriginProof,
PlannedPackageFingerprint, PrivilegedAction, PrivilegedActionRunner, PrivilegedCutoverContext,
PrivilegedCutoverMode, PrivilegedJobId, PrivilegedJobLaunchState, PrivilegedJobStatus,
PrivilegedJobStore, PrivilegedJobsError, PrivilegedMutationResult, PrivilegedPlanResolver,
PrivilegedResultCode, PrivilegedRunnerFailure, ResolvedActionPlan,
INSTALL_RECEIPT_SCHEMA_VERSION, MAX_PRIVILEGED_RECORD_BYTES, PRIVILEGED_JOB_SCHEMA_VERSION,
};
use proxywarden_lib::process::{
known_service_path_matches_exact, service_path_matches_exact, KnownWindowsService,
};
use proxywarden_lib::safe_fs;
use proxywarden_lib::storage::StoragePaths;
use serde_json::Value;
use std::cell::{Cell, RefCell};
use std::ffi::OsString;
use std::fs;
#[cfg(windows)]
use std::fs::{File, OpenOptions};
use std::io;
#[cfg(windows)]
use std::io::{Read, Seek};
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use uuid::Uuid;
#[test]
fn early_mode_accepts_only_exact_canonical_uuid_v4_arguments() {
let id = Uuid::new_v4().hyphenated().to_string();
assert_eq!(
parse_early_helper_arguments(Vec::<OsString>::new()).expect("normal launch"),
None
);
assert_eq!(
parse_early_helper_arguments([OsString::from("--other")]).expect("unreserved args"),
None
);
assert_eq!(
parse_early_helper_arguments([OsString::from("--elevated-helper"), OsString::from(&id),])
.expect("exact helper args")
.expect("helper id")
.as_str(),
id
);
assert!(matches!(
parse_early_helper_arguments([
OsString::from("--elevated-helper"),
OsString::from(id.to_uppercase()),
]),
Err(PrivilegedJobsError::InvalidJobId)
));
for invalid in ["not-a-uuid", "00000000-0000-0000-0000-000000000000"] {
assert!(matches!(
parse_early_helper_arguments([
OsString::from("--elevated-helper"),
OsString::from(invalid),
]),
Err(PrivilegedJobsError::InvalidJobId)
));
}
assert!(matches!(
parse_early_helper_arguments([
OsString::from("--elevated-helper"),
OsString::from(&id),
OsString::from("extra"),
]),
Err(PrivilegedJobsError::InvalidArguments)
));
}
#[test]
fn native_service_boundary_has_fixed_names_and_service_specific_paths() {
let expected = Path::new(r"C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe");
assert_eq!(KnownWindowsService::Proxifyre.name(), "ProxiFyreService");
assert_eq!(KnownWindowsService::SingBox.name(), "ProxyWardenSingBox");
assert!(service_path_matches_exact(
r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe""#,
expected
));
let bare = Path::new(r"C:\ProxyWarden\sing-box.exe");
assert!(service_path_matches_exact(
r"c:\proxywarden\sing-box.exe",
bare
));
assert!(!service_path_matches_exact(
r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe" --other"#,
expected
));
assert!(!service_path_matches_exact(
r"C:\Program Files\ProxyWarden\components\other.exe",
expected
));
assert!(known_service_path_matches_exact(
KnownWindowsService::Proxifyre,
r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe" -displayname "ProxiFyre Service" -servicename "ProxiFyreService""#,
expected,
));
assert!(!known_service_path_matches_exact(
KnownWindowsService::Proxifyre,
r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe""#,
expected,
));
}
#[test]
fn request_is_typed_and_contains_no_paths_programs_arguments_or_service_names() {
let fixture = Fixture::new("typed-request");
let launcher = RecordingLauncher::completed();
let launched = fixture.launch(
PrivilegedAction::InstallProxifyre,
&StaticResolver::new(plan(PrivilegedAction::InstallProxifyre)),
&launcher,
);
let json = serde_json::to_string(&launched.request).expect("serialize request");
assert!(json.contains("proxifyre.install"));
assert!(!json.contains("C:\\"));
assert!(!json.contains("serviceName"));
assert!(!json.contains("program"));
assert!(!json.contains("arguments"));
let calls = launcher.calls.borrow();
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].0, fixture.executable);
assert_eq!(calls[0].1, launched.request.job_id().as_str());
}
#[test]
fn schema_v2_actions_and_operation_fingerprints_are_typed_and_domain_separated() {
assert_eq!(PRIVILEGED_JOB_SCHEMA_VERSION, 2);
assert_eq!(
serde_json::to_string(&PrivilegedAction::CutoverProxifyre).expect("cutover action"),
"\"proxifyre.cutover\""
);
assert_eq!(
serde_json::to_string(&PrivilegedAction::CleanupProxifyreQuarantine)
.expect("cleanup action"),
"\"proxifyre.cleanup-quarantine\""
);
let stop = plan(PrivilegedAction::StopProxifyre);
let uninstall = plan(PrivilegedAction::UninstallProxifyre);
let cutover_new = cutover_plan(PrivilegedCutoverMode::New);
let cutover_recovery = cutover_plan(PrivilegedCutoverMode::Recovery);
assert_ne!(stop.operation_fingerprint, uninstall.operation_fingerprint);
assert_ne!(
cutover_new.operation_fingerprint,
cutover_recovery.operation_fingerprint
);
for fingerprint in [
&stop.operation_fingerprint,
&uninstall.operation_fingerprint,
&cutover_new.operation_fingerprint,
&cutover_recovery.operation_fingerprint,
] {
assert_eq!(fingerprint.len(), 64);
assert!(fingerprint.bytes().all(|byte| byte.is_ascii_hexdigit()));
}
}
#[test]
fn cutover_asset_contract_is_conditional_and_exactly_ordered() {
let fixture = Fixture::new("cutover-assets");
let launcher = RecordingLauncher::completed();
for (action, value) in [
(
PrivilegedAction::CutoverProxifyre,
cutover_plan(PrivilegedCutoverMode::New),
),
(
PrivilegedAction::CutoverProxifyre,
cutover_plan(PrivilegedCutoverMode::Recovery),
),
(PrivilegedAction::CleanupProxifyreQuarantine, cleanup_plan()),
] {
fixture.launch(action, &StaticResolver::new(value), &launcher);
}
let new_plan = cutover_plan(PrivilegedCutoverMode::New);
assert_eq!(
new_plan
.package
.as_ref()
.expect("new cutover package")
.assets
.iter()
.map(|asset| asset.component_id)
.collect::<Vec<_>>(),
vec![
CatalogComponentId::Proxifyre,
CatalogComponentId::WindowsPacketFilter,
CatalogComponentId::VcRuntime,
]
);
assert!(cutover_plan(PrivilegedCutoverMode::Recovery)
.package
.is_none());
assert!(cleanup_plan().package.is_none());
let mut recovery_with_package = cutover_plan(PrivilegedCutoverMode::Recovery);
recovery_with_package.package = new_plan.package.clone();
let mut cleanup_with_package = cleanup_plan();
cleanup_with_package.package = new_plan.package;
for (action, invalid) in [
(PrivilegedAction::CutoverProxifyre, recovery_with_package),
(
PrivilegedAction::CleanupProxifyreQuarantine,
cleanup_with_package,
),
] {
assert!(matches!(
launch_privileged_job(
&fixture.store,
action,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(invalid),
&RecordingLauncher::completed(),
),
Err(PrivilegedJobsError::InvalidRecord)
));
}
}
#[test]
fn cutover_operation_fingerprint_binds_startup_session_and_typed_cleanup_evidence() {
let first_new = cutover_plan(PrivilegedCutoverMode::New);
let mut second_context = first_new
.cutover_context
.clone()
.expect("new cutover context");
second_context.initiating_startup_session_id =
Some("44444444-4444-4444-8444-444444444444".to_string());
let second_new = ResolvedActionPlan::new(
PrivilegedAction::CutoverProxifyre,
first_new.package.clone(),
first_new.inventory_fingerprint.clone(),
first_new.configuration_fingerprint.clone(),
Some(second_context),
);
assert_ne!(
first_new.operation_fingerprint,
second_new.operation_fingerprint
);
let cleanup = cleanup_plan();
let mut changed_context = cleanup.cutover_context.clone().expect("cleanup context");
changed_context
.user_evidence
.as_mut()
.expect("typed user evidence")
.startup_session_id = "55555555-5555-4555-8555-555555555555".to_string();
let changed_cleanup = ResolvedActionPlan::new(
PrivilegedAction::CleanupProxifyreQuarantine,
None,
cleanup.inventory_fingerprint.clone(),
cleanup.configuration_fingerprint.clone(),
Some(changed_context),
);
assert_ne!(
cleanup.operation_fingerprint,
changed_cleanup.operation_fingerprint
);
let fixture = Fixture::new("cleanup-evidence");
fixture.launch(
PrivilegedAction::CleanupProxifyreQuarantine,
&StaticResolver::new(cleanup.clone()),
&RecordingLauncher::completed(),
);
let mut invalid_context = cleanup.cutover_context.expect("cleanup context");
invalid_context
.user_evidence
.as_mut()
.expect("typed user evidence")
.route_smoke_confirmed = false;
let invalid = ResolvedActionPlan::new(
PrivilegedAction::CleanupProxifyreQuarantine,
None,
"a".repeat(64),
"e".repeat(64),
Some(invalid_context),
);
assert!(matches!(
launch_privileged_job(
&fixture.store,
PrivilegedAction::CleanupProxifyreQuarantine,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(invalid),
&RecordingLauncher::completed(),
),
Err(PrivilegedJobsError::InvalidRecord)
));
}
#[test]
fn sealed_recovery_probe_is_canonical_and_bound_to_exact_inventory() {
let inventory = "a".repeat(64);
let valid = recovery_probe_plan(&inventory);
let fixture = Fixture::new("valid-recovery-probe");
fixture.launch(
PrivilegedAction::CutoverProxifyre,
&StaticResolver::new(valid.clone()),
&RecordingLauncher::completed(),
);
let mut wrong_hash_context = valid.cutover_context.clone().expect("probe context");
wrong_hash_context.evidence_fingerprint = "b".repeat(64);
let wrong_hash = ResolvedActionPlan::new(
PrivilegedAction::CutoverProxifyre,
None,
inventory.clone(),
"e".repeat(64),
Some(wrong_hash_context),
);
let wrong_inventory = ResolvedActionPlan::new(
PrivilegedAction::CutoverProxifyre,
None,
"c".repeat(64),
"e".repeat(64),
valid.cutover_context,
);
for (case, invalid) in [
("wrong-hash", wrong_hash),
("wrong-inventory", wrong_inventory),
] {
let fixture = Fixture::new(case);
assert!(matches!(
launch_privileged_job(
&fixture.store,
PrivilegedAction::CutoverProxifyre,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(invalid),
&RecordingLauncher::completed(),
),
Err(PrivilegedJobsError::InvalidRecord)
));
}
}
#[test]
fn strict_schema_and_size_limit_reject_before_runner() {
let fixture = Fixture::new("strict-schema");
let resolver = StaticResolver::new(plan(PrivilegedAction::StartProxifyre));
let launched = fixture.launch(
PrivilegedAction::StartProxifyre,
&resolver,
&RecordingLauncher::completed(),
);
let request_path = fixture.request_path(launched.request.job_id());
let mut json: Value =
serde_json::from_slice(&fs::read(&request_path).expect("read request")).expect("json");
json.as_object_mut().expect("request object").insert(
"unexpectedPath".to_string(),
Value::String("C:\\evil".to_string()),
);
fs::write(&request_path, serde_json::to_vec(&json).expect("encode")).expect("mutate request");
let runner = CountingRunner::success();
assert!(matches!(
fixture.execute(&launched, &resolver, &runner),
Err(PrivilegedJobsError::InvalidRecord)
));
assert_eq!(runner.calls.get(), 0);
let second = fixture.launch(
PrivilegedAction::StartProxifyre,
&resolver,
&RecordingLauncher::completed(),
);
fs::write(
fixture.request_path(second.request.job_id()),
vec![b' '; MAX_PRIVILEGED_RECORD_BYTES as usize + 1],
)
.expect("oversize request");
assert!(matches!(
fixture.execute(&second, &resolver, &runner),
Err(PrivilegedJobsError::RecordTooLarge)
));
assert_eq!(runner.calls.get(), 0);
}
#[test]
fn expired_and_future_dated_requests_are_rejected_without_claim() {
for (name, created_at, expected) in [
("expired", 699_u64, "expired"),
("future", 1_031_u64, "future"),
] {
let fixture = Fixture::new(name);
let resolver = StaticResolver::new(plan(PrivilegedAction::StopProxifyre));
let launched = fixture.launch(
PrivilegedAction::StopProxifyre,
&resolver,
&RecordingLauncher::completed(),
);
rewrite_request_field(
&fixture.request_path(launched.request.job_id()),
"createdAtEpochSeconds",
Value::from(created_at),
);
let error = fixture
.execute(&launched, &resolver, &CountingRunner::success())
.expect_err("invalid time");
assert!(
(expected == "expired" && matches!(error, PrivilegedJobsError::Expired))
|| (expected == "future" && matches!(error, PrivilegedJobsError::FutureDated))
);
assert!(!fixture.claim_path(launched.request.job_id()).exists());
}
}
#[test]
fn non_elevated_helper_does_not_create_system_state_or_call_runner() {
let fixture = Fixture::new("not-elevated");
let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox));
let launched = fixture.launch(
PrivilegedAction::StartSingBox,
&resolver,
&RecordingLauncher::completed(),
);
let runner = CountingRunner::success();
let error = execute_privileged_job(
&fixture.store,
launched.request.job_id(),
&fixture.clock,
&FixedElevation(false),
&resolver,
&runner,
)
.expect_err("non-elevated helper must fail");
assert!(matches!(error, PrivilegedJobsError::NotElevated));
assert_eq!(runner.calls.get(), 0);
assert!(!fixture.system_dir().exists());
}
#[test]
fn same_job_is_consumed_once_and_result_identity_is_validated() {
let fixture = Fixture::new("replay");
let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox));
let launched = fixture.launch(
PrivilegedAction::StartSingBox,
&resolver,
&RecordingLauncher::completed(),
);
let runner = CountingRunner::success();
let result = fixture
.execute(&launched, &resolver, &runner)
.expect("first execution");
assert_eq!(result.status, PrivilegedJobStatus::Succeeded);
assert_eq!(result.code, PrivilegedResultCode::Completed);
assert_eq!(runner.calls.get(), 1);
assert!(matches!(
fixture.execute(&launched, &resolver, &runner),
Err(PrivilegedJobsError::Replay)
));
assert_eq!(runner.calls.get(), 1);
assert_eq!(
fixture
.store
.read_result(&launched.request)
.expect("validated result"),
result
);
}
#[test]
fn cutover_success_and_failure_codes_are_stable_and_redacted() {
for (index, code) in [
PrivilegedResultCode::CutoverAwaitingNextStart,
PrivilegedResultCode::CutoverQuarantinePending,
PrivilegedResultCode::CutoverRolledBack,
PrivilegedResultCode::CutoverCleanupPending,
PrivilegedResultCode::CutoverComplete,
]
.into_iter()
.enumerate()
{
let fixture = Fixture::new(&format!("cc-{index}"));
let action = PrivilegedAction::CutoverProxifyre;
let resolver = StaticResolver::new(cutover_plan(PrivilegedCutoverMode::Recovery));
let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed());
let runner = CountingRunner {
calls: Cell::new(0),
result: Ok(PrivilegedMutationResult {
activation: None,
changed: true,
reboot_required: false,
result_code: Some(code),
}),
};
let result = fixture
.execute(&launched, &resolver, &runner)
.expect("stable cutover result");
assert_eq!(result.code, code);
assert!(!result.message.contains("C:\\"));
assert!(!result.message.contains(&"d".repeat(64)));
}
for (index, (failure, code)) in [
(
PrivilegedRunnerFailure::CutoverRecoveryRequired,
PrivilegedResultCode::CutoverRecoveryRequired,
),
(
PrivilegedRunnerFailure::CutoverStateConflict,
PrivilegedResultCode::CutoverStateConflict,
),
(
PrivilegedRunnerFailure::CutoverIdentityRejected,
PrivilegedResultCode::CutoverIdentityRejected,
),
]
.into_iter()
.enumerate()
{
let fixture = Fixture::new(&format!("cf-{index}"));
let action = PrivilegedAction::CutoverProxifyre;
let resolver = StaticResolver::new(cutover_plan(PrivilegedCutoverMode::Recovery));
let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed());
let result = fixture
.execute(&launched, &resolver, &CountingRunner::failure(failure))
.expect("stable failure result");
assert_eq!(result.code, code);
assert_eq!(result.status, PrivilegedJobStatus::Failed);
}
}
#[test]
fn precreated_result_blocks_before_claim_and_runner() {
let fixture = Fixture::new("precreated-result");
let resolver = StaticResolver::new(plan(PrivilegedAction::StopSingBox));
let launched = fixture.launch(
PrivilegedAction::StopSingBox,
&resolver,
&RecordingLauncher::completed(),
);
fixture.prepare_portable_system_dir();
let result_path = fixture.result_path(launched.request.job_id());
fs::write(&result_path, b"{}").expect("precreate result");
safe_fs::protect_path_for_owner_admin_system(&result_path).expect("protect result");
let runner = CountingRunner::success();
assert!(matches!(
fixture.execute(&launched, &resolver, &runner),
Err(PrivilegedJobsError::ResultAlreadyExists)
));
assert_eq!(runner.calls.get(), 0);
assert!(!fixture.claim_path(launched.request.job_id()).exists());
}
#[test]
fn uac_cancel_removes_only_held_request_and_never_creates_system_state() {
let fixture = Fixture::new("uac-cancel");
let resolver = StaticResolver::new(plan(PrivilegedAction::InstallSingBox));
let launched = fixture.launch(
PrivilegedAction::InstallSingBox,
&resolver,
&RecordingLauncher::canceled(),
);
assert_eq!(launched.state, PrivilegedJobLaunchState::Canceled);
assert!(!fixture.request_path(launched.request.job_id()).exists());
assert!(!fixture.system_dir().exists());
}
#[test]
fn indeterminate_wait_keeps_request_for_the_already_started_helper() {
let fixture = Fixture::new("indeterminate-wait");
let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox));
let launched = fixture.launch(
PrivilegedAction::StartSingBox,
&resolver,
&RecordingLauncher::indeterminate(),
);
assert_eq!(launched.state, PrivilegedJobLaunchState::Indeterminate);
assert!(fixture.request_path(launched.request.job_id()).exists());
assert!(!fixture.system_dir().exists());
fixture
.execute(&launched, &resolver, &CountingRunner::success())
.expect("helper terminal result");
wait_until_request_is_removed(&fixture, launched.request.job_id());
}
#[cfg(windows)]
#[test]
fn indeterminate_keeper_blocks_request_write_delete_and_rename_until_terminal_result() {
let fixture = Fixture::new("indeterminate-keeper");
let action = PrivilegedAction::StartSingBox;
let resolver = StaticResolver::new(plan(action));
let launched = fixture.launch(action, &resolver, &RecordingLauncher::indeterminate());
let request = fixture.request_path(launched.request.job_id());
let renamed = request.with_extension("moved.json");
assert!(fs::write(&request, b"{}").is_err());
assert!(fs::remove_file(&request).is_err());
assert!(fs::rename(&request, &renamed).is_err());
assert!(request.exists());
assert!(!renamed.exists());
fixture
.execute(&launched, &resolver, &CountingRunner::success())
.expect("helper terminal result");
wait_until_request_is_removed(&fixture, launched.request.job_id());
}
#[cfg(windows)]
#[test]
fn parent_lease_blocks_tamper_while_helper_reads_and_executes() {
let fixture = Fixture::new("request-lease");
let action = PrivilegedAction::StartProxifyre;
let resolver = StaticResolver::new(plan(action));
let runner = CountingRunner::success();
let tamper_blocked = Cell::new(false);
let launcher = InlineHelperLauncher {
store: &fixture.store,
clock: &fixture.clock,
resolver: &resolver,
runner: &runner,
request_dir: fixture.paths.privileged_jobs_dir.clone(),
tamper_blocked: &tamper_blocked,
};
let launched = fixture.launch(action, &resolver, &launcher);
assert!(tamper_blocked.get());
let helper_result = fixture
.store
.read_result(&launched.request)
.expect("inline helper result");
assert_eq!(
helper_result.status,
PrivilegedJobStatus::Succeeded,
"{helper_result:?}"
);
assert_eq!(runner.calls.get(), 1);
assert!(matches!(
launched.state,
PrivilegedJobLaunchState::Completed { exit_code: 0 }
));
}
#[test]
fn helper_rebuilds_plan_and_blocks_changed_inventory_before_runner() {
let fixture = Fixture::new("plan-change-helper");
let original = plan(PrivilegedAction::StartProxifyre);
let launched = fixture.launch(
PrivilegedAction::StartProxifyre,
&StaticResolver::new(original.clone()),
&RecordingLauncher::completed(),
);
let mut changed = original;
changed.inventory_fingerprint = "b".repeat(64);
let runner = CountingRunner::success();
let result = fixture
.execute(&launched, &StaticResolver::new(changed), &runner)
.expect("structured rejected result");
assert_eq!(result.status, PrivilegedJobStatus::Failed);
assert_eq!(result.code, PrivilegedResultCode::PreconditionFailed);
assert_eq!(runner.calls.get(), 0);
}
#[test]
fn elevated_cutover_mode_mismatch_is_stable_and_never_calls_runner() {
let fixture = Fixture::new("cutover-mode-change");
let launched = fixture.launch(
PrivilegedAction::CutoverProxifyre,
&StaticResolver::new(cutover_plan(PrivilegedCutoverMode::New)),
&RecordingLauncher::completed(),
);
let runner = CountingRunner::success();
let result = fixture
.execute(
&launched,
&StaticResolver::new(cutover_plan(PrivilegedCutoverMode::Recovery)),
&runner,
)
.expect("structured cutover mode rejection");
assert_eq!(result.status, PrivilegedJobStatus::Failed);
assert_eq!(result.code, PrivilegedResultCode::CutoverStateConflict);
assert_eq!(runner.calls.get(), 0);
}
#[test]
fn client_rebuilds_plan_before_launch_and_removes_stale_request() {
let fixture = Fixture::new("plan-change-client");
let action = PrivilegedAction::InstallProxifyre;
let mut changed = plan(action);
changed.inventory_fingerprint = "c".repeat(64);
let resolver = SequenceResolver::new([plan(action), changed]);
let launcher = RecordingLauncher::completed();
assert!(matches!(
launch_privileged_job(
&fixture.store,
action,
&fixture.executable,
&fixture.clock,
&resolver,
&launcher,
),
Err(PrivilegedJobsError::PlanChanged)
));
assert!(launcher.calls.borrow().is_empty());
assert!(fs::read_dir(&fixture.paths.privileged_jobs_dir)
.expect("request dir")
.next()
.is_none());
}
#[test]
fn client_removes_request_when_commit_plan_cannot_be_rebuilt() {
let fixture = Fixture::new("client-plan-error");
let action = PrivilegedAction::StartProxifyre;
let resolver = FailOnSecondResolve {
plan: plan(action),
calls: Cell::new(0),
};
let launcher = RecordingLauncher::completed();
assert!(launch_privileged_job(
&fixture.store,
action,
&fixture.executable,
&fixture.clock,
&resolver,
&launcher,
)
.is_err());
assert_eq!(launcher.calls.borrow().len(), 0);
let requests = fs::read_dir(&fixture.paths.privileged_jobs_dir)
.expect("request directory")
.filter_map(Result::ok)
.filter(|entry| {
entry
.file_name()
.to_string_lossy()
.ends_with(".request.json")
})
.count();
assert_eq!(requests, 0);
}
#[test]
fn store_owned_helper_binding_rejects_another_absolute_executable_before_launcher() {
let fixture = Fixture::new("helper-binding");
let action = PrivilegedAction::StartProxifyre;
let launcher = RecordingLauncher::completed();
let other_executable = fixture
.executable
.parent()
.expect("app root")
.join("CopiedProxyWarden.exe");
assert!(matches!(
launch_privileged_job(
&fixture.store,
action,
&other_executable,
&fixture.clock,
&StaticResolver::new(plan(action)),
&launcher,
),
Err(PrivilegedJobsError::InvalidArguments)
));
assert!(launcher.calls.borrow().is_empty());
assert!(!fixture.paths.privileged_jobs_dir.exists());
}
#[test]
fn package_source_contract_is_bundled_for_install_and_cache_for_update() {
let fixture = Fixture::new("package-source");
let bad_install = plan_with_source(
PrivilegedAction::InstallProxifyre,
InstalledPackageSource::Cache,
);
let bad_update = plan_with_source(
PrivilegedAction::UpdateProxifyre,
InstalledPackageSource::Bundled,
);
for (action, bad_plan) in [
(PrivilegedAction::InstallProxifyre, bad_install),
(PrivilegedAction::UpdateProxifyre, bad_update),
] {
assert!(matches!(
launch_privileged_job(
&fixture.store,
action,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(bad_plan),
&RecordingLauncher::completed(),
),
Err(PrivilegedJobsError::InvalidRecord)
));
}
let install = PrivilegedAction::InstallProxifyre;
let mut wrong_component = plan(install);
wrong_component
.package
.as_mut()
.expect("install package")
.assets[1]
.component_id = CatalogComponentId::Winsw;
let mut wrong_asset_version = plan(install);
wrong_asset_version
.package
.as_mut()
.expect("install package")
.assets[1]
.version = "latest".to_string();
let mut reordered = plan(install);
reordered
.package
.as_mut()
.expect("install package")
.assets
.swap(0, 1);
for invalid in [wrong_component, wrong_asset_version, reordered] {
assert!(matches!(
launch_privileged_job(
&fixture.store,
install,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(invalid),
&RecordingLauncher::completed(),
),
Err(PrivilegedJobsError::InvalidRecord)
));
}
let update = PrivilegedAction::UpdateSingBox;
let mut invalid_origin_plans = Vec::new();
for mutate in [
|proof: &mut PlannedGithubOriginProof| proof.release_id = 0,
|proof: &mut PlannedGithubOriginProof| proof.asset_id = 0,
|proof: &mut PlannedGithubOriginProof| proof.stable_tag = "v9.9.9".to_string(),
|proof: &mut PlannedGithubOriginProof| proof.asset_name = "other.zip".to_string(),
|proof: &mut PlannedGithubOriginProof| proof.repository = "attacker/repo".to_string(),
|proof: &mut PlannedGithubOriginProof| proof.size += 1,
|proof: &mut PlannedGithubOriginProof| proof.sha256_from_api = "f".repeat(64),
] {
let mut invalid = plan(update);
mutate(
invalid
.package
.as_mut()
.and_then(|package| package.origin_proof.as_mut())
.expect("cache origin proof"),
);
invalid_origin_plans.push(invalid);
}
for invalid in invalid_origin_plans {
assert!(matches!(
launch_privileged_job(
&fixture.store,
update,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(invalid),
&RecordingLauncher::completed(),
),
Err(PrivilegedJobsError::InvalidRecord)
));
}
let mut ambiguous_cache = plan(update);
ambiguous_cache
.package
.as_mut()
.expect("update package")
.assets
.push(PlannedAssetFingerprint {
component_id: CatalogComponentId::SingBox,
version: "1.2.3".to_string(),
name: "second-component.zip".to_string(),
sha256: "f".repeat(64),
size: 43,
});
let launcher = RecordingLauncher::completed();
assert!(matches!(
launch_privileged_job(
&fixture.store,
update,
&fixture.executable,
&fixture.clock,
&StaticResolver::new(ambiguous_cache),
&launcher,
),
Err(PrivilegedJobsError::InvalidRecord)
));
assert!(launcher.calls.borrow().is_empty());
}
#[test]
fn helper_blocks_cached_origin_proof_id_swap_before_runner() {
let fixture = Fixture::new("origin-proof-swap");
let action = PrivilegedAction::UpdateProxifyre;
let initial = plan(action);
let launched = fixture.launch(
action,
&StaticResolver::new(initial.clone()),
&RecordingLauncher::completed(),
);
let mut changed = initial;
changed
.package
.as_mut()
.and_then(|package| package.origin_proof.as_mut())
.expect("cache origin proof")
.release_id += 1;
let runner = CountingRunner::success();
let result = fixture
.execute(&launched, &StaticResolver::new(changed), &runner)
.expect("structured precondition failure");
assert_eq!(result.code, PrivilegedResultCode::PreconditionFailed);
assert_eq!(runner.calls.get(), 0);
}
#[test]
fn helper_revalidates_ttl_after_resolver_and_returns_fixed_failure_without_runner() {
let fixture = Fixture::new("ttl-after-resolver");
let action = PrivilegedAction::StartProxifyre;
let original = plan(action);
let launched = fixture.launch(
action,
&StaticResolver::new(original.clone()),
&RecordingLauncher::completed(),
);
let execution_clock = MutableClock::new(1_000);
let resolver = ExpireDuringResolve {
plan: original,
clock: &execution_clock,
};
let runner = CountingRunner::success();
let result = execute_privileged_job(
&fixture.store,
launched.request.job_id(),
&execution_clock,
&FixedElevation(true),
&resolver,
&runner,
)
.expect("fixed terminal failure");
assert_eq!(result.status, PrivilegedJobStatus::Failed);
assert_eq!(result.code, PrivilegedResultCode::PreconditionFailed);
assert_eq!(runner.calls.get(), 0);
}
#[cfg(windows)]
#[test]
fn failed_final_result_replacement_preserves_running_bytes_exactly() {
let fixture = Fixture::new("atomic-final-result");
let action = PrivilegedAction::StopSingBox;
let resolver = StaticResolver::new(plan(action));
let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed());
let runner = ResultPinningRunner::new(fixture.result_path(launched.request.job_id()));
assert!(matches!(
fixture.execute(&launched, &resolver, &runner),
Err(PrivilegedJobsError::Io(_))
));
let running_bytes = runner.release().expect("captured running bytes");
let persisted = fs::read(fixture.result_path(launched.request.job_id()))
.expect("running result remains readable");
assert_eq!(persisted, running_bytes);
let persisted_json: Value = serde_json::from_slice(&persisted).expect("result json");
assert_eq!(persisted_json["status"], "running");
}
#[cfg(windows)]
#[test]
fn result_watcher_open_read_lease_allows_terminal_atomic_replace() {
let fixture = Arc::new(Fixture::new("result-watcher-replace"));
let action = PrivilegedAction::StartProxifyre;
let resolver = StaticResolver::new(plan(action));
let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed());
let (entered_tx, entered_rx) = mpsc::channel();
let (release_tx, release_rx) = mpsc::channel();
let runner = BlockingRunner {
entered: Mutex::new(Some(entered_tx)),
release: Mutex::new(release_rx),
};
let fixture_thread = Arc::clone(&fixture);
let resolver_thread = resolver.clone();
let job_id = launched.request.job_id().clone();
let handle = thread::spawn(move || {
execute_privileged_job(
&fixture_thread.store,
&job_id,
&fixture_thread.clock,
&FixedElevation(true),
&resolver_thread,
&runner,
)
});
entered_rx
.recv()
.expect("runner entered after Running write");
let (observed_running, mut read_lease) = fixture
.store
.read_result_with_open_lease_for_tests(&launched.request)
.expect("watcher reads Running and keeps the bounded read handle");
assert_eq!(observed_running.status, PrivilegedJobStatus::Running);
release_tx.send(()).expect("release runner");
let terminal = handle
.join()
.expect("helper thread")
.expect("ReplaceFileW succeeds while watcher read handle remains open");
assert_eq!(terminal.status, PrivilegedJobStatus::Succeeded);
assert_eq!(
fixture
.store
.read_result(&launched.request)
.expect("new terminal result is visible"),
terminal
);
let mut original_running_bytes = Vec::new();
read_lease
.seek(std::io::SeekFrom::Start(0))
.expect("rewind old result handle");
read_lease
.read_to_end(&mut original_running_bytes)
.expect("old open handle still references the replaced Running file");
let original_running: Value =
serde_json::from_slice(&original_running_bytes).expect("old Running json");
assert_eq!(original_running["status"], "running");
}
#[test]
fn results_are_fixed_and_do_not_echo_plan_fingerprints_or_paths() {
let fixture = Fixture::new("redaction");
let resolver = StaticResolver::new(plan(PrivilegedAction::UpdateProxifyre));
let launched = fixture.launch(
PrivilegedAction::UpdateProxifyre,
&resolver,
&RecordingLauncher::completed(),
);
let result = fixture
.execute(
&launched,
&resolver,
&CountingRunner::failure(PrivilegedRunnerFailure::PackageVerificationFailed),
)
.expect("structured failure");
let json = serde_json::to_string(&result).expect("result json");
assert_eq!(result.code, PrivilegedResultCode::PackageVerificationFailed);
assert!(!json.contains(&"a".repeat(64)));
assert!(!json.contains("C:\\"));
assert!(!json.contains("https://"));
}
#[test]
fn system_directory_is_never_blessed_when_precreated_by_issuer() {
let fixture = Fixture::new("foreign-system");
let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox));
let launched = fixture.launch(
PrivilegedAction::StartSingBox,
&resolver,
&RecordingLauncher::completed(),
);
let system = fixture.system_dir();
fs::create_dir_all(&system).expect("foreign system dir");
let sentinel = system.join("sentinel.txt");
fs::write(&sentinel, b"issuer-owned").expect("sentinel");
assert!(fixture
.execute(&launched, &resolver, &CountingRunner::success())
.is_err());
assert_eq!(
fs::read(&sentinel).expect("unchanged sentinel"),
b"issuer-owned"
);
assert!(!fixture.claim_path(launched.request.job_id()).exists());
}
#[test]
fn production_system_layout_is_outside_programdata_and_under_the_verified_app_root() {
let fixture = Fixture::new("production-system-layout");
let app_root = fixture.executable.parent().expect("app root");
let store = PrivilegedJobStore::from_storage_paths_with_verified_app_root_for_tests(
&fixture.paths,
app_root,
&fixture.executable,
);
let system = store.system_directory_for_tests();
assert_eq!(system, app_root.join(".proxywarden-privileged-jobs"));
assert!(!system.starts_with(&fixture.paths.root));
assert!(!system.starts_with(&fixture.paths.privileged_jobs_dir));
}
#[test]
fn global_lifecycle_lock_serializes_different_jobs() {
let fixture = Arc::new(Fixture::new("global-lock"));
let action = PrivilegedAction::StartProxifyre;
let resolver = StaticResolver::new(plan(action));
let first = fixture.launch(action, &resolver, &RecordingLauncher::completed());
let second = fixture.launch(action, &resolver, &RecordingLauncher::completed());
let (entered_tx, entered_rx) = mpsc::channel();
let (release_tx, release_rx) = mpsc::channel();
let blocking = BlockingRunner {
entered: Mutex::new(Some(entered_tx)),
release: Mutex::new(release_rx),
};
let fixture_thread = Arc::clone(&fixture);
let resolver_thread = resolver.clone();
let first_id = first.request.job_id().clone();
let handle = thread::spawn(move || {
execute_privileged_job(
&fixture_thread.store,
&first_id,
&fixture_thread.clock,
&FixedElevation(true),
&resolver_thread,
&blocking,
)
});
entered_rx.recv().expect("first runner entered");
assert!(matches!(
fixture.execute(&second, &resolver, &CountingRunner::success()),
Err(PrivilegedJobsError::LifecycleBusy)
));
release_tx.send(()).expect("release first runner");
assert!(handle.join().expect("helper thread").is_ok());
}
#[test]
fn install_receipt_schema_rejects_wrong_root_traversal_hash_and_unknown_fields() {
let fixture = Fixture::new("receipt");
let root = CanonicalComponentRoot::from_current_exe_for_tests(
&fixture.executable,
ManagedComponent::Proxifyre,
)
.expect("canonical root");
let receipt = valid_receipt(&root);
validate_install_receipt(&root, &receipt).expect("valid receipt");
let mut wrong = receipt.clone();
wrong.install_root.push_str("-other");
assert!(matches!(
validate_install_receipt(&root, &wrong),
Err(PrivilegedJobsError::InvalidReceipt)
));
let mut traversal = receipt.clone();
traversal.installed_files[0].relative_path = "..\\evil.exe".to_string();
assert!(validate_install_receipt(&root, &traversal).is_err());
let mut uppercase_hash = receipt.clone();
uppercase_hash.asset.sha256 = "A".repeat(64);
assert!(validate_install_receipt(&root, &uppercase_hash).is_err());
for invalid_version in ["", "running", "1.02", "1.2.3.4.5"] {
let mut invalid = receipt.clone();
invalid.version = invalid_version.to_string();
assert!(
validate_install_receipt(&root, &invalid).is_err(),
"version {invalid_version:?}"
);
}
for invalid_path in [
"CON",
"con.txt",
"PRN.json",
"AUX",
"NUL.log",
"CONIN$",
"conout$.txt",
"CLOCK$.json",
"COM1.exe",
"COM¹.txt",
"LPT9.txt",
"folder.\\file.exe",
"folder \\file.exe",
"bad<name>.exe",
"bad|name.exe",
"bad?name.exe",
"bad*name.exe",
"bad\"name.exe",
] {
let mut invalid = receipt.clone();
invalid.installed_files[0].relative_path = invalid_path.to_string();
assert!(
validate_install_receipt(&root, &invalid).is_err(),
"path {invalid_path:?}"
);
}
let mut invalid_asset = receipt.clone();
invalid_asset.asset.name = "bundle?.zip".to_string();
assert!(validate_install_receipt(&root, &invalid_asset).is_err());
let mut unicode_case_duplicate = receipt.clone();
unicode_case_duplicate
.installed_files
.push(InstalledFileReceipt {
relative_path: "BIN\\ÄPP.EXE".to_string(),
sha256: "c".repeat(64),
size: 7,
});
unicode_case_duplicate.installed_files[0].relative_path = "bin\\äpp.exe".to_string();
assert!(validate_install_receipt(&root, &unicode_case_duplicate).is_err());
let mut json = serde_json::to_value(&receipt).expect("receipt json");
json.as_object_mut().expect("object").insert(
"arbitraryCommand".to_string(),
Value::String("cmd.exe".to_string()),
);
assert!(serde_json::from_value::<InstallReceipt>(json).is_err());
}
#[test]
fn copied_executable_in_user_writable_location_is_not_a_trusted_component_root() {
let fixture = Fixture::new("untrusted-app-root");
fs::create_dir_all(fixture.executable.parent().expect("exe parent")).expect("app dir");
fs::write(&fixture.executable, b"not-an-installed-app").expect("copied exe");
assert!(CanonicalComponentRoot::from_current_exe(
&fixture.executable,
ManagedComponent::Proxifyre,
)
.is_err());
}
struct Fixture {
root: PathBuf,
paths: StoragePaths,
store: PrivilegedJobStore,
executable: PathBuf,
clock: FixedClock,
}
impl Fixture {
fn new(name: &str) -> Self {
let root = std::env::temp_dir().join(format!(
"proxywarden-privileged-{name}-{}",
Uuid::new_v4().hyphenated()
));
fs::create_dir_all(&root).expect("create fixture root");
let paths = StoragePaths::new(root.join("ProgramData"));
let executable = root.join("Program Files").join("ProxyWarden.exe");
let store = PrivilegedJobStore::from_storage_paths_for_tests(&paths, &executable);
Self {
root,
paths,
store,
executable,
clock: FixedClock(1_000),
}
}
fn launch(
&self,
action: PrivilegedAction,
resolver: &dyn PrivilegedPlanResolver,
launcher: &dyn ElevatedJobLauncher,
) -> LaunchedPrivilegedJob {
launch_privileged_job(
&self.store,
action,
&self.executable,
&self.clock,
resolver,
launcher,
)
.expect("launch job")
}
fn execute(
&self,
launched: &LaunchedPrivilegedJob,
resolver: &dyn PrivilegedPlanResolver,
runner: &dyn PrivilegedActionRunner,
) -> Result<proxywarden_lib::privileged_jobs::PrivilegedJobResult, PrivilegedJobsError> {
execute_privileged_job(
&self.store,
launched.request.job_id(),
&self.clock,
&FixedElevation(true),
resolver,
runner,
)
}
fn request_path(&self, id: &PrivilegedJobId) -> PathBuf {
self.paths
.privileged_jobs_dir
.join(format!("{}.request.json", id.as_str()))
}
fn system_dir(&self) -> PathBuf {
self.paths.privileged_jobs_dir.join("system")
}
fn claim_path(&self, id: &PrivilegedJobId) -> PathBuf {
self.system_dir()
.join(format!("{}.claim.json", id.as_str()))
}
fn result_path(&self, id: &PrivilegedJobId) -> PathBuf {
self.system_dir()
.join(format!("{}.result.json", id.as_str()))
}
fn prepare_portable_system_dir(&self) {
let system = self.system_dir();
fs::create_dir_all(&system).expect("system dir");
safe_fs::protect_path_for_owner_admin_system(&system).expect("protect system dir");
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
fn wait_until_request_is_removed(fixture: &Fixture, id: &PrivilegedJobId) {
let request = fixture.request_path(id);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
while request.exists() && std::time::Instant::now() < deadline {
thread::sleep(std::time::Duration::from_millis(10));
}
assert!(!request.exists(), "keeper did not release terminal request");
}
#[derive(Clone, Copy)]
struct FixedClock(u64);
impl EpochClock for FixedClock {
fn now_epoch_seconds(&self) -> u64 {
self.0
}
}
struct MutableClock(Cell<u64>);
impl MutableClock {
fn new(value: u64) -> Self {
Self(Cell::new(value))
}
}
impl EpochClock for MutableClock {
fn now_epoch_seconds(&self) -> u64 {
self.0.get()
}
}
struct FixedElevation(bool);
impl ElevationProbe for FixedElevation {
fn is_elevated(&self) -> bool {
self.0
}
}
#[derive(Clone)]
struct StaticResolver {
plan: ResolvedActionPlan,
}
impl StaticResolver {
fn new(plan: ResolvedActionPlan) -> Self {
Self { plan }
}
}
impl PrivilegedPlanResolver for StaticResolver {
fn resolve(
&self,
_action: PrivilegedAction,
) -> Result<ResolvedActionPlan, PrivilegedRunnerFailure> {
Ok(self.plan.clone())
}
}
struct SequenceResolver {
plans: RefCell<Vec<ResolvedActionPlan>>,
}
impl SequenceResolver {
fn new(plans: impl IntoIterator<Item = ResolvedActionPlan>) -> Self {
let mut plans = plans.into_iter().collect::<Vec<_>>();
plans.reverse();
Self {
plans: RefCell::new(plans),
}
}
}
impl PrivilegedPlanResolver for SequenceResolver {
fn resolve(
&self,
_action: PrivilegedAction,
) -> Result<ResolvedActionPlan, PrivilegedRunnerFailure> {
self.plans
.borrow_mut()
.pop()
.ok_or(PrivilegedRunnerFailure::PreconditionFailed)
}
}
struct FailOnSecondResolve {
plan: ResolvedActionPlan,
calls: Cell<usize>,
}
impl PrivilegedPlanResolver for FailOnSecondResolve {
fn resolve(
&self,
_action: PrivilegedAction,
) -> Result<ResolvedActionPlan, PrivilegedRunnerFailure> {
let call = self.calls.get();
self.calls.set(call + 1);
if call == 0 {
Ok(self.plan.clone())
} else {
Err(PrivilegedRunnerFailure::OperationFailed)
}
}
}
struct ExpireDuringResolve<'a> {
plan: ResolvedActionPlan,
clock: &'a MutableClock,
}
impl PrivilegedPlanResolver for ExpireDuringResolve<'_> {
fn resolve(
&self,
_action: PrivilegedAction,
) -> Result<ResolvedActionPlan, PrivilegedRunnerFailure> {
self.clock
.0
.set(1_000 + proxywarden_lib::privileged_jobs::PRIVILEGED_JOB_TTL_SECONDS + 1);
Ok(self.plan.clone())
}
}
struct CountingRunner {
calls: Cell<usize>,
result: Result<PrivilegedMutationResult, PrivilegedRunnerFailure>,
}
impl CountingRunner {
fn success() -> Self {
Self {
calls: Cell::new(0),
result: Ok(PrivilegedMutationResult {
activation: None,
changed: true,
reboot_required: false,
result_code: None,
}),
}
}
fn failure(failure: PrivilegedRunnerFailure) -> Self {
Self {
calls: Cell::new(0),
result: Err(failure),
}
}
}
impl PrivilegedActionRunner for CountingRunner {
fn run(
&self,
_action: PrivilegedAction,
_plan: &ResolvedActionPlan,
) -> Result<PrivilegedMutationResult, PrivilegedRunnerFailure> {
self.calls.set(self.calls.get() + 1);
self.result.clone()
}
}
#[cfg(windows)]
struct ResultPinningRunner {
result_path: PathBuf,
running_bytes: RefCell<Option<Vec<u8>>>,
pin: RefCell<Option<File>>,
}
#[cfg(windows)]
impl ResultPinningRunner {
fn new(result_path: PathBuf) -> Self {
Self {
result_path,
running_bytes: RefCell::new(None),
pin: RefCell::new(None),
}
}
fn release(&self) -> Option<Vec<u8>> {
self.pin.borrow_mut().take();
self.running_bytes.borrow_mut().take()
}
}
#[cfg(windows)]
impl PrivilegedActionRunner for ResultPinningRunner {
fn run(
&self,
_action: PrivilegedAction,
_plan: &ResolvedActionPlan,
) -> Result<PrivilegedMutationResult, PrivilegedRunnerFailure> {
use std::os::windows::fs::OpenOptionsExt;
let running =
fs::read(&self.result_path).map_err(|_| PrivilegedRunnerFailure::OperationFailed)?;
let mut options = OpenOptions::new();
options.read(true).share_mode(0);
let pin = options
.open(&self.result_path)
.map_err(|_| PrivilegedRunnerFailure::OperationFailed)?;
self.running_bytes.replace(Some(running));
self.pin.replace(Some(pin));
Ok(PrivilegedMutationResult {
activation: None,
changed: true,
reboot_required: false,
result_code: None,
})
}
}
struct BlockingRunner {
entered: Mutex<Option<mpsc::Sender<()>>>,
release: Mutex<mpsc::Receiver<()>>,
}
impl PrivilegedActionRunner for BlockingRunner {
fn run(
&self,
_action: PrivilegedAction,
_plan: &ResolvedActionPlan,
) -> Result<PrivilegedMutationResult, PrivilegedRunnerFailure> {
if let Some(sender) = self.entered.lock().expect("entered lock").take() {
sender.send(()).expect("signal entered");
}
self.release
.lock()
.expect("release lock")
.recv()
.expect("release signal");
Ok(PrivilegedMutationResult {
activation: None,
changed: true,
reboot_required: false,
result_code: None,
})
}
}
struct RecordingLauncher {
outcome: ElevatedLaunchOutcome,
calls: RefCell<Vec<(PathBuf, String)>>,
}
impl RecordingLauncher {
fn completed() -> Self {
Self {
outcome: ElevatedLaunchOutcome::Completed { exit_code: 0 },
calls: RefCell::new(Vec::new()),
}
}
fn canceled() -> Self {
Self {
outcome: ElevatedLaunchOutcome::Canceled,
calls: RefCell::new(Vec::new()),
}
}
fn indeterminate() -> Self {
Self {
outcome: ElevatedLaunchOutcome::Indeterminate,
calls: RefCell::new(Vec::new()),
}
}
}
impl ElevatedJobLauncher for RecordingLauncher {
fn launch(
&self,
executable: &Path,
job_id: &PrivilegedJobId,
) -> io::Result<ElevatedLaunchOutcome> {
self.calls
.borrow_mut()
.push((executable.to_path_buf(), job_id.as_str().to_string()));
Ok(self.outcome)
}
}
#[cfg(windows)]
struct InlineHelperLauncher<'a> {
store: &'a PrivilegedJobStore,
clock: &'a dyn EpochClock,
resolver: &'a dyn PrivilegedPlanResolver,
runner: &'a dyn PrivilegedActionRunner,
request_dir: PathBuf,
tamper_blocked: &'a Cell<bool>,
}
#[cfg(windows)]
impl ElevatedJobLauncher for InlineHelperLauncher<'_> {
fn launch(
&self,
_executable: &Path,
job_id: &PrivilegedJobId,
) -> io::Result<ElevatedLaunchOutcome> {
let request = self
.request_dir
.join(format!("{}.request.json", job_id.as_str()));
self.tamper_blocked.set(fs::write(&request, b"{}").is_err());
execute_privileged_job(
self.store,
job_id,
self.clock,
&FixedElevation(true),
self.resolver,
self.runner,
)
.map_err(io::Error::other)?;
Ok(ElevatedLaunchOutcome::Completed { exit_code: 0 })
}
}
fn plan(action: PrivilegedAction) -> ResolvedActionPlan {
let package = action.requires_test_package().map(|source| {
let components: &[CatalogComponentId] = match action {
PrivilegedAction::InstallProxifyre => &[
CatalogComponentId::Proxifyre,
CatalogComponentId::WindowsPacketFilter,
CatalogComponentId::VcRuntime,
],
PrivilegedAction::UpdateProxifyre => &[CatalogComponentId::Proxifyre],
PrivilegedAction::InstallSingBox => {
&[CatalogComponentId::SingBox, CatalogComponentId::Winsw]
}
PrivilegedAction::UpdateSingBox => &[CatalogComponentId::SingBox],
_ => &[],
};
let assets = components
.iter()
.enumerate()
.map(|(index, component_id)| PlannedAssetFingerprint {
component_id: *component_id,
version: if index == 0 {
"1.2.3".to_string()
} else {
format!("1.2.{}", index + 3)
},
name: format!("component-{index}.zip"),
sha256: format!("{:x}", index + 13).repeat(64)[..64].to_string(),
size: 42 + index as u64,
})
.collect::<Vec<_>>();
let asset = assets.first().expect("package asset");
let origin_proof =
(source == InstalledPackageSource::Cache).then(|| PlannedGithubOriginProof {
repository: match action.component() {
ManagedComponent::Proxifyre => "wiresock/proxifyre",
ManagedComponent::SingBox => "SagerNet/sing-box",
}
.to_string(),
stable_tag: "v1.2.3".to_string(),
release_id: 11,
asset_id: 22,
asset_name: asset.name.clone(),
size: asset.size,
sha256_from_api: asset.sha256.clone(),
});
PlannedPackageFingerprint {
source,
version: "1.2.3".to_string(),
assets,
origin_proof,
}
});
ResolvedActionPlan::new(action, package, "a".repeat(64), "e".repeat(64), None)
}
fn cutover_plan(mode: PrivilegedCutoverMode) -> ResolvedActionPlan {
let package = (mode == PrivilegedCutoverMode::New).then(|| {
plan(PrivilegedAction::InstallProxifyre)
.package
.expect("bundle")
});
ResolvedActionPlan::new(
PrivilegedAction::CutoverProxifyre,
package,
"a".repeat(64),
"e".repeat(64),
Some(PrivilegedCutoverContext {
mode,
cutover_id: (mode == PrivilegedCutoverMode::Recovery)
.then(|| "11111111-1111-4111-8111-111111111111".to_string()),
initiating_startup_session_id: (mode == PrivilegedCutoverMode::New)
.then(|| "22222222-2222-4222-8222-222222222222".to_string()),
evidence_fingerprint: "d".repeat(64),
user_evidence: None,
}),
)
}
fn recovery_probe_plan(inventory_fingerprint: &str) -> ResolvedActionPlan {
ResolvedActionPlan::new(
PrivilegedAction::CutoverProxifyre,
None,
inventory_fingerprint.to_string(),
"e".repeat(64),
Some(PrivilegedCutoverContext {
mode: PrivilegedCutoverMode::Recovery,
cutover_id: None,
initiating_startup_session_id: None,
evidence_fingerprint: cutover_recovery_probe_fingerprint(inventory_fingerprint),
user_evidence: None,
}),
)
}
fn cleanup_plan() -> ResolvedActionPlan {
ResolvedActionPlan::new(
PrivilegedAction::CleanupProxifyreQuarantine,
None,
"a".repeat(64),
"e".repeat(64),
Some(PrivilegedCutoverContext {
mode: PrivilegedCutoverMode::Cleanup,
cutover_id: Some("11111111-1111-4111-8111-111111111111".to_string()),
evidence_fingerprint: "c".repeat(64),
initiating_startup_session_id: None,
user_evidence: Some(ComponentCutoverUserEvidence {
schema_version: CUTOVER_USER_EVIDENCE_SCHEMA_VERSION,
cutover_id: "11111111-1111-4111-8111-111111111111".to_string(),
startup_session_id: "33333333-3333-4333-8333-333333333333".to_string(),
current_inventory_fingerprint: "a".repeat(64),
route_smoke_confirmed: true,
observed_at_epoch_seconds: 700,
confirmed_at_epoch_seconds: Some(701),
}),
}),
)
}
fn plan_with_source(
action: PrivilegedAction,
source: InstalledPackageSource,
) -> ResolvedActionPlan {
let mut value = plan(action);
value.package.as_mut().expect("package action").source = source;
value
}
trait TestActionPackage {
fn requires_test_package(self) -> Option<InstalledPackageSource>;
}
impl TestActionPackage for PrivilegedAction {
fn requires_test_package(self) -> Option<InstalledPackageSource> {
match self {
Self::InstallProxifyre | Self::InstallSingBox => Some(InstalledPackageSource::Bundled),
Self::UpdateProxifyre | Self::UpdateSingBox => Some(InstalledPackageSource::Cache),
_ => None,
}
}
}
fn rewrite_request_field(path: &Path, name: &str, value: Value) {
let mut json: Value =
serde_json::from_slice(&fs::read(path).expect("read request")).expect("request json");
json.as_object_mut()
.expect("request object")
.insert(name.to_string(), value);
fs::write(path, serde_json::to_vec(&json).expect("encode request")).expect("write request");
}
fn valid_receipt(root: &CanonicalComponentRoot) -> InstallReceipt {
InstallReceipt {
schema_version: INSTALL_RECEIPT_SCHEMA_VERSION,
manager: InstallReceiptManager::ProxyWarden,
component: root.component(),
install_root: root.path().display().to_string(),
service_name: "ProxiFyreService".to_string(),
version: "2.2.1".to_string(),
source: InstalledPackageSource::Bundled,
asset: InstalledAssetReceipt {
name: "ProxiFyre.zip".to_string(),
sha256: "a".repeat(64),
size: 100,
},
installed_files: vec![InstalledFileReceipt {
relative_path: "ProxiFyre.exe".to_string(),
sha256: "b".repeat(64),
size: 10,
}],
installed_at: 1_000,
windows_packet_filter: None,
}
}