No BAR bounds validation on TPM-reported buffer offsets and sizes
- File:
sys/dev/crypto/tpm/tpm_crb.c - Lines: 187β196 (register reads), 208β219 (overlap check), 367β397 (use)
- Severity: Medium
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-1284 Improper Validation of Specified Quantity in Input, CWE-787 Out-of-bounds Write (via bus_space)
- Confidence: likely
- Status: new
Summary
tpmcrb_attach reads cmd_off, cmd_buf_size, rsp_off, rsp_buf_size
directly from TPM CRB registers (lines 187-196) and never validates that each
buffer plus its offset fits within the allocated MMIO BAR.
tpmcrb_fix_buff_offsets (lines 124-145) only converts absolute addresses to
relative offsets β it does not bounds-check.
The subsequent bus_write_region_stream_1 / bus_read_region_stream_1 calls
in tpmcrb_transmit (lines 367-370, 385-386, 396-397) pass these unchecked
values straight to the bus_space layer, which performs no bounds checking of
its own. A buggy or malicious TPM (e.g., a software TPM emulator in a VM, or
compromised firmware) can report offsets/sizes that cause MMIO accesses past
the mapped resource, producing a kernel panic.
Root cause
tpmcrb_attach (lines 187-196): crb_sc->cmd_off, cmd_buf_size, rsp_off,
rsp_buf_size are populated by RD4/RD8 from TPM_CRB_CTRL_CMD_* and
TPM_CRB_CTRL_RSP_* registers.
After tpmcrb_fix_buff_offsets (lines 124-145) adjusts them, the only
validation is the overlap/size-equality check at lines 208-219 β there is no
comparison against rman_get_size(sc->mem_res).
In tpmcrb_transmit:
bus_write_region_stream_1(sc->mem_res, crb_sc->cmd_off, sc->buf, length)at line 367bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off + TPM_HEADER_SIZE, &sc->buf[TPM_HEADER_SIZE], bytes_available - TPM_HEADER_SIZE)at line 396
use these unvalidated offsets. bus_write_region_stream_1 is a bare macro
(sys/bus.h:595-596) that forwards to bus_space_write_region_stream_1 with
no bounds gate.
If cmd_off + length (or rsp_off + bytes_available) exceeds the BAR, the
access walks off the end of the kernel virtual mapping established by
bus_alloc_resource_any (line 163) β on x86 this hits unmapped KVA and
panics.
Reachable without user privileges: tpm20_save_state (tpm20.c:292-322)
is wired to device_shutdown/device_suspend (lines 412-413 of this file)
and calls tpmcrb_transmit at shutdown/suspend time.
Threat model
An attacker who controls the TPM's reported register values β a VM hypervisor presenting a malicious CRB emulator (swtpm/QEMU), compromised platform firmware, or even a TPM with a register-implementation bug β can crash the guest/host kernel on any buffer access.
Most realistic scenario: a cloud/VM host feeds a guest a CRB device whose CMD/RSP address or size registers report values exceeding the BAR; the guest kernel panics on first transmit (shutdown, suspend, or any root TPM command).
No unprivileged-user path to /dev/tpm0 is needed for the
shutdown-triggered variant.
Impact: kernel panic (A:H).
On platforms where bus_space is direct-mapped to RAM, this could
additionally corrupt adjacent kernel memory (I:H), though DragonFlyBSD's
primary x86 target maps MMIO via pmap_mapdev so the practical result is a
reliable panic.
Proof of concept
Setup
A QEMU guest with a custom/swtpm CRB backend whose TPM_CRB_CTRL_CMD_LADDR/
HADDR (offset 0x5C/0x60) or TPM_CRB_CTRL_CMD_SIZE (offset 0x58) return a
value placing cmd_off + cmd_buf_size beyond the 0x5000-byte BAR (e.g.,
report cmd_off=0x6000 while BAR is 0x5000).
Trigger from inside the guest, no privileges required
# init 0 # shutdown
# OR
# acpiconf -s 3 # suspend
The kernel calls tpm20_save_state β tpmcrb_transmit β
bus_write_region_stream_1(mem_res, 0x6000, buf, 12), which writes past the
mapped MMIO region and panics.
Alternative (root)
# Issue any TPM command (10-byte TPM2_Startup is sufficient)
dd if=/dev/urandom bs=10 count=1 | dd of=/dev/tpm0 bs=10
Success: kernel panic with a page-fault or "fatal trap" in
bus_space_write_region_stream_1 / the MMIO fault handler.
A simpler PoC for a guest with a buggy swtpm: configure swtpm to return a
cmd_buf_size of 0xFFFFFFFF (so length <= cmd_buf_size passes at line 317
but the MMIO write of length bytes at a bogus cmd_off faults) and boot
the guest.
Recommended fix
Add BAR-bounds validation in tpmcrb_attach after the offset adjustment and
overlap check, before making the device live via tpm20_init.
Use rman_get_size (sys/rman.h:146) to get the BAR size and reject
configurations where the buffer extends past the BAR.
--- a/sys/dev/crypto/tpm/tpm_crb.c
+++ b/sys/dev/crypto/tpm/tpm_crb.c
@@ -219,6 +219,27 @@ tpmcrb_attach(device_t dev)
}
}
+ /*
+ * Validate that the command and response buffers actually fit
+ * within the allocated MMIO BAR. The TPM reports these via CRB
+ * registers; a buggy or malicious implementation (common with
+ * software emulators) can report values that would cause OOB
+ * bus_space accesses in tpmcrb_transmit.
+ */
+ bus_size_t bar_size = rman_get_size(sc->mem_res);
+ if (crb_sc->cmd_off >= bar_size ||
+ crb_sc->cmd_buf_size > bar_size - crb_sc->cmd_off) {
+ device_printf(sc->dev,
+ "Command buffer exceeds BAR (off=0x%jx size=0x%jx bar=0x%jx)\n",
+ (uintmax_t)crb_sc->cmd_off, (uintmax_t)crb_sc->cmd_buf_size,
+ (uintmax_t)bar_size);
+ tpmcrb_detach(dev);
+ return (ENXIO);
+ }
+ if (crb_sc->rsp_off >= bar_size ||
+ crb_sc->rsp_buf_size > bar_size - crb_sc->rsp_off) {
+ device_printf(sc->dev,
+ "Response buffer exceeds BAR (off=0x%jx size=0x%jx bar=0x%jx)\n",
+ (uintmax_t)crb_sc->rsp_off, (uintmax_t)crb_sc->rsp_buf_size,
+ (uintmax_t)bar_size);
+ tpmcrb_detach(dev);
+ return (ENXIO);
+ }
+
sc->transmit = tpmcrb_transmit;
References
sys/dev/crypto/tpm/tpm_crb.c:187-196β register reads without BAR bounds checksys/dev/crypto/tpm/tpm_crb.c:208-219β only overlap/size-equality checkedsys/dev/crypto/tpm/tpm_crb.c:367-397β unchecked offsets used inbus_*_region_stream_1sys/sys/bus.h:595-596βbus_write_region_stream_1is bare macro with no boundssys/dev/crypto/tpm/tpm_crb.c:412-413β shutdown/suspend triggers transmit without userspace
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1989 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| tpm_crb_oob.c | trigger-source | Placeholder PoC: documents the trigger and the audit-guest HW absence (no real in-guest PoC is possible without a TPM CRB device). | 1.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o tpm_crb_oob tpm_crb_oob.c | 189 B | view raw |
| run.sh | run-script | Runs the placeholder; prints the HW-gated message | 126 B | view raw |
| build.log | build-log | Baseline (unpatched) tpm.ko module build, full output, rc=0 | 1.1 KB | view raw |
| run.log | run-log | PoC build+run on audit guest: prints HW-gated message, RUN_RC=0 | 259 B | view raw |
| fix_build.log | build-log | Patched tpm.ko module build with fix.diff applied, full output, rc=0 | 1.1 KB | view raw |
| fix.diff | suggested-fix | Add BAR-bounds validation in tpmcrb_attach using rman_get_size; git-apply-able | 1.3 KB | view raw |
| env.txt | environment | uname, cc version, kern.version, pciconf vga check (no TPM) | 399 B | view raw |
| VERDICT.md | verdict | Full narrative: source-traced mechanism, why HW-gated, fix validation | 6.2 KB | β raw |
| README.md | readme | Evidence-pack slot pointer | 317 B | β raw |
DF-1989 PoC
See the parent finding markdown at findings/DF-1989-*.md for the full threat
model and PoC steps. This directory is the evidence-pack slot for the PoC
runner; the runner will populate it with sources, build.sh / run.sh, full
untrimmed logs, env.txt, VERDICT.md, and manifest.json after verification.
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)
-
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. -
tpmcrb_fix_buff_offsetsonly 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. -
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 againstrman_get_size(sc->mem_res)anywhere intpmcrb_attach. (rman_get_sizeis the canonical BAR-size accessor,sys/sys/rman.h:146.) -
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_1are bare macros with no bounds gate (they forward straight tobus_space_*_region_stream_1). -
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 throughtpm20_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 checksys/dev/crypto/tpm/tpm_crb.c:124-145βtpmcrb_fix_buff_offsetsonly adjusts, no boundssys/dev/crypto/tpm/tpm_crb.c:208-219β only overlap/size-equality check; no rman_get_sizesys/dev/crypto/tpm/tpm_crb.c:367-368,396-397β unchecked offsets used in bus_spacesys/dev/crypto/tpm/tpm_crb.c:412-413β shutdown/suspend reaches transmitsys/sys/rman.h:146βrman_get_size(r)=r_end - r_start + 1
Fix verification
not_testableVALIDATED module build. Patch applies, tpm.ko rebuilds rc=0 -Werror.
patch -p1 PATCH_RC=0; make rc=0 tpm.ko=37488B.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- c
- r
- y
- p
- t
- o
- /
- t
- p
- m
- /
- t
- p
- m
- _
- c
- r
- b
- .
- c
- :
- 1
- 8
- 7
- s
- y
- s
- /
- d
- e
- v
- /
- c
- r
- y
- p
- t
- o
- /
- t
- p
- m
- /
- t
- p
- m
- _
- c
- r
- b
- .
- c
- :
- 2
- 0
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- c
- r
- y
- p
- t
- o
- /
- t
- p
- m
- /
- t
- p
- m
- _
- c
- r
- b
- .
- c
- :
- 3
- 6
- 7
- s
- y
- s
- /
- d
- e
- v
- /
- c
- r
- y
- p
- t
- o
- /
- t
- p
- m
- /
- t
- p
- m
- _
- c
- r
- b
- .
- c
- :
- 3
- 9
- 6
Detail
Exploit chain
none (panic on unmapped KVA on x86; MMIO-OOB class).
Evidence (decisive lines)
Source-trace only. pciconf: no tpm/crypto device, /dev/tpm* absent.
Verified recommended fix
Add BAR-bounds validation using rman_get_size(sc->mem_res) after overlap check at :219.
Verdict
HW-GATED (no TPM CRB device). Bug CONFIRMED source-trace. tpm_crb.c:187-196 reads cmd_off/cmd_buf_size/rsp_off/rsp_buf_size from CRB MMIO with NO bounds check vs rman_get_size. tpmcrb_fix_buff_offsets only adjusts offsets. Path reachable via device_shutdown/suspend without /dev/tpm0 access.
No comments yet.