An enrolment cannot be ended by a finger release
A three-tap enrolment declared itself complete. The transcript says why:
frame 2: metric=308 FINGER ev5 rem=10
frame 3: metric=187 ev6 rem=0
samples: 10 of 10 accepted
The release event never enters do_enroll, so its response leaves
samples-remaining untouched at 0 -- which is indistinguishable from "none
remaining, you are finished". The session believed it, stopped after one press,
and called SAVE_DATA on an algorithm holding no template. That answered -1 and
wrote nothing, so the store was undamaged, but only by luck: the guard meant to
prevent a partial save was itself satisfied by the bogus count.
A reading is only meaningful when it came from the event that runs the enrol
path, and nothing about the value says so -- the caller has to. Observe now
takes that as an argument. Two further guards: a FIRST reading of 0 is an
unpopulated field rather than a finished enrolment, and the count only ever
falls, so an increase is noise.
Verified by mutation: trusting the release event's count, and accepting a
leading zero, each fail the suite.
This commit is contained in:
parent
4e8a763cf9
commit
c785aad653
3 changed files with 70 additions and 10 deletions
|
|
@ -1264,7 +1264,10 @@ int Probe() {
|
|||
// Samples remaining rides in the response on the common path,
|
||||
// whether or not the sample was accepted -- which matters
|
||||
// because the trustlet's log starves exactly when one is.
|
||||
enrol.Observe(r.samplesRemaining);
|
||||
// Only the event that runs the enrol path reports a real
|
||||
// count. A release leaves the field at 0, which reads exactly
|
||||
// like "finished".
|
||||
enrol.Observe(r.samplesRemaining, ev == ta::Event::FingerTouched);
|
||||
note += std::format(" ev{} rem={}", static_cast<unsigned>(ev),
|
||||
r.samplesRemaining);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,11 +119,32 @@ export namespace fingerprintd::engine {
|
|||
// remaining into the response payload on the common path whether or not
|
||||
// the sample was accepted, and the log starves exactly when a frame is
|
||||
// accepted. `rem` counting down is the only reliable progress signal.
|
||||
//
|
||||
// "The common path" means do_enroll's common path -- and a finger-RELEASE
|
||||
// event never enters do_enroll at all. Its response leaves the field
|
||||
// untouched, so it reads 0, which is indistinguishable from "no samples
|
||||
// remaining, you are finished". Taking that at face value ends an
|
||||
// enrolment after one press and then calls SAVE_DATA on an algorithm
|
||||
// holding no template, which answers -1.
|
||||
//
|
||||
// So a reading is only meaningful when it came from the event that runs
|
||||
// the enrol path. The caller has to say so; there is no way to tell from
|
||||
// the value.
|
||||
class EnrolSession {
|
||||
public:
|
||||
void Observe(std::int32_t remaining) {
|
||||
if (remaining < 0) return; // not populated by this command
|
||||
if (!started_) { total_ = remaining; started_ = true; }
|
||||
void Observe(std::int32_t remaining, bool fromEnrolPath) {
|
||||
if (!fromEnrolPath) return; // a release reports nothing
|
||||
if (remaining < 0) return; // not populated at all
|
||||
if (!started_) {
|
||||
// A first reading of 0 is an unpopulated field, not a finished
|
||||
// enrolment: the count starts at max_enrolling_samples.
|
||||
if (remaining == 0) return;
|
||||
total_ = remaining;
|
||||
remaining_ = remaining;
|
||||
started_ = true;
|
||||
return;
|
||||
}
|
||||
if (remaining > remaining_) return; // the count only ever falls
|
||||
remaining_ = remaining;
|
||||
}
|
||||
bool Started() const { return started_; }
|
||||
|
|
@ -139,7 +160,7 @@ export namespace fingerprintd::engine {
|
|||
private:
|
||||
bool started_ = false;
|
||||
std::int32_t total_ = 0;
|
||||
std::int32_t remaining_ = 0;
|
||||
std::int32_t remaining_ = -1;
|
||||
};
|
||||
|
||||
// ---- Authentication accounting ----------------------------------------
|
||||
|
|
|
|||
|
|
@ -140,19 +140,55 @@ int main() {
|
|||
{
|
||||
EnrolSession e;
|
||||
Check(!e.Started(), "not started");
|
||||
e.Observe(-1);
|
||||
e.Observe(-1, true);
|
||||
Check(!e.Started(), "an unpopulated field does not start the session");
|
||||
e.Observe(10);
|
||||
e.Observe(10, true);
|
||||
Check(e.Started() && e.Total() == 10 && e.Stages() == 10, "first response sets the total");
|
||||
Check(e.Accepted() == 0 && !e.Complete(), "nothing accepted yet");
|
||||
e.Observe(9);
|
||||
e.Observe(9, true);
|
||||
Check(e.Accepted() == 1, "rem 10 -> 9 is one accepted sample");
|
||||
for (std::int32_t r : {8, 7, 6, 5, 4, 3, 2, 1}) e.Observe(r);
|
||||
for (std::int32_t r : {8, 7, 6, 5, 4, 3, 2, 1}) e.Observe(r, true);
|
||||
Check(!e.Complete() && e.Remaining() == 1, "not complete at one remaining");
|
||||
e.Observe(0);
|
||||
e.Observe(0, true);
|
||||
Check(e.Complete() && e.Accepted() == 10, "complete at zero");
|
||||
}
|
||||
|
||||
// ---- A release event must not end the enrolment
|
||||
//
|
||||
// This shipped: the release event never enters do_enroll, so its response
|
||||
// leaves samples-remaining at 0, and a session that trusts it declares
|
||||
// itself finished after ONE press. SAVE_DATA is then called on an
|
||||
// algorithm holding no template and answers -1.
|
||||
{
|
||||
EnrolSession e;
|
||||
e.Observe(10, true); // touch: ten to go
|
||||
e.Observe(0, false); // release: reads zero, means nothing
|
||||
Check(!e.Complete(), "a release reading of 0 does NOT complete the enrolment");
|
||||
Check(e.Remaining() == 10, "and does not move the count");
|
||||
Check(e.Accepted() == 0, "nothing was accepted");
|
||||
}
|
||||
|
||||
// A first reading of 0 is an unpopulated field, not a finished enrolment.
|
||||
{
|
||||
EnrolSession e;
|
||||
e.Observe(0, true);
|
||||
Check(!e.Started(), "a leading zero does not start a session");
|
||||
Check(!e.Complete(), "and certainly does not finish one");
|
||||
e.Observe(10, true);
|
||||
Check(e.Started() && e.Total() == 10, "a real count still starts it");
|
||||
}
|
||||
|
||||
// The count only falls; a jump back up is noise, not progress.
|
||||
{
|
||||
EnrolSession e;
|
||||
e.Observe(10, true);
|
||||
e.Observe(9, true);
|
||||
e.Observe(7, true);
|
||||
Check(e.Remaining() == 7, "decreases are taken");
|
||||
e.Observe(9, true);
|
||||
Check(e.Remaining() == 7, "an increase is ignored");
|
||||
}
|
||||
|
||||
// ---- The three recorded runs, replayed in order
|
||||
{
|
||||
auto enrolled = LoadRun("auth-enrolled-finger.txt");
|
||||
|
|
|
|||
Loading…
Reference in a new issue