论文《SEV-Step:A Single-Stepping Framework for AMD-SEV》总结
本文将介绍发表在 arXiv 上的论文《SEV-Step: A Single-Stepping Framework for AMD-SEV》。
文章贡献
- 在 SEV 环境下引入了可靠(reliable)的单步执行方法。
- 将交互式单步执行、页面错误追踪和基于缓存组置换的缓存攻击(eviction set-based cache attacks)整合到一个可复用的框架中。
背景知识
基于中断的单步执行
基于中断的单步执行方法是一种通过控制处理器中断(如 APIC 时钟中断)来提升微架构攻击的时间分辨率(temporal resolution)的技术。核心思想是利用高频率的中断强制目标程序暂停执行,从而实现对微架构状态(如缓存等)的细粒度观测。
APIC 提供了高精度的定时功能,攻击者可以通过配置定时器周期性触发中断,强制目标程序在执行过程中频繁暂停。暂停后,攻击者可以利用侧信道攻击等方法,读取此时的微架构状态,再恢复下一条指令执行。这种方式将时间分辨率从页错误级别提升到了指令级。
缓存侧信道攻击
下列内容主要来自《操作系统:原理与实现》(银杏书)的在线章节:操作系统安全的 16.6.2 节。
缓存侧信道是利用时间信息推断程序执行中缓存的行为,进而推断出程序中的关键信息。
Flush + Reload
Flush + Reload
方法思路是:假设攻击进程和目标进程共享一块内存 ,攻击者的目标是想知道目标进程是否访问了这块共享内存中的某个变量。
共享内存如果不存在,需要事先构造,构造方法在此不做介绍。
具体步骤如下:
- 攻击进程首先将 cache 清空,方法是不断访问其他内存,用其他内存的数据填满 cache,或直接通过
flush
指令将 cache 清空; - 等待目标进程执行;
- 攻击进程访问共享内存中的某个变量,并记录访问的时间:若时间长,则表示 cache miss,意味着目标进程在第二步中没有访问过该变量;若时间短,则表示 cache hit,意味着目标进程在第二步中访问过该变量。
Flush + Flush
Flush + Flush
方法思路是基于缓存刷新时间(如 cflush
)来推测数据在缓存中的状态,进而推断出程序之前的执行行为。若数据在缓存中,则 cflush
执行时间相比不在缓存中更长。具体步骤如下:
- 攻击进程首先将 cache 清空;
- 等待目标进程执行;
- 再次清空不同的缓存区域,若时间较短说明缓存中无数据,时间较长则说明缓存中有数据,意味着目标进程曾经访问过对应的内存。
Prime + Probe
Prime + Probe
和 Flush + Reload
类似,但是 无需共享内存的支持 。具体步骤如下:
- 攻击进程用自己的数据将 cache set 填满(Prime);
- 等待目标进程执行;
- 再次访问自己的数据,若时间很短,表示 cache hit,说明目标进程没有将该数据 evict,推导出目标进程没有访问过某个关键数据,反之则说明目标进程访问了某个关键数据。
Evict + Reload
与 Prime + Probe
方法不同的是,Evict + Reload
先执行目标进程,之后将 cache set 中的数据清出去,之后再次执行目标进程。比较两次执行的时间,从而得出关键数据是否被访问到。
具体步骤如下:
- 等待目标进程执行,并测量执行时间;
- 将关键数据所在的 cache set 都替换成攻击进程的数据;
- 再次执行目标进程,并测量执行时间。若时间较短,表示 cache hit,说明攻击进程 evict 的数据没有被目标进程访问,推导出目标进程没有访问过某个关键数据,反之则说明目标进程访问了某个关键数据。
设计与实现
SEV-Step 的架构图如下所示:
设计目标
设计目标包括:交互性(interactivity)和可复用性(reusability)。
交互性要求 SEV-Step 不仅能够中断虚拟机的执行,而且要能够在中断时通知攻击者,并在攻击者完成相应的操作(如缓存侧信道攻击)前暂停虚拟机的执行。
可复用性要求 SEV-Step 将对虚拟机的单步执行与具体的攻击方法解耦,能够被多种攻击方法所适用。
单步执行
单步执行成功的关键在于配置合适的 APIC 时钟周期,既不能太长也不能太短:太长将导致每次中断可能有多条指令被执行,使得“单步执行”的粒度变粗;太短将导致虚拟机在未执行第一条的情况下就被中断,造成零步现象(zero-step)。
为了实现可靠的单步操作,作者采用了下列方法:
增大单步窗口
为了能够有足够的粒度找到合适的时钟配置,单步的窗口应该尽可能大。
作者选择每次调度虚拟机执行前,清空虚拟机的 TLB 条目,并清除包含第一条指令的页表条目的访问位(accessed bit),尽可能增大从 VMRUN 到第一条指令开始执行的窗口。
同时,为了系统速度的稳定。作者还做了下列工作:
- 将运行虚拟机的内核线程固定到特定的 CPU 核心,该核心不运行其他线程。
- 通过 BIOS 或者 Linux 的
cpufreq
子系统固定 CPU 频率。 - 禁用硬件缓存预取。
因为机密虚拟机的威胁模型中,特权软件属于不可信的范畴,因此上述操作都是可行的。
阻止虚拟时钟中断
在正常的虚拟机中,在每次宿主机的时钟中断触发后,hypervisor 都需要向虚拟机注入虚拟时钟中断,以实现对虚拟机时钟中断的模拟。但是这将导致虚拟机运行后转到自己的时钟中断处理程序执行,与我们想要攻击用户态程序的目标相悖,因此需要对其进行禁用。
但是时钟中断对于正常的操作系统来说至关重要,是任务调度的时间指标来源,因此如果虚拟机长时间没有时钟中断可能导致一些问题,还需要定期进行注入。
确定步长大小
为了迭代 APIC 时钟周期以确定是否成功实现了单步,还需要确定本次虚拟机执行与上次虚拟机执行间执行的指令数量。
观察页表的访问位是一种方法,但是它确定页面是被访问或是没有被访问,无法确定访问次数。作者还使用了另外一种方法:观察虚拟机的性能计数器事件。
有关性能计数器事件,在 IEEE SP ‘22 论文 A systematic look at ciphertext side channels on AMD SEV-SNP 中有介绍。