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

Use-after-free via un-drained discard_buffer_callout on device detach

Summary

tpm20_write arms 500ms discard_buffer_callout. tpm20_close does not stop it (no-op). tpm20_release frees sc->buf and uninits dev_lock WITHOUT draining discard_buffer_callout (only harvest_callout drained under #ifdef TPM_HARVEST). If detach occurs within 500ms of last write callout fires on freed memory: memset(sc->buf...) UAF write and lockmgr(&sc->dev_lock...) lock-use-after-destroy kernel panic or heap corruption. Attacker: root (device 0600 root:wheel). Trigger: write then kldunload/ACPI hot-remove within 500ms. Impact: kernel panic DoS. UAF write memset into freed slab could corrupt adjacent heap if reused within window. [VERIFIED pass2: reproduced - stale 500ms callout after release fires memset through resurrected sc->buf 0x41414141 Fatal trap 9 GP at tpm20_discard_buffer+0x3d; callout_drain fix-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2409 Β· 11 files
FileTypeDescriptionSize
README.md β€” 367 B ↓ raw
VERDICT.md β€” 1.3 KB ↓ raw
build.sh β€” 86 B view raw
dfhw.c β€” 29.3 KB view raw
env.txt β€” 254 B view raw
fix.diff β€” 352 B view raw
manifest.json β€” 770 B view raw
panic.txt β€” 1.2 KB view raw
run.sh β€” 174 B view raw
t20.c β€” 941 B view raw
verdict.json β€” 2.0 KB view raw

DF-2409 -- evidence pack

Verdict: reproduced (reproduced=1, impact=panic, confidence=certain)

Build: see build.sh (dfhw.ko + tpm.ko) Run: /root/t20 (open+write+close /dev/tpm0 then sysctl t20_snapshot + t20_release_canary; sleep 2)

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-2409 VERDICT

Status: reproduced Impact: panic Confidence: certain

UAF write through the stale 500ms discard_buffer_callout: production tpm20_init created a real /dev/tpm0, a userland open+write+close armed the callout via production tpm20_write, production tpm20_release freed/uninited without draining it, the sc was freed and immediately resurrected at the same address with sc->buf = 0x4141414141414141, and the callout fired into the freed object: Fatal trap 9 general protection fault at tpm20_discard_buffer+0x3d (movq $0,(%rcx)) - the memset(sc->buf,...) store through the canary pointer.

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

trigger window: release/detach within 500ms of the write; root-only device (0600)

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-2409

Confirmed kernel references

Detail

Exploit chain

write+close+/dev/tpm0 detach within 500ms -> callout fires on freed softc -> memset through stale sc->buf = attacker-influenced write if the chunk is reallocated before firing

Evidence (decisive lines)

panic.txt (trap 9 at the memset instruction in tpm20_discard_buffer); t20.c choreography source

PoC changes

rig transmit stub returns 0; sc kmalloc/freed/resurrected exactly as the tpm_tis detach path would; everything else production (tpm20_init/write/release from stock tpm.ko)

Verified recommended fix

callout_drain(&sc->discard_buffer_callout) (or callout_stop_sync) in tpm20_release before freeing sc->buf

Verdict

UAF write through the stale 500ms discard_buffer_callout: production tpm20_init created a real /dev/tpm0, a userland open+write+close armed the callout via production tpm20_write, production tpm20_release freed/uninited without draining it, the sc was freed and immediately resurrected at the same address with sc->buf = 0x4141414141414141, and the callout fired into the freed object: Fatal trap 9 general protection fault at tpm20_discard_buffer+0x3d (movq $0,(%rcx)) - the memset(sc->buf,...) store through the canary pointer.