From 91f027ae61103d2014909b620d6b316cdd3c542d51630a23b5e0e01ab8867134 Mon Sep 17 00:00:00 2001 From: Tyler J King Date: Sat, 25 Apr 2026 03:18:56 -0400 Subject: [PATCH] libgsh: complete scenario coverage for corpus_check execution paths Adds the ReadFailed scenario (binary path resolves to a directory so exists() succeeds but read() fails) and a scenarios coverage map at the top of the test module. The map links each test to the audit fix scenarios: - valid CID, content matches: Allowed - valid CID at admission, tampered content at execution: ContentMismatch - missing binary where directory exists: Denied (sanity preserved) - binary present but unreadable: ReadFailed (fail-closed) Plus the existing sentinels for ungoverned-CID and corpus-not-mounted. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Tyler J King --- libgsh/src/corpus.rs | 51 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/libgsh/src/corpus.rs b/libgsh/src/corpus.rs index 4fff4d7..bb8d5b4 100644 --- a/libgsh/src/corpus.rs +++ b/libgsh/src/corpus.rs @@ -149,11 +149,30 @@ pub fn corpus_check_with_base(corpus_cid: &str, command: &str, base_dir: &str) - #[cfg(test)] mod tests { + //! Scenario coverage map (execution half of the CID-content + //! verification audit fix): + //! + //! - **Valid CID, content matches: execution allowed** — + //! `binary_with_matching_content_is_allowed`. + //! - **Valid CID at admission, tampered content at execution: + //! execution denies** — `tampered_content_triggers_content_mismatch`. + //! - **Missing binary where directory exists: denied (existing + //! behavior preserved as sanity check)** — + //! `missing_binary_in_corpus_is_denied`. + //! - **Binary present but unreadable: denied fail-closed** — + //! `unreadable_binary_triggers_read_failed`. + //! - **Sentinel: ungoverned CID** — `ungoverned_skips_check`. + //! - **Sentinel: corpus directory not mounted on host** — + //! `missing_corpus_dir_reports_not_mounted`. + //! + //! The admission half (forged CID rejected at CRD reconcile) is + //! covered in corpus-operator::verifier. + use super::*; - /// Write bytes to `dir/cid/name` and return the CID derived from those - /// bytes so the caller can pass a matching CID for the happy path or - /// a different one to simulate tamper. + /// Write bytes to `dir/cid/name` and return the path so the caller can + /// pass a matching CID for the happy path or a different one to + /// simulate tamper. fn write_binary(dir: &Path, cid: &str, name: &str, contents: &[u8]) -> PathBuf { let corpus_dir = dir.join(cid); std::fs::create_dir_all(&corpus_dir).unwrap(); @@ -231,4 +250,30 @@ mod tests { other => panic!("expected ContentMismatch, got {other:?}"), } } + + /// Place a directory at the path where the binary should live; the + /// `exists()` check passes but `read()` fails. Verifies the fail-closed + /// path: an unreadable binary is denied rather than allowed. + #[test] + fn unreadable_binary_triggers_read_failed() { + let dir = tempfile::tempdir().unwrap(); + let claimed = cid_of(b"any-content"); + let corpus_dir = dir.path().join(&claimed); + // Make a directory at the binary path — it satisfies `exists()` but + // `read()` will fail with EISDIR or similar. + std::fs::create_dir_all(corpus_dir.join("kubectl")).unwrap(); + + let base = dir.path().to_str().unwrap(); + match corpus_check_with_base(&claimed, "kubectl", base) { + CorpusCheckResult::ReadFailed { + corpus_cid, + command, + .. + } => { + assert_eq!(corpus_cid, claimed); + assert_eq!(command, "kubectl"); + } + other => panic!("expected ReadFailed, got {other:?}"), + } + } }