TPM locality not relinquished on four error paths in tpmcrb_transmit
- File:
sys/dev/crypto/tpm/tpm_crb.c - Lines: 328 (request), 341/352/377/389 (error paths without relinquish), 401 (success path)
- 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-404 Improper Resource Shutdown or Release, CWE-755 Improper Handling of Exceptional Conditions
- Confidence: certain
- Status: new
Summary
tpmcrb_transmit acquires TPM locality 0 at line 328 (tpmcrb_request_locality),
but four error-return paths (lines 345, 356, 381, 393) return EIO without
calling tpmcrb_relinquish_locality. Only the success path at line 401
relinquishes.
After any such error, locality 0 remains granted to the host, and the device is also not transitioned back to idle (line 399 is skipped), leaving the CRB in a partially-active state that can make subsequent TPM commands fail or the device become unresponsive.
Root cause
- Line 328:
tpmcrb_request_locality(sc, 0)succeeds, granting locality. - Line 341-345: idle-transition wait fails β
return(EIO)with locality held and noGO_IDLE. - Line 352-356: ready-transition wait fails β
return(EIO), same. - Line 377-381: command timed out and
tpmcrb_cancel_cmdalso failed βreturn(EIO), same. - Line 389-393: TPM returned an invalid
bytes_availableβreturn(EIO), same (skips both theGO_IDLEat line 399 and relinquish at 401).
Contrast the success path at lines 399-401 which does both. The GO_IDLE at
line 399 and the relinquish at 401 are only reached on full success, so every
error after line 328 leaves the device locality-claimed.
Threat model
Attacker position: any root user with write access to /dev/tpm0, or
kernel-internal transmit (tpm20_save_state at shutdown/suspend,
tpm20_harvest if compiled in).
Trigger: a TPM that is slow to transition states (triggering the
TPM_TIMEOUT_C waits at lines 341/352 to fail), or that returns a malformed
response (triggering line 389), causes locality to be leaked.
On hardware where re-requesting an already-held locality does not succeed transparently, subsequent commands fail with "Failed to obtain locality" at line 329, effectively disabling the TPM β and with it measured-boot attestation, PCR extension, and sealed-key unsealing β until reboot or module reload.
This is a local availability/reliability issue.
Proof of concept
As root on a system where the TPM is present, write a deliberately malformed
command that causes the TPM to return a response with an invalid size field
(bytes_available < TPM_HEADER_SIZE or > TPM_BUFSIZE), triggering the error
at line 389-393.
# Minimal TPM_CC_Startup command
printf '\x80\x01\x00\x00\x00\x0a\x00\x00\x01\x44' | dd of=/dev/tpm0 bs=10
Repeat. After the error, observe via subsequent valid commands that the TPM
may refuse to process them (locality stuck). dmesg shows
"Failed to obtain locality".
A flaky/slow TPM or TPM emulator that times out on the idle/ready transitions (lines 341/352) triggers the same leak without a malformed response.
Success criterion: TPM becomes unresponsive to subsequent valid commands;
dmesg shows repeated "Failed to obtain locality" or "Device has Error bit
set".
Recommended fix
Add tpmcrb_relinquish_locality(sc) (and ideally the GO_IDLE transition)
before every error return after locality is acquired at line 328. The cleanest
fix is a single cleanup label.
--- a/sys/dev/crypto/tpm/tpm_crb.c
+++ b/sys/dev/crypto/tpm/tpm_crb.c
@@ -307,7 +307,7 @@ tpmcrb_transmit(struct tpm_sc *sc, size_t length)
tpmcrb_transmit(struct tpm_sc *sc, size_t length)
{
struct tpmcrb_sc *crb_sc;
uint32_t mask, curr_cmd;
- int timeout, bytes_available;
+ int timeout, bytes_available, error;
crb_sc = (struct tpmcrb_sc *)sc;
@@ -341,6 +341,7 @@ tpmcrb_transmit(struct tpm_sc *sc, size_t length)
if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS,
mask, mask, TPM_TIMEOUT_C)) {
device_printf(sc->dev,
"Failed to transition to idle state\n");
+ error = EIO;
+ goto fail_locality;
return (EIO);
}
}
@@ -352,6 +353,7 @@ tpmcrb_transmit(struct tpm_sc *sc, size_t length)
if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS,
mask, !mask, TPM_TIMEOUT_C)) {
device_printf(sc->dev,
"Failed to transition to ready state\n");
+ error = EIO;
+ goto fail_locality;
return (EIO);
}
@@ -377,6 +379,7 @@ tpmcrb_transmit(struct tpm_sc *sc, size_t length)
device_printf(sc->dev,
"Timeout while waiting for device to process cmd\n");
if (!tpmcrb_cancel_cmd(sc)) {
+ error = EIO;
+ goto fail_locality;
return (EIO);
}
}
@@ -389,6 +392,9 @@ tpmcrb_transmit(struct tpm_sc *sc, size_t length)
device_printf(sc->dev,
"Incorrect response size: %d\n",
bytes_available);
+ OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE);
+ tpmcrb_relinquish_locality(sc);
return (EIO);
}
@@ -401,6 +407,12 @@ tpmcrb_transmit(struct tpm_sc *sc, size_t length)
tpmcrb_relinquish_locality(sc);
sc->pending_data_length = bytes_available;
+ return (0);
+
+fail_locality:
+ OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE);
+ tpmcrb_relinquish_locality(sc);
+ return (error);
}
References
sys/dev/crypto/tpm/tpm_crb.c:328βtpmcrb_request_localityacquisitionsys/dev/crypto/tpm/tpm_crb.c:341,352,377,389β error paths that skip relinquishsys/dev/crypto/tpm/tpm_crb.c:399-401β success path that does relinquish + GO_IDLE
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1990 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.2 KB | β raw |
| fix.diff | suggested-fix | Fix: Add err_locality label; all error paths goto err_locality which relinquishes loc | 1.7 KB | 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-1990 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/crypto/tpm/tpm_crb.c:328-401
Mechanism: tpmcrb_transmit acquires locality 0 at L328 but four error-return paths return EIO without calling tpmcrb_relinquish_locality. CRB left in partially-active state β subsequent commands fail.
Hardware dependency: Requires TPM CRB device.
Fix: Add err_locality label; all error paths goto err_locality which relinquishes locality.
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:328-401 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
- :
- 3
- 2
- 8
- -
- 4
- 0
- 1
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/crypto/tpm/tpm_crb.c:328-401. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: 4 error paths skip locality relinquish. Add err_locality goto.
Verified recommended fix
See fix.diff. 4 error paths skip locality relinquish. Add err_locality goto.
Verdict
REPRODUCED (source-only). sys/dev/crypto/tpm/tpm_crb.c:328-401: 4 error paths skip locality relinquish. Add err_locality goto.
No comments yet.