% 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} 技术,它的作用是在指令 流中添加数据相关,使得装载指令使用的地址依赖于分支结果。 retpoline\supercite{retpoline} 是 Google 提出的防御 Spectre-BTB 的方法。 它的作用是把程序中的间接转移指令修改为一个指令序列,最终使用 ret 指令 完成跳转,从而使用 RSB 而不是 BTB 来进行间接转移的转移预测。 % index masking Webkit 在数组访问中使用 index masking\supercite{webkit} 方法,它让数组 下标和一个值进行与操作,去掉下标高位的1,将数组下标控制在一定范围内, 防止处理器在推测式执行中访问数组指定范围之外的数据。Linux 构造了一个粒 度更细的 array\_index\_nospec 宏\supercite{linux-spec},使得在推测式执 行的过程中,数组的索引始终在界内,避免了推测式的访问秘密数据。 % poison value Webkit 还使用了指针投毒(pointer poisoning)的方式,它考虑分支用于做类 型检查的情形。对于不同类型的数据,Webkit 将这种类型的指针和一个类型对 应的值异或,要使用的时候再异或这个值得到指向数据的指针,如果类型不匹配, 则得到的指针会指向一个程序无法访问的内存区域。通过这种方法,程序在推测 式执行中无法通过这种指针访问到程序中的数据,从而避免了秘密数据的泄露。 % site isolation, FIXME: needs citing Google 为 Chrome 浏览器使用了站点隔离(site isolation)技术,使得浏览 器用不同的进程访问不同的网站,从而攻击者无法通过用 Javascript 通过侧信 道攻击获取另一站点相关的数据。 % timer reduction 降低计时器精度可以降低计时攻击所用侧信道的精度,一个例子是在浏览器中降 低 Javascript 引擎中 performance.now 等计时器的精度。 \supercite{webkit} 但是相关研究发现,攻击者可以使用其他方式构造高精度 的计时器\supercite{js-timer},因此降低计时器精度不是一个有效的防御方法。 \subsection{硬件防御方案} \subsubsection{SafeSpec} SafeSpec\supercite{safespec} 提出了一种设计准则:使用临时结构保存推测式执行产 生的状态,而不影响处理器的主要微架构状态。在实现中,SafeSpec 为缓存和 TLB 添加了影子结构,推测式执行的指令对缓存和 TLB 修改临时写入至相应的 影子结构,直到此前分支正确或指令提交时,再将影子结构的数据更新至主结构。 \subsubsection{InvisiSpec} %%%%%% gtran %%%%%% 在本文中,我们提出了InvisiSpec,这是一种通过在数据缓存层次结构中使推测不可见来抵御多处理器中的硬件推测攻击的新策略。目标是通过多处理器数据高速缓存层次结构来阻止微架构隐蔽和侧通道,这是由于推测性负载 - 例如,源自高速缓存集占用,线路替换信息和高速缓存一致性状态的信道。我们希望不仅防止基于分支机构推测的类似幽灵的攻击;我们还希望防止任何投机负载可能构成威胁的未来攻击。 InvisiSpec的微架构基于两种机制。首先,不安全的推测性加载将数据读入新的推测缓冲区(SB)而不是缓存中,而不修改缓存层次结构。 SB中的数据不会观察缓存一致性事务,这可能导致丢失内存一致性违规。我们的第二种机制解决了这个问当推测性负载最终是安全的时,InvisiSpec硬件通过将其重新发送到内存系统并将数据加载到高速缓存中使其对系统的其余部分可见。在此过程中,如果InvisiSpec认为负载可能违反了内存一致性,InvisiSpec会验证负载首次读取的数据是否正确 - 如果数据不正确则压缩负载。 % memory consistency 内存一致性模型(或内存模型)指定内核执行内存操作的顺序,并由共享内存系统中的其他内核观察[10]。当商店退休时,其数据将存入写缓冲区。从那里,当内存一致性模型允许时,数据被合并到缓存中。我们说当数据合并到缓存中时执行存储,并且所有其他核心都可以观察到存储。负载可以在到达ROB头之前从内存中读取。我们说负载是在接收数据时执行的。负载可以从存储器读取并且不按顺序执行 - 即,在ROB中的早期加载和存储之前。无序负载可能导致内存一致性违规,核心通过推测执行的压缩和回滚机制恢复[11]。细节取决于内存模型;我们将在下面描述两种常见模型,我们将其假设为添加InvisiSpec的基线。 总存储顺序(TSO)[12],[13]是x86架构的内存模型。 TSO禁止所有可观察的加载和存储重新排序,但存储→加载重新排序,即当负载绕过较早的存储到不同的地址时。实现通过确保负载在执行时读取的值在负载退出时保持有效,从而阻止可观察负载→负载重新排序。如果核心接收到负载读取的线路的高速缓存无效请求(或遭受高速缓存驱逐),则通过压缩已执行但尚未退出的负载来维持此保证。通过使用FIFO写入缓冲区来防止存储→存储重新排序,确保存储按程序顺序执行。如果需要,可以通过使用fence指令分隔存储和加载来防止存储→加载重新排序,该指令在完成所有先前的访问之前不会完成。原子指令具有栅栏语义。 释放一致性(RC)[14]允许任何重新排序,除了跨同步指令。装载和存储不得通过先前的获取或随后的发布进行重新排序。因此,仅当存在先前的非退休获取并且具有非FIFO写缓冲区时,RC实现压缩在接收到其高速缓存行的无效时执行加载。 \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} 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的验证或公开事务将从缓存层次结构中的任何位置获取该行的最新副本。 %%%%%%%%%%%%%%%%%%% InvisiSpec\supercite{invisispec} 和 SafeSpec 类似,使用一个称为推测式 执行缓冲区(Speculative Buffer)的结构存放推测式执行的 load 指令从存储 系统中获取的数据,直到这条指令安全的时候再让它更新缓存。和 SafeSpec 不 同的是,InvisiSpec 考虑了缓存一致性和存储一致性的问题,在 load 指令确 认安全之后,增加一个验证的步骤,确认使用没有违反存储一致性。 在 gem5 模拟器下, 使用 SPEC CPU2006 和 PARSEC 对 InvisiSpec 进行评测, 以 Spectre 为威胁模型,在 TSO 内存模型下,InvisiSpec 平均性能损失为 21\%,而使用 fence 性能下降 74\%. \subsubsection{DAWG} Dynamic Allocated Way Guard(DAWG)\supercite{dawg} 是一种防御缓存侧信道 攻击的缓存设计。DAWG让不同的程序使用不同的安全域,一个安全域中的程序只 能使用缓存中的某些路,和 Intel CAT 允许程序在缓存任意一路命中不同, DAWG 不允许程序在安全域规定的路之外中发生缓存命中。 在 zsim 模拟器中使用 SPEC CPU2006, PARSEC, GAPBS 进行模拟,相对于 Intel CAT,在不同的评测程序和划分方式下,大多数程序性能下降为 4\%\~7\%. DAWG 的主要缺点是需要操作系统的支持。此外,DAWG 不能防御 NetSpectre 等可 在同一安全域内进行的攻击。 \subsubsection{Context-Sensitive Fencing} 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{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} 在条件推测式执行的流水线中,在发射队列中引入了安全冒险检测,以识别具有 安全依赖的可疑的不安全指令。只要在执行阶段确认了真正的冒险,那些不安全 的推测式执行将会使用现有的重执行和推测式执行恢复机制终止并丢弃。 % the following is from gtran 我们设计了基于比特矩阵的安全检测逻辑,如图2所示。比特矩阵是一些商品处理器用来跟踪数据依赖和年龄信息的流行方式[26],[27],[28]。通常,数据依赖矩阵和年龄矩阵一起可以确定要发布的指令。对于安全检测模块,安全依赖矩阵还必须确定要发布的指令是否具有任何安全依赖性。 矩阵组织:安全依赖矩阵需要有效地支持行和列访问。假设问题队列具有N个项,则安全依赖性矩阵将包含NxN位的寄存器阵列。它由IQPos(问题队列位置)编制索引。此矩阵的读端口数等于调度宽度,写端口数等于发布宽度。给定任何指令X,IQPos X表示其在问题队列中的位置。如果Matrix [IQPos X,IQPos Y]的值为1,则X对Y具有安全依赖性。否则,这意味着它们之间没有安全依赖性。矩阵初始化:当新指令X被分派到问题队列中时,一个条目被分配索引IQPos X.对于此时在问题队列中有效的每个指令Y,计算Matrix [IQPos X,IQPos Y]根据以下公式。 Matrix[X, Y ] = (IssueQ[X].opcode == MEMORY) and (IssueQ[Y ].opcode == MEMORY or BRANCH) and IssueQ[Y ].valid and \!IssueQ[Y].issued 此公式基于以下逻辑来确定指令之间的安全依赖性。首先,如果在X被分派到问题队列之前Y是有效的,则意味着Y在X之前。其次,对于Specter变体,我们仅检查存储器指令是否依赖于先前的分支或存储器指令。第三,如果在发出存储器指令时,先前的分支或存储器指令仍然在问题队列中等待,则该存储器指令将被认为具有安全依赖性。 危险检测:图2说明了问题队列的三个阶段选项。在第一阶段,数据依赖矩阵生成依赖向量。在第二阶段,然后将该向量发送到年龄矩阵以选择要发布的最早的就绪指令。在第3阶段,对于选择发布的那些指令,查询安全依赖矩阵以获得它们的安全依赖性,然后更新问题队列的相应条目的状态。特别是,安全依赖矩阵的每一行中的位由OR运算处理,结果表明是否存在潜在的安全隐患。当选择发出一条指令并检测到安全危险时,它将被标记为可疑推测标记。 依赖性清除:在发出一条指令X后,更新向量寄存器中的相应位将被设置为0.由IQPos X索引的安全依赖矩阵列将在下一个周期复位。这种操作意味着清除了相应指令和X之间的安全依赖性。 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 为了性能,安全性和透明性的平衡,条件推测式执行提出了两种过滤机制,以找 出错误识别的安全安全冒险。基于缓存命中的冒险过滤器针对命中缓存的推测式 执行指令。因为它们的推测式执行不会改变缓存的内容,所以它们是安全的。 另一个提出的过滤器,基于可信页面缓冲区(Trusted Page Buffer, TPBuf)的 冒险过滤器,从另一个角度识别安全的推测推测式执行的指令。对于使用基于共 享内存(例如 Flush+Reload)的缓存侧通道攻击窃取内存信息的 Spectre 变体, 他们对恶意组件的推测式执行具有名为 S-Pattern 的共同特征。 TPBuf 从所有 推测式执行中捕获 S-Pattern,对于任何推测式执行的访存指令,如果它与 S-Pattern 不匹配,则被认为是安全的。 % gtran 一种简单的危险消除方法是简单地阻止在发布队列中标记有可疑推测标记的所有指令。这样的政策显然会导致性能下降。同时,只有导致高速缓存未命中的内存请求将改变高速缓存内容,并且大多数应用程序表现出良好的时间和空间局部性。受这两个观察的启发,基于缓存命中的危险过滤器被提议用于减少保守地不执行具有可疑推测标记的指令的性能影响。 % hit-based 对于标记有可疑推测标志的存储器指令,它将被推测性地发布到存储器访问管线。如果推测性存储器访问命中L1高速缓存,则其执行将继续作为正常存储器指令。但是,如果遇到L1高速缓存中的未命中,则丢弃的请求将被丢弃。信号从L1高速缓存发送回问题队列,重新发出逻辑应该应用于存储器指令,直到其安全依赖性得到解决。这些被阻止的指令在发布队列中等待安全依赖性在重新发布之前清除。此设计仅需要L1高速缓存控制逻辑中的最小更改:将不会处理具有可疑推测标记的未命中请求。 % TPBuf 基于缓存命中的危险过滤器仅考虑在L1 DCache中命中的推测性内存指令是安全的,并允许它们被推测性地执行。对于具有高L1 DCache未命中率的应用程序,它将无法恢复投机执行的大部分好处。对于这些应用,我们提出了一种新的危险过滤器可信页面缓冲器(TPBuf)来检测L1 DCache中丢失的指令的安全推测。 TPBuf基于以下观察:并非所有推测性缓存未命中都可用于构建推测性侧通道。基于第3节中定义的威胁模型,我们关注使用共享内存(例如,Flush + Reload)范例和非法访问内存页面信息的Spectre变体。如图3所示,恶意小工具的推测性执行在我们的目标Spectre攻击中被用作通用内存访问模式。特别是,观察到恶意推测执行流程总是包含两个特殊的存储器指令(A和B)。这两条指令具有以下用法和行为。 1)A用于推测性地访问敏感数据。 B推测访问攻击者共享的内存区域,用于构建受害者和攻击者之间的缓存侧通道。由于用于侧信道的秘密数据和存储区域通常位于不同的存储页面,因此这两条指令访问不同的页面。 2)为了构建缓存侧通道,攻击者需要首先刷新特定的共享内存数据。然后,B的诱导推测执行具有高速缓存未命中,因此将高速缓存行重新加载到L1 DCache中。攻击者可以通过缓存侧信道感知状态信息的这种变化。因此,B的高速缓存未命中对于通过高速缓存侧信道泄漏敏感信息是必要的。 3)B是数据依赖于A.A的结果用于计算共享存储区域的索引。这种精心设计的依赖性也是攻击者推断秘密价值的另一个重要点。 在上述观察的推动下,我们将上述共同特征行为称为S模式。具体地,如果观察到推测执行的指令序列具有以下特征,则我们认为该推测指令序列具有S模式行为。 1)至少有两个指令(A和B)分别访问不同的存储页面。 2)指令B导致L1 DCache未命中。 3)指令B具有数据依赖于指令A. 4)A和B之间可能有多个指令(计算,存储器或其他类型的指令)。 尽管Spectre攻击的恶意小工具被称为S-Pattern行为,但应该注意的是,具有S-Pattern的指令流不一定是幽灵攻击。出于安全原因,我们试图在微体系结构层面上防止形成具有S-Pattern的推测性指令流。在确保安全性的同时,这种机制自然也会导致性能损失。在第6节中,我们详细评估了该策略的性能,并分析了正常程序(如SPEC CPU 2006)中S-Pattern的比例。 TPBuf旨在通过所有推测执行的S-Pattern捕获内存访问行为。 它记录所有动态推测存储器访问请求并跟踪它们的执行状态(例如,是否重新填充所请求的高速缓存行)。 当一个错过L1 DCache的新内存请求时,TPBuf会将其页面地址与其历史记录进行比较。 并且它根据表II中描述的逻辑决定这种新的推测指令是否安全。 TPBuf的微体系结构如图4所示。一个主要的设计原则是尽可能利用现有逻辑来降低实现的复杂性,例如避免TPBuf成为核心管道内的新时序关键路径。 TPBuf靠近加载存储队列(LSQ),其条目与LSQ的条目具有1:1映射。 TPBuf条目的分配,提交和压缩与LSQ的Head和Tail指针的移动一起操作。此外,TPBuf涵盖了推测执行窗口中的所有动态推测存储器指令。为了防止攻击者直接推测性地访问未授权数据然后将数据传播到他自己的存储空间,必须首先检查访问地址并使用TLB获取物理页码(PPN)。 TPBuf记录并使用PPN作为每个条目的标记。此外,每个TPBuf条目都存储一个掩码和许多状态位。 TPBuf检测S模式并将结果传递给Cache-hit过滤器,该过滤器决定是否应该阻止可疑的推测性未命中请求。这样,原始内存一致性模型和缓存一致性不受影响。 TPBuf的查找如图4所示。 分配:当在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 进行评测,平均性能开销为 6.8\%. \subsubsection{SpectreGuard} %%% gtran %%% 自Spectre披露以来,已经提出了几种基于软件和硬件的缓解策略。基于软件的缓解策略通过手动插入序列化指令[12]或在条件跳转和后续存储器加载指令之间引入其他数据依赖性[7,21]来防止推测。但是,手动识别程序的易受攻击的分支是困难的,而通过编译器自动化保护所有分支会导致过多的性能开销[21]。基于硬件的方法通常通过引入额外的硬件结构来缓冲推测结果来集中隐藏攻击者可观察的微架构状态变化[13,27]。虽然它们不需要手动更改代码,但它们会导致侵入式硬件更改和高性能开销[13,27]。 在本文中,我们提出了SpectreGuard,一种针对幽灵攻击的新型跨层防御。我们的方法是以数据为中心的,因为我们专注于程序的数据而不是代码。我们观察到,对于软件开发人员来说,识别程序的秘密数据(例如,密钥)可能比识别易受攻击的代码块(即,幽灵小工具)更容易,这些代码块可以出现在程序的任何分支中,即使它们不相关处理秘密数据。因此,我们的方法首先要识别敏感的内存块,这些内存块保存秘密数据,并将它们标记为非推测性内存区域。识别的存储区域通过简单的OS /库API被通知给OS,然后由硬件利用以通过低成本的微架构扩展选择性地和有效地防止推测性攻击。 我们的微架构扩展很小,并且基于一个基本观察,即成功的Specter变体1攻击需要以下三个不同的步骤推测性地发生:(步骤1)秘密数据从存储器层次加载; (步骤2)然后将其转发给相关指令; (步骤3)执行从属指令,通过微架构隐蔽信道(例如,高速缓存)泄漏秘密。重要的是要注意秘密是通过第二和第三步骤泄露的,在此期间留下了攻击者可观察的,秘密依赖的,微观建筑的足迹。换句话说,即使秘密数据被加载到CPU管道上,除非它被转发到秘密相关指令,否则秘密不会泄露给攻击者。 基于这种观察,我们的方法允许第一步发生但延迟第二步直到所有先前的分支被解决之后。请注意,仅当第一步中的虚拟地址在非推测内存区域内时才需要此延迟。所有其他“正常”地址可以立即转发到任何等待的相关指令。因此,不经常访问的秘密将在整体性能上具有可忽略的开销。 %%%%%%%%%%%%%% SpectreGuard\supercite{spectreguard} 是一种软硬件结合防御 Spectre 攻击 的方法。软件在页表中标记一个页是否存在秘密数据,如果一条指令在推测式执 行中读取了带标记的页中的数据,处理器则禁止这条指令将读取的值转发至其他 指令,直到这条指令之前的所有预测都已验证,确认推测式执行正确。 在 gem5 模拟器中用 SPEC CPU2006 评测,如果标记内存的所有页,则 SpectreGuard 的平均性能开销为 20\%, 而如果标记堆区域,则性能开销减少至 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