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:
parent
55a44239ff
commit
be4ceedbd1
2 changed files with 176 additions and 21 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 gpio0–3), 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 81–84. TASK-45's pin-conflict problem (GPIO 85/86, SE5) is real and
|
||||
|
|
|
|||
Loading…
Reference in a new issue