\chapter{针对 Spectre 攻击的微体系结构设计}\label{sec:mywork}

本章讲解本文提出的一种防御 Spectre 攻击的方法，该方法使用动态信息流追
踪的方法，检测 Spectre 组件指令流中可能泄露秘密数据的访存指令，并使用
一种安全的方法执行这些访存指令。

这种微体系结构设计不需要软件的支持，未修改的软件和操作系统可以直接在采用这
种微体系结构改进的处理器中执行。

\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}，本文所用的方
法则是这种观点的一种具体设计。在其他使用了 DIFT 的防御 Spectre 的设计中，
所追踪的对象有所不同。CSF\supercite{context-sensitive-fencing} 中的译码
级信息流追踪框架 DIFT，用于追踪处理器使用的数据是否来源于用户输入，从而
处理器可以根据此信息判断是否需要插入 fence 微码。有的指令系
统\supercite{oisa}在定义中即包含了 DIFT 技术，用于追踪一个数据是否为秘
密数据。

和 Conditional Speculation
\supercite{conditional-speculation} 的 TPBuf 过滤器一样，本文的工作同样
实现了一个用于识别一条指令是否泄露数据的方法。和 TPBuf 不同的是，基
于DIFT 的方法并不检测不同的访存指令之间的地址关系，而是直接寻找读取数据
的指令和泄露数据的指令之间直接或间接的依赖关系。TPBuf 过滤器不能防范非
共享内存区域的缓存侧信道攻击，但是基于 DIFT 的方法可以。
而SpectreGuard\supercite{spectreguard} 中可以不依赖软件实现
的 SG(Full)方案，目的也是阻止推测式执行中从存储系统读取的数据不被泄露，
方法是直接禁止读出的数据被后续的推测式执行的指令使用，本文的方法允许这
样的数据被使用，但是使用这个数据的指令需要用不产生侧信道的方式执行。

在本文的工作中，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}
\centering
\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}
\centering
\end{table}

以下描述这个检测方法的具体细节。

标记的设置：在推测式执行时，对于所有从内存读取数据的指令，将其所有目的
寄存器的标记设为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}[frame=single,linenos=true]{C}
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}
\centering
\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]| 的值存在依赖关系。

因此我们需要转移指令中的标记。如果一条指令在被标记的转移指令之后执行，
则该指令和一个被标记的数据存在依赖关系，如果指令为装载指令，由于它改变
了缓存的状态，攻击者可以通过缓存信道得知该指令被执行过，推测出被标记转
移指令的执行结果，从而推测出内存中秘密数据的值，因此这样的装载指令也是
不安全的指令。

这种检测方案的微体系结构实现如图\ref{fig:spectre_dift}所示。

\begin{figure}[htbp]
  \centering
  \includegraphics[width=0.8\textwidth]{spectre_dift.eps}
  \caption{基于 DIFT 的 Spectre 检测方法的微体系结构示意图}
  \label{fig:spectre_dift}
\end{figure}

\section{不安全指令的执行方式}

对于检测到的不安全的指令，最简单的方式是阻止它的执行，直到指令因推测式
执行错误回卷，或确认为安全状态，再重新调度至执行单元执行。这种做法会导
致推测式执行中，后续所有依赖于这条指令的指令都会被推迟，降低流水线的利
用率，导致性能下降。

一种方法是使用 InvisiSpec \supercite{invisispec}，和 Spectre 攻击的检测
机制结合后，可以作为单条不安全指令的执行机制。它将不安全指令从内存中读
到的数据放入推测式执行缓冲区中，后续的指令可以使用这条指令的执行结果，
确保指令流的继续执行。在推测式执行错误时，推测式执行缓冲区的内容会被丢
弃，缓存状态不会改变，因此不会产生侧信道。推测式执行被验证为安全时，缓
冲区中的数据将会更新至存储系统。为类确保不发生访存违例 InvisiSpec 需要
在确认指令安全后，对读到的数据做一次验证，因此需要再次访问存储系统。

由于缓存命中的指令不会改变缓存的内容，因此还有一种方法是在推测式执行时，
如果一级缓存命中，则允许该指令继续执行，否则中止该指令的执行，等待指令
被回卷或转为安全的指令。如果在推测式执行时，缓存命中率高，则这种方案不
会阻止后续指令的执行，如果程序在推测式执行中缓存命中率低，则性能损失较
大。这种方法和 Conditional Speculation
\supercite{conditional-speculation} 中的基于缓存命中的过滤器基本相同。

\section{针对 Spectre 攻击的微体系结构在 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{安全执行装载指令方案的实现}

图\ref{fig:load_exec}是 gem5 中执行一条装载指令的总体流程。

\begin{figure}[htbp]
  \centering
  \includegraphics[width=0.8\textwidth]{load_exec.eps}
  \caption{gem5 O3CPU 执行装载指令的过程}
  \label{fig:load_exec}
\end{figure}

在执行阶段，处理器将装载指令传递至 LSQ，装载指令将会按序进入 LQ. gem5
用指令系统特定的 initiatAcc 方法模拟该指令系统中装载指令访问存储系统的
方式。最后 LSQ 将装载指令的信息封装为一个请求，用 sendTimingReq 将这个
请求发送至存储系统。

发送至存储系统的读请求并不会立刻得到结果，需要等待来自存储系统的响应。
图\ref{fig:load_exec}的右半部分是处理器接收响应的流程，
在 recvTimingResp 后，处理器可以得到此前发送的请求对应的结果，根据得到
的结果，LSQ 执行回写操作，将结果写入至指令的目的寄存器，并将指令送至提
交阶段，完成指令的执行。

对于不安全指令的执行，可以为其增加一种 SpecLoad 请求，不同于普通的Load
请求，SpecLoad 执行后，不会改变任何缓存状态和缓存一致性状态，因此不会留
下缓存的侧信道。

% 一致性协议

对于 InvisiSpec，在指令确认安全后，还需要向存储系统再发一个请求，以验
证 SpecLoad 读取的结果正确，或直接确认 SpecLoad 的结果，因此还需要再增
加一个 Expose 请求，用于确认或验证结果。

在 gem5 中，多核系统的缓存和缓存一致性模型由 Ruby 存储系统模拟。本文模
拟一个带二级缓存的处理器，缓存一致性协议为 MESI，需要修改的相关文件
为 src/mem/protocol 下以 MESI\_Two\_Level 开头的文件。

对于一级缓存，为了增加对不安全内存读取的支持，我们修改状态机文
件 MESI\_Two\_Level-L1cache.sm. 除了添加 SpecLoad 和 Expose 两个请求外，
还要处理从其他处理器转发的响应的请求。此外，在处理器的实现中，状态机接
收到处理器的请求，到从存储系统得到数据之间，状态机处于一个过渡状态，因
此在状态机中添加一个状态 IX，表示接收了 SpecLoad 之后缓存缺失，向下级存
储系统发送读请求，还未得到响应。

InvisiSpec 中，一级缓存处理 SpecLoad 和 Expose 的部分状态如
图\ref{fig:invisispec_mesi}所示。

\begin{figure}[htbp]
  \centering
  \includegraphics[width=0.8\textwidth]{invisispec_mesi.eps}
  \caption{InvisiSpec 一级缓存处理 SpecLoad 和 Expose 请求}
  \label{fig:invisispec_mesi}
\end{figure}

其中，对于任意一种状态，接收到 Expose 请求后，处理器都会更新缓存状态，
将指令从存储系统中读到的数据写入缓存。对于 SpecLoad 缓存命中的情
形，spec\_load\_hit 取出缓存行，返回至处理器，和 load\_hit 的区别是推测
式执行中访问缓存，不会更新缓存替换算法的状态，避免产生侧信道信息。

% hit based

% 而对于只在缓存命中时执行装载指令的方案，设计相对简单。它只需要处理
% SpecLoad 请求。

% 在这种情形下，不需要添加额外状态。缓存行在任一状态下，接收 SpecLoad 时
% 状态不变。缓存命中时，则执行 spec\_load\_hit 将缓存行中的内容返回至处
% 理器。

% 而在缓存缺失时，则在其中添加一个 spec\_load\_miss 操作，它向处理器返回
% 一个缓存缺失的响应。LSQ 在写回的过程中处理这个响应，为其对应的缓存缺失
% 的指令清除表\ref{tab:inst_status}中的 ReadyToExpose 属性，并通知指令队列需
% 要推迟这条访存指令的执行。

\subsection{执行流水线的修改}

我们在 DynInst 类为指令添加新的状态和属性，添加的主要的指令状态和属性
见表\ref{tab:inst_status}。

\begin{table}
\begin{tabular}{|p{0.25\textwidth}|p{0.75\textwidth}|}
\hline 
指令状态和属性 & 含义\tabularnewline
\hline 
\hline 
PrevBrsResolved & 记录指令之前的分支是否得出结果\tabularnewline
\hline
IsTainted & 在基于 DIFT 的检测方案中，记录指令是否依赖于被标记的寄存器\tabularnewline
\hline 
AfterTaintedBranch & 在基于 DIFT 的检测方案中，记录指令是否在被标记的转
                    移指令之后\tabularnewline
\hline 
L1Hit & 记录指令的 SpecLoad 请求是否命中一级缓存并且缓存内容不变，如果
        是，则不需要发送验证请求\tabularnewline
\hline 
ExposeSent & 已发送 Expose 请求\tabularnewline
\hline 
ExposeCompleted & Expose 已执行完成\tabularnewline
\hline 
ValidationCompleted & 数据已验证完成\tabularnewline
\hline
NeedPostFetch & 需要再次发送 Expose 请求\tabularnewline
\hline
ReadyToExpose & 指令已确认安全，可以发送 Expose 请求，或直接用基本的方
                式执行指令\tabularnewline
\hline
NeedExposeOnly & 只需要发送 Expose，而不需要通过再次读取数据用于验证\tabularnewline                
\hline
FenceDelay & 指令被阻止执行，直到确认安全，在 InvisiSpec 之外的执行方
             案中使用\tabularnewline
\hline
\end{tabular}
\caption{新增的指令状态}
\label{tab:inst_status}
\centering
\end{table}

在每一周期，处理器扫描 ROB 中的每条指令，更新流水线中每条指令的
PrevBrsResolved 状态。在 LSQ 中，对于每一个未完成的装载指令，根据指令
之前是否存在未决分支，判断指令是否安全，设定 ReadyToExpose 属性，取消
FenceDelay 标志。

\subsection{动态信息流追踪的实现}

动态信息流追踪的实现分为标记设置、传播、清除和使用四个部分，以下分别介
绍它们在 gem5 中的实现。

对所有推测式执行中的装载指令，其目的寄存器都需要设置标记。在扫描 LSQ
中指令的时候，指令前存在未决分支，则对这个指令的目的寄存器设置标记。

标志的传播在执行阶段进行。对于非访存类指令，如果指令有设置了标记的源寄
存器，则设置该指令的目的寄存器的标记。同时也设置指令的 IsTainted 属性，
它作为转移指令的标记。

指令的 IsTainted 属性和目的寄存器的标记在更新 ROB 中每条指令
的 PrevBrsResolved 状态的时候清除，这个过程同时更新指令
的 AfterTaintedBranch 属性。

标记的使用在更新 LSQ 中指令状态的时候发生，在加入动态信息流追踪的设计中，
指令的 ReadyToExpose 属性设定的条件有：（1）指令已设置
了 PrevBrsResolved 属性，或者（2）AfterTaintedBranch 未设置，并且源寄存
器均未被标记。

\section{本章小结}

本章前两节介绍本文设计的基于信息流追踪的 Spectre 组件的检测方法，讨论
了执行可能泄露数据的装载指令的几种方法。第三节介绍了在 gem5 中实现这些
技术的方法，包括对 gem5 流水线的分析，gem5 装载指令执行流程的分析，对
执行流水线作出的修改，实现装载指令安全执行的方法。
