Watch
1
0
Fork
You've already forked SouveraineOS
0

task-44: the TA clears the latch, and the sensor does not free-run

fpctzappfingerprint loads from cold boot and answers; init/idle/arm all 0x0.
gpio121 goes idle-low after the sequence - the latch that killed piece 1 is
cleared, so a rising edge is possible for the first time. init accounts for 68
of the edges; armed and untouched is a flat 0 over 15s, so the 1444-event run
was the re-arm loop. Open: whether a finger raises an edge.

Reconcile the file with the code it now contradicts (the sysfs is shipped on
purpose) and register arbitrary signed-TA loading as a new Tier-0 primitive.
This commit is contained in:
Fimeg 2026-08-01 19:01:42 -04:00
commit be4ceedbd1
2 changed files with 176 additions and 21 deletions

View file

@ -136,9 +136,47 @@ Tracked gaps. Each has a status; none are silently accepted.
| SDDM-vs-greetd asymmetry (two greeters) | Deferred debt, not accepted | doctrine §12 |
| Autonomous invisible capability use | Open — P3 not yet enforced | P3 |
| Cross-machine user-data sync (contacts federation) | Parked — separate trust domain | `contacts-design.md` |
| **Arbitrary signed TA loading from the AP** | **New 2026-08-01 — ungated beyond file perms** | below; TASK-44 |
---
## New primitive 2026-08-01 — the AP can load trusted applications
Recorded deliberately, because it is a genuine widening of what the device can
do and it happened as a side effect of chasing a fingerprint sensor.
**What changed.** Until today the QSEECOM client could only *look up* apps
already resident in TrustZone (`GET_APP_ID`), and on a mainline userspace
nothing ever loads one — Android's `qseecomd` does that — so every lookup
returned `-ENOENT` and the surface was inert. The kernel now implements
`qcom_scm_qseecom_app_load()` and a `LOAD_APP` ioctl that reassembles a QSEE
image (`.mdt` + `.bNN`) and hands TZ its physical address. **Any signed TA
image placed in `/lib/firmware` can now be started and issued commands.**
Proven end-to-end: `fpctzappfingerprint` loads from cold boot and answers.
**What backstops it, and what does not.** TZ verifies the image signature, so
we cannot load our own code into the secure world — that check is Qualcomm's
and Google's, not ours, and it is the real boundary. What TZ does *not* decide
is **which** signed vendor TA gets started, **when**, or **on whose behalf**.
Today the only access control on that is file permissions:
`/dev/qseecom` is `crw------- root root`. So the primitive is
root-gated and nothing more.
**Why it matters to this threat model.** A TA is a privileged execution
context that outlives the process that started it and may hold keys or device
state. This sits **below the session authority entirely** — sessiond, the
capability tiers and the lock state have no visibility into it, so none of P2
(capability = live state), P3 (use is visible) or P6 (narrow granularity)
currently apply. It is Tier-0 surface reachable by any root-capable process,
including a compromised one, and a wedged or misdriven TA can take TZ or the
modem with it.
**Not yet decided, and the reason this is a watch-list row rather than a
finding:** whether TA loading should be a capability at all — gated per-app,
attested to a caller, and *visible* when used (P3) — or whether root-only is
the accepted answer for a single-user device. Do not let "the fingerprint
needed it" become the implicit policy for every TA on the device.
## Long-arc goal — process isolation + integrity monitoring
**Goal:** isolate every process into a known, gated section, with a Wazush-style

View file

@ -1,10 +1,18 @@
# TASK 44 — Fingerprint (FPC1020) as a capability factor
**Status:** piece 1 **built and compiling** 2026-08-01, unverified on glass;
piece 2 open. Raised 2026-07-28 (Casey: "a huge win possible"). **Size:** two
separable pieces — one was a session, the other is a research frontier. Do not
scope them as one thing. **Repos:** `Pixel3Arch` (kernel + DT), `souveraine`
(the factor, if the second piece lands).
**Status:** **the TrustZone wall is down** (2026-08-01 evening). The kernel
loads and authenticates a signed TA, `fpctzappfingerprint` runs, and its SPI
traffic **clears the interrupt latch that killed piece 1** — the sensor now
sits armed with the line idle-low. One question remains open and it is the
whole question: *does a finger raise an edge.* Raised 2026-07-28 (Casey: "a
huge win possible"). **Size:** two separable pieces — one was a session, the
other is a research frontier. Do not scope them as one thing. **Repos:**
`Pixel3Arch` (kernel + DT), `souveraine` (the factor, if the second piece
lands).
**Read the 18:5x section below before the 16:0x one.** The earlier measurement
concluded piece 1 "does not exist on this hardware"; that conclusion was
correct about the AP acting alone and is superseded by the TA path.
## The substrate was extracted — but from the wrong driver
@ -72,16 +80,105 @@ the polarity trap that cost a day on FTS, `PAF/touch.md`), `vdd_io-supply =
`CONFIG_INPUT_FPC1020=m` is in `kernel/config-blueline.aarch64` and survives
`olddefconfig` in place, so the PKGBUILD drift guard stays quiet.
**Done:** compiles clean on archdev (zero warnings); the DTB builds and
decompiles back to the right phandles, pins and flags.
**Not done — needs the device:** a probe line in `dmesg`, an interrupt on
finger contact, and a wake from a blanked screen. Until a touch has actually
produced an event this is a driver that builds, not a sensor that works.
### Measured on glass 2026-08-01 — the AP half is complete, and it is not enough
Everything the AP owns is correct and verified on the running kernel
(`7.1.1-sdm845-g8eebcaff65ef`): module loaded, driver bound at
`/sys/bus/platform/drivers/fpc1020`, `FPC1020 Fingerprint Sensor` registered as
`input4`/`event4` advertising `KEY_WAKEUP`, `line 7: output active-low
consumer="reset"`, and IRQ 168 wired as `msmgpio 121 Edge fpc1020`.
**And a touch produces nothing.** `evtest` on `event4` for 60 s across repeated
touches: zero events, and the interrupt count never moved.
The reason is measurable, not inferred:
- `gpioget --chip gpiochip2 121` reads **`active`** — the IRQ line is stuck
high, so an `IRQ_TYPE_EDGE_RISING` request can only ever see one edge.
- Unbind the driver and the line **stays high**, against the DT's
`bias-pull-down`. The *sensor* is driving it, not a pull or a stale config.
- Rebind and the count goes 1 → 2, one edge per reset, then latched again.
So the FPC1020 asserts its interrupt at power-up and **holds it until the host
clears it over SPI**. That is the "half a driver" this task already named,
now with a number on it: without the SPI side the line latches once and the pin
is dead thereafter.
**There is no SPI path to it from the AP.** The device's own DTBO gives
`fp_fpc1020` exactly `interrupts`, `fpc,gpio_rst`, `fpc,gpio_irq` and
`vdd_io-supply` — no SPI node, no bus. The one nearby SPI, `qupv3_se0_spi`
(fragment@36, TLMM gpio03), hosts `intel,mnh-spi` — the Pixel Visual Core.
The fingerprint's SPI is not in the AP's device tree anywhere.
**Therefore piece 1, as scoped above, does not exist on this hardware.** "Wire
the DT and the phone gains a real button under the reader" was the premise, and
it is wrong: a finger cannot raise an edge the AP can see, because the first
edge is already spent and only a TrustZone SPI transaction rearms it. The
driver is correct and worth keeping — it is the AP half of whatever piece 2
becomes — but it is not an input, not a wake source, and must not be described
as either.
> **SUPERSEDED the same evening — and by its own prediction.** "Only a
> TrustZone SPI transaction rearms it" was the exit condition, and the TA path
> below satisfies it: the latch clears and the line returns to idle-low. What
> stays true is the narrower claim that **the AP alone** can never rearm the
> sensor. Piece 1 does not exist *without TZ*; with the TA loaded it is no
> longer blocked on the hardware.
**What this does to Mis012's GPIO-mode lead.** Driving the sensor in GPIO mode
means bit-banging its SPI from TLMM pins, which requires knowing which pins
carry it. On blueline they are absent from the DTBO, so the lead is not bounded
from here — it needs a schematic, a probe, or a downstream source that names the
pins. Worth exactly that much investigation and no more before it is believed.
**This piece does not authenticate anything.** Do not let it get described
as fingerprint unlock, in a commit or a settings string. An input that looks
like an auth factor and is not is worse than no input.
## Measured 2026-08-01 18:5x — the latch clears, and the sensor does not free-run
First cold boot on `7.1.1-sdm845-g08e9db54cfce`. The TA loads from a cold
start with nothing resident: `app_id=2`, and `init` (0), `idle` (5) and
`arm` (3) each return `0x00000000`.
**The interrupt latch clears.** This is the measurement that moves the task.
At 16:0x the line read `active` and stayed high with the driver unbound — the
sensor driving it against the DT's `bias-pull-down`. After the TA sequence,
`/sys/kernel/debug/gpio` reads:
gpio121 : in low func0 2mA pull down
Idle-low, pull-down effective, and **stable across seconds**. An
`IRQ_TYPE_EDGE_RISING` request can fire again, which it could not before.
Read the level through debugfs, not `gpioget` — while the driver holds the
line, a request returns `EBUSY`.
**Edges are TA-driven SPI traffic, and every one is accounted for.** Counted
from `/proc/interrupts` around each command:
| command | IRQs | line after |
|---|---|---|
| `init` (0) | **+68** | low |
| `idle` (5) | +1 | low |
| `arm` (3) | +1 | low |
68 edges on `init` is a real register conversation with the sensor, not noise.
The boot count went 1 → 91 over the first sequence and 91 → 161 over a second.
**Free-running is refuted.** Armed once, never re-armed, 15 s with nothing on
the reader: **0 hardware IRQs, 0 poll notifications**, and the count held flat
per-second for the whole window. The 1444-event run that raised this doubt
re-armed on every interrupt, so one assert became a loop — the loop was the
signal, not the sensor. Any future instrument must arm exactly once per window;
`fp-window.py` on the phone does, and prints per-second deltas so a real
detection can be seen to *cluster* rather than spread.
**Still open, and it is the only thing left: does a finger raise an edge.** A
60 s armed window recorded 0 IRQs, but **no finger was confirmed on the reader
during it**, so that run is not evidence about detection in either direction —
do not cite it as one. The rig is ready: TA resident, line idle-low, baseline
a hard zero, so a single confirmed touch settles it.
## Piece 2 — biometrics, which is a TrustZone question
Matching needs either QSEECOM and Google's TA, or an open SPI image path with
@ -111,13 +208,26 @@ expected**:
So what is actually missing is narrower than "can we talk to TZ":
1. **There is no app-*loading* path.** `5fa36934b` only queries already-resident
apps; it deliberately is not the Android load-app ABI and does no ELF
loading. `QSEECom_start_app`/`load_external_elf` is the one family the
archaeology explicitly never disassembled.
2. **Nothing has ever exercised `app_send` live**, so the blocked-call resume
path (`8f737e819`) is unproven end-to-end.
3. The FPC TA blob and its command protocol are still vendor-opaque.
1. ~~**There is no app-*loading* path.**~~ **BUILT 2026-08-01.**
`qcom_scm_qseecom_app_load()` — app-mgr command 1 under `QSEE_OS`, three VAL
params (`mdt_len`, `img_len`, `phys`), downstream's `TZ_OS_APP_START_ID`
(`qseecomi.h:456`) — plus a `LOAD_APP` ioctl that reassembles the QSEE image
the way TZ expects: the `.mdt` ELF header blob plus one `.bNN` per program
header, laid out contiguously at `p_paddr`-relative offsets, staged with
`dma_alloc_coherent` under a 32-bit mask because the request carries a
32-bit physical address. Commits `d7d2e1222`, `cb150d71d`, `4d52867fe`.
2. ~~**Nothing has ever exercised `app_send` live.**~~ **DONE.** Three commands
round-tripped to a real TA on hardware, all `0x00000000`.
3. The FPC TA blob stays vendor-opaque, but **its command protocol is no longer
entirely so**: `0` init, `3` arm, `5` idle, sent through a 0x58-byte shared
aux buffer (`fpc_tac_alloc_shared`) whose first two words are `0x0A` and the
command. Payload-in-a-second-buffer is `ac9a4dd2a`.
**The prediction below was tested and resolved.** Every `GET_APP_ID` returned
`-ENOENT` — for *all* names, not just `uefisecapp` — because TZ only resolves
apps something already started, and on Android that something is `qseecomd`.
So "apps must be loaded before they can be called" was the right branch, and
loading is now built rather than a frontier.
Cheapest next probe, and it is small: run the existing `/dev/qseecom`
`GET_APP_ID` against candidate names on the device. `uefisecapp` is already
@ -141,10 +251,17 @@ permission/capability gating is in-tree, not vendored.
## Do not
- **Do not build Piece 2 first.** The cheap win is real and independent.
- **Do not ship the shim's sysfs to userspace as an API.** It is a vendor
interface for a TA that does not exist here; a settings switch bound to
`device_prepare` would be a success-shaped control over nothing. The port
drops that surface entirely — do not add it back for convenience.
- **Do not ship the shim's sysfs to userspace as an API.** ~~The port drops
that surface entirely~~ — **AMENDED 2026-08-01** (`329737a23`): the surface
is back, because the premise it rested on ("a vendor interface for a TA that
does not exist here") stopped being true the moment the TA loaded. The TZ
stack needs `device_prepare`/`hw_reset`/`wakeup_enable` to sequence the
sensor. **The prohibition still holds where it was aimed:** these are
root-only and write-only on the device (`--w-------`; only `irq` is
readable), and none of them may become a settings switch or an agent verb.
A control that reports success while authenticating nothing is exactly the
failure this bullet was written against — that argument survives, only its
factual premise changed.
- ~~**Do not confuse FPC1020 with the FPC1075**~~ — RESOLVED 2026-08-01. This
unit's own DTBO carries FPC1020 on gpio7/gpio121 and nothing reserves
GPIO 8184. TASK-45's pin-conflict problem (GPIO 85/86, SE5) is real and