β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2407

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2407 Β· 11 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

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_testable
↓ fix.diffper-fix-DF-2407

Confirmed 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.