# DF-4407 VERDICT — untested (source-verified; hardware- and privilege-gated)

## Reproduced?

No. Status `untested`: the audit guest exposes no TPM (no `/dev/tpm`, no `tpm`
module, QEMU image has no TPM TIS device — `env.txt`), and even on hardware
both racing processes must be root (`make_dev(..., UID_ROOT, GID_WHEEL, 0600)`
at `tpm.c:253-254`). No unprivileged trigger exists, so this does not meet the
PoC bar of the audit (not Critical/High, no unprivileged local program).

## Defect (certain from source)

1. **TOCTOU on the exclusive-open flag.** `tpmopen` (`tpm.c:1146-1161`):

   ```c
   if (sc->sc_flags & TPM_OPEN)   /* check  */
       return EBUSY;
   sc->sc_flags |= TPM_OPEN;      /* set    — not atomic w.r.t. the check */
   ```

   No lock, no atomic op. On SMP, N concurrent `open("/dev/tpm")` calls can
   all observe the flag clear. `tpmclose` (`tpm.c:1172-1175`) clears it the
   same racy way.

2. **No inter-CPU serialization of transactions.** `tpmread`
   (`tpm.c:1193-1252`) and `tpmwrite` (`tpm.c:1267-1292`) bracket the whole
   command with `crit_enter()/crit_exit()`. A DragonFly critical section is
   per-CPU (interrupt/preemption shielding only — see `sys/kern/kern_synch.c`
   which freely combines crit sections with sleeping, e.g. `tstop()`); it
   does **not** exclude another CPU from executing the same driver entry
   simultaneously. Consequences of two concurrent transactions:
   - interleaved `TPM_DATA` FIFO writes from `tpm_tis12_write`
     (`tpm.c:821-843`) — the TPM receives two merged commands; the response
     stream is then parsed by `tpmread` (`tpm.c:1208`) with a bogus length,
     typically wedging the chip until `TPM_STS_CMD_READY` abort
     (`tpm.c:750`) or power cycle;
   - interleaved locality handoff in `tpm_request_locality`
     (`tpm.c:412-427`) — one thread releases locality
     (`tpm_tis12_end`, `tpm.c:891-892`) while the other still assumes it;
   - data races on shared `sc->sc_stat` (`tpm.c:736, 832, 851, 878, 895`)
     and on the tsleep/wakeup channels (function pointers used as idents,
     `tpm.c:420, 463, 548, 604` vs `tpm_intr` `tpm.c:932-942`).

3. **Provenance:** the file header carries `$FreeBSD: head/sys/dev/tpm/tpm.c
   365144 2020-09-01 mjg $` — FreeBSD r365144 is the commit that added
   `struct sx dev_lock` serialization to tpm(4). The DragonFly import kept
   the revision tag but the lock (and the softc member) is absent here; the
   `crit_enter()` calls are the OpenBSD `spl()` heritage, which never provided
   SMP exclusion.

## Impact ceiling

Privileged-only DoS of the TPM subsystem (chip wedge / tcsd hang / forced
reboot to recover). No kernel memory corruption: all copies stay within
`buf[TPM_BUFSIZ]` regardless of interleaving, because every length used
against the buffer is `MIN(sizeof(buf), …)` (`tpm.c:1273`) or bounded by the
response length check (`tpm.c:1212`, `len > uio->uio_resid` with `uio_resid`
being `size_t`, which also rejects negative lengths — verified separately as
a killed candidate).

## Fix

`fix.diff` — add `struct lock sc_lock` to `struct tpm_softc`, init in
`tpm_attach`, uninit at the end of `tpm_detach`, and take it exclusively in
`tpmopen`/`tpmclose`/`tpmread`/`tpmwrite`. Mirrors FreeBSD r365144.
Unvalidated on hardware (fix_status: not_testable) for the same gating
reasons as above.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
