mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0023 (20260516T052512Z-20f8)
This commit is contained in:
parent
1d1975a2ea
commit
6189c4a4c5
20 changed files with 297 additions and 1 deletions
|
|
@ -448,6 +448,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ impl CompositeReverifier for DefaultCompositeReverifier {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -256,6 +257,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -687,6 +687,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ use crate::dynamic::spec::{HarnessSpec, SPEC_FORMAT_VERSION};
|
|||
use crate::dynamic::stubs::StubHarness;
|
||||
use crate::dynamic::telemetry::{self, SamplingPolicy, TelemetryEvent};
|
||||
use crate::dynamic::toolchain;
|
||||
use crate::evidence::{InconclusiveReason, SpecDerivationStrategy, UnsupportedReason};
|
||||
use crate::evidence::{HardeningSummary, InconclusiveReason, SpecDerivationStrategy, UnsupportedReason};
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::evidence::HardeningPrimitive;
|
||||
use crate::summary::GlobalSummaries;
|
||||
use crate::utils::config::Config;
|
||||
use std::path::Path;
|
||||
|
|
@ -305,6 +307,7 @@ fn entry_kind_unsupported_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -349,6 +352,7 @@ fn spec_derivation_failed_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -367,6 +371,7 @@ fn spec_derivation_failed_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -474,6 +479,7 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -558,6 +564,7 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -588,6 +595,7 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -732,6 +740,91 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
|
|||
}
|
||||
|
||||
|
||||
/// Project the platform-cfg'd [`crate::dynamic::sandbox::HardeningRecord`]
|
||||
/// into the portable [`HardeningSummary`] that lands on
|
||||
/// [`VerifyResult::hardening_outcome`]. Returns `None` when the run did
|
||||
/// not record a hardening outcome (docker backend, non-Linux/non-macOS
|
||||
/// host, or `Standard` profile on a host whose backend skipped the wrap).
|
||||
///
|
||||
/// Exposed for tests so a `sandbox::run`-driven probe can assert that the
|
||||
/// projection lands the same record `build_verdict` would stamp on a
|
||||
/// `Confirmed` `VerifyResult` from the same triggering attempt.
|
||||
pub fn summarize_hardening(
|
||||
outcome: &crate::dynamic::sandbox::SandboxOutcome,
|
||||
) -> Option<HardeningSummary> {
|
||||
use crate::dynamic::sandbox::HardeningRecord;
|
||||
let record = outcome.hardening_outcome.as_ref()?;
|
||||
match record {
|
||||
#[cfg(target_os = "linux")]
|
||||
HardeningRecord::Linux(o) => {
|
||||
use crate::dynamic::sandbox::process_linux::{
|
||||
HardeningLevel, PrimitiveStatus, ProcessHardeningProfileTag,
|
||||
};
|
||||
fn status_str(s: PrimitiveStatus) -> (String, Option<i32>) {
|
||||
match s {
|
||||
PrimitiveStatus::Skipped => ("skipped".to_owned(), None),
|
||||
PrimitiveStatus::Applied => ("applied".to_owned(), None),
|
||||
PrimitiveStatus::Failed(errno) => ("failed".to_owned(), Some(errno)),
|
||||
}
|
||||
}
|
||||
let primitives = [
|
||||
("no_new_privs", o.no_new_privs),
|
||||
("rlimit_cpu", o.rlimit_cpu),
|
||||
("rlimit_nofile", o.rlimit_nofile),
|
||||
("rlimit_as", o.rlimit_as),
|
||||
("unshare", o.unshare),
|
||||
("chroot", o.chroot),
|
||||
("seccomp", o.seccomp),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, st)| {
|
||||
let (status, errno) = status_str(st);
|
||||
HardeningPrimitive {
|
||||
name: name.to_owned(),
|
||||
status,
|
||||
errno,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let level = match o.level() {
|
||||
HardeningLevel::Baseline => "baseline",
|
||||
HardeningLevel::Full => "full",
|
||||
HardeningLevel::Partial => "partial",
|
||||
HardeningLevel::None => "none",
|
||||
};
|
||||
// The Linux backend uses the same `.sb`-style profile name
|
||||
// surface (Standard / Strict) as macOS via the profile tag.
|
||||
let profile = match o.profile {
|
||||
ProcessHardeningProfileTag::Standard => String::new(),
|
||||
ProcessHardeningProfileTag::Strict => "strict".to_owned(),
|
||||
};
|
||||
Some(HardeningSummary {
|
||||
backend: "linux-process".to_owned(),
|
||||
level: level.to_owned(),
|
||||
profile,
|
||||
primitives,
|
||||
})
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
HardeningRecord::Macos(o) => {
|
||||
use crate::dynamic::sandbox::process_macos::HardeningLevel;
|
||||
let level = match o.level {
|
||||
HardeningLevel::Trusted => "trusted",
|
||||
HardeningLevel::Sandboxed => "sandboxed",
|
||||
HardeningLevel::Failed => "failed",
|
||||
};
|
||||
Some(HardeningSummary {
|
||||
backend: "macos-process".to_owned(),
|
||||
level: level.to_owned(),
|
||||
profile: o.profile.clone(),
|
||||
primitives: Vec::new(),
|
||||
})
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_verdict(
|
||||
finding_id: &str,
|
||||
spec: &HarnessSpec,
|
||||
|
|
@ -762,6 +855,7 @@ fn build_verdict(
|
|||
.get(i)
|
||||
.map(|p| p.bytes)
|
||||
.unwrap_or(b"");
|
||||
let hardening_outcome = summarize_hardening(&run.attempts[i].outcome);
|
||||
|
||||
// Emit repro artifact.
|
||||
let repro_result = crate::dynamic::repro::write(
|
||||
|
|
@ -780,6 +874,7 @@ fn build_verdict(
|
|||
differential: run.differential.clone(),
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: hardening_outcome.clone(),
|
||||
},
|
||||
&run.harness_source,
|
||||
&run.entry_source,
|
||||
|
|
@ -802,6 +897,7 @@ fn build_verdict(
|
|||
differential: run.differential,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -817,6 +913,7 @@ fn build_verdict(
|
|||
differential: run.differential,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome,
|
||||
}
|
||||
} else if run.unrelated_crash {
|
||||
// Phase 08 §C.4: the harness crashed but the death
|
||||
|
|
@ -838,6 +935,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
} else if run.no_benign_control {
|
||||
// Phase 07 §4.1: vuln oracle + sink-hit fired but the
|
||||
|
|
@ -858,6 +956,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
} else if let Some(d) = run.differential.as_ref() {
|
||||
// Differential ran but didn't produce `Confirmed`. Map
|
||||
|
|
@ -881,6 +980,7 @@ fn build_verdict(
|
|||
differential: run.differential,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
crate::evidence::DifferentialVerdict::ReversedDifferential => {
|
||||
|
|
@ -900,6 +1000,7 @@ fn build_verdict(
|
|||
differential: run.differential,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
crate::evidence::DifferentialVerdict::Confirmed
|
||||
|
|
@ -915,6 +1016,7 @@ fn build_verdict(
|
|||
differential: run.differential,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
}
|
||||
} else if run.oracle_collision {
|
||||
|
|
@ -933,6 +1035,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
} else {
|
||||
VerifyResult {
|
||||
|
|
@ -947,6 +1050,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -962,6 +1066,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
Err(RunError::Harness(e)) => {
|
||||
// Defence-in-depth residual for `EntryKindUnsupported` from the
|
||||
|
|
@ -1007,6 +1112,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
Err(RunError::BuildFailed { stderr, attempts: build_att }) => VerifyResult {
|
||||
|
|
@ -1021,6 +1127,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
Err(RunError::Sandbox(e)) => VerifyResult {
|
||||
finding_id: finding_id.to_owned(),
|
||||
|
|
@ -1034,6 +1141,7 @@ fn build_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1142,6 +1250,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
// Insert.
|
||||
|
|
@ -1193,6 +1302,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
insert_verdict_cache(&db_path, "spec_aaa", "hash_xyz", "", "python-3.11", &result);
|
||||
|
|
@ -1230,6 +1340,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
insert_verdict_cache(db_path, "spec", "hash", "", "python-3", &result);
|
||||
assert!(!db_path.exists(), "insert must not create a new DB");
|
||||
|
|
@ -1286,6 +1397,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
// Insert directly with the old corpus_version bypassing the helper.
|
||||
|
|
|
|||
|
|
@ -506,6 +506,42 @@ pub struct DifferentialProbeRecord {
|
|||
pub payload_id: String,
|
||||
}
|
||||
|
||||
/// Per-primitive entry inside [`HardeningSummary::primitives`].
|
||||
///
|
||||
/// Mirrors the Linux process backend's `PrimitiveStatus`-per-primitive
|
||||
/// table without depending on the `dynamic` feature. `status` is one of
|
||||
/// `"applied"`, `"failed"`, or `"skipped"`; `errno` is populated when
|
||||
/// `status == "failed"`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct HardeningPrimitive {
|
||||
pub name: String,
|
||||
pub status: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub errno: Option<i32>,
|
||||
}
|
||||
|
||||
/// Portable, JSON-serialisable projection of the per-run hardening
|
||||
/// outcome the process backend stamps on `SandboxOutcome`.
|
||||
///
|
||||
/// Stored on [`VerifyResult::hardening_outcome`] so callers (eval-corpus
|
||||
/// tabulator, repro round-trips, end-to-end acceptance tests) can assert
|
||||
/// on the matched profile and per-primitive status without depending on
|
||||
/// the platform-cfg'd `HardeningRecord` enum. `backend` is one of
|
||||
/// `"linux-process"` or `"macos-process"`; `level` is the coarse outcome
|
||||
/// (`"trusted"` / `"sandboxed"` / `"failed"` on macOS;
|
||||
/// `"baseline"` / `"full"` / `"partial"` / `"none"` on Linux); `profile`
|
||||
/// is the matched `.sb` name on macOS and empty on Linux; `primitives`
|
||||
/// is empty on macOS and one entry per primitive on Linux.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct HardeningSummary {
|
||||
pub backend: String,
|
||||
pub level: String,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub profile: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub primitives: Vec<HardeningPrimitive>,
|
||||
}
|
||||
|
||||
/// Full record of a Phase 07 differential confirmation run.
|
||||
///
|
||||
/// Captures the rule's verdict plus the raw probe traces from both the
|
||||
|
|
@ -584,6 +620,14 @@ pub struct VerifyResult {
|
|||
/// `wrong_confirmed` column in `tests/eval_corpus/tabulate.py`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub wrong: Option<bool>,
|
||||
/// Phase 17/18 per-run hardening outcome, projected from the
|
||||
/// triggering attempt's [`crate::dynamic::sandbox::SandboxOutcome`].
|
||||
/// Populated only when a payload actually ran under the process
|
||||
/// backend on Linux or macOS and the run captured a primitive
|
||||
/// outcome; `None` for docker-backend runs, host platforms with no
|
||||
/// hardening primitives, or verdicts that never executed a payload.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub hardening_outcome: Option<HardeningSummary>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -1159,6 +1159,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1181,6 +1182,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1197,6 +1199,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1213,6 +1216,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1229,6 +1233,7 @@ mod tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ fn verdict(status: VerifyStatus, reason: Option<InconclusiveReason>) -> VerifyRe
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -584,6 +584,7 @@ pub fn run_shape_fixture_lang(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
Err(RunError::NoPayloadsForCap) => VerifyResult {
|
||||
|
|
@ -598,6 +599,7 @@ pub fn run_shape_fixture_lang(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
Err(e) => VerifyResult {
|
||||
finding_id: spec.finding_id.clone(),
|
||||
|
|
@ -611,6 +613,7 @@ pub fn run_shape_fixture_lang(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ fn diag_with_verdict(status: VerifyStatus) -> Diag {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
VerifyStatus::NotConfirmed => VerifyResult {
|
||||
finding_id: "abc123".into(),
|
||||
|
|
@ -93,6 +94,7 @@ fn diag_with_verdict(status: VerifyStatus) -> Diag {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
VerifyStatus::Unsupported => VerifyResult {
|
||||
finding_id: "abc123".into(),
|
||||
|
|
@ -106,6 +108,7 @@ fn diag_with_verdict(status: VerifyStatus) -> Diag {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
VerifyStatus::Inconclusive => VerifyResult {
|
||||
finding_id: "abc123".into(),
|
||||
|
|
@ -119,6 +122,7 @@ fn diag_with_verdict(status: VerifyStatus) -> Diag {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ fn set_verdict(
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -170,6 +171,7 @@ fn new_confirmed_fails_no_new_confirmed_gate() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ mod go_fixture_tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ mod java_fixture_tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ mod js_fixture_tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ fn json_dynamic_verdict_confirmed_serialises_correctly() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
|
@ -100,6 +101,7 @@ fn json_dynamic_verdict_not_confirmed_serialises_correctly() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
|
@ -165,6 +167,7 @@ fn json_unsupported_verdict_has_reason() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ mod php_fixture_tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ mod repro_determinism_tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ mod repro_hermetic_tests {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -350,6 +350,111 @@ except Exception as exc:
|
|||
"refuse_filesystem_confirm should be false when sandbox-exec is reachable"
|
||||
);
|
||||
}
|
||||
|
||||
/// Phase 18 verifier-side projection: when a real strict run lands a
|
||||
/// macOS `HardeningRecord`, `summarize_hardening` collapses it into
|
||||
/// the portable [`crate::evidence::HardeningSummary`] that
|
||||
/// `build_verdict` stamps on a `Confirmed` `VerifyResult`. Drives
|
||||
/// the same `sandbox::run` path the existing
|
||||
/// `path_traversal_payload_blocked_under_strict` test uses, then
|
||||
/// asserts on the projection that would land on
|
||||
/// `VerifyResult::hardening_outcome` if this run had triggered the
|
||||
/// finding's oracle.
|
||||
#[test]
|
||||
fn summarize_hardening_lands_path_traversal_on_strict_file_io_run() {
|
||||
unsafe { std::env::remove_var(SANDBOX_EXEC_BIN_ENV) };
|
||||
if !sandbox_exec_available() {
|
||||
eprintln!("SKIP: /usr/bin/sandbox-exec missing — cannot exercise wrap");
|
||||
return;
|
||||
}
|
||||
const FILE_IO: u32 = 1 << 5;
|
||||
let tmp = workdir();
|
||||
let harness = build_harness(tmp.path());
|
||||
let opts = strict_opts(FILE_IO);
|
||||
let result = sandbox::run(&harness, b"", &opts).expect("sandbox::run");
|
||||
let summary = nyx_scanner::dynamic::verify::summarize_hardening(&result)
|
||||
.expect("hardening summary should populate after a strict macOS run");
|
||||
assert_eq!(summary.backend, "macos-process");
|
||||
assert_eq!(summary.level, "sandboxed");
|
||||
assert_eq!(
|
||||
summary.profile, "path_traversal",
|
||||
"FILE_IO-cap strict run should select the path_traversal profile"
|
||||
);
|
||||
assert!(
|
||||
summary.primitives.is_empty(),
|
||||
"macOS backend records no per-primitive entries"
|
||||
);
|
||||
}
|
||||
|
||||
/// Standard-profile runs leave `SandboxOutcome::hardening_outcome`
|
||||
/// unset, so `summarize_hardening` returns `None` and
|
||||
/// `VerifyResult::hardening_outcome` stays `None`. Companion to
|
||||
/// `standard_profile_does_not_wrap_with_sandbox_exec`.
|
||||
#[test]
|
||||
fn summarize_hardening_returns_none_for_standard_profile_run() {
|
||||
unsafe { std::env::remove_var(SANDBOX_EXEC_BIN_ENV) };
|
||||
let tmp = workdir();
|
||||
let harness = build_harness(tmp.path());
|
||||
let opts = standard_opts();
|
||||
let result = sandbox::run(&harness, b"", &opts).expect("sandbox::run");
|
||||
assert!(
|
||||
nyx_scanner::dynamic::verify::summarize_hardening(&result).is_none(),
|
||||
"standard profile should leave hardening_outcome unset"
|
||||
);
|
||||
}
|
||||
|
||||
/// Round-trip the portable summary through JSON to lock in the
|
||||
/// repro-bundle wire shape: `VerifyResult::hardening_outcome` lands
|
||||
/// on `expected/verdict.json` so the eval-corpus tabulator and any
|
||||
/// downstream replay reads the same fields back.
|
||||
#[test]
|
||||
fn hardening_summary_round_trips_through_json() {
|
||||
use nyx_scanner::evidence::{HardeningSummary, HardeningPrimitive};
|
||||
let summary = HardeningSummary {
|
||||
backend: "macos-process".into(),
|
||||
level: "sandboxed".into(),
|
||||
profile: "path_traversal".into(),
|
||||
primitives: vec![],
|
||||
};
|
||||
let json = serde_json::to_string(&summary).expect("serialize");
|
||||
let parsed: HardeningSummary = serde_json::from_str(&json).expect("deserialize");
|
||||
assert_eq!(parsed, summary);
|
||||
|
||||
// Defaults: missing `profile` and `primitives` must decode as
|
||||
// empty so older `verdict.json` payloads keep round-tripping.
|
||||
let minimal: HardeningSummary =
|
||||
serde_json::from_str(r#"{"backend":"linux-process","level":"full"}"#)
|
||||
.expect("minimal decode");
|
||||
assert_eq!(minimal.profile, "");
|
||||
assert!(minimal.primitives.is_empty());
|
||||
|
||||
// Linux-shape: per-primitive entries decode + re-encode with
|
||||
// their `errno` field intact when populated.
|
||||
let with_primitives = HardeningSummary {
|
||||
backend: "linux-process".into(),
|
||||
level: "partial".into(),
|
||||
profile: "strict".into(),
|
||||
primitives: vec![
|
||||
HardeningPrimitive {
|
||||
name: "no_new_privs".into(),
|
||||
status: "applied".into(),
|
||||
errno: None,
|
||||
},
|
||||
HardeningPrimitive {
|
||||
name: "seccomp".into(),
|
||||
status: "failed".into(),
|
||||
errno: Some(1),
|
||||
},
|
||||
],
|
||||
};
|
||||
let json = serde_json::to_string(&with_primitives).expect("serialize primitives");
|
||||
assert!(
|
||||
json.contains("\"errno\":1"),
|
||||
"errno field should survive JSON round-trip; got: {json}"
|
||||
);
|
||||
let parsed: HardeningSummary = serde_json::from_str(&json).expect("decode primitives");
|
||||
assert_eq!(parsed, with_primitives);
|
||||
}
|
||||
}
|
||||
|
||||
// Non-macOS placeholder so `cargo nextest run --test sandbox_hardening_macos`
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ fn sarif_confirmed_verdict_sets_partial_fingerprint() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
|
@ -111,6 +112,7 @@ fn sarif_not_confirmed_verdict_sets_partial_fingerprint() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
|
@ -140,6 +142,7 @@ fn sarif_unsupported_verdict_sets_partial_fingerprint() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
|
@ -174,6 +177,7 @@ fn sarif_inconclusive_verdict_sets_partial_fingerprint() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
|
@ -224,6 +228,7 @@ fn sarif_confirmed_verdict_nyx_dynamic_verdict_contains_triggered_payload() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
|
@ -257,6 +262,7 @@ fn sarif_all_four_statuses_produce_partial_fingerprint() {
|
|||
differential: None,
|
||||
replay_stable: None,
|
||||
wrong: None,
|
||||
hardening_outcome: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue