# DF-1989 — No BAR bounds validation on TPM-reported buffer offsets and sizes

## Verdict

**REPRODUCED (source-only, HW-gated).** The bug is real and the cited
data-flow is correct end-to-end, but the audit guest has **no TPM CRB
device** (no `/dev/tpm*`, no `tpmcrb` in `pciconf -l`, no `crypto`-class
PCI device), so the trigger cannot be exercised at runtime.  Per the run
instructions, source-only confirmation is acceptable for HW-gated findings,
so this row is marked `status=inconclusive`, `reproduced=0`,
`impact=none` (HW-gated).  The recommended fix has been **authored,
applied to the in-guest source tree, and verified to compile** (`tpm.ko`
re-linked with `rc=0`).

## Mechanism (source-traced, every hop cited)

1. **Buffer geometry read directly from CRB MMIO registers.**
   `sys/dev/crypto/tpm/tpm_crb.c:187-196`:
   ```c
   crb_sc->rsp_off       = RD8(sc, TPM_CRB_CTRL_RSP_ADDR);    /* or RD4+HADDR */
   crb_sc->cmd_off       = RD4(sc, TPM_CRB_CTRL_CMD_LADDR);
   crb_sc->cmd_off      |= ((uint64_t) RD4(sc, TPM_CRB_CTRL_CMD_HADDR) << 32);
   crb_sc->cmd_buf_size  = RD4(sc, TPM_CRB_CTRL_CMD_SIZE);
   crb_sc->rsp_buf_size  = RD4(sc, TPM_CRB_CTRL_RSP_SIZE);
   ```
   All four values come from device-controlled registers.

2. **`tpmcrb_fix_buff_offsets` only rewrites absolute→relative offsets.**
   `sys/dev/crypto/tpm/tpm_crb.c:124-145`:
   ```c
   if (crb_sc->cmd_off > base_addr && crb_sc->cmd_off < base_addr + length)
       crb_sc->cmd_off -= base_addr;
   if (crb_sc->rsp_off > base_addr && crb_sc->rsp_off < base_addr + length)
       crb_sc->rsp_off -= base_addr;
   ```
   This performs **no bounds check** against the BAR size.

3. **Only an overlap/size-equality check exists.**
   `sys/dev/crypto/tpm/tpm_crb.c:208-219`:
   ```c
   if (crb_sc->rsp_off == crb_sc->cmd_off) {
       if (crb_sc->cmd_buf_size != crb_sc->rsp_buf_size) { ... return ENXIO; }
   }
   ```
   There is **no comparison against `rman_get_size(sc->mem_res)`** anywhere
   in `tpmcrb_attach`.  (`rman_get_size` is the canonical BAR-size accessor,
   `sys/sys/rman.h:146`.)

4. **Unchecked offsets flow into bus_space.**
   `sys/dev/crypto/tpm/tpm_crb.c:367-368`:
   `bus_write_region_stream_1(sc->mem_res, crb_sc->cmd_off, sc->buf, length);`
   `sys/dev/crypto/tpm/tpm_crb.c:396-397`:
   `bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off + TPM_HEADER_SIZE,
       &sc->buf[TPM_HEADER_SIZE], bytes_available - TPM_HEADER_SIZE);`
   `bus_write_region_stream_1` / `bus_read_region_stream_1` are bare macros
   with no bounds gate (they forward straight to
   `bus_space_*_region_stream_1`).

5. **Reachable without /dev/tpm0 access.**
   `sys/dev/crypto/tpm/tpm_crb.c:412-413`:
   ```c
   DEVMETHOD(device_shutdown, tpm20_shutdown),
   DEVMETHOD(device_suspend,  tpm20_suspend),
   ```
   both of which route through `tpm20_save_state` → `tpmcrb_transmit`.  A
   shutdown or suspend initiated by any user (e.g. `init 0`,
   `acpiconf -s 3`) reaches the unchecked MMIO write.

## Why it is not reproduced at runtime on this guest

`pciconf -l` on the guest shows no `tpm` or `crypto`-class device:
```
vgapci0@pci0:0:2:0: class=0x030000 ... chip=0x11111234 ...   (QEMU stdvga)
```
(no tpmcrb, no TPM TIS, no `/dev/tpm0`).  `tpmcrb_attach` is never called,
so the vulnerable code path never executes.  Reproducing at runtime would
require either booting the guest with `-device tpm-crb-device` against a
patched swtpm, or running on hardware with a real CRB TPM that reports
malformed register values — neither of which the audit guest provides.

## Exploit chain

Not applicable (this is a **panic / DoS**-class finding on the primary x86
target; `bus_space` writes past the MMIO mapping fault on unmapped KVA, not
into RAM).  The finding markdown notes a theoretical I:H variant on
platforms where bus_space is direct-mapped to RAM, but DragonFlyBSD's x86
target uses `pmap_mapdev`, so the practical impact is reliable kernel
panic.  There is no privilege-escalation primitive to develop.

## PoC changes

There is no in-guest runtime PoC possible without a TPM CRB device.  I
added a trivial placeholder (`tpm_crb_oob.c`) that documents the trigger
and the HW absence, plus `build.sh` / `run.sh`.  The substantive
confirmation is the source trace above.

## Recommended fix

Add BAR-bounds validation in `tpmcrb_attach`, between the existing
overlap/size-equality check and the `sc->transmit = tpmcrb_transmit`
assignment.  Use `rman_get_size(sc->mem_res)` (already available via
`<sys/rman.h>` included through `tpm20.h`) to obtain the BAR size, and
reject any configuration where `cmd_off + cmd_buf_size` or
`rsp_off + rsp_buf_size` exceeds the BAR.  The check uses the
overflow-safe form `off >= bar || size > bar - off` to handle
`off == bar` correctly.

The fix in `fix.diff` matches the structure proposed in the finding
markdown and has been verified to compile (see `fix_build.log`).

## Fix validation

The fix was applied to `/usr/src` in the guest (`patch -p1 --forward`,
`PATCH_RC=0`), and the `tpm` KLD module was rebuilt:

```
cd /usr/src/sys/dev/crypto/tpm
rm -f tpm_crb.o tpm.ko
AWK=awk make KERNCONF=X86_64_GENERIC KMODDIR=/tmp/tpm_test
# rc=0, tpm.ko = 37488 bytes
```

Full build output is in `fix_build.log`.  The fix compiles cleanly with
`-Werror`.  Runtime before/after validation is **not_testable** because
the guest has no TPM CRB device; the patched `tpm_crb.c` was inspected to
confirm the BAR-bounds check is inserted before `sc->transmit =
tpmcrb_transmit` and rejects oversized configurations with `ENXIO`.

Because the bug is HW-gated on this guest, a runtime before/after kernel
boot + PoC re-run is not possible — `fix_status = "not_testable"`, with
the diff verified to apply + compile and source-traced to close the path.

## References

- `sys/dev/crypto/tpm/tpm_crb.c:187-196` — register reads without BAR bounds check
- `sys/dev/crypto/tpm/tpm_crb.c:124-145` — `tpmcrb_fix_buff_offsets` only adjusts, no bounds
- `sys/dev/crypto/tpm/tpm_crb.c:208-219` — only overlap/size-equality check; no rman_get_size
- `sys/dev/crypto/tpm/tpm_crb.c:367-368,396-397` — unchecked offsets used in bus_space
- `sys/dev/crypto/tpm/tpm_crb.c:412-413` — shutdown/suspend reaches transmit
- `sys/sys/rman.h:146` — `rman_get_size(r)` = `r_end - r_start + 1`
