# DF-4407 — /dev/tpm open/read/write are not serialized across CPUs (TOCTOU on TPM_OPEN + interleaved TIS transactions)

- File: `sys/dev/crypto/tpm/tpm.c` (pass 2, GLM 5.3)
- Class: CWE-362 (race condition) / CWE-667 (improper locking)
- Severity: Low — the device node is created 0600 root:wheel (`tpm.c:253-254`),
  so both racers must already be privileged (tcsd runs as root). Impact is
  TPM state-machine corruption / self-inflicted DoS, not memory corruption.

## Build / run

Not runnable on the audit guest: no TPM TIS device is exposed by QEMU here,
the driver is `optional tpm` (not in X86_64_GENERIC) and no `tpm` module is
loaded — see `env.txt`. On real hardware with a TIS 1.2 TPM:

```
# as root, on an SMP machine, two processes:
A: while :; do dd if=/dev/tpm of=/dev/null bs=64 count=1 &  done
B: while :; do dd if=/dev/zero of=/dev/tpm bs=64 count=1 &   done
```
Success criterion: with the bug, `tpmopen`'s single-open check is bypassed
(two simultaneous opens succeed) and the TIS locality/STS state machine
receives interleaved register writes; expect TPM_E_IO / device wedges
(tcsd hangs) and `tpm_request_locality` EBUSY storms that require a power
cycle. Expected after fix: second open returns EBUSY; transactions serialize.

## Why (source)

- `tpmopen` (`tpm.c:1146-1161`) does a plain check-then-set of
  `sc->sc_flags |= TPM_OPEN` with no lock — two CPUs can both observe the
  flag clear and both succeed (classic TOCTOU), defeating the exclusive-open
  invariant.
- `tpmread`/`tpmwrite` (`tpm.c:1181-1292`) use `crit_enter()`/`crit_exit()`
  as their only "serialization". In DragonFly a critical section blocks
  interrupts and preemption **on the current CPU only**; it provides no
  inter-CPU mutual exclusion. Two processes that won the open race (or a
  process racing the interrupt handler / suspend path) can run
  `tpm_tis12_write` concurrently on different CPUs, interleaving
  `TPM_DATA` FIFO writes, `TPM_ACCESS` locality requests (`tpm.c:412-413`)
  and shared `sc->sc_stat` updates (`tpm.c:736, 832, 851, 878, 895`).
- This driver was imported from FreeBSD r365144 (see `$FreeBSD:` tag at
  `tpm.c:18`), which is precisely the commit that added `sx` device locking
  to FreeBSD's tpm(4); the DragonFly port kept the tag but not the lock.
- `tpmclose` (`tpm.c:1163-1178`) has the same unlocked flag clear.

## Recommended fix

Add a lock to `struct tpm_softc` and hold it across open/close/read/write
bodies (see `fix.diff`). This mirrors upstream FreeBSD r365144.
