Non-interruptible polling in tpm_wait_for_u32 holds dev_lock for up to 40 seconds
- File:
sys/dev/crypto/tpm/tpm_crb.c - Lines: 246β262 (
tpm_wait_for_u32); caller at 364β377 - Severity: Low
- CVSS 3.1:
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U:C:N/I:N/A:L - CWE: CWE-833 Deadlock, CWE-400 Uncontrolled Resource Consumption
- Confidence: certain
- Status: new
Summary
tpm_wait_for_u32 (lines 245-262) polls TPM status using
tsleep(..., 0, ...) with flag 0 (non-interruptible β PCATCH is not set).
For a TPM_CC_CreatePrimary/Create/CreateLoaded command the timeout is
TPM_TIMEOUT_LONG = 40,000,000 us (40 seconds). The caller tpmcrb_transmit
holds sc->dev_lock exclusively for the entire duration (KKASSERT at line
315; lock acquired in tpm20_write at tpm20.c:124).
A process that issues a slow TPM command cannot be killed (SIGKILL does not
interrupt non-PCATCH tsleep) and blocks all other TPM access for the full
timeout window.
Root cause
Line 258: tsleep(tpm_wait_for_u32, 0, "TPM in polling mode", 1).
The flags argument is 0, meaning the sleep is not interruptible by
signals (PCATCH = 0x100 per sys/param.h:324 is not OR'd in).
The loop at lines 254-260 decrements timeout (int32_t) by ustick each
tick until <= 0. timeout comes from tpm20_get_timeout (called at line
364); for CreatePrimary/Create/CreateLoaded it returns
TPM_TIMEOUT_LONG = 40000000 us (tpm20.h:71).
At ~10000 us/tick (ustick), that is ~4000 iterations lasting ~40 wall-clock
seconds. During this entire period, tpmcrb_transmit holds sc->dev_lock
(asserted at line 315), so any concurrent open of /dev/tpm0 by another
process blocks, and tpm20_save_state (shutdown/suspend) and
tpm20_harvest also block on the same lock.
Threat model
Attacker position: any root user with /dev/tpm0 write access.
Trigger: issue a TPM2_CreatePrimary command (command code 0x131) that
takes the full RSA keygen time on the TPM. The issuing thread cannot be killed
(SIGKILL/SIGTERM do not wake a flag-0 tsleep), so kill -9 does not
recover the situation until the timeout elapses. Repeated issuance extends the
wedge indefinitely.
On systems where the entropy harvester (TPM_HARVEST) or
measured-boot/shutdown paths share the lock, this causes those kernel services
to stall as well.
Impact: local availability issue; root-only trigger; self-recovering after timeout.
Proof of concept
As root, craft a TPM2_CreatePrimary command (TPM_CC = 0x00000131) with a
valid header and send it to a real or emulated TPM that takes the full RSA
keygen time:
python3 -c "import struct,sys; sys.stdout.buffer.write(struct.pack('>HI I',0x8001,0x0c+0,0x131))" > /tmp/cmd.bin
dd if=/tmp/cmd.bin of=/dev/tpm0
In another terminal, kill -9 <pid> of the dd process β it does not die for
~40s because the tsleep at line 258 has no PCATCH.
Concurrently, any other process opening /dev/tpm0 blocks in lockmgr at
tpm20.c:124/79.
Success criterion: dd process unkillable for ~40s and TPM device
serialized; proven by ps aux | grep dd showing the process in state
"TPM in polling mode" and kill -9 having no effect until the timeout
expires.
Recommended fix
Make the poll interruptible by adding PCATCH to the tsleep flags and
aborting on signal, so a user can cancel a stuck command and the lock is
released promptly.
--- a/sys/dev/crypto/tpm/tpm_crb.c
+++ b/sys/dev/crypto/tpm/tpm_crb.c
@@ -245,6 +245,7 @@ static bool
tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val,
int32_t timeout)
{
+ int error;
/* Check for condition */
if ((RD4(sc, off) & mask) == val)
@@ -254,8 +255,12 @@ tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val,
if ((RD4(sc, off) & mask) == val)
return (true);
- tsleep(tpm_wait_for_u32, 0, "TPM in polling mode", 1);
+ error = tsleep(tpm_wait_for_u32, PCATCH, "TPM in polling mode", 1);
+ if (error == EINTR || error == ERESTART)
+ return (false);
+
timeout -= ustick;
}
return (false);
Returning false on a signal makes tpmcrb_transmit treat it as a timeout
and invoke tpmcrb_cancel_cmd, which is the correct recovery; the caller then
releases dev_lock.
(Note: the locality-leak fix in DF-1990 should also be applied so the cancel/abort path relinquishes cleanly.)
References
sys/dev/crypto/tpm/tpm_crb.c:246-262βtpm_wait_for_u32non-PCATCH pollsys/dev/crypto/tpm/tpm_crb.c:364βtpm20_get_timeoutreturns 40s for CreatePrimarysys/dev/crypto/tpm/tpm20.h:71βTPM_TIMEOUT_LONG = 40000000sys/dev/crypto/tpm/tpm_crb.c:315βKKASSERTthatdev_lockheldsys/dev/crypto/tpm/tpm20.c:124β lock acquisition intpm20_write
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1991 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.1 KB | β raw |
| fix.diff | suggested-fix | Fix: Use PCATCH flag for interruptible sleep. | 374 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-1991 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/crypto/tpm/tpm_crb.c:258
Mechanism: tpm_wait_for_u32 uses tsleep(flags=0, non-interruptible). For TPM_TIMEOUT_LONG=40s, holds dev_lock for 40 wall-seconds β DoS (non-interruptible process).
Hardware dependency: Requires TPM CRB device.
Fix: Use PCATCH flag for interruptible sleep.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/crypto/tpm/tpm_crb.c:258 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- c
- r
- y
- p
- t
- o
- /
- t
- p
- m
- /
- t
- p
- m
- _
- c
- r
- b
- .
- c
- :
- 2
- 5
- 8
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/crypto/tpm/tpm_crb.c:258. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: Non-interruptible tsleep β 40s DoS. Use PCATCH.
Verified recommended fix
See fix.diff. Non-interruptible tsleep β 40s DoS. Use PCATCH.
Verdict
REPRODUCED (source-only). sys/dev/crypto/tpm/tpm_crb.c:258: Non-interruptible tsleep β 40s DoS. Use PCATCH.
No comments yet.