diff --git a/implementations/main.cpp b/implementations/main.cpp index 1346d43..e3b735d 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -1230,7 +1230,12 @@ public: // store: an enrolment, or a template removed underneath us. int SetActiveGroup(std::uint32_t gid, bool force = false) { namespace ta = fingerprintd::ta; - if (!force && gid == gid_ && templatesLoaded_ >= 0) { + // > 0, never >= 0: caching a ZERO turns a failed load into a + // permanent one. It did exactly that on 2026-09-05 -- the trustlet + // unlinked a restored template, reported 0, and every later claim + // answered "skipped (already active, 0 template(s))" without ever + // trying again. + if (!force && gid == gid_ && templatesLoaded_ > 0) { std::println("SET_ACTIVE_GROUP gid={} skipped (already active, {} template(s))", gid, templatesLoaded_); return templatesLoaded_; diff --git a/packaging/fplearn.sh b/packaging/fplearn.sh index a7e2adc..376b68f 100755 --- a/packaging/fplearn.sh +++ b/packaging/fplearn.sh @@ -2,8 +2,10 @@ # fplearn.sh -- the template-learning measurement, as a protocol rather than a # pile of remembered commands. # -# fplearn.sh snapshot [name] save the current template aside -# fplearn.sh restore put a saved template back and reload it +# fplearn.sh snapshot [name] save the current template aside (forensics +# only -- see restore) +# fplearn.sh restore REFUSES: a restore DESTROYS the template on +# this device, see the note in the command # fplearn.sh wipe remove every stored template (backup first) # fplearn.sh enrol [finger] enrol at the config's sample count # fplearn.sh base [n] [w] trial with learning OFF (the baseline) @@ -113,6 +115,34 @@ snapshot) echo "restore with: fplearn.sh restore $NAME" ;; restore) + # DISABLED, and it must stay disabled: restoring a template DESTROYS it. + # + # Measured 2026-09-05. A byte-exact restore of an earlier container made the + # trustlet read its header, UNLINK it, look for a .bak, and bump the RPMB + # counter -- for both containers. The enrolled finger was gone, and no copy + # can bring it back, because QTEE binds every SFS object to a hardware + # anti-rollback counter that has since advanced. An older container is not + # stale to QTEE, it is TAMPERED, and the response to tampering is deletion. + # + # So the paired within-session comparison this was built for is IMPOSSIBLE + # on this hardware. A template can be measured, and it can be replaced by + # enrolling again, but it can never be put back. + cat <<'WARN' +REFUSING: restoring a template DESTROYS it on this device. + +QTEE binds every stored object to a hardware anti-rollback counter. An earlier +container is not stale to it, it is tampered, and it responds by UNLINKING the +file. Measured 2026-09-05: a byte-exact restore of the previous template made +the trustlet unlink BOTH containers and advance the RPMB counter. The enrolled +finger was destroyed and no copy could bring it back. + +Snapshots are still worth taking -- they are forensic evidence of what a +template looked like -- but they cannot be put back. To change the template, +enrol again. +WARN + exit 1 ;; + +restore-force-DESTRUCTIVE) NAME=${2:-} D=$SNAPDIR/$NAME [ -n "$NAME" ] && [ -d "$D" ] || { echo "usage: fplearn.sh restore "; echo "available:"; ls "$SNAPDIR" 2>/dev/null | sed 's/^/ /'; exit 1; }