diff --git a/implementations/main.cpp b/implementations/main.cpp index 1a09b32..e983094 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -1297,7 +1297,15 @@ public: int frames = 0; }; - VerifyOutcome Verify(std::uint32_t gid, std::atomic& cancel, int maxFrames) { + // `accept` is the set of fids that count as a match for THIS request. The + // trustlet identifies against every template loaded in the group, and a + // group accumulates them: re-enrolling a finger does NOT replace its + // template, it adds one, because FF_CMD_TA_REMOVE is not implemented. So + // without this filter a verify answers for fingers the caller did not ask + // about, and for stale templates no longer named by anything -- which is + // both wrong by fprintd's contract and quietly ruins any measurement. + VerifyOutcome Verify(std::uint32_t gid, std::atomic& cancel, int maxFrames, + const std::vector& accept) { namespace ta = fingerprintd::ta; namespace en = fingerprintd::engine; VerifyOutcome out; @@ -1363,6 +1371,13 @@ public: auto r = SendCommand(app_, ta::Cmd::ReportEvent, evbuf); if (!r.invoked) continue; ta::Verdict v = ta::Classify(r.rc, r.fid); + // A match against a template the caller did not ask for is not + // a match for this request. + if (v == ta::Verdict::Match && !accept.empty() + && std::ranges::find(accept, r.fid) == accept.end()) { + std::println(" fid {} matched but is not the requested finger", r.fid); + v = ta::Verdict::Rejected; + } switch (v) { case ta::Verdict::Match: pressMatched = true; pressFid = r.fid; note += " MATCH"; break; case ta::Verdict::Rejected: pressRejected = true; note += " rej"; break; @@ -1512,6 +1527,7 @@ struct Job { enum class Kind { Claim, Enroll, Verify } kind; std::uint32_t uid = 0; std::string finger; + std::vector acceptFids; // Verify: which fids count GDBusMethodInvocation* invocation = nullptr; // Claim replies asynchronously }; @@ -1629,7 +1645,7 @@ private: break; } case Job::Kind::Verify: { - auto o = session_.Verify(j.uid, cancel_, /*maxFrames*/ 600); + auto o = session_.Verify(j.uid, cancel_, /*maxFrames*/ 600, j.acceptFids); std::println("verify: {} over {} press(es), {} frame(s)", o.cancelled ? "cancelled" : !o.decided ? "undecided" : o.matched ? "MATCH" : "NO MATCH", o.presses, o.frames); @@ -2037,8 +2053,22 @@ void HandleDevice(GDBusMethodInvocation* inv, std::string_view method, GVariant* ? std::string(store::NameOf(g_claim.fingers.Entries().front().finger)) : finger; EmitDevice("VerifyFingerSelected", g_variant_new("(s)", sel.c_str())); } + // Which templates may answer this request: the named finger's fid, or + // every named finger's for "any". A fid that no name maps to -- a + // template left behind by an earlier enrolment -- answers for nothing. + std::vector accept; + if (!enroll) { + for (const auto& e : g_claim.fingers.Entries()) + if (finger == store::AnyFinger || store::NameOf(e.finger) == finger) + accept.push_back(e.fid); + if (accept.empty()) { + EmitDevice("VerifyStatus", g_variant_new("(sb)", "verify-no-match", TRUE)); + return; + } + } g_worker->Post(Job{ .kind = enroll ? Job::Kind::Enroll : Job::Kind::Verify, - .uid = g_claim.uid, .finger = finger }); + .uid = g_claim.uid, .finger = finger, + .acceptFids = std::move(accept) }); return; } if (method == "EnrollStop" || method == "VerifyStop") { @@ -2161,7 +2191,7 @@ int RunProbe(bool doAuth, bool doEnrol, bool doCalSave, std::uint32_t gid, int f } if (doAuth) { for (int c = 3; c > 0; c--) { std::println("*** press your finger in {}... ***", c); std::this_thread::sleep_for(std::chrono::seconds(1)); } - auto o = s.Verify(gid, cancel, frames); + auto o = s.Verify(gid, cancel, frames, {}); // probe: any template counts std::println("verify: decided={} matched={} fid={}", o.decided, o.matched, o.fid); } s.Stop();