% Copyright (c) 2014,2016,2018 Casper Ti. Vector
% Public domain.

\chapter{Meltdown和Spectre攻击的防御方案分析}\label{sec:defense}

Meltdown 和 Spectre 及其多种变体被发现后，研究者提出了多种减轻这些攻击
的防御方法，它们需要对软件或硬件进行修改。也有研究者提出通过修改现有的
指令系统\supercite{oisa}，使得用这种指令系统的程序不受 Spectre 攻击的
影响。

\section{Meltdown型攻击的防御}

Meltdown型攻击利用了暂态指令可以读取体系结构层次上不可访问的数据，并且
用此数据做计算。因此一种防御方式是使体系结构层次上不可访问的数据，在微
架构层次上仍然不可访问。

KAISER\supercite{kaiser}是一种已经部署在 Linux 内核上的一种防御
Meltdown 攻击的方案。它的作用是在用户空间中去除内核空间的地址映射，使
得用户空间的执行的指令无法访问内核空间的存储区域。

防御 Meltdown 型攻击的第二种方法是阻止异常的产生。例如对于 LazyFP，最
新的 Linux 内核在上下文切换时，对所有进程都会保存和恢复浮点寄存器，从
而用户程序使用浮点寄存器都不会产生异常，从而消除这种攻击。

\section{Spectre型攻击的防御}

Spectre 型攻击的防御方案可以分为三类：阻止推测式执行、防止暂态指令访问
秘密数据、切断隐蔽信道或降低隐蔽信道的精度。研究者在软件和硬件方面都提
出了防御方案。

\subsection{软件防御方案}

Intel 和 AMD 都提出了在分支指令后插入 lfence 指令阻止推测式执行的方
法。\supercite{intel-spectre}\supercite{amd-spectre}lfence 指令在此作
为一条串行化指令使用，可以在 lfence 指令提交前阻止新的指令执行，从而阻
止了程序在推测式执行中对秘密数据进行操作。

由于 lfence 性能开销大，LLVM提出推测式装载指令加固（Speculative
Load Hardening）\supercite{spec-load-hardening} 技术，它的做法是将可能
为 Spectre 组件的代码转为图\ref{fig:SLH}中的代码：

\begin{figure}[htbp]
\begin{minted}[frame=single]{C}
  if (condition) {
    // ?: 的实现可以用无分支的 cmov
    predicate_state = !condition ? 0 : predicate_state;
    // ... a lot of code ...
    // 加固指针，使得它不可用于访问
    pointer1 &= predicate_state;
    leak(*pointer1);
  } else {
    predicate_state = condition ? 0 : predicate_state;
    // ... more code ...
    // 或加固装载得到的数据，使得数据无法泄露
    int value2 = *pointer2 & predicate_state;
    leak(value2);
  }
\end{minted}
\caption{推测式装载指令加固产生的代码}
\label{fig:SLH}
\end{figure}

产生的代码在两个分支中，都计算一个分支条件的状态，并将其作为存储访问指
令或泄露数据指令的一个数据依赖，从而在得出分支结果之前，无法访问或泄露
一个秘密数据。

retpoline\supercite{retpoline} 是 Google 提出的防御 Spectre-BTB 的方法。
它的作用是把程序中的间接转移指令修改为一个指令序列，最终使用 ret 指令
完成跳转，从而使用 RSB 而不是 BTB 来进行间接转移的转移预测。

% TODO:增加 retpoline 的代码？

% index masking
Webkit 在数组访问中使用 index masking\supercite{webkit} 方法，它让数组
下标和一个值进行与操作，去掉下标高位的1，将数组下标控制在一定范围内，防
止处理器在推测式执行中访问数组指定范围之外的数据。Linux 构造了一个粒度
更细的 array\_index\_nospec\supercite{linux-spec}，它的功能如
图\ref{fig:array-index-nospec}：

\begin{figure}[htbp]
\begin{minted}[frame=single]{C}
  size_t array_index_nospec(size_t index, size_t size) {
    mask = ~(signed long)(index | (size - 1 - index)) >> 63;
    return (index & mask);
  }
\end{minted}
\caption{array\_index\_nospec}
\label{fig:array-index-nospec}
\end{figure}

如果数组索引 index 小于 size，则 mask 为一个所有位为 1 的值，产生的索
引不变，否则 mask 为 0，产生的索引为 0，从而推测式执行时的数组索引始终
在数组大小范围内。

% poison value
Webkit 还使用了指针投毒（pointer poisoning）\supercite{webkit}的方式，
它考虑分支用于做类型检查的情形。对于不同类型的数据，Webkit 将这种类型的
指针和一个类型对应的值异或，要使用的时候再异或这个值得到指向数据的指针，
如果类型不匹配，则得到的指针会指向一个程序无法访问的内存区域。通过这种
方法，程序在推测式执行中无法通过这种指针访问到程序中的数据，从而避免了
秘密数据的泄露。

% site isolation, \Fixme: needs citing
Google 为 Chrome 浏览器使用了站点隔离（site isolation）技术，使得浏览
器用不同的进程访问不同的网站，从而攻击者无法通过用 Javascript 通过侧信
道攻击获取另一站点相关的数据。\supercite{here-to-stay}

% timer reduction
降低计时器精度可以降低计时攻击所用侧信道的精度，一个例子是在浏览器中降
低 Javascript 引擎中 performance.now 等计时器的精度。
\supercite{webkit} 但是相关研究发现，攻击者可以使用其他方式构造高精度
的计时器\supercite{js-timer}，因此降低计时器精度不是一个有效的防御方法。


\subsection{硬件防御方案}

\subsubsection{SafeSpec}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% gtran %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\Todo: SafeSpec III. SAFESPEC :LEAKAGE - FREE SPECULATION 一章，包含一
些设计选择

\Fixme: 需要重新翻译

SafeSpec\supercite{safespec}提出了一种原则性的方法来保护处理器免受推测
式执行攻击，同时保留执行推测性执行以从其性能中受益的能力。一般原理（如
图\ref{fig:safespec}所示）使用临时结构（图中的阴影状态）来保存任何以推
测方式产生的状态，而不会影响处理器的主要结构（我们在图中称为提交状态）。
例如，如果推测性加载指令导致加载高速缓存行，而不是将该高速缓存行加载到
处理器高速缓存中，我们将该行保存在临时结构中。如果稍后对加载指令进行压
缩，则会将这些效果移除（从提交状态开始的底部路径），不会从错误推测的指
令中更改缓存，并关闭漏洞。或者，如果指令提交，则将高速缓存行从临时结构
移动到L1高速缓存并从阴影状态中移除。

\begin{figure}[htbp]
  \centering
  \includegraphics[width=0.8\textwidth]{safespec.eps}
  \caption{SafeSpec 总体设计}
  \label{fig:safespec}
\end{figure}

虽然SafeSpec原则上很简单，但必须解决许多与其安全性，复杂性和性能相关的
问题。我们将在本节的其余部分概述这些问题。

% transient to commit
有两个选项可用于决定何时将状态从阴影移动到已提交状态。在第一个变量中，
我们称之为等待分支（WFB），我们可以假设一条指令在它所依赖的所有分支
（更一般地说，所有预测）都已被解析时不再是推测性的。 WFB阻止两种幽灵的
变种，它们依赖于错误分支预测;没有任何错误推测的指令移动到已提交状态。
但是，它不会阻止不依赖于分支预测器的Meltdown。第二个变量等待提交（WFC）
等待直到导致推测性副作用的指令在将其效果移动到提交状态之前提交，因此也
会阻止崩溃。我们注意到，根据此定义的重新排序缓冲区是一种阴影状态，只有
在提交指令时，其数据才会移动到永久状态（架构状态）。

阴影状态组织和大小：如果阴影状态结构太小，则替换推测状态（如果稍后将提
交此数据，则导致丢失对已提交状态的更新），或者指令必须停止，直到那里在
发布之前，它是投机结构的空间。因此，从性能的角度来看，应该设计影子结构
的组织和大小，使得结构可以保持通过在典型工作负载上测量的推测产生的推测
状态。但是，我们将证明安全考虑因素对投机状态提出了更严格的要求。

减轻瞬态猜测攻击：通过构造安全规范可以防止推测值影响已提交结构的状态，
这是在已发布的推测攻击中隐蔽地传递数据的途径。但是，它不会在处于推测状
态的指令之间创建隔离。这为我们称为瞬态推测攻击（TSA）的新型攻击创造了
可能性。特别是，由于提交的指令可以处于推测状态（在它们的依赖分支在WFB
中提交之前，或者在指令本身在WFC中提交之前），因此有一个时间窗口，它们
可以在它们之前与错误指定的指令共享推测状态。被压扁了。如果我们不小心，
可以在此期间创建一个隐蔽通道，将敏感数据从错误推测的分支传递到将要提交
的分支，从而允许数据被泄露。

考虑一个尺寸小的阴影结构的例子（比如说一个条目）。然后，读取特权数据的
恶意推测代码可以使用阴影状态秘密地将其传递给推测代码（将提交的“接收器”
代码）。例如，它可以替换阴影状态中的条目，导致接收者在提交后注意到其推
测状态（因为它被替换）的缺失。或者，如果我们在阴影结构已满时阻塞，接收
器可以检测到其代码执行时间较长。

尽管TSA的严重程度远低于原始攻击，但必须仔细考虑它们以确保无法进行泄漏。
解决此问题的一种方法是对每个分支的推测状态进行分区，或者对其进行大量调
整，或者甚至在最坏的情况下，以确保通过阴影状态不发生泄漏。 TSA还可以通
过在功能单元或其他共享结构上创建争用来秘密地进行通信;这是我们也考虑的
问题。我们将在第五节讨论如何减轻TSA攻击。

过滤延迟的副作用：当指令在执行过程中被压缩时，会发生SafeSpec的一个问题。
如果指令已经启动了高延迟操作，例如从内存中读取，我们必须确保在收到内存
后可以丢弃内存中的响应。 执行的指令推测性地将任何结果状态存储到阴影结
构。 因此，如果收到长等待时间回复并且没有匹配的事务，我们只是丢弃这些
值。 然而，也可能希望在系统中较低地过滤这些事务，使得提交的事务直接提
交，并且压缩的事务在适当的位置被取消。 为了控制此过滤器的大小，我们可
以在分支粒度中包含一个带有事务和跟踪操作的分支ID。 过滤器还可用于标记
已提交的分支，以便与其对应的内存响应直接提交到永久结构。

\Todo: SafeSpec for caches and TLB
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsubsection{InvisiSpec}

InvisiSpec \supercite{invisispec} 使推测式执行产生的副作用在数据缓存中
不可见，从而可以防御利用推测式执行的攻击。它的目标是阻止由推测式执行的
装载操作产生的微架构侧信道和隐蔽信道，如缓存占用、缓存行替换算法信息、
缓存一致性状态等。它不但要防御基于分支推测式执行的 Spectre 攻击，而且
还要防御未来可能利用到推测式执行中的装载操作的攻击。和 SafeSpec 不同，
InvisiSpec 支持多处理器。

InvisiSpec 的微架构基于两种机制。第一个机制中，不安全的推测式执行的装载
操作将数据读入至推测式执行缓冲区（Speculative Buffer，SB）中，而不读入
缓存，从而不修改缓存存储层次的状态。由于缓冲区中的数据不产生缓存一致性
事务，因此可能造成内存一致性违例。第二个机制解决这个问题，当推测式执行
的装载操作变得安全时，处理器将这个操作重新发射至存储系统，使其对整个系
统可见。如果这个装载指令可能造成访存违例，InvisiSpec 验证读出的指令是
否正确，如果验证失败，则重新执行这个访存指令及其后续指令。
% squash the load

InvisiSpec 在不同的内存一致性模型下，会使用不同的执行策略。内存一致性模
型（或内存模型）指定在一个共享内存的系统中，处理器核执行访存操作，并使
其对其他处理器核可见的顺序。在存储指令执行结束后，存储的数据存放在写缓
冲区中，随后写入缓存。当写操作写回缓存后，其他核可以看见这个数据，存储
操作在此时完成。而装载操作在提交前从存储系统读取数据，当它得到数据时，
则称为装载操作完成。装载操作的完成可以在程序序列中其他的访存操作之前，
可以导致内存一致性违例，此时处理器需要用恢复机制重新执行这个装载指令及
其后的指令。

TSO（Total Store Order）是 x86 体系结构所用的内存模型。TSO 允许一个装载
操作在不同地址的存储操作之前完成，除此之外禁止所有可见的装载和存储的重
排序。在实现中，为了防止可见的装载指令的重排序，需要确保一个装载操作读
入的数据，在提交时仍然有效。如果处理器核遇到一个外部的缓存失效请求，则
需要重新执行这个装载操作。存储指令的顺序由先入先出的写缓冲区保证。

RC（Release Consistency）则允许任何访存操作的重排序，除非使用同步指令。

\begin{tabular}{|c|c|}
\hline 
攻击 & 暂态指令的来源\tabularnewline
\hline 
\hline 
Meltdown & \multirow{2}{*}{虚拟内存异常}\tabularnewline
\cline{1-1} 
L1TF & \tabularnewline
\hline 
LazyFP & \multirow{2}{*}{读取禁用的或者特权寄存器发生异常}\tabularnewline
\cline{1-1} 
Rogue System Register Read & \tabularnewline
\hline 
Spectre & 控制流预测错误\tabularnewline
\hline 
Speculative Store Bypass & 装载指令和更早的存储指令地址别名\tabularnewline
\hline 
未来的攻击 & 异常、控制流预测错误、访存别名、一致性违例、中断等\tabularnewline
\hline 
\end{tabular}

InvisiSpec 关注不安全的推测式执行的装载指令（Unsafe Speculative
Load，USL），在 Spectre 的攻击模型中，USL 是在未决分支之后的装载指令，
在得到分支结果后，正确路径上的 USL 转为安全的装载指令。而在未来攻击的模
型中，USL 变为安全仅当指令到达 ROB 头部，或不被可能中断该指令的事件回
卷。

\Todo: InvisiSpec  V: INVISISPEC: THWARTING SPECULATION ATTACKS

\Fixme: 重新翻译以下内容

%1）不安全的推测负载：严格地说，任何在到达ROB头部之前启动读取的负载都是推测负载。在这项工作中，我们对可能由于推测而产生安全漏洞的（大）投机负载子集感兴趣。我们称这些负载为Unsafe Speculative Loads（USLs）。作为USL的一组推测性负载取决于攻击模型。

%在Spectre攻击模型中，USL是遵循未解决的控制流指令的推测性负载。一旦控制流指令解决，跟随它的USL在分支的正确路径中转换到安全负载 - 尽管它们仍然是推测性的。

%在我们的未来攻击模型中，USL是所有可以被先前指令压扁的推测性负载。一旦USL变为（i）非推测性的，因为它到达ROB的头部或者（ii）推测不能通过任何先前的指令（或推测性的不可压缩的短路）变为安全负载。推测性非可压缩负载是（i）不在ROB的头部并且（ii）在ROB之前仅通过指令而不是由表I中的任何压扁事件压缩的未来的负载。请注意，在表中，其中一个压缩事件是中断。因此，使负载安全包括延迟中断直到负载到达ROB的头部。

%要了解为什么这两个条件允许USL过渡到安全负载，请考虑以下内容。 ROB头上的负载本身不能是瞬态的;它可以被压扁（例如，由于异常），但它是一个正确的指令。关于推测性非可压缩负载也可以这样说。这是因为，虽然不在ROB头部，但是它们不能被任何先前的指令压扁，因此可以被认为是ROB头部处的指令的逻辑扩展。

2）使USLs看不见：InvisiSpec背后的想法是让USL隐形。这意味着USL无法以任何其他线程可见的方式修改缓存层次结构，包括一致性状态。 USL将数据加载到我们称为Speculative Buffer（SB）的特殊缓冲区中，而不是加载到本地缓存中。如上所述，USL可以转换到安全负载的时间点。此时，称为可见性点，InvisiSpec采取的行动将使USL可见 - 即，将使USL在内存层次结构中的所有副作用对所有其他线程都明显。 InvisiSpec通过重新加载数据使USL可见，这次将数据存储在本地缓存中并更改缓存层次结构状态。负载可能仍然是推测性的。

3）维护内存一致性：负载的可抑制可见性窗口是从USL发出负载到使其自身可见之间的时间段。在此期间，由于USL不会更改任何一致性状态，因此核心可能无法接收针对USL加载的线路的无效。因此，负载违反内存一致性模型存在未检测到的风险。这是因为这种违规通常是通过传入的失效来检测的，并且通过压缩负载来解决。要解决此问题，InvisiSpec可能必须在负载的可见点重新加载数据时执行验证步骤。

当USL发布并且线路加载到SB时，USL请求的线路可能已经在核心的L1缓存中。在这种情况下，核心可能会收到该行的无效。但是，USL忽略了这种失效，因为USL是不可见的，并且这种失效的任何影响都将在可见点处理。

4）USL的验证或曝光：在可见性点重新加载数据的操作有两种形式：验证和曝光。如果在该窗口期间，核心已经收到USL加载的行的无效，则验证是在可抑制可见性窗口期间（由于内存一致性考虑）使可见的USL可见的方法。验证操作包括将USL使用的实际字节（存储在SB中）与从缓存层次结构加载的最新值进行比较。如果它们不相同，USL及其所有连续指令都会被压扁。这是满足内存一致性模型所必需的。这一步让人想起Cain和Lipasti的基于价值的记忆排序[31]。

  验证可能很昂贵。持久验证的USL在事务结束之前不能退出 - 即，从高速缓存层次结构获得的行被加载到高速缓存中，行的数据与SB中使用的子集进行比较，并做出关于压缩的决定。因此，如果USL位于ROB头并且ROB已满，则管道停止。

值得庆幸的是，InvisiSpec识别出许多在其抑制可见性窗口期间无法违反内存一致性模型的USL，并允许它们以廉价的曝光可见。 如果在该窗口期间核心已经收到USL加载的行的无效，则这些USL在禁用可见性窗口期间不会被内存一致性模型压扁。 在曝光中，缓存层次结构返回的行只是存储在缓存中而不进行比较。 一旦将行请求发送到缓存层次结构，持续曝光的USL就会退出。 因此，USL不会阻止管道。

总而言之，有两种方法可以使USL可见：验证和曝光。 内存一致性模型确定需要哪一个。 图2显示了具有验证和曝光的USL的时间线。

InvisiSpec的负载有两个步骤。首先，当它作为USL发布到内存时，它访问缓存层次结构并获得所请求的缓存行的当前版本。该行仅存储在本地SB中，本地SB与L1高速缓存一样靠近核心。 USL不修改高速缓存一致性状态，高速缓存替换算法状态或任何其他高速缓存层次结构状态。没有其他线程（本地或远程）可以看到任何更改。但是，核心使用USL返回的数据来取得进展。 SB存储行而不是单个词来利用空间局部性。

当可以使USL可见时，并且总是在它收到其请求的高速缓存行之后，硬件触发验证或暴露事务。这样的事务再次重新请求该行，这次修改缓存层次结构，并将该行带到本地缓存。如第V-A4部分所述，验证和暴露交易的运作方式不同，并且具有不同的性能影响。

我们考虑两种攻击模型，幽灵和未来派，并提出略微不同的InvisiSpec设计来抵御这些攻击中的每一种。在我们对Specter攻击的防御中，USL在其所有先前的控制流指令解决时达到其可见性点。此时，硬件根据内存一致性模型和负载在ROB中的位置（第V-C节）发出负载的验证或暴露事务。如果多个USL可以发出验证或暴露交易，则交易必须按程序顺序开始，否则全部重叠（第V-D节）。

在我们对未来攻击的防御中，USL只有在以下情况下才能达到其可见性:( i）它不再是推测因为它位于ROB的头部，或者（ii）它仍然是推测性的，但不能再被压扁。此时，硬件根据内存一致性模型和负载在ROB中的位置（第V-C节）发出负载的验证或暴露。如果多个USL可以发出验证或暴露交易，则必须按程序顺序发布交易。但是，当发布验证交易时，后续验证或暴露交易不会与之重叠;在验证完成之前，他们都必须等待发布（第V-D节）。另一方面，当发出暴露交易时，直到并包括下一个验证交易的所有后续暴露交易都可以与之重叠（第V-D节）。

总的来说，在幽灵和未来派防御设计中，当验证事务阻止ROB头部的负载退出并且ROB已满时，可能会发生唯一的管道停顿。这在未来派中比在幽灵中更有可能。

我们将这些设计称为InvisiSpec-Spectre（或IS-Spectre）和InvisiSpec-Future（或IS-Future）。它们显示在表II的第一行中。为了进行比较，第二行显示了如何使用基于栅栏的方法防御这些相同的攻击 - 遵循当前提出的防御幽灵的建议[32]。我们将这些设计称为Fence-Spectre和Fence- Future。前者在每个间接或有条件的分支后放置围栏;后者在每次装载前放置围栏。

与基于栅栏的设计相比，InvisiSpec提高了性能。具体而言，负载是在传统的不安全机器中推测性地执行的。一个问题是ROB头部的负载的验证事务可能使管道停滞。但是，我们将展示如何以验证次数为代价最大化曝光次数（不会导致失速）。最后，InvisiSpec确实会创建更多缓存层次结构流量并争用各种缓存端口。应该设计硬件来处理它们。

内存一致性模型确定何时使用验证以及何时使用曝光。首先考虑TSO。在高性能TSO实现中，当ROB中没有较旧的负载（或栅栏）时读取的推测性负载将不会被随后的对其读取的线路的无效进行压缩。因此，这样的USL可以在其变得可见时使用曝光。另一方面，当ROB中存在至少一个较旧的负载（或栅栏）时读取的推测性负载将被其读取的行的无效压缩。因此，这样的USL需要使用验证。

现在考虑RC。在这种情况下，只有在ROB中存在至少一个早期栅栏时读取的推测性负载将被线路读取的无效压缩。因此，只有那些将被要求使用验证;绝大多数负载都可以使用暴露。

从这个讨论中，我们看到在TSO下观察停止管道验证的机会最高的设计是IS-Future。为了减少这些事件的发生，InvisiSpec实现了两种机制。第一个允许一些使用验证的USL代替使用曝光。第二个标识可以使用验证的USL，如果验证失败的可能性很大，则提前将它们压扁。我们接下来考虑这些机制。

1）将验证USL转换为曝光USL：假设在TSO下，USL1在ROB中存在较早的负载时启动读取。通常，InvisiSpec会将USL1标记为需要验证。但是，假设在读取时，早于USL1的ROB中的所有负载已经获得了他们请求的数据 - 特别是，如果它们是USL，则他们请求的数据已经到达SB并且已经到达传递到登记册。在这种情况下，USL1不会相对于任何早期的负载重新排序。因此，TSO不会要求在收到对其加载的线路无效时压缩USL1。因此，USL1被标记为需要曝光，而不是验证。

为了支持这种机制，我们用一个名为Performed的位来标记每个USL。当USL请求的数据已在SB中接收并传递到目标寄存器时设置。

2）USL的早期压缩需要验证：假设核心接收到其缓存中的行的失效，该行也恰好被标记为需要验证的USL加载到其SB中。接收失效表示该行已更新。此类更新通常会导致USL在可见性失败时的验证。如果此失效是由错误共享引起的，或者如果对该行的所有更新的净效果直到验证结果是无声的（即，它们将数据恢复到其初始值），则验证才能成功。由于这些条件不太可能发生，InvisiSpec会因接收失效而挤压这样的USL。还有第二个USL的案例，验证失败的可能性很高。假设USL1需要验证并且在SB中有数据。此外，有一个较早的USL2到同一行（但对于该行的不同单词），其在SB中也有其数据并需要验证。当USL2执行验证并将线路连接到核心时，InvisiSpec还会将该线路与SB中的USL1数据进行比较。如果数据不同，则表明USL1已读取陈旧数据。那时，InvisiSpec保守地压制了USL1。

  % skips subsection D, E

USL执行两个事务 - 一个是首次发布时，一个是验证或公开时。现在，假设USL的第一次访问错过最后一级缓存（LLC）并访问主内存。然后，USL的第二次访问可能也是对主内存的长延迟访问。

为了提高性能，我们设计了InvisiSpec，以避免在大多数情况下进行第二次主存访问。具体来说，我们在LLC旁边添加了每核LLC推特缓冲区（LLC-SB）。当USL的第一次访问从主存储器读取该行时，当该行被发送回请求核心的L1 SB时，InvisiSpec将其副本存储在核心的LLC-SB中。之后，当USL发布其验证或曝光时，它将从核心的LLC-SB读取该行，跳过对主存储器的访问。

如果在两次访问之间，第二个核心通过验证/暴露或安全访问访问该线路，则InvisiSpec使来自第一个核心的LLC-SB的线路无效。这保守地确保LLC-SB不保存陈旧数据。在这种情况下，原始USL的验证或公开事务将从缓存层次结构中的任何位置获取该行的最新副本。
%%%%%%%%%%%%%%%%%%%

在 gem5 模拟器下， 使用 SPEC CPU2006 和 PARSEC 对 InvisiSpec 进行评测，
以 Spectre 为威胁模型，在 TSO 内存模型下，InvisiSpec 平均性能损失为
21\%，而使用 fence 性能下降 74\%.

\subsubsection{DAWG}

\Todo: DAWG introduction

\Fixme: 重新翻译

在一个设计良好的系统中，攻击者无法在架构上观察到这个秘密，因为秘密应该
局限于一个保护域，阻止其他程序在架构上观察它。但是，当攻击者可以通过软
件方式观察执行的副作用时，可能存在漏洞。

进行这种观察的机制称为软件侧信道。必须根据受害者保护域中的活动来调制这
些信道，即它们的状态改变，并且攻击者必须能够检测那些状态变化。目前，最
广泛探索的渠道基于共享缓存的状态。例如，如果攻击者观察到地址上的命中，
则该地址必须已经缓存，这意味着某个方（可能是受害者）最近访问过该地址，
并且尚未被替换。确定访问是否是命中可以通过测量程序进行特定引用所花费的
时间来完成。

隐蔽通信信道在不允许通过现有保护机制进行通信的进程之间传输信息。例如，
当侧信道用于向攻击者传达“秘密”时，攻击将包括受害者保护域内用于访问秘密
的代码和用于将秘密传递给攻击者的发送器。它们一起形成数据抽头，将根据秘
密调制信道。由攻击者控制并在受害者保护域之外的接收器将监听信道上的信号
并对其进行解码以确定该秘密。这在图1中以图示的方式示出。

对RSA的经典攻击依赖于这种情况[9]。具体地，现有RSA代码遵循作为秘密的函
数的条件执行序列，并且通过根据该执行序列修改指令高速缓存状态而无意地发
送私有信息。这导致了一个隐蔽的通信，让观察对手确定秘密的位。在这种情况
下，访问秘密的代码和传送秘密的发送器预先存在于RSA代码中。因此，共享
icache的攻击者仅需要提供可以解调通过基于缓存标签状态的信道传送的秘密的
接收器。最近的工作表明，广泛的可行攻击通过共享缓存渗透信息。

最近，多个安全研究人员（例如，[22]，[31]，[35]）已经找到了攻击者在受害
者中创建新数据管道的方法。这里，攻击者能够在受害者的域中创建数据抽头和
/或影响数据抽头以访问和传输所选择的秘密。 Spectre和Meltdown利用了这样
一个事实，即推测性地执行的代码可以完全访问任何秘密。

虽然广泛定义了投机执行，但我们关注的是本文中的控制流量推测。现代处理器
不按顺序执行指令，只要保留依赖性，就允许下游指令在上游指令之前执行。现
代无序处理器上的大多数指令也是推测性的，即，它们创建检查点并沿着预测路
径执行，同时一个或多个先前条件分支正在等待解决。预测被解析为正确丢弃检
查点状态，而不正确一个强制处理器回滚到检查点并沿正确的路径继续。一段时
间内执行了错误预测的指令，但不修改架构状态。然而，诸如高速缓存标签状态
的微体系结构状态由于（不正确的）推测性执行而被修改，从而导致信道被调制，
这可能允许秘密泄漏。

通过利用错误推测的执行，攻击者可以执行通常无法访问的代码路径，从而绕过
软件不变量。一个例子是攻击者推测性地执行非法访问秘密的数据抽头代码，并
在异常发生之前通过微架构副作用引起传输[35]。另一个例子是攻击者强制分支
预测器状态以鼓励沿着攻击者选择的代码路径的错误推测，这实现了受害者域中
的数据抽头。因此，有三种创建数据点击的方法：
1）数据抽头预先存在于受害者的代码中，我们在RSA攻击[9]中描述过。
2）攻击者明确编程数据点击。 Meltdown [35]就是一个例子。
3）攻击者合成受害者现有代码中的数据抽头 - 例如幽灵变种[22]，[30]，[31]。

例如，该框架可以应用于除缓存状态之外的侧通道，描述通过分支预测器逻辑或
TLB状态的泄漏。鉴于对这种新攻击类的变体的研究兴趣越来越浓，我们也想象
出可以构建数据抽头的新方法。因此，我们希望设计一种针对广泛的当前和未来
攻击的防御。

已经提出了可以仅在软件中实现的防御机制（例如，[11]，[43]）。不幸的是，
这些机制看起来非常具有攻击性：例如，编译器分析[43]确定了一些易受幽灵变
种1影响的代码实例;微码更新或编译器和链接器修复减少了对Spectre Variant
2的暴露[11]。已经引入了关闭脆弱区域中的推测的指令（例如，[2]），供将来
的编译器使用。在本文中，我们针对硬件进行了最小程度的修改，以抵御广泛的
侧通道攻击，包括那些基于推测的攻击，其目标是通过改变缓存状态来消除与渗
透相关的整个攻击面。

为了防止泄漏，我们需要保护域之间的强隔离，这可以防止任何发送器/接收器
对共享相同的通道。缓存分区是一种实现隔离的吸引人的机制。不幸的是，设置
（例如，页面着色[29]，[50]）和方式（例如，英特尔的高速缓存分配技术
（CAT）[21]，[23]）当今处理器中可用的分区机制要么性能低要么要么不高提
供隔离。

我们提出了DAWG，动态分配方式保护，一种用于包括高速缓存的集合关联结构的
安全方式分区的通用机制。 DAWG赋予一组关联结构和保护域概念，以提供强大
的隔离。与CAT等现有机制不同，DAWG不允许跨保护域点击。这会影响命中路径
和缓存一致性[42]，DAWG通过对现代操作系统的最小修改来处理这些问题，同时
将操作系统的攻击面减少到一小部分带注释的部分，其中数据跨越保护域，或者
域是调整/重新分配。只有在这些少数例程中，DAWG保护才会放松，并且根据需
要应用其他防御机制，例如投机围栏。我们使用体系结构仿真和实际硬件的组合
来评估DAWG的性能影响，并与传统的服务质量分区缓存进行比较。我们得出结论，
DAWG提供了强大的隔离和合理的性能开销。

\Todo: DAWG 的设计

在 zsim 模拟器中使用 SPEC CPU2006, PARSEC, GAPBS 进行模拟，相对于
Intel CAT，在不同的评测程序和划分方式下，大多数程序性能下降为 4\%\~7\%.

DAWG 的主要缺点是需要操作系统的支持。此外，DAWG 不能防御 NetSpectre 等可
在同一安全域内进行的攻击。

\subsubsection{Context-Sensitive Fencing}

\Todo: CSF introduction

\Fixme: 重新翻译

这项工作提出了上下文敏感的防护，一种针对幽灵的新型微码级防御。防御策略
的关键组成部分包括：（a）微码定制机制，允许处理器手动将栅栏插入动态指
令流，以减轻推测性执行的不良副作用，（b）解码器级信息流跟踪（DLIFT）
）框架，用于识别可能不安全的执行模式以触发微代码定制，以及（c）缓解保
护分支预测器和返回地址堆栈的缓解措施。

为了在对性能影响最小的情况下执行安全的微码定制，这项工作利用了上下文敏
感解码（CSD）[68]，这是最近提出的对英特尔（和其他人）微操作转换机制的
扩展，可实现按需和上下文敏感自定义动态微操作指令流。此外，这项工作还利
用了CSD提供的重新配置框架，允许操作系统和其他可信实体动态控制通过外科
手术插入微操作流的投机栅栏的频率，类型和行为，以确保投机安全执行。

这项工作分析了一套显着扩展的围栏，考虑了不同的可能的执法阶段和执法策略。
特别是，我们引入了一个新的栅栏，可以防止推测性更新缓存状态，同时在指令
的动态调度中具有最小的干扰。

上下文敏感的防护能够通过基于解码器级信息流跟踪的新型检测机制自动识别网
格插入点。由于处理器前端通常以比管线的其余部分高得多的速率搅拌指令，因
此解码器级的信息流跟踪易于频繁过度和不确定的情况。虽然由于栅栏插入的频
率增加而导致过度拉伸可能会损害性能，但是对于短暂的窗口来说，不确定会破
坏系统的安全性。通过早期低开销的错误检测和恢复机制解决这些挑战，本文进
一步确定了解码器级信息流跟踪作为一种有效的攻击检测机制的可行性。

这项工作进一步提出了新的微操作流程，保护分支预测器和返回地址堆栈，防止
跨不同保护域的错误训练和/或逆向工程。虽然与英特尔提出的间接分支预测器
屏障（IBPB）指令的精神相似，但这些微操作流程更广泛地应用于更广泛的分支
预测器和返回地址堆栈，并提供更细粒度的控制。

\Todo: 更详细地介绍 CSF

Context-Sensitive Fencing(CSF)\supercite{context-sensitive-fencing} 是
一种微码级防御多种类型 Spectre 攻击的方法。它基于 Context-Sensitive
Decoding (CSD)\supercite{context-sensitive-decoding}，一种微码翻译机制
的扩展，用于动态按需自定义微操作指令流。CSF 利用 CSD，在微指令流中注入
fence 等微码，阻止不安全指令的推测式执行。为了降低 fence 的性能开销，
CSF 还提出了3种新的用于防御 Spectre 的 fence.

CSF 由以下几个关键部件组成：
\begin{itemize}
\item 微码自定义机制 CSD：使处理器精确地在指令流中插入 fence，减轻推测
  式执行中不期望的副作用
\item 译码级信息流追踪（DLIFT）框架：用于检测潜在不安全的执行模式，触
  发微码自定义机制
\item 错误训练防御：用于保护分支预测器、返回地址栈等部件
\end{itemize}

CSF 防御 Spectre 的开销在 8\% 以下。

\subsubsection{Conditional Speculation}

Conditional Speculation \supercite{conditional-speculation} 是一种检测
推测式执行中的潜在的泄露数据的访存指令，并阻止其执行的方法。为了检测可
能在推测式执行中泄露数据的指令，此工作提出安全依赖（security
dependence）的概念，类似于数据依赖和控制依赖，这种新的依赖性用于描述泄
漏微架构信息具有潜在安全风险的推测性执行的指令。对于信道 $c$，指令 $j$
安全依赖于指令 $i$，如果：

\begin{itemize}
\item 在程序序列中，指令 $j$ 在 $i$ 之前
\item $j$ 在 $i$ 之前推测式地执行，并且 $j$ 会通过信道 $c$ 泄露数据
\end{itemize}

因为信息可以从多种信道上泄露，所以安全依赖的定义指定了某种信道。针对缓
存信道，如果指令 $j$ 不修改缓存的内容，则它对于缓存信道不具有安全依赖，
尽管它可能会泄露某些信息。

根据以上定义，表\ref{tab:secdep}总结了 Spectre 攻击中的主要的安全依赖。
Spectre 攻击的安全依赖性来自两种情况，访存-访存推测和分支-访存推测。

\begin{table}
\begin{tabular}{|c|c|c|}
\hline 
Spectre 变体 & 指令 $i$ & 指令 $j$\tabularnewline
\hline 
\hline 
Spectre v1 & 条件分支 & 访存\tabularnewline
\hline 
Spectre v2 & 间接转移 & 访存\tabularnewline
\hline 
Spectre v4 & 访存 & 访存\tabularnewline
\hline 
SpectrePrime & 条件分支 & 访存\tabularnewline
\hline 
\end{tabular}
\caption{Spectre 攻击中的安全依赖}
\label{tab:secdep}
\end{table}

在条件推测式执行的流水线中，在发射队列中引入了安全冒险检测，以识别具有
安全依赖的可疑的不安全指令。只要在执行阶段确认了真正的冒险，那些不安全
的推测式执行将会使用现有的重执行和推测式执行恢复机制终止并丢弃。

% the following is from gtran

\begin{figure}[htbp]
  \centering
  \includegraphics[width=0.8\textwidth]{conditional-spec.eps}
  \caption{安全冒险的检测\supercite{conditional-speculation}}
  \label{fig:cond-spec}
\end{figure}

安全冒险的检测逻辑用一个矩阵实现。处理器中通常用矩阵跟踪数据依赖，而在
安全检测中，处理器使用安全依赖矩阵确定要发射的指令是否存在安全相关。添
加了安全依赖矩阵的发射阶段如图\ref{fig:cond-spec}所示。

安全依赖矩阵是一个 NxN 比特的数组，其中 N 为发射队列大小，用发射队列的
位置索引。对于指令队列中位置为 X, Y 的指令，如果该矩阵 Matrix 的位
置 $Matrix[X,Y]$ 为 1，则 X 安全依赖于 Y.

当新的指令分发至发射队列时，处理器在发射队列的位置 X 处分配一个条目，
此时对发射队列中所有有效的指令 Y，计算 $Matrix[X,Y]$：

$Matrix[X,Y] = (IQ[X].opcode == Memory) \\
             \phantom{=}\& (IQ[Y].opcode == Memory || IQ[Y].opcode == Branch) \\
             \phantom{=}\& IQ[Y].valid \\
             \phantom{=}\& !IQ[Y].issued$

这个公式的含义如下：X 在进入发射队列之前 Y 有效，则意味着 Y 在 X 之前；
对于多种 Spectre 变体，只需要检查访存指令是否依赖于先前的分支或访存指
令；如果在访存指令发出时，此前的分支或访存仍在等待，则认为新的访存指令
存在安全相关。

在实现中，图\ref{fig:cond-spec}展示了发射队列的三个阶段。第一阶段中，数
据依赖矩阵生成一个依赖向量；第二阶段中，该向量发送至年龄矩阵，用于选择
最老的可发射的指令；在第三阶段，处理器将被选择要发射的指令放进安全依赖
矩阵，查询它们的安全相关性。当一个将要发射的指令存在安全冒险时，它会被
标上可疑推测式执行(\emph{suspect speculation})标记。

在指令 X 发射后，更新向量寄存器中的对应位设为0，在下一周期，依赖矩阵中
X 对应的一列将会重置，意味着待发射指令和 X 的之间的安全相关被清除。

一种简单的冒险消除方式是阻止发射队列中标记了可疑推测式执行指令的执行，
但是这种策略会导致性能下降。为了实现性能，安全性和透明性的平衡，条件推
测式执行提出了两种过滤机制，以找出错误识别的安全安全冒险。基于缓存命中
的冒险过滤器针对命中缓存的推测式执行指令。因为它们的推测式执行不会改变
缓存的内容，所以它们是安全的。另一个提出的过滤器，基于可信页面缓冲区
（Trusted Page Buffer, TPBuf）的冒险过滤器，从另一个角度识别安全的推测
推测式执行的指令。对于使用基于共享内存（例如 Flush+Reload）的缓存侧通道
攻击窃取内存信息的 Spectre 变体，他们对恶意组件的推测式执行具有名
为 S-Pattern 的共同特征。 TPBuf 从所有推测式执行中捕获 S-Pattern，对于
任何推测式执行的访存指令，如果它与 S-Pattern 不匹配，则被认为是安全的。

% hit-based

考虑到大多数程序有良好的时间局部性和空间局部性，一级缓存命中的访存请求
不会修改缓存的内容，因此可以使用基于缓存命中的冒险过滤器，以减少阻止可
疑推测式执行的指令造成的性能损失。只要访存指令的访问命中一级缓存，则该
指令正常执行，但是如果缓存缺失，则该指令将会重新回到发射队列，直到安全
依赖得到解决，才可疑重新发射。这个过滤器只需要对一级缓存的控制逻辑做少
量修改，即不处理标记为可疑推测式执行的访存请求。

% TPBuf
如果一个程序的一级缓存命中率低，则基于缓存命中的冒险过滤器仍然会阻止大
量的推测式执行。对于利用共享内存构造侧信道的 Spectre 攻击方式，并非所有
的一级缓存缺失都可作为隐蔽信道使用。因此可以使用基于可信页面缓冲区的冒
险过滤器，检测这些指令中安全的指令。在这些攻击中，Spectre 组件的执行中
包含两个特殊的访存指令，记为 A 和 B，它们有如下的行为：

\begin{enumerate}
\item A 推测式地访问敏感数据，B 推测式地访问攻击者和受害者共享的内存区
  域，用于构造受害者和攻击者之间的侧信道。通常秘密数据的内存区域和用于
  构造侧信道的共享内存区域在不同的内存页，因此它们访问不同的页。
\item 为了构造缓存侧信道，攻击者需要清除共享内存对应的缓存行，其后处理
  器推测式执行中访问 B 造成缓存缺失，重新将缓存行载入一级缓存，攻击者
  再探测共享内存的缓存行发现这一变化。因此要通过缓存侧信道泄露信息，需
  要 B 在缓存中不命中。
\item 指令 B 依赖于指令 A，A的结果用于计算共享内存区域的索引。这种设计
  也是攻击者推断处秘密数据要点。
\end{enumerate}

在 TPBuf 过滤器中，以上行为模式称为 S-pattern. 如果观察到推测式执行的
指令序列具有以下特征，则认为它具有 S-pattern:

\begin{enumerate}
\item 至少有两个指令 A 和 B 访问不同的内存页
\item 指令 B 导致一级缓存缺失
\item B 依赖于 A
\item A 和 B 之间可能有多个指令
\end{enumerate}

TPBuf 它记录了所有推测式的存储器访问请求，当一个新的请求在一级缓存中缺
失时，其页地址将会和 TPBuf 中其他访问的页地址比较，如果有至少一个访问访
问了不同的页，并且正在写回阶段，则认为新的请求不安全。

\begin{figure}[htbp]
  \centering
  \includegraphics[width=0.8\textwidth]{tpbuf.eps}
  \caption{TPBuf 的结构\supercite{conditional-speculation}}
  \label{fig:tpbuf}
\end{figure}


TPBuf 的结构如图\ref{fig:tpbuf}所示。TPBuf 的条目和 LSQ 的条目一一对应，
它的条目分配、提交等操作和 LSQ 一起进行。TPBuf 包含了所有进行中的推测式
的访存指令。为了避免攻击者访问未授权的数据，并将其传播到自己的地址空间，
需要先用 TLB 获得访问的物理地址，将其写入 TPBuf 中该访问对应的一
项。TPBuf 检测 S-pattern，并将结果再传至缓存命中过滤器，决定是否阻止一
个可疑推测式执行的请求。

% 分配：当在LSQ中分配存储器访问指令时，它们也在TPBuf中分配并且A位被置位。并且根据TPBuf中的A位生成掩码。
% 它指示TPBuf中的哪些内存指令比程序顺序中的新条目旧。

% 更新：使用存储器指令附带的可疑推测标志更新S位。当PPN记录在TPBuf中时，V位置位。当存储器指令获取的数据可供其他指令使用时，W置位。

% 检测：当传入请求进入TPbuf时，TPBuf将其PPN与现有条目的PPN进行比较，然后生成地址匹配向量（匹配）。这些矢量，包括Match，V，W和S，用作等式1逻辑的输入，以确定请求是否安全。特别地，“|”表示减少OR，它对向量中的所有位进行OR运算以生成1位输出。
%%%%%%%

在 gem5 模拟器中用 SPEC CPU2006 进行评测，使用基于缓存命中的过滤器时，
平均性能开销为 12.8\%，再加上 TPBuf 过滤器，平均性能开销可以再降低至
为 6.8\%.

\subsubsection{SpectreGuard}

SpectreGuard\supercite{spectreguard} 是一种以数据为中心的软硬件结合
的Spectre 防御方案。这种方案考虑到对于软件开发人员来说，识别程序中的秘
密数据（如密钥）比识别可能受到攻击的 Spectre 组件代码容易。因
此，SpectreGuard 在操作系统中提供接口，使程序可以标记敏感的内存区域，硬
件上只需要简单的微架构扩展，就可以阻止 Spectre 攻击。和已有的防御方案相
比，它不需要依靠编译器识别程序中可能受攻击的代码，也不需要复杂的硬件修
改。

Spectre 攻击需要分为三个步骤：从存储系统装载秘密数据，将秘密数据转发至
依赖于这个数据的指令，执行依赖于这个数据的指令，通过微架构隐蔽信道泄露
秘密。数据的泄露在第二、三步进行，如果秘密数据被装载，但不被转发，秘密
也不会泄露给攻击者。

因此，SpectreGuard 允许第一步发生，但将第二步延迟至装载秘密数据的指令
之前所有分支均已解决。并且，在经过软件标记之后，第一步中仅当数据在敏感
内存区域时，才需要延迟转发。当秘密数据不经常访问时，总体开销可以忽略。

敏感数据的标记通过在页表中添加一个位实现，在软件中可以扩展操作系统
的mmap, mprotect 等接口，或通过运行库、编译器、连接器实现。在微架构中，
指令执行后，执行结果将转发至后续依赖于这个指令的指令，而如果指令是访问
了敏感区域的指令，则推迟它的转发。

在 gem5 模拟器中用 SPEC CPU2006 对几种标记方案进行评测，方案 SG(All) 标
记内存的所有页，平均性能开销为 20\%, SG(Heap) 只标记堆区域，性能开销减
少至8\%. 在使用 OpenSSL 的合成基准测试中，如果只标记私钥为秘密数据，则
性能可以接近原处理器的性能。

% \begin{comment}
% 
%   MI6: Secure Enclave in a Speculative Out-of-Order processor
% 
%   abs: 一个考虑了Spectre等攻击设计的enclave方案
% 
% 
%   Data Oblivious ISA(OISA)是一种 ISA 扩展，用于阻止信息通过侧信道泄露。
%   设计原则：
%   \begin{itemize}
%   \item ISA的安全性和微架构无关
%   \item ISA不会阻止现代微处理器的优化技术
%   \end{itemize}
% 
%   OISA有以下组件：
%   \begin{itemize}
%   \item 动态跟踪敏感数据：使用DIFT跟踪程序运行时私密数据在处理器中的传
%     播。所有数据都有 confidential/public 标记，标签在读取操作数时必须
%     解析
%   \item 指令定义操作数可以接受 public 还是 public/confidential 数据
%   \end{itemize}
% \end{comment}
% 
% % vim:ts=4:sw=4
