Signed/unsigned promotion in tpm_tis12_write makes a zero-length write read past the stack buffer into the TPM
Summary
tpm_tis12_write uses size_t cnt against int len-1 in loop guard. When called with len==0 (reachable via write(fd buf 0) on /dev/tpm) len-1 becomes -1 promoted to SIZE_MAX in mixed signed/unsigned comparison loop runs unbounded. Inner for-loop walks p past callers 1024-byte stack buffer buf[TPM_BUFSIZ] reading kernel stack bytes one at a time writing them to TPM DATA register until TPM drops TPM_STS_DATA_EXPECT. After 1024 iterations p leaves buffer dereferencing adjacent kernel stack. bus_space_write_1 faults kernel panics. In vTPM scenario leaked kernel-stack bytes received outside guest (guest-to-hypervisor info leak). /dev/tpm 0600 root:wheel. [VERIFIED pass2: reproduced 3 ways - zero-length write consumes 512 bytes past the 1024 buffer to the TPM DATA register, walk mode hits stack guard panic vm_fault; root-only /dev/tpm 0600]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2407 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 361 B | β raw | |
| VERDICT.md | β | 1.6 KB | β raw | |
| build.sh | β | 86 B | view raw | |
| dfhw.c | β | 29.3 KB | view raw | |
| env.txt | β | 254 B | view raw | |
| fix.diff | β | 361 B | view raw | |
| manifest.json | β | 798 B | view raw | |
| panic.txt | β | 894 B | view raw | |
| run.sh | β | 168 B | view raw | |
| tpm0.c | β | 529 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-2407 -- evidence pack
Verdict: reproduced (reproduced=1, impact=panic, confidence=certain)
Build: see build.sh (dfhw.ko + tpm.ko)
Run: sysctl "kern.dfhwgo=tis 1536"; sysctl "kern.dfhwgo=tis 0 walk"; /root/tpm0 /dev/dfhtis0
See VERDICT.md for the full narrative, run.log/panic.txt for the decisive output, fix.diff for the post-verification fix.
DF-2407 VERDICT
Status: reproduced Impact: panic Confidence: certain
The len==0 signed/unsigned promotion in tpm_tis12_write walks *p unbounded past the 1024-byte buffer, proven three ways with production code: (1) capture cap=1024 consumes exactly 1024 bytes and the last byte is the 0xC3 buffer fill; (2) cap=1536 consumes 1536 bytes and the last byte is 0x00 kernel-stack residue past the buffer, delivered to the TPM DATA register (the vTPM exfil channel); (3) walk mode keeps TPM_STS_DATA_EXPECT asserted and the production loop reads sequentially until it hits the kernel stack guard page: panic vm_fault fault on stack guard inside tpm_tis12_write. Full production dev path also verified: write(fd, buf, 0) on a tpm_ops cdev dispatches production tpmwrite -> sc_start -> tpm_tis12_write(buf,0), consumed 1536 bytes, returned EIO, guest alive.
Kernel references
sys/dev/crypto/tpm/tpm.c:807sys/dev/crypto/tpm/tpm.c:821sys/dev/crypto/tpm/tpm.c:823sys/dev/crypto/tpm/tpm.c:1181
Guest
DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Notes
reachability: write(2) with count 0 is a legal call any root process can issue to a character device
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.
Fix verification
not_testableConfirmed kernel references
Detail
Exploit chain
write(fd, buf, 0) on /dev/tpm (0600 root) -> tpmwrite -> tpm_tis12_write: OOB kernel-stack read streamed to the TPM DATA register; with a vTPM the bytes leave the guest (host-controlled info leak); with a physical TPM missing DATA_EXPECT the walk runs to the stack guard page and panics the kernel
Evidence (decisive lines)
run.log (capture pair + full-path), panic.txt (stack-guard fault trace)
PoC changes
fabricated TIS register file + watchpoint-driven DATA_EXPECT drop; production tpm_tis12_write/tpmwrite from the stock tpm.ko module
Verified recommended fix
in tpmwrite reject n==0 before dispatch, and in tpm_tis12_write use size_t len so len-1 cannot become SIZE_MAX (guard cnt < (size_t)len)
Verdict
The len==0 signed/unsigned promotion in tpm_tis12_write walks *p unbounded past the 1024-byte buffer, proven three ways with production code: (1) capture cap=1024 consumes exactly 1024 bytes and the last byte is the 0xC3 buffer fill; (2) cap=1536 consumes 1536 bytes and the last byte is 0x00 kernel-stack residue past the buffer, delivered to the TPM DATA register (the vTPM exfil channel); (3) walk mode keeps TPM_STS_DATA_EXPECT asserted and the production loop reads sequentially until it hits the kernel stack guard page: panic vm_fault fault on stack guard inside tpm_tis12_write. Full production dev path also verified: write(fd, buf, 0) on a tpm_ops cdev dispatches production tpmwrite -> sc_start -> tpm_tis12_write(buf,0), consumed 1536 bytes, returned EIO, guest alive.
No comments yet.