fingerprintd/project.cpp
Jorijn van der Graaf a91fb2ff58 Reach QTEE: credentials, client env and the app loader, with no QCBOR
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.
2026-09-02 18:02:28 +02:00

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;
}