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

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.

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

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1991 Β· 5 files
FileTypeDescriptionSize
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
VERDICT.md verdict Source verification narrative
↓ download 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_testable
baseline reproduced→ patch + rebuild →patched clean

not_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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026

Confirmed kernel references

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.