\chapter{针对 Spectre 攻击的微架构设计}\label{sec:mywork} 本章讲解本文提出的一种防御 Spectre 攻击的方法,该方法使用动态信息流追 踪的方法,检测 Spectre 组件指令流中可能泄露秘密数据的访存指令,并使用 InvisiSpec 的方法执行这些访存指令。 %这个设计有如下特点: %\begin{itemize} %\item 它不需要任何软件修改 %\item \section{威胁模型} 本文对攻击者做如下假设:攻击者知道受害者程序的代码、进程中的地址分布信 息,攻击者和受害者可以在同一进程,共享同一处理器核,或在不同处理器核上。 本文设计的方法用于防御利用控制流推测式执行的 Spectre-PHT, Spectre-BTB, Spectre-RSB 攻击,此方法可以扩展至 Spectre-STL 攻击。攻击者在 Spectre 攻击中使用高速缓存作为隐蔽信道,其他信道不在本文的考虑范围。本文不考虑 其他的侧信道攻击。本文针对攻击者通过 Spectre 攻击获取内存中秘密数据的 情形,不考虑攻击者通过攻击获取寄存器中秘密数据的情形。 \section{基于动态信息流追踪的Spectre检测方法} 动态信息流追踪(Dynamic Information Flow Tracking)\supercite{dift}是 一种硬件安全策略,通过识别可疑的信息流,并限制可疑信息的使用,保护程序 的安全。它最早用于防止攻击者利用缓冲区溢出攻击执行恶意代码,也可以用于 检测跨站脚本攻击、SQL注入等攻击。\supercite{raksha} DIFT 可以作为 Spectre 攻击的检测手段之一。Spectre 的论文中指出处理器可 以追踪数据是否在推测式执行中获取,进而阻止在后续可能泄露这个数据的操作 中使用,作为阻止数据进入隐蔽信道的方法。\supercite{spectre} CSF\supercite{context-sensitive-fencing} 中的译码级信息流追踪框架 DIFT, 用于追踪处理器使用的数据是否来源于用户输入,从而处理器可以根据此信息判 断是否需要插入 fence 微码。OISA\supercite{oisa} 在指令系统的定义中即包 含了 DIFT 技术,用于追踪一个数据是否为秘密数据。 在本文的工作中,DIFT 的作用是在运行时动态识别可能泄露秘密数据的指令。 以图\ref{lst:spectre_v1}的 Spectre v1 组件代码为例。攻击者希望通过 Spectre 攻击泄露 \verb|array1[x]| 的值,方法是让处理器在分支的推测式执行中,访问 \verb|array2[array1[x] * 4096]|, 从而将一个依赖于 \verb|array1[x]| 的地址处的数据写入了缓存中。使用了基于 DIFT 的检测手段后, 可以识别出 \verb|array2[array1[x] * 4096]| 的地址依赖于 \verb|array1[x]| 的值, 从而处理器可以阻止对这个地址的访问。 为了达到这个目的,我们为所有的物理寄存器都添加一个标记,用于表示它的值 是否来源于推测式执行中,从内存中读出的值。在上述例子中,有一条将内 存 \verb|array1[x]| 读取至寄存器的指令,该指令的目的寄存器的标记将会被 设为1. \begin{figure} \begin{minted}[frame=single,linenos=true]{nasm} xor eax, eax cmp qword [rip + 0x2b157f], rdi jbe loc.funcret lea rax, [rip + 0x2b14ae] add rax, rdi movzx eax, byte [rax] shl eax, 12 lea rdx, [rip + 0x2b425d] mov eax, dword [rdx + rax] loc.funcret: ret \end{minted} \caption{Spectre 组件的汇编代码} \label{lst:spectre_v1_asm} \end{figure} 上述 Spectre v1 的组件代码可以产生图\ref{lst:spectre_v1_asm}所示的指令。 我们用表\ref{tab:spectre_dift}展示 DIFT 在推测式执行分支中的指令的行为, 其中 T 表示寄存器的标记,在分支推测式执行前,所有寄存器的标记为0,为了 描述方便,这里用体系结构寄存器进行描述,实现中为物理寄存器的标记。 \begin{table} \begin{tabular}{|c|c|c|} \hline 指令 & 指令的语义 & DIFT 行为\tabularnewline \hline \hline lea rax, {[}rip + 0x2b14ae{]} & rax <- rip + 0x2b14ae & T{[}rax{]} <- T{[}rip{]} = 0\tabularnewline \hline add rax, rdi & rax <- rax + rdi & T{[}rax{]} <- T{[}rax{]} | T{[}rdi{]} = 0\tabularnewline \hline \multirow{2}{*}{movzx eax, byte {[}rax{]}} & \multirow{2}{*}{eax <- (uint8\_t){[}eax{]}} & T{[}rax{]} = 0, 指令安全\tabularnewline \cline{3-3} & & T{[}rax{]} <- 1\tabularnewline \hline shl eax, 12 & eax <- eax {*} 4096 & T{[}rax{]} <- T{[}rax{]} = 1\tabularnewline \hline lea rdx, {[}rip + 0x2b425d{]} & rdx <- rip + 0x2b425d & T{[}rdx{]} <- T{[}rip{]} = 0\tabularnewline \hline \multirow{2}{*}{eax, dword {[}rdx + rax{]}} & \multirow{2}{*}{eax <- {[}rdx + rax{]}} & (T{[}rdx{]} | T{[}rax{]}) = 1, 指令不安全 \tabularnewline \cline{3-3} & & T{[}rax{]} <- 1\tabularnewline \hline \end{tabular} \caption{分支中代码产生的 DIFT 行为} \label{tab:spectre_dift} \end{table} \Todo: 解释为什么使用这种方法,和其他相似方法(DLIFT, TPBuf, SG(Full))的比较 \Todo: 增加结构示意图和代码描述 以下描述这个检测方法的具体细节。 标记的设置:在推测式执行时,对于所有从内存读取数据的指令,将其所有目的 寄存器的标记设为1. 标记的传播:标记的传播在处理器的执行阶段进行,在这个阶段,处理器在读源 寄存器的同时,将它们对应的标记读出。对于不同类型的指令,标记传播的方式 如下: \begin{itemize} \item 对于算术类指令,它的源操作数均来源于寄存器,其目的寄存器的标记设 为所有源寄存器标记做或运算的结果。 \item 对于装载类指令,它从内存中读取数据,这是标记设置的基本条件,因此 目的寄存器的标志设为1. \item 对于存储类指令,它没有目的寄存器。和一般的 DIFT 实现不同,我们不 对任何内存进行标记。因为要使用存入内存中的数据,需要有一套指令将其重 新从内存中读出,这个操作会设置这条指令目的寄存器的标记。 \item 对于转移类指令,我们为每个指令添加一位标记,如果源寄存器中有标记 为1的指令,则设置这个指令的标记为1,否则设置为0.转移指令的的标记将在 下文描述。 \end{itemize} 标记的清除:当一条指令不再处于推测式执行的状态,即该指令此前的所有分支 都执行完成并通过验证,则要将这条指令所有目的寄存器的标记重置为0.对于转 移类指令,则清除该转移指令的标记。 寄存器中的标记可以处理读取内存中数据的指令,和泄露内存中数据的指令,存 在直接或间接数据相关的情形。读取内存的指令使得存放它的寄存器被标记,此 后和它存在直接或间接数据相关的指令,会将这个标记传播至这些指令的目的寄 存器。 考虑攻击者在 Spectre 攻击中利用缓存侧信道泄露内存中的数据,则 在 Spectre 的组件代码中,泄露数据的指令为装载指令,该装载指令的地址直接 或间接依赖于内存中的数据。在加入上述的 DIFT 检测机制后,由于该装载指令 依赖于内存中的数据,它必定有一个被标记的源寄存器,从而处理器可以得知该 指令不安全。 转移指令的标记用于处理可能泄露数据的指令,和内存中数据的值存在控制相关 的情形,如图\ref{lst:victim_v10}的例子\supercite{msvc}: \begin{figure}[htbp] \begin{minted}{C}[frame=single,linenos=true] void victim(size_t x, uint8_t k) { if (x < array1_size) { if (array1[x] == k) temp &= array2[0]; } } \end{minted} \caption{泄露数据的指令和内存中的数据存在控制相关的情形} \label{lst:victim_v10} \end{figure} 这个例子读取的 \verb|array1[x]| 和攻击者猜测的值 \verb|k| 进行比较,如 果两个值相等,则会访问 \verb|array2[0]|,即访问 \verb|array2[0]| 的指令 和访问 \verb|array1[x]| 的指令存在控制相关,攻击者可以通过探 测\verb|array2[0]| 是否在缓存中,判断 \verb|array1[x]| 的值是否 和\verb|k| 相等。只使用寄存器的标记,并不能检测出读取 \verb|array2[0]| 的指令和 \verb|array1[x]| 的值存在依赖关系。 因此我们需要转移指令中的标记。如果一条指令在被标记的转移指令之后执行, 则该指令和一个被标记的数据存在依赖关系,如果指令为装载指令,由于它改变 了缓存的状态,攻击者可以通过缓存信道得知该指令被执行过,推测出被标记转 移指令的执行结果,从而推测出内存中秘密数据的值,因此这样的装载指令也是 不安全的指令。 \section{使用推测式执行缓冲区} \Todo: 说明在检测到不安全的访存指令后,推迟它的执行和用 InvisiSpec 的 SpecBuf 执行的区别 \Todo: 为什么只对部分指令使用,对推测式执行中的部分指令使用 InvisiSpec 是否需要修改 \Todo: 本文使用的基于 DIFT 的检测方法和 InvisiSpec 结合 \section{针对 Spectre 攻击的微架构在 gem5 中的实现} \Todo: 这节介绍怎样在 gem5 中实现我的工作 以下介绍这种可抵抗 Spectre 攻击的微架构在 gem5 模拟器中的实现。首先分 析 gem5 中乱序执行处理器的实现,然后分别介绍 InvisiSpec 和本文使用的 DIFT 方案在 gem5 中的实现。 \subsection{gem5 的乱序执行处理器} \Todo: 做一个 gem5 流水线的示意图? gem5 的乱序执行处理器实现在 FullO3CPU 类中,它又用类实现类处理器的以下 流水级:取指(Fetch)、译码(Decode)、重命名(Rename)、发射/执行/回 写(IEW)、提交(Commit)。 gem5 的取指和译码阶段由 DefaultFetch 和 DefaultDecode 两个类实现。在 DefaultFetch 中,取指部件从指令缓存中取出处理器 PC 位置的指令,并用指 令系统对应的译码器进行译码,再取出指令对应的微指令,将微指令传至译码阶 段,译码阶段再将其传到重命名阶段。取指阶段取出的指令在 DynInst 类的实 例中保存。 gem5 的重命名阶段由 DefaultRename 类实现,它对指令的源寄存器和目的寄存 器进行重命名。重命名后,指令的 DynInst 实例中的源寄存器和目的寄存器均 保存它们对应的物理寄存器,同时还保存目的寄存器原来对应的物理寄存器用于 恢复。 gem5 的发射、执行和回写三个阶段由一个类 DefaultIEW 实现,它模拟了处理 器将指令发射至功能单元和处理器执行指令的过程。gem5 中用一个专门的语言 定义了每个指令系统的指令的语义,为每个指令和微指令生成一个 StaticInst 类,里面定义了指令的执行方式。对于存储访问类指令,gem5 用 LSQ 类定义处 理器中的装载和存储指令队列,这些指令在执行时添加至队列中,进行存储访问 操作。 gem5 的提交阶段由 DefaultCommit 类实现,它提交 ROB 队列头部的指令,更 新 ROB 的状态。 \subsection{InvisiSpec的LSQ的实现} \Todo: InvisiSpec 所用的 load 指令执行逻辑在 gem5 LSQ 中的实现 \subsection{推测式执行缓冲区的实现} \Todo: InvisiSpec 的 SpecBuf 在 gem5 中的实现 \subsection{InvisiSpec对缓存一致性协议的修改} \Todo: InvisiSpec 的 SpecBuf 在 gem5 中的实现 \subsection{动态信息流追踪的实现} \Todo: 本文所用的 DIFT 方案在 gem5 中的实现,和已有 InvisiSpec 代码的结合 \Todo: 如何添加一个总结性的章节?