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.
This commit is contained in:
Jorijn van der Graaf 2026-09-02 18:02:28 +02:00
commit a91fb2ff58
6 changed files with 708 additions and 25 deletions

82
packaging/make-libqcomtee.sh Executable file
View file

@ -0,0 +1,82 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-3.0-only
# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
# make-libqcomtee.sh — build libqcomtee for a target, without QCBOR.
#
# packaging/make-libqcomtee.sh [--target=<triple> --sysroot=<dir>] [outdir]
#
# libqcomtee is Qualcomm's BSD-3 userspace client for QTEE (quic/quic-teec).
# Upstream's CMake does find_package(QCBOR REQUIRED) and falls back to libcbor,
# but that dependency exists for exactly one file, src/objects/credentials_obj.c,
# which builds the {AttrUid, AttrSystemTime} CBOR map.
#
# fingerprintd builds that map itself in Fingerprintd:Tee, where it is thirteen
# bytes under test rather than a library dependency, and implements the
# credentials object's two-op read protocol in its own shell. So only two of
# the three sources are needed:
#
# src/qcomtee_object.c the object/marshalling core
# src/objects/mem_obj.c memory objects (CAPTURE_IMAGE needs a real region)
#
# Nothing in either references credentials_obj, so dropping it leaves the
# library with no dependency beyond libc.
set -eu
REPO="${QUIC_TEEC_REPO:-https://github.com/quic/quic-teec.git}"
# Pinned. Upstream is small and moves rarely; an unpinned clone would change
# the marshalling under us without notice.
COMMIT="${QUIC_TEEC_COMMIT:-736419e}"
SRC="${QUIC_TEEC_DIR:-$HOME/.cache/fingerprintd/quic-teec}"
TARGET=""
SYSROOT=""
MARCH=""
OUT=""
for a in "$@"; do
case "$a" in
--target=*) TARGET="${a#--target=}" ;;
--sysroot=*) SYSROOT="${a#--sysroot=}" ;;
--march=*) MARCH="${a#--march=}" ;;
-*) echo "unknown option: $a" >&2; exit 1 ;;
*) OUT="$a" ;;
esac
done
[ -n "$OUT" ] || OUT="$HOME/.cache/fingerprintd/libqcomtee${TARGET:+-$TARGET}"
if [ ! -d "$SRC/.git" ]; then
echo ">> cloning $REPO -> $SRC"
mkdir -p "$(dirname "$SRC")"
git clone -q "$REPO" "$SRC"
git -C "$SRC" checkout -q "$COMMIT"
elif [ -n "${QUIC_TEEC_DIR:-}" ]; then
# A checkout the caller pointed us at is THEIRS. Never move its HEAD --
# silently checking out a pin in someone's working tree is how you lose
# uncommitted work. Report the mismatch and let them decide.
have=$(git -C "$SRC" rev-parse --short HEAD)
case "$COMMIT" in
"$have"*) : ;;
*) echo "!! $SRC is at $have, not the pinned $COMMIT." >&2
echo "!! Building from it anyway; unset QUIC_TEEC_DIR to use a pinned clone." >&2 ;;
esac
else
git -C "$SRC" fetch -q --all 2>/dev/null || true
git -C "$SRC" checkout -q "$COMMIT"
fi
mkdir -p "$OUT"
set -- -O2 -Wall -fPIC
[ -n "$TARGET" ] && set -- "$@" --target="$TARGET"
[ -n "$SYSROOT" ] && set -- "$@" --sysroot="$SYSROOT"
[ -n "$MARCH" ] && set -- "$@" -march="$MARCH"
echo ">> building libqcomtee.a (no QCBOR) for ${TARGET:-native}"
for f in src/qcomtee_object.c src/objects/mem_obj.c; do
clang "$@" -I"$SRC/libqcomtee/include" -I"$SRC/libqcomtee/src" \
-c "$SRC/libqcomtee/$f" -o "$OUT/$(basename "$f" .c).o"
done
rm -f "$OUT/libqcomtee.a"
llvm-ar rcs "$OUT/libqcomtee.a" "$OUT"/*.o
mkdir -p "$OUT/include"
cp "$SRC/libqcomtee/include/"*.h "$OUT/include/"
echo ">> done: $OUT/libqcomtee.a"