Delete a finger's template, not just its name
FF_CMD_TA_REMOVE, recovered the way AUTHENTICATE was: read the stub, read the handler. The 0x2006 stub at 0xa15c is a bare `ldp w0, w1, [payload]`, so the request is two u32s -- gid at +0, fid at +4 -- and the 0x2000 dispatcher validates no length. Walking the jump table reproduces authenticate at 0xa180, which is the address already on record, so the table read is sound. Three preconditions, all the trustlet's own. The gid must be the ACTIVE group (it compares against device+0x30, the field SET_ACTIVE_GROUP writes). The fid must be non-zero: zero is not "remove all", it is an error the trustlet logs and refuses. And the fid must be among the loaded templates, because it removes by the SLOT INDEX it finds, not by id. It persists: on a hit the trustlet formats ff_template_<gid>_<slot>.bin and calls ff_file_delete, which arrives on our gpfile listener as an unlink -- so this only works with the store served writable. Proven harmlessly first. --probe-remove sends one command with no map involvement, and a fid the group does not hold answers rc=-2 with the real template untouched -- which is what established that both words are read where we send them, before anything was deleted. Then for real, through fprintd-delete: both 347202-byte containers and their .bak companions unlinked, templates loaded 1 -> 0, and a re-enrolment afterwards completed 20 stages with SAVE_DATA rc=0, so the store is consistent after a removal rather than merely emptier. The ordering the transcript shows is worth keeping: the group index is rewritten and the RPMB anti-rollback counter bumped BEFORE each unlink. That is precisely why an orderly removal leaves a valid store where restoring an older container leaves a tampered one -- the counter has already moved past it. The delete reply now waits for the worker, because only that thread invokes the trustlet and fprintd's Delete methods are synchronous. Names are dropped before templates on purpose: a template that survives a failed removal is a slot leak, while a name that survives a successful one keeps offering a finger that can no longer match.
This commit is contained in:
parent
b228287c5b
commit
41e86f84f4
4 changed files with 178 additions and 13 deletions
|
|
@ -44,6 +44,7 @@ export namespace fingerprintd::ta {
|
|||
Cancel = 0x2004,
|
||||
ResetLockout = 0x200a,
|
||||
Enumerate = 0x2005,
|
||||
Remove = 0x2006,
|
||||
SetActiveGroup = 0x2007,
|
||||
Authenticate = 0x2008,
|
||||
};
|
||||
|
|
@ -330,6 +331,45 @@ export namespace fingerprintd::ta {
|
|||
out[AuthCoveredOff] = static_cast<std::byte>(covered ? 1 : 0);
|
||||
}
|
||||
|
||||
// REMOVE (TA 0xd7b0, reached from the 0x2006 stub at 0xa15c, which is a
|
||||
// bare `ldp w0, w1, [payload]`):
|
||||
// +0x00 u32 gid
|
||||
// +0x04 u32 fid
|
||||
// Declared length 0x08. The 0x2000-range dispatcher range-checks the
|
||||
// command id and jumps; it validates no length, so the payload is exactly
|
||||
// the two fields.
|
||||
//
|
||||
// Three preconditions, all of them the trustlet's own:
|
||||
//
|
||||
// gid must equal the ACTIVE group. ff_trustlet_remove compares it
|
||||
// against device+0x30 -- the same field SET_ACTIVE_GROUP writes and
|
||||
// AUTHENTICATE checks -- and logs "templates with gid(%u != %u) hasn't
|
||||
// been loaded." on a mismatch.
|
||||
//
|
||||
// fid must be NON-ZERO. Zero is not "remove them all": the trustlet
|
||||
// logs "error at %s[%s:%u]: removing template with fid equ 0." and
|
||||
// refuses. Removing every finger means calling this once per fid.
|
||||
//
|
||||
// The fid must be among the templates currently LOADED. The trustlet
|
||||
// walks its loaded list for a matching id and removes by SLOT INDEX,
|
||||
// not by id -- libfp_template_remove takes the index it found.
|
||||
//
|
||||
// It persists. On a hit the trustlet logs "template (gid = %u, fid = %u)
|
||||
// is found at slot %d.", formats "%s/ff_template_%d_%d.bin" and calls
|
||||
// ff_file_delete, which arrives on the gpfile listener as an unlink -- so
|
||||
// the daemon must be serving the store WRITABLE or the container survives
|
||||
// the call that reported success.
|
||||
inline constexpr std::size_t RemovePayloadSize = 0x08;
|
||||
inline constexpr std::size_t RemoveGidOff = 0x00;
|
||||
inline constexpr std::size_t RemoveFidOff = 0x04;
|
||||
|
||||
inline void BuildRemovePayload(std::span<std::byte> out, std::uint32_t gid,
|
||||
std::uint32_t fid) {
|
||||
std::ranges::fill(out.first(RemovePayloadSize), std::byte{0});
|
||||
detail::StoreU32(out, RemoveGidOff, gid);
|
||||
detail::StoreU32(out, RemoveFidOff, fid);
|
||||
}
|
||||
|
||||
// SET_ACTIVE_GROUP writes its gid to device+0x30 and AUTHENTICATE compares
|
||||
// its own against the same field (0xeb08), logging
|
||||
// "templates with gid(%u != %u) hasn't been loaded." and returning -200 on
|
||||
|
|
|
|||
Loading…
Reference in a new issue