fingerprintd's own code now talks to QTEE. On the phone:
root object on /dev/tee0
client env obtained (uid 0, 13-byte credentials)
QSEECOM-compat app loader (UID 122) opened
The credentials object is ours rather than libqcomtee's. Upstream's exists only
to build a thirteen-byte CBOR map and drags in QCBOR to do it, so
packaging/make-libqcomtee.sh compiles the two sources that matter and drops
credentials_obj.c entirely -- nothing else references it, and the library then
has no dependency beyond libc. The map is built in Fingerprintd:Tee where it is
pinned byte-for-byte against the string verified on-device, and the object's
two-op read protocol is served here.
Three interop details, all of which cost a build cycle:
* libqcomtee's headers carry no extern "C" guard, having only ever been
consumed from C, so everything came out C++-mangled. They also pull in
<stdatomic.h> and <stdio.h>, which under libc++ drag in templates that may
not appear inside extern "C" -- so those are included first.
* tee_call_t's second parameter is unsigned long on glibc and int on musl.
The native build is glibc and the phone is musl; both forms are compiled.
* On the callback path a UBUF_OUTPUT param arrives with addr = NULL. The
dispatcher supplies the buffer, so a handler POINTS the param at its own
storage rather than writing through the incoming address. Doing the latter
is a null dereference that takes the supplicant thread with it, which is
how the first run against real QTEE ended -- with the correct behaviour
already spelled out in the module comment above the code that ignored it.
That comment now says so in as many words.
93 lines
3.8 KiB
C++
93 lines
3.8 KiB
C++
// SPDX-License-Identifier: GPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
// lint-disable-file no-char-pointer
|
|
import std;
|
|
import Crafter.Build;
|
|
#include "lint-rules.h"
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
// libqcomtee — Qualcomm's BSD-3 userspace client for QTEE, built by
|
|
// packaging/make-libqcomtee.sh into a per-target cache dir. It is not vendored
|
|
// here: it is upstream code we pin, and the script builds it WITHOUT QCBOR
|
|
// (see the script for why that dependency is avoidable).
|
|
static void ApplyQcomteeFlags(Configuration& cfg) {
|
|
fs::path base = fs::path(std::getenv("HOME") ? std::getenv("HOME") : ".")
|
|
/ ".cache" / "fingerprintd";
|
|
// make-libqcomtee.sh names its output dir after --target, and plain
|
|
// "libqcomtee" when built for the host. cfg.target is always populated
|
|
// (it defaults to the host triple), so try the target-specific dir first
|
|
// and fall back to the host one.
|
|
std::error_code ec;
|
|
fs::path root = base / ("libqcomtee-" + cfg.target);
|
|
if (!fs::exists(root / "libqcomtee.a", ec))
|
|
root = base / "libqcomtee";
|
|
|
|
if (!fs::exists(root / "libqcomtee.a", ec)) {
|
|
std::println(std::cerr,
|
|
"libqcomtee not built for '{}'. Run:\n"
|
|
" packaging/make-libqcomtee.sh{}{}",
|
|
cfg.target.empty() ? std::string("native") : cfg.target,
|
|
cfg.target.empty() ? std::string() : " --target=" + cfg.target,
|
|
cfg.sysroot.empty() ? std::string() : " --sysroot=" + cfg.sysroot);
|
|
}
|
|
cfg.compileFlags.push_back("-I" + (root / "include").string());
|
|
cfg.linkFlags.push_back((root / "libqcomtee.a").string());
|
|
}
|
|
|
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
|
// fingerprintd-core — the wire formats and state machines as a static
|
|
// library of pure C++ modules. Deliberately free of GLib, libqcomtee and
|
|
// every system header: the byte-level decisions in here were the whole
|
|
// cost of this project, so they are pinned by tests that run on a dev box
|
|
// with no phone, no TEE and no sensor.
|
|
static auto Core = std::make_unique<Configuration>();
|
|
Core->path = "./";
|
|
Core->name = "fingerprintd-core";
|
|
Core->outputName = "fingerprintd-core";
|
|
ApplyStandardArgs(*Core, args);
|
|
Core->type = ConfigurationType::LibraryStatic;
|
|
{
|
|
std::array<fs::path, 7> ifaces = {
|
|
"interfaces/Fingerprintd",
|
|
"interfaces/Fingerprintd-Sfs",
|
|
"interfaces/Fingerprintd-Rpmb",
|
|
"interfaces/Fingerprintd-Ta",
|
|
"interfaces/Fingerprintd-Engine",
|
|
"interfaces/Fingerprintd-Store",
|
|
"interfaces/Fingerprintd-Tee",
|
|
};
|
|
std::array<fs::path, 0> impls = {};
|
|
Core->GetInterfacesAndImplementations(ifaces, impls);
|
|
}
|
|
|
|
// fingerprintd — the daemon: sensor rail, QTEE session, the gpfile and
|
|
// RPMB listeners, and the bus surface, wrapped around the core.
|
|
Configuration cfg;
|
|
cfg.path = "./";
|
|
cfg.name = "fingerprintd";
|
|
cfg.outputName = "fingerprintd";
|
|
ApplyStandardArgs(cfg, args);
|
|
cfg.type = ConfigurationType::Executable;
|
|
cfg.dependencies = { Core.get() };
|
|
{
|
|
std::array<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "implementations/main" };
|
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
|
}
|
|
|
|
ApplyQcomteeFlags(cfg);
|
|
cfg.linkFlags.push_back("-lpthread"); // the supplicant thread
|
|
|
|
cfg.AddTest("Sfs").Dependencies({ Core.get() });
|
|
cfg.AddTest("Rpmb").Dependencies({ Core.get() });
|
|
cfg.AddTest("Ta").Dependencies({ Core.get() });
|
|
cfg.AddTest("Engine").Dependencies({ Core.get() });
|
|
cfg.AddTest("Store").Dependencies({ Core.get() });
|
|
cfg.AddTest("Tee").Dependencies({ Core.get() });
|
|
|
|
ProjectLint::AddProjectLintRules(cfg);
|
|
|
|
return cfg;
|
|
}
|