use crate::contract::backend::Backend;
use crate::contract::budget::{BudgetAvailability, BudgetProfile};
use crate::contract::budget_witness::BudgetWitnesses;
use crate::contract::capability::{Enforcement, EvidenceClaim, EvidenceSet, SupportVerdict};
use crate::contract::host_control::HostControl;
use crate::contract::ids::BackendId;
use crate::contract::plan::{BoundaryPlan, BoundaryRequirement, Workload};
use crate::contract::report::{
BoundaryFinding, BoundaryReportBody, CaptureRefs, DeniedAttempt, ExitStatus, ObservedFact,
Outcome, BOUNDARY_REPORT_SCHEMA_VERSION,
};
use crate::contract::support::{
BackendProfile, BackendProfileSnapshot, RequirementKind, SupportMatrix,
};
use crate::sim::ground_truth::{GroundTruth, Lie};
use crate::sim::Prng;
use batpak::store::{Clock, SimClock};
use std::collections::BTreeMap;
use std::sync::Mutex;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LieMode {
Honest,
Lie(Lie),
}
impl LieMode {
#[must_use]
pub fn token(self) -> u64 {
match self {
LieMode::Honest => 0x0000_0001,
LieMode::Lie(lie) => 0x1000_0000 | (lie as u64),
}
}
#[must_use]
pub fn label(self) -> String {
match self {
LieMode::Honest => "honest".to_string(),
LieMode::Lie(lie) => format!("lie:{}:{lie:?}", lie.gate()),
}
}
}
pub trait LieInjector: Send + Sync {
fn consult(&self) -> LieMode;
}
pub struct SeededLiar {
prng: Mutex<Prng>,
catalogue: Vec<LieMode>,
}
impl SeededLiar {
#[must_use]
pub fn new(seed: u64) -> Self {
Self {
prng: Mutex::new(Prng::new(seed)),
catalogue: full_catalogue(),
}
}
#[must_use]
pub fn over(seed: u64, catalogue: Vec<LieMode>) -> Self {
let catalogue = if catalogue.is_empty() {
vec![LieMode::Honest]
} else {
catalogue
};
Self {
prng: Mutex::new(Prng::new(seed)),
catalogue,
}
}
}
impl LieInjector for SeededLiar {
fn consult(&self) -> LieMode {
let draw = match self.prng.lock() {
Ok(mut prng) => prng.next_u64(),
Err(poisoned) => poisoned.into_inner().next_u64(),
};
let idx = usize::try_from(draw % self.catalogue.len() as u64).unwrap_or(0);
self.catalogue.get(idx).copied().unwrap_or(LieMode::Honest)
}
}
pub struct OneShotLiar {
mode: LieMode,
}
impl OneShotLiar {
#[must_use]
pub fn new(mode: LieMode) -> Self {
Self { mode }
}
}
impl LieInjector for OneShotLiar {
fn consult(&self) -> LieMode {
self.mode
}
}
fn full_catalogue() -> Vec<LieMode> {
let mut modes = vec![LieMode::Honest];
for lie in [
Lie::ClaimEnforcedButAllowRead,
Lie::ClaimEnforcedButAllowNet,
Lie::WriteEscapesQuarantine,
Lie::SpawnDespiteDeny,
Lie::DropOrphanFromReport,
Lie::ProxyInheritedFd,
Lie::AutoCommitButReportFalse,
Lie::SkipSealing,
Lie::DropDeniedAttempt,
Lie::MisreportEnforcementDepth,
Lie::CrashMidBoundary,
] {
modes.push(LieMode::Lie(lie));
}
modes
}
pub struct SimBackend {
id: BackendId,
support: SupportMatrix,
injector: Box<dyn LieInjector>,
budget: BudgetProfile,
clock: SimClock,
simulated_wall_micros: u64,
truth: Mutex<GroundTruth>,
}
fn sim_budget_profile() -> BudgetProfile {
let enforced = |available: u64, mechanism: &str| {
let mut evidence = EvidenceSet::new();
evidence.insert(EvidenceClaim::ResourceUsage);
BudgetAvailability {
available,
enforcement: Enforcement::Enforced,
evidence,
mechanism: mechanism.to_string(),
}
};
BudgetProfile {
wall_micros: enforced(60_000_000, "sim_timer"),
cpu_micros: enforced(60_000_000, "sim_cpu_accountant"),
resident_bytes: enforced(1u64 << 32, "sim_mem_accountant"),
process_count: enforced(64, "sim_process_table"),
handle_count: enforced(1024, "sim_descriptor_table"),
storage_bytes: enforced(1u64 << 32, "sim_storage_quota"),
network_bytes: enforced(1u64 << 30, "sim_network_meter"),
}
}
impl SimBackend {
pub const ID: &'static str = "sim";
#[must_use]
pub fn new(injector: Box<dyn LieInjector>) -> Self {
Self {
id: BackendId::new(Self::ID),
support: deep_support(),
injector,
budget: sim_budget_profile(),
clock: SimClock::new(),
simulated_wall_micros: 0,
truth: Mutex::new(GroundTruth::new()),
}
}
#[must_use]
pub fn with_simulated_wall_micros(mut self, micros: u64) -> Self {
self.simulated_wall_micros = micros;
self
}
#[must_use]
pub fn ground_truth(&self) -> GroundTruth {
match self.truth.lock() {
Ok(truth) => truth.clone(),
Err(poisoned) => poisoned.into_inner().clone(),
}
}
fn record(&self, f: impl FnOnce(&mut GroundTruth)) {
match self.truth.lock() {
Ok(mut truth) => f(&mut truth),
Err(poisoned) => f(&mut poisoned.into_inner()),
}
}
fn reset_truth(&self) {
match self.truth.lock() {
Ok(mut truth) => *truth = GroundTruth::new(),
Err(poisoned) => *poisoned.into_inner() = GroundTruth::new(),
}
}
}
fn deep_verdict() -> SupportVerdict {
SupportVerdict::new(
Enforcement::Enforced,
[
EvidenceClaim::TerminalOutcome,
EvidenceClaim::CapturedStreams,
EvidenceClaim::ResourceUsage,
EvidenceClaim::AllowedActions,
EvidenceClaim::DeniedAttempts,
EvidenceClaim::FilesystemDelta,
EvidenceClaim::ProcessTree,
EvidenceClaim::NetworkActivity,
EvidenceClaim::ArtifactLineage,
EvidenceClaim::MechanismAttestation,
]
.into_iter()
.collect(),
)
}
fn deep_support() -> SupportMatrix {
let mut best = BTreeMap::new();
for kind in [
RequirementKind::Filesystem,
RequirementKind::NetworkDenyAll,
RequirementKind::NetworkAllowList,
RequirementKind::ChildSpawnDenyNewTasks,
RequirementKind::ChildSpawnAllowThreads,
RequirementKind::ChildSpawnAllowDescendants,
RequirementKind::Environment,
RequirementKind::InheritedFdsNone,
RequirementKind::InheritedFdsOnly,
RequirementKind::LaunchWorkload,
RequirementKind::CaptureStreams,
RequirementKind::TempRoot,
RequirementKind::ExposePath,
RequirementKind::CommitArtifact,
RequirementKind::DiscardArtifact,
RequirementKind::Kill,
RequirementKind::ListOutputs,
] {
best.insert(kind, deep_verdict());
}
SupportMatrix::from_best_case(best)
}
fn deep_ceiling() -> BackendProfile {
let mut ceiling = BTreeMap::new();
for kind in [
RequirementKind::Filesystem,
RequirementKind::NetworkDenyAll,
RequirementKind::NetworkAllowList,
RequirementKind::ChildSpawnDenyNewTasks,
RequirementKind::ChildSpawnAllowThreads,
RequirementKind::ChildSpawnAllowDescendants,
RequirementKind::Environment,
RequirementKind::InheritedFdsNone,
RequirementKind::InheritedFdsOnly,
RequirementKind::LaunchWorkload,
RequirementKind::CaptureStreams,
RequirementKind::TempRoot,
RequirementKind::ExposePath,
RequirementKind::CommitArtifact,
RequirementKind::DiscardArtifact,
RequirementKind::Kill,
RequirementKind::ListOutputs,
] {
ceiling.insert(kind, deep_verdict());
}
BackendProfile::from_ceiling(ceiling)
}
impl Backend for SimBackend {
fn id(&self) -> BackendId {
self.id.clone()
}
fn support(&self) -> &SupportMatrix {
&self.support
}
fn probe(&self) -> BackendProfileSnapshot {
let mut probed = BTreeMap::new();
probed.insert("backend".to_string(), "sim".to_string());
probed.insert("confinement".to_string(), "simulated".to_string());
BackendProfileSnapshot {
backend: self.id.clone(),
probed,
budget: self.budget.clone(),
}
}
fn profile(&self, _snap: &BackendProfileSnapshot) -> BackendProfile {
deep_ceiling()
}
fn classify(&self, req: &BoundaryRequirement, profile: &BackendProfile) -> SupportVerdict {
self.support.classify(req, profile)
}
fn execute(&self, plan: &BoundaryPlan) -> BoundaryReportBody {
self.reset_truth();
let mode = self.injector.consult();
let mut run = RunState::new();
let launched_mono_ns = self.clock.now_mono_ns();
self.simulate(plan, mode, &mut run);
let wall_micros =
u64::try_from((self.clock.now_mono_ns() - launched_mono_ns) / 1_000).unwrap_or(0);
run.into_body(plan, &self.id, &self.probe(), wall_micros)
}
}
impl SimBackend {
fn simulate(&self, plan: &BoundaryPlan, mode: LieMode, run: &mut RunState) {
match mode {
LieMode::Honest => self.simulate_honest(plan, run),
LieMode::Lie(lie) => self.simulate_lie(plan, lie, run),
}
}
fn simulate_honest(&self, _plan: &BoundaryPlan, run: &mut RunState) {
self.clock
.advance_us(i64::try_from(self.simulated_wall_micros).unwrap_or(i64::MAX));
self.record(GroundTruth::reached_terminal);
run.outcome = Outcome::Completed;
run.exit = Some(ExitStatus::Code(0));
run.mechanism_override = Some("sim:honest:no-confinement".to_string());
}
fn simulate_lie(&self, plan: &BoundaryPlan, lie: Lie, run: &mut RunState) {
self.record(GroundTruth::reached_terminal);
run.outcome = Outcome::Completed;
run.exit = Some(ExitStatus::Code(0));
match lie {
Lie::ClaimEnforcedButAllowRead => {
self.record(|t| t.served_secret("secret.key"));
}
Lie::ClaimEnforcedButAllowNet => {
self.record(|t| t.opened_socket("10.0.0.1:443"));
}
Lie::WriteEscapesQuarantine => {
self.record(|t| t.wrote_outside_quarantine("/etc/passwd"));
}
Lie::SpawnDespiteDeny => {
self.record(|t| t.spawned_child(4242));
}
Lie::DropOrphanFromReport => {
self.record(|t| t.live_pid(9001));
}
Lie::ProxyInheritedFd => {
self.record(|t| t.inherited_fd_reachable(7));
}
Lie::AutoCommitButReportFalse => {
self.record(|t| t.committed_artifact("out.bin"));
}
Lie::SkipSealing => {
}
Lie::DropDeniedAttempt => {
self.record(|t| t.denied_attempt("network:DenyAll"));
}
Lie::MisreportEnforcementDepth => {
self.record(|t| t.enforcement_depth("none/no-confinement"));
run.mechanism_override = Some("sim:landlock_abi4+pivot_root".to_string());
}
Lie::CrashMidBoundary => {
self.reset_truth();
run.outcome = Outcome::Completed;
}
}
let _ = plan;
}
}
struct RunState {
outcome: Outcome,
exit: Option<ExitStatus>,
observed: Vec<ObservedFact>,
denied: Vec<DeniedAttempt>,
mechanism_override: Option<String>,
}
impl RunState {
fn new() -> Self {
Self {
outcome: Outcome::Completed,
exit: None,
observed: Vec::new(),
denied: Vec::new(),
mechanism_override: None,
}
}
fn into_body(
mut self,
plan: &BoundaryPlan,
backend: &BackendId,
profile: &BackendProfileSnapshot,
wall_micros: u64,
) -> BoundaryReportBody {
let mut admitted = plan.admitted.clone();
if let Some(mechanism) = &self.mechanism_override {
for a in &mut admitted {
a.mechanism = mechanism.clone();
}
}
let mut findings = Vec::new();
for a in &admitted {
findings.push(BoundaryFinding::RequirementAdmitted {
requirement: a.requirement.clone(),
enforcement: a.enforcement,
});
}
if plan.admitted.iter().any(|a| {
matches!(
a.requirement,
BoundaryRequirement::HostControl(HostControl::LaunchWorkload)
)
}) {
self.observed.push(ObservedFact {
kind: "workload_launched".to_string(),
detail: workload_detail(&plan.workload),
});
}
BoundaryReportBody {
schema_version: BOUNDARY_REPORT_SCHEMA_VERSION,
plan_id: plan.plan_id,
backend: backend.clone(),
profile: profile.clone(),
outcome: self.outcome,
admitted,
observed: self.observed,
denied: self.denied,
exit: self.exit,
captured: CaptureRefs::default(),
budget: BudgetWitnesses::with_wall(&plan.budgets, wall_micros),
artifacts: Vec::new(),
findings,
}
}
}
fn workload_detail(workload: &Workload) -> String {
match workload {
Workload::Process { exe, .. } => format!("sim launched process {exe}"),
Workload::Wasm { module_ref } => format!("sim launched wasm {module_ref}"),
}
}
#[must_use]
pub(crate) fn run_seals(mode: LieMode) -> bool {
!matches!(mode, LieMode::Lie(Lie::SkipSealing))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seeded_liar_same_seed_same_sequence() {
let a = SeededLiar::new(0x1234);
let b = SeededLiar::new(0x1234);
for _ in 0..16 {
assert_eq!(a.consult(), b.consult(), "same seed ⇒ same lie sequence");
}
}
#[test]
fn one_shot_liar_is_fixed() {
let liar = OneShotLiar::new(LieMode::Lie(Lie::SpawnDespiteDeny));
assert_eq!(liar.consult(), LieMode::Lie(Lie::SpawnDespiteDeny));
assert_eq!(liar.consult(), LieMode::Lie(Lie::SpawnDespiteDeny));
}
#[test]
fn run_seals_only_when_not_seal_suppressing() {
assert!(run_seals(LieMode::Honest));
assert!(run_seals(LieMode::Lie(Lie::SpawnDespiteDeny)));
assert!(
!run_seals(LieMode::Lie(Lie::SkipSealing)),
"G8 seals nothing"
);
assert!(
run_seals(LieMode::Lie(Lie::CrashMidBoundary)),
"G11 seals a (lying) terminal report"
);
}
#[test]
fn honest_sim_admits_a_budget_that_the_inert_floor_refuses() {
use crate::backend::inert::InertBackend;
use crate::contract::budget::{
budget_admit, BudgetFailure, BudgetRefusal, BudgetRequest, BudgetRequirements,
DerivedMinimums, MinGuarantee,
};
let dimension = || {
let mut evidence = EvidenceSet::new();
evidence.insert(EvidenceClaim::ResourceUsage);
BudgetRequest {
limit: 8,
guarantee: MinGuarantee::Mediated,
evidence,
}
};
let request = BudgetRequirements {
wall_micros: dimension(),
cpu_micros: dimension(),
resident_bytes: dimension(),
process_count: dimension(),
handle_count: dimension(),
storage_bytes: dimension(),
network_bytes: dimension(),
};
let derived = DerivedMinimums::default();
let sim = SimBackend::new(Box::new(OneShotLiar::new(LieMode::Honest)));
let admitted = budget_admit(&request, &sim.probe().budget, &derived, [0u8; 32]);
assert!(
admitted.is_ok(),
"honest sim admits a Mediated-guarantee budget: {admitted:?}"
);
let inert = InertBackend::new();
let refused = budget_admit(&request, &inert.probe().budget, &derived, [0u8; 32]);
assert!(
matches!(
refused,
Err(BudgetRefusal {
failure: BudgetFailure::GuaranteeInsufficient,
..
})
),
"the Inert floor cannot guarantee any budget: {refused:?}"
);
}
#[test]
fn honest_sim_witnesses_wall_time_within_budget_and_trips_when_over() {
use crate::contract::budget::{BudgetRequirements, MinGuarantee};
use crate::contract::budget_witness::BudgetFinding;
use crate::contract::host_control::HostControl;
use crate::contract::ids::BackendId;
use crate::contract::plan::{BoundarySpec, EvidenceRequirements, Workload};
use crate::contract::registry::{BackendRegistry, BoundaryPlanner, BoundaryRunner};
use std::sync::Arc;
let spec = BoundarySpec {
workload: Workload::Process {
exe: "true".to_string(),
args: Vec::new(),
},
capabilities: Vec::new(),
controls: vec![HostControl::LaunchWorkload],
budgets: BudgetRequirements::uniform(50, MinGuarantee::Mediated),
evidence: EvidenceRequirements::default(),
};
let within = run_wall(&spec, 40);
assert_eq!(within.budget.wall_micros.observed_usage, 40);
assert_eq!(
within.budget.wall_micros.finding,
BudgetFinding::WithinLimit
);
assert_eq!(
within.budget.cpu_micros.finding,
BudgetFinding::ObservationUnavailable
);
let over = run_wall(&spec, 90);
assert_eq!(over.budget.wall_micros.observed_usage, 90);
assert_eq!(
over.budget.wall_micros.finding,
BudgetFinding::LimitReachedEnforced
);
fn run_wall(
spec: &BoundarySpec,
wall_micros: u64,
) -> crate::contract::report::BoundaryReportBody {
let backend = SimBackend::new(Box::new(OneShotLiar::new(LieMode::Honest)))
.with_simulated_wall_micros(wall_micros);
let mut registry = BackendRegistry::new();
registry.register(Arc::new(backend));
let plan = BoundaryPlanner::new(®istry)
.plan(spec, &BackendId::new(SimBackend::ID))
.expect("honest sim admits");
BoundaryRunner::new(®istry)
.run(&plan)
.expect("run seals")
.body
}
}
}