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)
PoC verification
Evidence pack
findings/poc/DF-2039 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedVALIDATED: 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.
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.
No comments yet.