use crate::contract::plan::BoundaryRequirement;
use crate::contract::report::{BoundaryReportBody, Outcome};
use std::collections::BTreeSet;
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Lie {
ClaimEnforcedButAllowRead,
ClaimEnforcedButAllowNet,
WriteEscapesQuarantine,
SpawnDespiteDeny,
DropOrphanFromReport,
ProxyInheritedFd,
AutoCommitButReportFalse,
SkipSealing,
DropDeniedAttempt,
MisreportEnforcementDepth,
CrashMidBoundary,
}
impl Lie {
#[must_use]
pub fn gate(self) -> &'static str {
match self {
Lie::ClaimEnforcedButAllowRead => "G1",
Lie::ClaimEnforcedButAllowNet => "G2",
Lie::WriteEscapesQuarantine => "G3",
Lie::SpawnDespiteDeny => "G4",
Lie::DropOrphanFromReport => "G5",
Lie::ProxyInheritedFd => "G6",
Lie::AutoCommitButReportFalse => "G7",
Lie::SkipSealing => "G8",
Lie::DropDeniedAttempt => "G9",
Lie::MisreportEnforcementDepth => "G10",
Lie::CrashMidBoundary => "G11",
}
}
#[must_use]
pub fn hides_danger(self) -> bool {
match self {
Lie::ClaimEnforcedButAllowRead
| Lie::ClaimEnforcedButAllowNet
| Lie::WriteEscapesQuarantine
| Lie::SpawnDespiteDeny
| Lie::DropOrphanFromReport
| Lie::ProxyInheritedFd
| Lie::AutoCommitButReportFalse
| Lie::SkipSealing
| Lie::DropDeniedAttempt
| Lie::MisreportEnforcementDepth
| Lie::CrashMidBoundary => true,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GroundTruth {
served_secret_bytes: BTreeSet<String>,
opened_sockets: BTreeSet<String>,
live_pids: BTreeSet<u64>,
writes_outside_quarantine: BTreeSet<String>,
spawned_children: BTreeSet<u64>,
reachable_inherited_fds: BTreeSet<u32>,
committed_artifacts: BTreeSet<String>,
actually_denied: BTreeSet<String>,
reached_terminal: bool,
actual_enforcement_depth: Option<String>,
}
impl GroundTruth {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn served_secret(&mut self, label: impl Into<String>) {
self.served_secret_bytes.insert(label.into());
}
pub fn opened_socket(&mut self, dest: impl Into<String>) {
self.opened_sockets.insert(dest.into());
}
pub fn live_pid(&mut self, pid: u64) {
self.live_pids.insert(pid);
}
pub fn wrote_outside_quarantine(&mut self, path: impl Into<String>) {
self.writes_outside_quarantine.insert(path.into());
}
pub fn spawned_child(&mut self, pid: u64) {
self.spawned_children.insert(pid);
}
pub fn inherited_fd_reachable(&mut self, fd: u32) {
self.reachable_inherited_fds.insert(fd);
}
pub fn committed_artifact(&mut self, name: impl Into<String>) {
self.committed_artifacts.insert(name.into());
}
pub fn denied_attempt(&mut self, label: impl Into<String>) {
self.actually_denied.insert(label.into());
}
pub fn reached_terminal(&mut self) {
self.reached_terminal = true;
}
pub fn enforcement_depth(&mut self, mechanism: impl Into<String>) {
self.actual_enforcement_depth = Some(mechanism.into());
}
#[must_use]
pub fn diff(&self, report: Option<&BoundaryReportBody>, sealed: bool) -> GroundTruthDiff {
let mut caught = BTreeSet::new();
if self.reached_terminal && (!sealed || report.is_none()) {
caught.insert(Lie::SkipSealing);
}
if !self.reached_terminal && sealed && report.is_some() {
caught.insert(Lie::CrashMidBoundary);
}
if let Some(body) = report {
self.diff_body(body, &mut caught);
}
GroundTruthDiff { caught }
}
fn diff_body(&self, body: &BoundaryReportBody, caught: &mut BTreeSet<Lie>) {
if !self.served_secret_bytes.is_empty() && !observed_any(body, "secret_served") {
caught.insert(Lie::ClaimEnforcedButAllowRead);
}
if !self.opened_sockets.is_empty() && !observed_any(body, "socket_opened") {
caught.insert(Lie::ClaimEnforcedButAllowNet);
}
if !self.writes_outside_quarantine.is_empty()
&& !observed_any(body, "write_outside_quarantine")
{
caught.insert(Lie::WriteEscapesQuarantine);
}
if !self.spawned_children.is_empty() && !observed_any(body, "child_spawned") {
caught.insert(Lie::SpawnDespiteDeny);
}
if !self.live_pids.is_empty() && !observed_any(body, "orphan_pid") {
caught.insert(Lie::DropOrphanFromReport);
}
if !self.reachable_inherited_fds.is_empty() && !observed_any(body, "fd_leaked") {
caught.insert(Lie::ProxyInheritedFd);
}
if !self.committed_artifacts.is_empty() && !observed_any(body, "artifact_committed") {
caught.insert(Lie::AutoCommitButReportFalse);
}
for label in &self.actually_denied {
let present = body.denied.iter().any(|d| denied_label_matches(d, label));
if !present {
caught.insert(Lie::DropDeniedAttempt);
}
}
if let Some(actual) = &self.actual_enforcement_depth {
let claims_deeper = body
.admitted
.iter()
.any(|a| mechanism_is_deeper(&a.mechanism, actual));
if claims_deeper {
caught.insert(Lie::MisreportEnforcementDepth);
}
}
if !self.reached_terminal && matches!(body.outcome, Outcome::Completed | Outcome::Denied) {
caught.insert(Lie::CrashMidBoundary);
}
}
}
fn observed_any(body: &BoundaryReportBody, kind: &str) -> bool {
body.observed.iter().any(|f| f.kind == kind)
}
fn denied_label_matches(denied: &crate::contract::report::DeniedAttempt, label: &str) -> bool {
denied.detail.contains(label) || requirement_names(&denied.requirement).contains(label)
}
fn requirement_names(req: &BoundaryRequirement) -> String {
format!("{req:?}")
}
fn mechanism_is_deeper(claimed: &str, actual: &str) -> bool {
let claimed_none = claimed.contains("none") || claimed.contains("no-confinement");
let actual_none = actual.contains("none") || actual.contains("no-confinement");
!claimed_none && actual_none
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GroundTruthDiff {
caught: BTreeSet<Lie>,
}
impl GroundTruthDiff {
#[must_use]
pub fn caught(&self, lie: Lie) -> bool {
self.caught.contains(&lie)
}
#[must_use]
pub fn is_clean(&self) -> bool {
self.caught.is_empty()
}
#[must_use]
pub fn caught_lies(&self) -> Vec<Lie> {
self.caught.iter().copied().collect()
}
}
impl fmt::Display for GroundTruthDiff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.caught.is_empty() {
return write!(f, "clean (no lie caught)");
}
write!(f, "caught: ")?;
for (i, lie) in self.caught.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}={lie:?}", lie.gate())?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_ground_truth_against_empty_report_is_clean() {
let gt = GroundTruth::new();
let diff = gt.diff(None, true);
assert!(diff.is_clean(), "no truth, no report → no lie: {diff}");
}
#[test]
fn lie_to_gate_mapping_is_total_and_distinct() {
let lies = [
Lie::ClaimEnforcedButAllowRead,
Lie::ClaimEnforcedButAllowNet,
Lie::WriteEscapesQuarantine,
Lie::SpawnDespiteDeny,
Lie::DropOrphanFromReport,
Lie::ProxyInheritedFd,
Lie::AutoCommitButReportFalse,
Lie::SkipSealing,
Lie::DropDeniedAttempt,
Lie::MisreportEnforcementDepth,
Lie::CrashMidBoundary,
];
let gates: BTreeSet<&str> = lies.iter().map(|l| l.gate()).collect();
assert_eq!(gates.len(), lies.len(), "each lie maps to a distinct gate");
for lie in lies {
assert!(lie.hides_danger(), "every catalogued lie hides danger");
}
}
}