该仓库包含针对 SEV 固件中一个漏洞的利用代码。该利用代码能够解密正在运行的 SEV-SNP 客户机的任意内存。
已在版本 1.55.16(撰写本文时最新版本)上完成测试。
SEV_INIT_EX 命令的 nv_paddr 字段可用于向固件捐赠一块内存,使其替代持久性闪存使用。如果启用了 SEV-SNP,此内存必须处于 FIRMWARE 状态。固件在执行 SEV_INIT_EX 命令时检查一次该状态。此后,固件假定该内存一直处于 FIRMWARE 状态并直接写入,不再进行额外检查。
该内存始终处于 FIRMWARE 状态的假设并非总是正确,主机可以使用 SNP_PAGE_RECLAIM 命令将其状态改回 HYPERVISOR。一旦页面处于 HYPERVISOR 状态,便可转换为其他状态,例如 CONTEXT。即使这些页面不再处于 FIRMWARE 状态,固件仍会写入它们,从而破坏了某些页面状态所要求的完整性。
我们可以通过攻击 CONTEXT 页面来利用此内存破坏。CONTEXT 页面是很有价值的目标,但存在一些问题:
CONTEXT 页面使用与其他内存不同的密钥进行加密。因此,即使我们能控制密文,也很难控制明文。内存破坏会使 CONTEXT 页面填充随机数据,因此不太可能直接以对攻击者有用的方式破坏 CONTEXT 页面。为了解决这个问题,我们可以反复触发该漏洞引起破坏,并使用 SNP_GUEST_STATUS 命令读取被破坏的 CONTEXT 页面的相关字段,直到观察到有用的值。
SNP_DBG_DECRYPT 命令可用于解密启用了 DEBUG 策略的 SEV-SNP 客户机的内存。如果我们能构造一个具有 DEBUG 标志且包含另一个客户机 ASID 的 CONTEXT,我们就可以用它来解密另一个客户机的内存,即使该客户机没有设置 DEBUG 策略。
事实证明,SNP_DBG_DECRYPT 忽略了 CONTEXT 页面中的大多数字段,它仅检查 gctx->guest.asid、gctx->guest.policy_snp 和 gctx->guest.guest_flags。内存破坏后这些字段正确的概率不高,但并非不可能。好消息是我们可以使用 GUEST_STATUS 命令读取所有这些字段。
总之,我们可以通过以下步骤利用该漏洞:
rmpupdate 指令将 nv_paddr 转换为 FIRMWARE 状态。SEV_INIT_EX 命令。SNP_RECLAIM_PAGE 命令将 nv_paddr 转换回 HYPERVISOR 状态。nv_paddr 处创建一个或多个 CONTEXT 页面。SEV_PDH_GEN 命令诱使固件写入 nv_paddr,从而破坏 CONTEXT 页面。GUEST_STATUS 命令检查 SNP_DBG_DECRYPT 是否能够成功,如果不成功则返回步骤 5。主要瓶颈在于 ASID 存储在 32 位整数中,但有效 ASID 数量少得多(509 或 1006,取决于 CPU),因此需要大量尝试才能正确匹配。该利用代码大部分时间消耗在步骤 5 和 6 上。在 EPYC Milan 上,满足所有条件的概率约为 1/20,000,000,我们每秒可尝试约 100 次,因此预计大约每两天满足一次条件(警告:计算仅为近似值,我可能搞错了某些东西,但根据经验,每两天一次感觉差不多)。我们可以通过一次攻击多个 CONTEXT 页面来加速:在 nv_paddr、nv_paddr+4096 和 nv_paddr+8192 处放置三个 CONTEXT 页面(SEV_PDG_GEN 会破坏三个页面)。方便的是,这些步骤可以在启动受害者客户机之前完成,并且只需成功一次即可攻击任意数量的客户机(注意,目前的 PoC 只攻击一个客户机)。
尽管我尚未能测试,但我认为一旦攻击者利用此漏洞泄露了客户机的虚拟机平台通信密钥,她应该能够代表客户机向固件发送客户机消息,并利用此功能请求证明报告。这违反了 SEV-SNP 的一项关键原则:只有客户机才能请求证明报告。
某些接受 FIRMWARE 页面的命令(例如 SNP_RECLAIM_PAGE、SNP_GCTX_CREATE、RING_BUFFER,可能还有其他命令,甚至为安全起见全部命令?)应检查该页面是否与 nv_paddr 重叠,如果重叠则失败。
还有一个问题我不确定是否有效,很想听听大家的意见:据我理解,SEV 固件可以在不中断正在运行的客户机的情况下升级。这意味着可以将被破坏的 CONTEXT 页面从旧的易受攻击的固件版本带到新的已修复固件版本。是否可能先运行旧的易受攻击版本,执行上述利用,升级并提交新的已修复固件,然后使用新固件启动客户机(这样旧固件版本不会出现在证明报告中),最后使用通过旧固件创建的被破坏的 CONTEXT 页面来攻击使用新固件创建的客户机?使用新客户机创建的证明报告的消费者能否判断旧固件版本在启动新客户机之前的某个时间点曾经运行过?如果不能,是否需要进一步的缓解措施来防止这种情况?
linux-patches 文件夹中的补丁应用到 https://github.com/AMDESE/linux/commits/snp-host-v10 的最新提交。编译、安装并启动内核。root@server:~/sev-exploit# cargo run --release
Finished release [optimized] target(s) in 0.12s
Running `target/release/sev-exploit`
Corrupt guest context page so that ASID is in range 1..510
Smallest ASID: 0x0000001f iterations: 14052175 zeros: 10539628 unique asids: 31500727 elapsed time: 1d 19h 20m 31s
Creating VM with same ASID
[03, 00, 00, 00, 00, 00, 00, 00, 11, 0f, a0, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, f0, 51, a5, 03, 3f, 69, 6b, 93, e8, d8, 61, 0d, 2e, 5a, 45, f1, ea, 6d, bf, 49, fe, e4, a9, 2d, 8d, af, 76, 5e, 2e, 56, e0, fa, a9, b3, a7, e0, bc, 09, d9, 4f, 28, 5c, 9f, 84, d2, 7e, 34, eb, ea, 3f, 29, 88, 30, 01, 28, 65, 8b, 73, 3c, 84, 00, ae, 4a, 74, a2, 7a, d1, c7, 4f, 63, 7f, 72, 7b, 3b, 2f, 08, b3, 1a, 8c, 99, 1b, ad, b5, 1d, 42, 0b, 4d, 98, d4, 7d, c1, 0b, d6, 2f, b4, 6c, 6b, 51, a2, 92, 17, 3b, 01, e8, 82, 11, 1e, cb, cb, a2, 8f, c9, b0, 52, 1d, 1d, b7, d2, 25, 8d, 32, a9, 7a, 6f, 86, e4, 40, 44, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 80, 00, 88, 00, 00, 00, 00, ee, ff, 00, 00, f0, ff, ff, ff, ff, ff, ff, ff, ff, 3f, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, <cut off zeros>]
thread 'main' panicked at src/main.rs:170:13:
not yet implemented: use the leaked secrets to send guest messages
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
注意,该利用代码运行很长时间是正常的(运气好时数小时,运气不好时数天)。在 EPYC Genoa 上运行可能会更快,因为有效 ASID 数量几乎多了一倍。
在步骤 5 和 6 期间,PoC 会显示一些指标:
CONTEXT 页面处于固件认为尚未分配 ASID 的状态。此时 SNP_GUEST_STATUS 在 ASID 字段中返回 0。在大多数情况下,废弃被破坏的 CONTEXT 页面会导致固件崩溃(很可能在这里)。据我所知,固件崩溃会导致整个系统重置。为避免此类崩溃,内核补丁禁止废弃 CONTEXT 页面。其缺点之一是 ccp 内核模块无法卸载。一旦 PoC 启动,整个系统必须重新启动才能再次运行(无论 PoC 成功还是被中止)。
CONTEXT 页面中的 ASID 启动(并可选择运行)一个受害者客户机。记录其 secrets 页。这是可行的,因为 SEV 固件内部跟踪活动 ASID,但不会检查活动的 CONTEXT 页面是否有重复。CONTEXT 页面在受害者客户机的 secrets 页上执行 SNP_DBG_DECRYPT。