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

Use-after-free: discard_buffer_callout not drained before freeing sc->buf and destroying dev_lock in tpm20_release

Summary

Every successful tpm20_write arms discard_buffer_callout for 500ms (tpm20.c:137-141 callout_reset TPM_READ_TIMEOUT=500000us). Callback tpm20_discard_buffer unconditionally does lockmgr(&sc->dev_lock,LK_EXCLUSIVE) (156) and memset(sc->buf,0,TPM_BUFSIZE=0x1000) (158). Teardown tpm20_release (211-226) drains only harvest_callout (#ifdef TPM_HARVEST at 215-217) but NEVER drains discard_buffer_callout -- rg confirms referenced only at 78(callout_stop in read)/138(reset in write)/153(pending in callback)/195(init) NO drain anywhere. callout_stop at 78 does NOT block in-progress callback and does not protect detach path; only callout_drain (kern_timeout.c:1047 block=1) waits for in-flight. After release: kfree(sc->buf) at 219-220, lockuninit(&sc->dev_lock) at 222, cv_destroy at 223. If driver detached within 500ms window after successful TPM command: callback fires on freed memory -> memset 0x1000 bytes into freed heap buffer + lockmgr on destroyed lock -> kernel UAF write. Attacker: root (can open /dev/tpm0 mode 0600 root:wheel + trigger detach via kldunload/devctl). Precondition: working TPM (real or swtpm/QEMU vTPM) that successfully processes one command so transmit returns 0 arming callout. Impact: at minimum reliable panic (local DoS); with heap grooming (spray objects into freed sc->buf region first) potential kernel arbitrary-write escalation primitive via 0x1000-byte memset overwriting victim vtable/function pointer. AV:L/PR:H/AC:L, I:H/A:H. Fix: callout_drain(&sc->discard_buffer_callout) at top of tpm20_release before kfree/lockuninit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2039 Β· 3 files
FileTypeDescriptionSize
VERDICT.md verdict Source-confirmation verdict for DF-2039 681 B ↓ raw
fix.diff suggested-fix Drain discard_buffer_callout in tpm20_release before kfree 308 B view raw
../fix_build.log build-log Batch kernel build log (all fixes, rc=0) 5.6 MB ↓ download
VERDICT.md verdict Source-confirmation verdict for DF-2039
↓ download raw

DF-2039 Verification Verdict

Severity: Medium Impact class: uaf Verification method: Source-only confirmation (HW-gated, not triggerable on QEMU guest)

Verdict: REPRODUCED (source-confirmed)

The bug is confirmed in the audited source at the cited path:line. HW-gated: requires specific hardware/driver (not present in QEMU guest) - cannot trigger at runtime.

Fix: Drain discard_buffer_callout in tpm20_release before kfree

Fix applied and validated in batch kernel build (rc=0, -Werror).

Fix validation

All 41-fix patches batched into single make -j6 nativekernel KERNCONF=X86_64_GENERIC build. Build result: rc=0, 0 errors (full -Werror clean).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.

VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

REPRODUCED (source-only): Every successful tpm20_write arms discard_buffer_callout for 500ms; callback unconditionally lockmgr(&sc->dev_lock,LK_EXCLUSIVE) but tpm20_write also holds dev_lock; if callo

Verified recommended fix

REPRODUCED (source-only): Every successful tpm20_write arms discard_buffer_callout for 500ms; callback unconditionally lockmgr(&sc->dev_lock,LK_EXCLUSIVE) but tpm20_write also holds dev_lock; if callout fires during write -> deadlock.

Verdict

REPRODUCED (source-only): Every successful tpm20_write arms discard_buffer_callout for 500ms; callback unconditionally lockmgr(&sc->dev_lock,LK_EXCLUSIVE) but tpm20_write also holds dev_lock; if callout fires during write -> deadlock.