UPDATE_TEMPLATE: the command stock learns with, and the field that was killing it
Stock rewrites the stored template on every successful press. Its post-match loop is QUERY_FINGER_STATUS, CAPTURE_IMAGE, 0x1015 UPDATE_TEMPLATE while the finger stays down, with no REPORT_EVENT in it -- so the matcher does not re-run and the verdict cannot change. Forty-six of those against eighty-six captures in one reference session, and the stored body measurably grows: 333278 bytes at enrolment, 360822 at the next session's load, 371734 after one authentication session. This daemon sent none of them. The command shares REPORT_EVENT's event context. The stock wrapper memsets 732 bytes and writes six fields: a zero byte at +0x2a4, the scan-slot count, a zero word, the count of frames folded so far in this press, a flags word of 0x00080000 with bit 6 set on the frame whose event was FingerTouched, and a zero at +0x2d8. Declared length 0x2dc. +0x2d8 is the one that matters, and it matters by staying zero. The dispatcher stub reads it after the handler returns and only if it is non-zero does it read +0x2dc and make that the response length. Every earlier attempt in this project set both fields and varied the declared length across 0x2e0, 0x400 and 0x1000; all of them answered -90, the trustlet gone, and the conclusion recorded was "do not retry until a template is loaded". A loaded template was necessary but not sufficient. The stock HAL sets neither field. Measured on the device with two templates loaded and no finger: both branches answer rc=0 and ENUMERATE still reports 2, so the app did not fault. Those fields are little endian, assembled low-address-first by the handler. An earlier reading called them big endian, off the bfi order, and was wrong. Bit 6 selects which algorithm entry runs: clear takes libfp_template_x_update, set takes the other, which also reads the scan-slot count.
This commit is contained in:
parent
a44e0963ce
commit
e03ce4ae5a
2 changed files with 124 additions and 0 deletions
|
|
@ -175,6 +175,56 @@ int main() {
|
|||
Check((SaveMaskCalibration & (1u << 30)) == 0, "calibration save clears bit 30");
|
||||
Check(SaveMaskTemplate != SaveMaskCalibration, "the two masks differ");
|
||||
|
||||
// ---- UPDATE_TEMPLATE: template learning
|
||||
{
|
||||
std::vector<std::byte> up(UpdateTemplatePayloadSize);
|
||||
BuildUpdateTemplate(up, /*slotIndex*/ 0, /*touchFrame*/ true);
|
||||
|
||||
// The declared length stock sends. 0x2e0 was tried in this project and
|
||||
// answered -90; the length is not a free parameter.
|
||||
Check(UpdateTemplatePayloadSize == 0x2dc, "declared length is 732, as stock sends");
|
||||
|
||||
Check(Get32(up, UpdScanSlotsOff) == 1, "scan slots default to 1, as REPORT_EVENT");
|
||||
Check(Get32(up, UpdZeroAOff) == 0, "+716 is zero");
|
||||
Check(Get32(up, UpdSlotIndexOff) == 0, "the first folded frame is slot 0");
|
||||
Check(Get32(up, UpdFlagsOff) == 0x00080040, "a touch frame sets bit 6 over the base");
|
||||
|
||||
// THE invariant. The dispatcher stub reads this word after the handler
|
||||
// returns and, if it is non-zero, computes the response length from
|
||||
// +0x2dc. Every attempt in this project that set it killed the app.
|
||||
Check(Get32(up, UpdRespLenOff) == 0,
|
||||
"+728 MUST be zero or the stub computes a response length");
|
||||
|
||||
BuildUpdateTemplate(up, /*slotIndex*/ 3, /*touchFrame*/ false);
|
||||
Check(Get32(up, UpdSlotIndexOff) == 3, "the slot index counts folded frames");
|
||||
Check(Get32(up, UpdFlagsOff) == 0x00080000, "a held frame leaves bit 6 clear");
|
||||
Check(Get32(up, UpdRespLenOff) == 0, "+728 stays zero on every frame");
|
||||
|
||||
// The flags word is NOT the event context's, and confusing the two is
|
||||
// an easy mistake because the payloads are otherwise the same struct.
|
||||
Check(UpdFlagsBase != EvDefaultFlags,
|
||||
"the update flags are 0x00080000, not the event context's 0x08080000");
|
||||
|
||||
// The fields it shares with REPORT_EVENT really are at the same
|
||||
// offsets; that is why one struct serves both commands.
|
||||
Check(UpdScanSlotsOff == EvScanSlotsOff && UpdSlotIndexOff == EvSlotIndexOff
|
||||
&& UpdFlagsOff == EvFlagsOff,
|
||||
"the update payload reuses the event context's field offsets");
|
||||
|
||||
// The event id is deliberately NOT written: stock memsets and never
|
||||
// touches +4, and this command must not re-run the matcher.
|
||||
Check(Get32(up, EvEventOff) == 0, "no event id -- the matcher must not re-run");
|
||||
|
||||
// Every byte outside the written fields stays zero: the whole 732-byte
|
||||
// payload carries three non-zero bytes here -- the scan-slot count,
|
||||
// the slot index, and the one set byte of 0x00080000. Anything else
|
||||
// non-zero means a field was written that stock does not write.
|
||||
std::size_t nonZero = 0;
|
||||
for (std::size_t i = 0; i < UpdateTemplatePayloadSize; i++)
|
||||
if (up[i] != std::byte{0}) nonZero++;
|
||||
Check(nonZero == 3, "only scan slots, slot index and the flags byte are set");
|
||||
}
|
||||
|
||||
// ---- AUTHENTICATE payload
|
||||
{
|
||||
std::vector<std::byte> au(AuthPayloadSize);
|
||||
|
|
|
|||
Loading…
Reference in a new issue