Bug 2528538 (CVE-2026-85769) - CVE-2026-85769 libtpms: libtpms: heap out-of-bounds read in TPM2 state unmarshalling via unchecked block_skip_read() blocksize [NEEDINFO]
Summary: CVE-2026-85769 libtpms: libtpms: heap out-of-bounds read in TPM2 state unmars...
Keywords:
Status: NEW
Alias: CVE-2026-85769
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-09-04 15:20 UTC by OSIDB Bzimport
Modified: 2026-09-08 18:42 UTC (History)
3 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:
iska.sanuj: needinfo? (bzimport)


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-09-04 15:20:08 UTC
libtpms implements the software TPM 2.0 engine used by swtpm/QEMU. Its NV/volatile state (de)serializer in src/tpm2/NVMarshal.c uses block_skip_read() (src/tpm2/NVMarshal.c:158-188) to skip an optional versioned sub-block when the running code does not need it. The function reads an attacker-controlled 16-bit blocksize field from the stream and then does '*buffer += blocksize; *size -= blocksize;' with NO check that blocksize <= *size. Because *size is a signed INT32, a forged blocksize larger than the remaining bytes drives *size negative. Every scalar unmarshal primitive in src/tpm2/Unmarshal.c (UINT8/16/32/64_Unmarshal, Array_Unmarshal) guards its bounds check as '(UINT32)*size < sizeof(...)', which implicitly casts the now-negative *size to UINT32, wrapping it to a huge unsigned value that passes the check. The next field read then dereferences *buffer past the end of the heap allocation backing the state blob (CWE-125).

Reachable from TPMLIB_SetState(TPMLIB_STATE_VOLATILE/PERMANENT, ...) via VolatileState_Unmarshal()/PERSISTENT_ALL_Unmarshal(), i.e. the power-on/restore and state-import paths. block_skip_read() only performs pointer arithmetic and never copies skipped bytes, so the realistic impact is denial of service (process crash / TPM failure mode), not information disclosure.

Reported by Isuka Sanuj (CyberCrew Inc.) on 2026-09-01; independently reported publicly by Leyao (ICT CAS) as github.com/stefanberger/libtpms/issues/614 on 2026-09-04. PSIRTSUPT-23184.

Affected: all released libtpms versions carrying TPM 2.0 state deserialization code, including v0.10.2 (commit 03ff2481e133540be3b3ffe3daa1483d2a73d967). Upstream fix on master in commit b1462888180d896af03cae0487e8d45009cc445e ('tpm2: Add checks for *size < 0 before casting it to UINT32'), adding '*size < 0 ||' guards to UINT8/16/32/64_Unmarshal and Array_Unmarshal. Not yet present in any tagged release as of 2026-09-04.

Comment 1 Isuka Sanuj 2026-09-04 18:49:49 UTC
Hello Red Hat Product Security Team,
I am writing to request a correction to the "Acknowledgements" section on the official portal page for CVE-2026-85769 (https://redhat.com). 
Currently, the page states: "Red Hat would like to thank Isuka Sanuj (CyberCrew Inc.) and Leyao (ICT CAS) for reporting this issue." 
This phrasing implies a joint report. However, I (Isuka Sanuj) am the sole discoverer and author of the original vulnerability report. Leyao is the independent upstream developer who subsequently committed the remediation patch to the GitHub repository. We did not collaborate on the initial discovery or reporting phase.
To ensure accurate historical record and proper researcher credit, I request that the wording be updated to distinguish discovery from remediation. 
Suggested wording:
"Red Hat would like to thank Isuka Sanuj (CyberCrew Inc. (株式会社CyberCrew)) for discovering and reporting this issue."
Please let me know once this update has been reviewed and applied.
Best regards,
Isuka Sanuj
Security Analyst, 株式会社CyberCrew

Comment 2 lcelant 2026-09-07 12:17:31 UTC
Upstream fix verified on libtpms master (2026-09-07).

Root cause: block_skip_read() in src/tpm2/NVMarshal.c can subtract a crafted blocksize from the remaining INT32 *size, driving it negative. Primitive unmarshalling in src/tpm2/Unmarshal.c then checked bounds via (UINT32)*size, which wraps negative values to a large unsigned value and bypasses the insufficient-data guard. Subsequent reads in VolatileState_Unmarshal() (and other state restore paths reached via TPMLIB_SetState()) can then read past the buffer (heap OOB read, DoS).

Upstream fix: commit b146288 on master ("tpm2: Add checks for *size < 0 before casting it to UINT32", PR #613). Adds *size < 0 guard to UINT8/16/32/64_Unmarshal() and Array_Unmarshal() before any UINT32 cast. Reporter independently verified with ASan: pre-fix triggers heap-buffer-overflow READ; post-fix returns TPM_RC_INSUFFICIENT (0x9a) with no ASan report.

Credit note: Isuka Sanuj (CyberCrew Inc.) and Leyao (ICT CAS) reported independently. Leyao authored the upstream fix (PR #613). Isuka privately reported to upstream maintainer on 2026-09-01; Leyao's public report was libtpms issue #614 on 2026-09-04.

Cross-ref: PSIRTSUPT-23184 reporter follow-up comment 2026-09-04.


Note You need to log in before you can comment on or make changes to this bug.