fips205-slhdsa-verified/verification/check-selftest.sh
mrwulf eb1d9f108a review round 1: fix the fail-open audit gate + remove the overclaimed framing
External review (both standing reviewers, 2026-07-24) returned DO NOT ATTEST.
The eleven Lean theorems compile with genuinely clean cones (both reviewers
independently reconstructed them), but two real defects were found and are
fixed here.

FIX 1 — the axiom audit was FAIL-OPEN (the critical blocker). check.sh Phase 3
grepped a single physical line of each `#print axioms` report; Lean WRAPS long
cones across lines, so for ht/fors_outer/APEX the audit checked only `[propext,`
and silently ignored the continuation lines — a disallowed axiom on line 2+
passed (the GPT reviewer demonstrated `review_evil_ax` passing). Since check.sh
is the sole source of the word "proven", this is unacceptable.
  - New parser: FLATTEN the whole report (join newlines) BEFORE parsing, then
    extract each certificate's complete bracketed cone with a literal-string
    (regex-safe) scan and subset-check every axiom. Missing/empty report => FAIL
    CLOSED. The audit now prints the count of axioms actually audited per cert
    (apex: 8, previously 1).
  - check-selftest.sh gains ATTACK 3: a smuggled axiom bundled with the apex so
    its cone WRAPS with the evil axiom on a continuation line — the exact
    exploit. Verified: all three attacks now rejected, attack 3 via the axiom
    gate naming the continuation-line axiom. (Also fixed attack 2's leftover
    EvilSpec.lean tripping attack 3's dead-file gate.)

FIX 2 — remove the overclaimed framing (refuted by both reviewers). Corrected
in README, the ApexSpec header + apex docstring, and (separately) the control
MANIFEST:
  - "composes all ten loop-fidelity certificates" — FALSE. The apex proof is a
    STRUCTURAL FACTORIZATION; it references NONE of the ten (grep: 0) and would
    remain provable if one were deleted. They are independent local-fidelity
    lemmas, not links in the apex proof.
  - "every loop is individually fidelity-certified" — FALSE. base_2b's inner
    accumulation loop is threaded opaquely and uncertified — and it determines
    the FORS indices / WOTS digits, so a defect there could change the recomputed
    root while all eleven theorems still hold.
  - "the deployed verifier" — the proved subject is verify_mono, a private
    #![allow(dead_code)] monomorphic facade NOT called by the public API; the
    bridge to the deployed generic verifier is the finite differential test,
    not a machine-checked refinement.
  - "verify-path pyramid complete" — replaced with "intermediate verification
    layer"; the apex is an ACCEPTANCE CHARACTERIZATION, not closed-form FIPS-205
    correctness.
Also: FunsExternal header noted the Take axiom "remains" (stale — deleted in
de-plumbing round 2); corrected.

check.sh green over all eleven certificates under the fixed fail-closed parser
(exit 0, 8 axioms audited for the apex). Nothing about the theorems changed —
they were and are sound; only the audit tool and the claims about them are fixed.

NOT DONE (remaining reviewer blockers, tracked): reproducible extract tuple
(pin commits, de-hard-code extract.sh) + Cargo.lock / toolchain pin. Attestation
remains gated behind review round 2 + the operator halt + the appeal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:55:10 +02:00

105 lines
5.3 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Adversarial self-test of the check.sh gates (the R3-5 tradition: an audit
# that cannot fail is theater). Three attacks, all MUST make check.sh fail:
#
# 1. DEAD FILE — a stray Proofs/*.lean not in the manifest.
# 2. SMUGGLED AXIOM (short cone) — an axiom outside {kernel-3} {5 oracles},
# on the FIRST line of the cone.
# 3. SMUGGLED AXIOM (WRAPPED cone) — an axiom on a CONTINUATION line of a
# cone that wraps (the exact fail-open exploit external review found on
# 2026-07-24: the old single-line parser saw only line 1). This attack
# guards the flattened-parse fix; a self-test that only plants short cones
# cannot detect a wrapped-cone parser regression.
#
# Green here means: the gates genuinely reject all three. Self-cleaning.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
cd "$HERE"
cleanup() { rm -f Proofs/Stray.lean Proofs/Stray.olean Proofs/EvilSpec.lean \
Proofs/EvilSpec.olean Proofs/EvilWrapSpec.lean \
Proofs/EvilWrapSpec.olean check-evil-tmp.sh check-evilwrap-tmp.sh; }
trap cleanup EXIT
echo "check-selftest: attacking the gates"
echo "===================================="
# ── Attack 1: dead file ─────────────────────────────────────────────────────
echo "-- stray" > Proofs/Stray.lean
if ./check.sh > /tmp/selftest-dead.out 2>&1; then
echo "✗ ATTACK 1 SUCCEEDED: check.sh stayed green with a dead file"; exit 1
fi
grep -q "DEAD FILE" /tmp/selftest-dead.out \
|| { echo "✗ ATTACK 1: failed, but not via the dead-file gate"; exit 1; }
rm -f Proofs/Stray.lean Proofs/Stray.olean
echo "✓ attack 1 rejected (dead-file gate works)"
# ── Attack 2: smuggled axiom ────────────────────────────────────────────────
cat > Proofs/EvilSpec.lean <<'EOF'
import Proofs.ChainSpec
axiom evil_ax : True
theorem evil_thm : True := evil_ax
EOF
python3 - <<'PY'
s = open("check.sh").read()
s = s.replace('PROOFS=(\n "ChainSpec"\n)', 'PROOFS=(\n "ChainSpec"\n "EvilSpec"\n)')
# Robust to the growing PROOFS / CERTS lists (do NOT hard-code their current
# contents — that rots the self-test as certificates are added): inject the
# evil entries right after each array's opening paren.
assert 'PROOFS=(\n' in s and 'CERTS=(\n' in s, "check.sh array shape changed"
s = s.replace('PROOFS=(\n', 'PROOFS=(\n "EvilSpec"\n', 1)
s = s.replace('CERTS=(\n', 'CERTS=(\n "evil_thm"\n', 1)
assert '{ echo "import Proofs.ChainSpec"' in s, "check.sh audit import shape changed"
s = s.replace('{ echo "import Proofs.ChainSpec"',
'{ echo "import Proofs.EvilSpec"; echo "import Proofs.ChainSpec"', 1)
open("check-evil-tmp.sh","w").write(s)
PY
chmod +x check-evil-tmp.sh
if ./check-evil-tmp.sh > /tmp/selftest-evil.out 2>&1; then
echo "✗ ATTACK 2 SUCCEEDED: audit passed a smuggled axiom"; exit 1
fi
grep -q "DISALLOWED" /tmp/selftest-evil.out \
|| { echo "✗ ATTACK 2: failed, but not via the axiom gate"; exit 1; }
rm -f Proofs/EvilSpec.lean Proofs/EvilSpec.olean check-evil-tmp.sh
echo "✓ attack 2 rejected (axiom gate works)"
# ── Attack 3: smuggled axiom on a WRAPPED cone (the fail-open exploit) ───────
# evil_wrapped_thm bundles a disallowed axiom with the apex theorem, so its cone
# is 9 axioms and WRAPS across physical lines with review_evil_ax on a
# continuation line — exactly what the old single-line parser missed.
cat > Proofs/EvilWrapSpec.lean <<'EOF'
import Proofs.ApexSpec
open Aeneas Aeneas.Std Result
open fips205
axiom review_evil_ax : True
theorem evil_wrapped_thm
(mprime : Slice Std.U8)
(sig : types.SlhDsaSig 12#usize 7#usize 9#usize 14#usize 35#usize 16#usize)
(pk : types.SlhPublicKey 16#usize) :
True ∧ (verify_mono.slh_verify_128s mprime sig pk
= (do let root ← slhVerifyRoot 63#usize 30#usize mprime sig pk
ok (decide (root.val = pk.pk_root.val)))) :=
⟨review_evil_ax, slh_verify_128s_accepts_iff mprime sig pk⟩
EOF
python3 - <<'PY'
s = open("check.sh").read()
assert 'PROOFS=(\n' in s and 'CERTS=(\n' in s, "check.sh array shape changed"
s = s.replace('PROOFS=(\n', 'PROOFS=(\n "EvilWrapSpec"\n', 1)
s = s.replace('CERTS=(\n', 'CERTS=(\n "evil_wrapped_thm"\n', 1)
assert '{ echo "import Proofs.ChainSpec"' in s, "check.sh audit import shape changed"
s = s.replace('{ echo "import Proofs.ChainSpec"',
'{ echo "import Proofs.EvilWrapSpec"; echo "import Proofs.ChainSpec"', 1)
open("check-evilwrap-tmp.sh","w").write(s)
PY
chmod +x check-evilwrap-tmp.sh
if ./check-evilwrap-tmp.sh > /tmp/selftest-evilwrap.out 2>&1; then
echo "✗ ATTACK 3 SUCCEEDED: audit passed a smuggled axiom on a WRAPPED cone (fail-open!)"; exit 1
fi
grep -q "DISALLOWED" /tmp/selftest-evilwrap.out \
|| { echo "✗ ATTACK 3: failed, but not via the axiom gate (wrapped-cone parse?)"; exit 1; }
grep -q "review_evil_ax" /tmp/selftest-evilwrap.out \
|| { echo "✗ ATTACK 3: rejected, but the audit did not name the continuation-line axiom"; exit 1; }
echo "✓ attack 3 rejected (wrapped-cone axiom gate works — the continuation-line axiom was seen)"
echo
echo "SELFTEST GREEN: all three gates genuinely reject their attacks."