Load the trustlet and configure it
On the phone, end to end from the daemon:
lookupTA('focal64') -> result=23 (nothing to unload)
trustlet loaded from /lib/firmware/focal64.mbn, distName='fingerprint'
config /lib/firmware/fingerprintd.json: 349 bytes
CMD 0x100d -> result=0 rc=0 (Success)
CMD 0x2005 -> result=0 rc=0 (Success)
The config is the one fp6fpcfg.py --daemon generates, so the reduction of nine
hand-edited ffcfg files to one generator is confirmed against the trustlet
rather than only against the files it replaced.
A stale instance is unloaded before loading, which is what stops a crashed
experiment costing a reboot; result=23 is the clean-slate answer.
The request envelope moves into Fingerprintd:Ta with the rest of the layouts:
command id at +0, declared length at +4, payload at +0x10, and on the way back
the trustlet's own rc at +8 and the capture metric at +0x0c. Both are HEADER
fields ahead of the payload -- the metric has been miscalled "payload+12" in
this project's notes, and every recorded finger number depends on reading it
where it actually is.
ENUMERATE answering rc=0 is correct here and not a regression: no group is
active and no storage listeners are registered yet, so there are no templates
to count.
This commit is contained in:
parent
a91fb2ff58
commit
2648e46d43
3 changed files with 225 additions and 2 deletions
|
|
@ -45,7 +45,10 @@ import Fingerprintd;
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr const char* Version = "0.0.2";
|
||||
constexpr const char* Version = "0.0.3";
|
||||
|
||||
std::string g_taPath = "/lib/firmware/focal64.mbn";
|
||||
std::string g_cfgPath = "/lib/firmware/fingerprintd.json";
|
||||
|
||||
qcomtee_object* g_root = QCOMTEE_OBJECT_NULL;
|
||||
|
||||
|
|
@ -212,6 +215,118 @@ qcomtee_object* OpenService(qcomtee_object* env, std::uint32_t uid) {
|
|||
return p[1].object;
|
||||
}
|
||||
|
||||
// ---- The trustlet
|
||||
//
|
||||
// The loader is IQSEEComCompatAppLoader (UID 122): op 1 loadFromBuffer, op 2
|
||||
// lookupTA. A stale instance from a crashed run is unloaded first, which is
|
||||
// what stops a bad experiment costing a reboot.
|
||||
constexpr const char* TaName = "focal64";
|
||||
|
||||
void UnloadStale(qcomtee_object* loader) {
|
||||
qcomtee_param p[3] = {};
|
||||
std::array<std::byte, 4> ob{};
|
||||
p[0].attr = QCOMTEE_UBUF_INPUT;
|
||||
p[0].ubuf.addr = const_cast<char*>(TaName);
|
||||
p[0].ubuf.size = std::strlen(TaName);
|
||||
p[1].attr = QCOMTEE_UBUF_OUTPUT;
|
||||
p[1].ubuf.addr = ob.data();
|
||||
p[1].ubuf.size = ob.size();
|
||||
p[2].attr = QCOMTEE_OBJREF_OUTPUT;
|
||||
qcomtee_result_t result = 0;
|
||||
if (qcomtee_object_invoke(loader, 2, p, 3, &result) || result) {
|
||||
std::println("lookupTA('{}') -> result={} (nothing to unload)", TaName,
|
||||
static_cast<int>(result));
|
||||
return;
|
||||
}
|
||||
if (!qcomtee_object_invoke(p[2].object, 2, nullptr, 0, &result))
|
||||
std::println("unloaded a stale '{}' -> result={}", TaName, static_cast<int>(result));
|
||||
qcomtee_object_refs_dec(p[2].object);
|
||||
}
|
||||
|
||||
qcomtee_object* LoadTrustlet(qcomtee_object* loader, const std::string& path) {
|
||||
UnloadStale(loader);
|
||||
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) {
|
||||
std::println(std::cerr, "cannot open {}", path);
|
||||
return QCOMTEE_OBJECT_NULL;
|
||||
}
|
||||
std::vector<char> image((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (image.empty()) {
|
||||
std::println(std::cerr, "{} is empty", path);
|
||||
return QCOMTEE_OBJECT_NULL;
|
||||
}
|
||||
|
||||
std::array<char, 128> distName{};
|
||||
qcomtee_param p[4] = {};
|
||||
p[0].attr = QCOMTEE_UBUF_INPUT;
|
||||
p[0].ubuf.addr = image.data();
|
||||
p[0].ubuf.size = image.size();
|
||||
p[1].attr = QCOMTEE_UBUF_INPUT;
|
||||
p[1].ubuf.addr = const_cast<char*>(TaName);
|
||||
p[1].ubuf.size = std::strlen(TaName);
|
||||
p[2].attr = QCOMTEE_UBUF_OUTPUT;
|
||||
p[2].ubuf.addr = distName.data();
|
||||
p[2].ubuf.size = distName.size();
|
||||
p[3].attr = QCOMTEE_OBJREF_OUTPUT;
|
||||
qcomtee_result_t result = 0;
|
||||
if (qcomtee_object_invoke(loader, 1, p, 4, &result) || result) {
|
||||
std::println(std::cerr, "loadFromBuffer failed, result={}",
|
||||
static_cast<int>(result));
|
||||
return QCOMTEE_OBJECT_NULL;
|
||||
}
|
||||
std::println("trustlet loaded from {} ({} bytes), distName='{}'", path,
|
||||
image.size(), distName.data());
|
||||
return p[3].object;
|
||||
}
|
||||
|
||||
// sendRequest is op 0 with arity 0x0424: four input buffers, two output, four
|
||||
// object slots. The request and response buffers go in and come back out; the
|
||||
// trustlet's own return code rides in the returned request's header.
|
||||
struct CommandResult { bool invoked = false; qcomtee_result_t result = 0; std::int32_t rc = 0; std::int32_t metric = 0; };
|
||||
|
||||
CommandResult SendCommand(qcomtee_object* app, fingerprintd::ta::Cmd cmd,
|
||||
std::span<const std::byte> payload) {
|
||||
namespace ta = fingerprintd::ta;
|
||||
static std::vector<std::byte> req(8192), rsp(16384), reqOut(8192), rspOut(16384);
|
||||
std::ranges::fill(rsp, std::byte{0});
|
||||
std::ranges::fill(reqOut, std::byte{0});
|
||||
std::ranges::fill(rspOut, std::byte{0});
|
||||
ta::BuildRequest(req, cmd, payload);
|
||||
|
||||
std::uint32_t is64 = 1;
|
||||
qcomtee_param p[10] = {};
|
||||
p[0].attr = QCOMTEE_UBUF_INPUT; p[0].ubuf.addr = req.data(); p[0].ubuf.size = req.size();
|
||||
p[1].attr = QCOMTEE_UBUF_INPUT; p[1].ubuf.addr = rsp.data(); p[1].ubuf.size = rsp.size();
|
||||
p[2].attr = QCOMTEE_UBUF_INPUT; p[2].ubuf.addr = nullptr; p[2].ubuf.size = 0;
|
||||
p[3].attr = QCOMTEE_UBUF_INPUT; p[3].ubuf.addr = &is64; p[3].ubuf.size = sizeof(is64);
|
||||
p[4].attr = QCOMTEE_UBUF_OUTPUT; p[4].ubuf.addr = reqOut.data(); p[4].ubuf.size = reqOut.size();
|
||||
p[5].attr = QCOMTEE_UBUF_OUTPUT; p[5].ubuf.addr = rspOut.data(); p[5].ubuf.size = rspOut.size();
|
||||
for (int i = 6; i < 10; i++) {
|
||||
p[i].attr = QCOMTEE_OBJREF_INPUT;
|
||||
p[i].object = QCOMTEE_OBJECT_NULL;
|
||||
}
|
||||
|
||||
CommandResult out;
|
||||
if (qcomtee_object_invoke(app, fingerprintd::tee::AppSendRequestOp, p, 10, &out.result))
|
||||
return out;
|
||||
out.invoked = true;
|
||||
out.rc = ta::ResultCode(reqOut);
|
||||
out.metric = ta::CaptureMetric(reqOut);
|
||||
return out;
|
||||
}
|
||||
|
||||
void Report(fingerprintd::ta::Cmd cmd, const CommandResult& r) {
|
||||
namespace ta = fingerprintd::ta;
|
||||
if (!r.invoked) {
|
||||
std::println(" CMD 0x{:04x} -> INVOKE FAILED", static_cast<unsigned>(cmd));
|
||||
return;
|
||||
}
|
||||
std::println(" CMD 0x{:04x} -> result={} rc={} ({})", static_cast<unsigned>(cmd),
|
||||
static_cast<int>(r.result), r.rc, ta::StrError(r.rc));
|
||||
}
|
||||
|
||||
int Probe() {
|
||||
namespace tee = fingerprintd::tee;
|
||||
|
||||
|
|
@ -243,7 +358,41 @@ int Probe() {
|
|||
std::println("QSEECOM-compat app loader (UID {}) opened",
|
||||
tee::UidQseecomCompatAppLoader);
|
||||
|
||||
std::println("\nreached QTEE. Not driving the sensor yet.");
|
||||
qcomtee_object* app = LoadTrustlet(loader, g_taPath);
|
||||
if (app == QCOMTEE_OBJECT_NULL)
|
||||
return 1;
|
||||
|
||||
// SYNC_CONFIG first, always. The trustlet reads its whole configuration
|
||||
// from this one JSON payload, and two keys in it are load-bearing:
|
||||
// algorithm.enrolling_overlap_intervals must be PRESENT (its default is
|
||||
// the empty string, which faults the trustlet's own sscanf), and
|
||||
// device.preferred_device_id selects the chip driver.
|
||||
std::ifstream cf(g_cfgPath);
|
||||
if (!cf) {
|
||||
std::println(std::cerr, "cannot open config {}", g_cfgPath);
|
||||
return 1;
|
||||
}
|
||||
std::string json((std::istreambuf_iterator<char>(cf)),
|
||||
std::istreambuf_iterator<char>());
|
||||
// The trustlet wants the terminating NUL counted.
|
||||
std::vector<std::byte> cfg(json.size() + 1, std::byte{0});
|
||||
for (std::size_t i = 0; i < json.size(); i++)
|
||||
cfg[i] = static_cast<std::byte>(json[i]);
|
||||
std::println("config {}: {} bytes", g_cfgPath, cfg.size());
|
||||
|
||||
auto r = SendCommand(app, fingerprintd::ta::Cmd::SyncConfig, cfg);
|
||||
Report(fingerprintd::ta::Cmd::SyncConfig, r);
|
||||
if (!r.invoked || r.result != 0 || r.rc != 0) {
|
||||
std::println(std::cerr, "SYNC_CONFIG did not succeed; stopping here");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A storage read needs no sensor. It exercises the whole SFS listener path
|
||||
// if listeners are registered, and answers -2 when they are not.
|
||||
auto e = SendCommand(app, fingerprintd::ta::Cmd::Enumerate, {});
|
||||
Report(fingerprintd::ta::Cmd::Enumerate, e);
|
||||
|
||||
std::println("\ntrustlet is up and configured. Sensor not powered yet.");
|
||||
pthread_cancel(th);
|
||||
pthread_join(th, nullptr);
|
||||
return 0;
|
||||
|
|
@ -260,6 +409,8 @@ int main(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
if (a == "--probe-tee") probe = true;
|
||||
if (a.starts_with("--ta=")) g_taPath = a.substr(5);
|
||||
if (a.starts_with("--config=")) g_cfgPath = a.substr(9);
|
||||
}
|
||||
if (probe)
|
||||
return Probe();
|
||||
|
|
|
|||
|
|
@ -233,6 +233,46 @@ export namespace fingerprintd::ta {
|
|||
// itself is the caller's to choose.
|
||||
inline constexpr std::size_t SetActiveGroupGidOff = 0;
|
||||
|
||||
// ---- The request/response envelope ------------------------------------
|
||||
//
|
||||
// sendRequest carries two buffers in and two back. The request is:
|
||||
//
|
||||
// +0x00 u32 command id
|
||||
// +0x04 u32 declared payload length
|
||||
// +0x10 the payload
|
||||
//
|
||||
// and the returned copy of it carries the trustlet's own return code and
|
||||
// the capture metric in the header, ahead of the payload:
|
||||
//
|
||||
// +0x08 i32 rc the trustlet's result, distinct from QTEE's
|
||||
// +0x0c i32 metric CAPTURE_IMAGE's finger signal
|
||||
//
|
||||
// The metric is a HEADER field. It has been called "payload+12" in this
|
||||
// project's notes and it is not; it tracks the finger reproducibly and
|
||||
// every recorded number depends on reading it here.
|
||||
inline constexpr std::size_t ReqCmdOff = 0x00;
|
||||
inline constexpr std::size_t ReqLenOff = 0x04;
|
||||
inline constexpr std::size_t ReqPayloadOff = 0x10;
|
||||
inline constexpr std::size_t RespRcOff = 0x08;
|
||||
inline constexpr std::size_t RespMetricOff = 0x0c;
|
||||
|
||||
inline void BuildRequest(std::span<std::byte> req, Cmd cmd,
|
||||
std::span<const std::byte> payload) {
|
||||
std::ranges::fill(req, std::byte{0});
|
||||
detail::StoreU32(req, ReqCmdOff, static_cast<std::uint32_t>(cmd));
|
||||
if (!payload.empty()) {
|
||||
detail::StoreU32(req, ReqLenOff, static_cast<std::uint32_t>(payload.size()));
|
||||
std::ranges::copy(payload, req.begin() + static_cast<std::ptrdiff_t>(ReqPayloadOff));
|
||||
}
|
||||
}
|
||||
|
||||
inline std::int32_t ResultCode(std::span<const std::byte> reqOut) {
|
||||
return static_cast<std::int32_t>(detail::LoadU32(reqOut, RespRcOff));
|
||||
}
|
||||
inline std::int32_t CaptureMetric(std::span<const std::byte> reqOut) {
|
||||
return static_cast<std::int32_t>(detail::LoadU32(reqOut, RespMetricOff));
|
||||
}
|
||||
|
||||
// ---- Responses --------------------------------------------------------
|
||||
//
|
||||
// THE TRAP. The buffer that comes back is the whole REQUEST, and the
|
||||
|
|
|
|||
|
|
@ -203,6 +203,38 @@ int main() {
|
|||
"reading at the payload offset directly gives the wrong word");
|
||||
}
|
||||
|
||||
// ---- The request/response envelope
|
||||
{
|
||||
std::vector<std::byte> req(256);
|
||||
std::array<std::byte, 4> payload{ std::byte{1}, std::byte{2},
|
||||
std::byte{3}, std::byte{4} };
|
||||
BuildRequest(req, Cmd::SyncConfig, payload);
|
||||
Check(Get32(req, ReqCmdOff) == 0x100d, "command id at +0");
|
||||
Check(Get32(req, ReqLenOff) == 4, "declared length at +4");
|
||||
Check(std::to_integer<unsigned>(req[ReqPayloadOff]) == 1, "payload at +0x10");
|
||||
Check(ReqPayloadOff == ResponsePayloadOff, "request and response payloads share the offset");
|
||||
|
||||
// An empty payload leaves the declared length zero rather than
|
||||
// pointing at uninitialised bytes.
|
||||
BuildRequest(req, Cmd::Enumerate, {});
|
||||
Check(Get32(req, ReqLenOff) == 0, "no payload, no declared length");
|
||||
Check(Get32(req, ReqCmdOff) == 0x2005, "ENUMERATE");
|
||||
|
||||
// rc and the metric are HEADER fields, ahead of the payload, and are
|
||||
// distinct from each other.
|
||||
std::vector<std::byte> out(256);
|
||||
auto put = [&](std::size_t off, std::uint32_t v) {
|
||||
for (std::size_t i = 0; i < 4; i++)
|
||||
out[off + i] = static_cast<std::byte>((v >> (8 * i)) & 0xFF);
|
||||
};
|
||||
put(RespRcOff, static_cast<std::uint32_t>(-11));
|
||||
put(RespMetricOff, 345);
|
||||
Check(ResultCode(out) == -11, "rc at +8, signed");
|
||||
Check(CaptureMetric(out) == 345, "metric at +0x0c");
|
||||
Check(RespRcOff != RespMetricOff && RespMetricOff < ResponsePayloadOff,
|
||||
"both sit in the header, ahead of the payload");
|
||||
}
|
||||
|
||||
// ---- Poisoning
|
||||
{
|
||||
std::vector<std::byte> payload(64);
|
||||
|
|
|
|||
Loading…
Reference in a new issue