ARM处理器分支预测漏洞分析测评及新漏洞发现
2021-07-13王春露田瑞冬赵旭吕勇强汪东升
王春露,田瑞冬,赵旭,吕勇强,汪东升
(1.北京邮电大学可信分布式计算与服务教育部重点实验室,100876,北京;2.北京信息科技大学计算机学院,100192,北京;3.清华大学北京信息科学与技术国家研究中心,100084,北京;4.清华大学计算机科学与技术系,100084,北京)
传统高性能处理器通过指令流水线技术来提高指令执行并行度,从而提高处理器性能。但是,程序指令之间会存在数据、控制和资源相关性,这些相关性会造成流水线阻塞[1]。为此,现代高性能处理器采用了多种微架构优化技术来降低相关性对指令流水线的影响。然而,最新的研究表明,由于当前的处理器架构始终以性能优先作为导向,未对性能优化机制进行周密的安全性分析,导致当前处理器系统存在严重的安全漏洞,可能造成计算机机密信息的泄露,危害性极大[2-4]。
分支预测技术作为最基本的优化技术,广泛应用于Intel、AMD和ARM等主流的商业处理器中。在分支预测机制中,存在一些指令被提前执行,但最终被废弃,无法提交。虽然这些指令的执行不会改变体系结构的状态,不影响程序的正确执行,但这些被废弃的指令可能会在微体系结构中留下痕迹,从而产生被攻击的风险,此类漏洞被称为分支预测漏洞[5]。Kocher等在2018年发表的Spectre是利用分支预测漏洞攻击的典型代表[2]。自从此后,学术界对Spectre及其变种进行了大量研究,先后提出了SpectreRSB[3-4]、BranchScope[6]、NetSpectre[7]和SGXpectre[8-9]等攻击变种,对其威胁程度和相关防御手段也进行了充分讨论,但目前的研究主要集中在x86架构上。与x86架构芯片主要服务于桌面和服务器市场不同,ARM架构芯片主要集中在移动端和嵌入式领域,更追求低功耗,分支预测器的设计更为保守。因此,专门针对ARM架构处理器的分支预测漏洞研究具有重要意义。
本文首先总结了目前已发现的分支预测漏洞,在此基础上提出了一种适用于所有分支预测漏洞的攻击模型。其次,根据分支预测漏洞攻击过程中利用的微体系结构和针对的地址空间,将分支预测漏洞分为了9类。通过实验探究了9种漏洞对ARM处理器的影响。实验结果表明,部分低功耗的ARM处理器完全不受分支预测漏洞影响。性能较高的ARM处理器只受部分漏洞影响。在防御方面,现有的防御手段只能单独防御某一类分支预测漏洞,想要达到良好的防御效果需要多种防御方法的组合。除此之外,本文还发现了一种新的ARM预测执行漏洞——顺序预测漏洞。
1 分支预测漏洞
1.1 分支预测漏洞原理
当指令流在执行过程中遇到分支指令时,可能需要等待目的地址被解析之后才能确定下一步需要执行的指令。此时若使流水线停顿,等待目的地址解析完成再恢复流水线,会严重影响性能。为了提升性能,处理器会预测下一步需要执行的指令并提前执行[1]。预测执行的结果不会被提交而是暂时保存,当目的地址解析完成后,若预测正确,就提交执行结果。但是,如果预测不正确,处理器会丢弃预测指令的结果并使流水线恢复到预测执行前的状态。
现代处理器中设置了专门负责分支预测的微体系结构——分支预测器[10],分支预测器通过记录分支指令的历史执行信息,对分支指令的跳转方向和跳转地址进行预测。分支预测器如图1所示。现代分支预测器通常由一个二级自适应分支预测组件[11]和一个目标分支地址缓存区[12](BTB)组成。全局历史记录寄存器(GHR)记录了分支指令的历史跳转信息。每个GHR的数据可以和历史模式表(PHT)的一个条目相关联,PHT根据GHR的数据做出是否跳转的判断。BTB记录了分支指令的地址和跳转地址的映射。如果PHT预测将跳转,则从BTB中根据分支指令的地址查找跳转地址。
图1 分支预测器
现代处理器中通常使用返回堆栈缓冲区(RSB)来预测函数返回地址[4]。RSB是一种特殊微体系结构,如图2所示。在函数调用时(ARM中是BL指令),将跳转指令的下一条指令的地址作为预测地址储存在RSB。函数返回时(ARM中是RET指令),如果出现流水线阻塞,则将栈顶元素作为预测地址放入程序计数器。
图2 返回堆栈缓冲区
现代分支预测器的正确率很高,但仍无法保证100%的正确预测。当分支预测器预测失败时,为了保证最终结果的正确性,处理器会放弃分支预测指令的执行结果,使用者在体系结构层面无法感知到分支预测指令的执行。但是,分支预测指令可能仍会在微体系结构状态中留下痕迹,从而使得攻击者可以利用这些痕迹恢复分支预测指令的执行结果。这种设计上的漏洞导致了被攻击的可能。
1.2 分支预测漏洞攻击模型
分支预测漏洞攻击模型如图3所示,总结为6个步骤。
虚线表示此步骤是在预测执行时完成;实线表示此步骤是在正常的流水线执行时完成。
步骤1 毒化分支预测器。根据分支预测器的运行原理可知,分支预测器各部件储存的历史信息会随着程序的执行而变化。攻击者可以设计特殊的程序以修改分支预测器中的内容,从而控制分支预测的预测结果。这一恶意训练的过程被称为毒化。
步骤2 主动触发分支预测。在处理器需要读取数据时,首先会从高速缓存中读取,如果高速缓存中没有处理器需要的数据,处理器再从内存中寻找数据[13]。但是,从内存中读取数据要花费很多时钟周期,处理器不会停顿流水线等待数据读取完成,而是使用预测机制来继续执行。因此,可以通过将程序所需数据从高速缓存中驱逐来主动触发预测执行。
步骤3 执行攻击代码。触发分支预测后,处理器将分支预测器的预测结果放入程序计数器,但被毒化的分支预测器的预测结果是由攻击者控制,攻击者将预测结果指向特定的攻击代码。
步骤4 访问机密数据。分支预测的指令执行过程中不会进行权限检查,攻击代码可以直接访问那些受权限保护的地址。
步骤5 提取机密数据到高速缓存。通过执行攻击代码将机密数据提取到高速缓存。当分支预测失败时,虽然处理器会丢弃分支预测指令执行的结果,但是攻击代码的执行结果仍然在高速缓存中。
步骤6 复原机密数据。攻击者通常采用缓存侧信道的方式来获取分支预测在高速缓存中留存的信息[14-15]。缓存侧信道利用高速缓存的多级存储结构造成的数据访问延迟来推测高速缓存中的数据。常见的缓存侧信道攻击有Flush+Reload[16]、Evict+Reload[17]和Prime+Probe[18]等。
2 ARM处理器分支预测漏洞测评
2.1 分支预测漏洞分类
目前,曝光的分支预测漏洞攻击主要通过利用PHT、BTB和RSB这3个不同的分支预测组件[2,4,6]实现。
2.1.1 利用PHT实现分支预测攻击 图4是利用PHT的分支预测漏洞攻击代码。用合法的x(x 图4 利用PHT的分支预测漏洞攻击代码 2.1.2 利用BTB实现分支预测攻击 图5是利用BTB的分支预测漏洞攻击代码。执行func函数会跳转到funcA函数执行。重复执行一定次数后,BTB会建立func函数的虚拟地址A到funcA函数的虚拟地址B的映射。在受害者进程中,gadget函数和funcA函数的虚拟地址相同,两个进程中的func函数地址也相同。受害者进程执行func函数时本应跳转到funcB函数。但是,如果发生了流水线阻塞,分支预测器则会用func函数的虚拟地址A在BTB中索引到虚拟地址B,并将B作为分支预测的结果放入程序计数器,B地址指向gadget函数而不是funcB函数。 图5 利用BTB的分支预测漏洞攻击代码 2.1.3 利用RSB实现分支预测攻击 图6是利用RSB的分支预测漏洞攻击代码。攻击者进程中第6行代码的虚拟地址和受害者进程中gadget函数的虚拟地址相同,都为A。在攻击者进程中执行funcA函数时,RSB会将跳转指令的下一条指令的地址A入栈。在执行funcA函数时,sched函数触发进程切换,指令流跳转到受害者进程的sched函数开始执行。在受害者进程中,如果在return执行时发生了流水线阻塞,RSB会将之前攻击者进程填充的A作为分支预测的结果放入程序计数器,此时虚拟地址A指向gadget函数。 图6 利用RSB的分支预测漏洞攻击代码 以上3种分支预测漏洞攻击方法都可以针对同进程地址空间和内核地址空间开展攻击,除此之外,还可以针对跨进程地址空间。这是因为不同的进程可以拥有相同的虚拟地址,并且由于分支预测器是根据指令的虚拟地址来进行预测的,所以当攻击者进程恶意训练分支预测器时,也可以对其他进程的分支预测结果造成影响[10]。根据分支预测漏洞攻击利用的微体系结构和针对的目的地址的不同,将分支预测漏洞攻击分成了9类,如图7所示。 图7 分支预测漏洞攻击分类 依据图7所示的分类,本文分别为9种分支预测漏洞攻击方法编写了可运行在ARMv8架构上的攻击代码。选择了3种主流的ARMv8架构处理器作为实验平台:处理器A是一款常用的ARM低功耗处理器,其性能也较低;处理器B是一款高性能ARM处理器;处理器C是最新的高性能ARM处理器。代码运行环境为linux-4.15。评判攻击是否成功的标准是攻击者能否获得受害者存储在不同地址空间中的数据。 文献[19]的研究已经表明,9种攻击方法在主流的x86架构都可以攻击成功。但是,本文研究发现,这9种攻击方法在ARM处理器上有不同的表现。处理器A不受任何分支预测漏洞攻击的影响,因为这款处理器属于低功耗处理器,会适当减少复杂寄存器的使用,所以分支预测的功能有限。处理器B的测试结果如表1所示。可以看出,由于处理器B性能较高,其分支预测功能更加完善,更容易受到分支预测漏洞的影响,但仍有3种攻击无法在处理器B上起作用,原因可能是ARM处理器在进程切换和用户-内核态切换时会对分支预测器的内容做出调整。处理器C的测试结果如表2所示。可以看出,除了在处理器B无法攻击成功的3种方法外,利用BTB针对跨进程空间的攻击也无法成功,原因是这款处理器已经兼顾了安全性设计,集成了硬件防御措施。 表1 处理器B分支预测漏洞测评结果 表2 处理器C分支预测漏洞测评结果 目前,ARM处理器上的防御手段主要有指令屏障、无效缓冲区和内核页表隔离KPTI,本小节对这3种防御手段进行分析和测评。 3.1.1 指令屏障 分支预测漏洞最简单的防御方法就是在处理器读取机密信息时禁止分支预测。可以通过指令屏障来实现这一功能。指令屏障强制指令屏障之后的指令等待其之前的指令执行完毕再执行。在ARMv8架构中,可以使用DSB SY+ISB的指令组合和CSDB[20]来实现指令屏障。用户可以在重要的数据操作前插入指令屏障来防止分支预测指令对这些数据的访问。 3.1.2 无效微体系结构缓冲区 除指令屏障外,另一种可行的防御方法是破坏分支预测漏洞攻击中的毒化过程。通过消除攻击者对分支预测相关微体系结构的影响,避免分支预测器引导处理器执行攻击者准备的攻击代码,从而起到防御的作用。这种无效微体系结构的方法通常都插入在进程切换和内核-用户态切换的过程中。用户可以通过linux cmdline中的nospectre_v2参数使操作系统在进程切换时刷新微体系结构缓冲区。 3.1.3 内核页表隔离 内核页表隔离[21]是一种Linux内核功能,将用户空间页表和内核空间页表完全分开,在用户模式下使用的页表包含用户空间的副本和部分的内核空间,用户程序在执行时无法直接访问内核数据,但对于目标为窃取另一个进程用户空间数据的分支预测攻击,KPTI没有效果。用户可以通过linux cmdline中的kpti参数来开启/关闭KPTI。 针对本文中几种成功的攻击方式,在处理器B和处理器C上进行了防御措施的有效性测评。判断防御措施是否有效的依据是开启防御措施后攻击者能否获得受害者存储在不同地址空间中的数据,若无法得到预期的数据,则判断防御措施生效。本文实施防御措施的方法是:在受害者进程访问机密数据的代码段前插入指令屏障,并通过nospectre_v2和kpti两个cmdline参数开启无效缓冲区功能和KPTI,结果如表3和表4所示。 从表3和表4可以看出,指令屏障可以防御所有的分支预测攻击,但这需要事先对运行程序进行代码分析,实现难度较大。在实际的应用中,往往只在内核代码插入指令屏障。无效缓冲区一般在用户-内核态切换和进程切换时执行,因此只能防御针对内核空间和跨进程空间的攻击。KPTI只隔离了内核空间,因此只对针对内核空间的攻击有效。 表3 处理器B防御措施有效性测评 表4 处理器C防御措施有效性测评 从本小节可知,3种防御方法都无法独自防御所有的分支预测漏洞攻击。因此,为了实现有效防御,通常结合多种防御措施来搭建有效的防御体系。当3种防御方法全部打开时,3种ARM处理器都能够有效防御针对分支预测漏洞的攻击。 ARM处理器在执行RET指令时,会触发一种ARM处理器独有的预测执行——顺序预测。顺序预测同样有可能造成处理器数据泄露。 在ARM体系结构中,当指令流水线执行到RET指令时,如果函数返回地址不在高速缓存中,处理器就需要花费较长的时间从内存中提取数据,这会造成处理器流水线阻塞。此时,ARM处理器的顺序预测就会被触发,处理器会使用RET指令紧邻的下一条指令来预测执行。当处理器发现预测错误时,会回滚处理器中各个寄存器的状态,保证计算机结果的正确性,与分支预测相同,顺序预测同样会在处理器高速缓存中留下可观察的痕迹。 顺序预测原理如图8所示。函数F1在RET指令返回时,按照顺序预测的执行路径,会直接执行函数F2的指令,而不是跳转到函数main继续执行。 图8 顺序预测原理 顺序预测和RSB都依靠RET指令来作为触发指令,不同的是RSB依靠微体系结构缓冲区来决定预测执行的下一条指令,但目前还未发现顺序预测需要借助任何寄存器或缓冲区。 本文尚未在x86架构中发现顺序预测。这是由于x86注重于高性能,而ARM更追求低功耗。两者在预测执行上的设计会有不同。实际应用中,顺序预测大部分情况下都是失败的,本文猜测顺序预测是32位ARM架构遗留下来的机制,但由于ARM没有公开这方面的具体信息,很难考证。之后会进行进一步研究。 依据顺序预测的原理,本文实现了利用顺序预测的攻击方法。由于顺序预测的预测方向是固定的,攻击者无需毒化特定的微体系结构组件,其攻击方法比分支预测漏洞更加简单。总体而言,顺序预测攻击仍然符合本文提出的攻击模型。 本文编写的攻击代码如图9所示。顺序预测会执行RET指令紧邻的下一条指令,因此顺序预测的预测方向是固定的,只需设计特殊的代码布局,在RET指令之后插入本文的攻击代码。当speculation函数返回时,可以利用flush函数将函数返回地址从高速缓存中驱逐,触发预测执行,处理器会在接下来的预测执行中执行gadget函数。当程序执行完成后,处理器发现预测错误并回滚,但还是在高速缓存中留下了执行gadget函数的相关信息,本文就可以通过Flush+Reload将高速缓存中的信息取出,成功获取机密信息。 图9 顺序预测漏洞攻击代码示例 本文在处理器C上运行了漏洞利用代码,结果表明利用顺序预测的攻击可以成功泄露同一进程的用户空间的任意数据,但是由于顺序预测不借助任何中间微体系结构,因此顺序预测无法对跨进程空间产生影响。另一方面,由于顺序预测的预测执行窗口不够大,顺序预测攻击无法泄露内核空间的数据。多数情况下RET指令仍然是按照RSB提供的函数返回地址来预测执行。 由于顺序预测不借助任何中间缓冲区,因此常用的无效缓冲区的方法无法防止利用顺序预测的攻击。KPTI只能防止针对内核地址空间的攻击,但目前本文尚未成功利用顺序预测漏洞攻击内核地址。利用指令屏障可以有效防止这类攻击,可以在每次RET指令之后插入指令屏障来防止利用顺序预测漏洞的攻击,并可以将此功能集成到编译器当中,但对性能有较大影响。 本文对分支预测漏洞进行了深入研究,总结了分支预测漏洞的攻击模型,并提出了一种新的分类方法。系统性地研究了目前已经曝光的分支预测漏洞攻击在ARM上的危害程度,并评估了目前ARM处理器上的防御措施的有效性。在ARM处理器上,发现了一种新的预测执行漏洞——顺序预测漏洞。本文得出的结论如下。 (1)现有的分支预测漏洞攻击方法在ARM处理器上的表现与x86不同,一些攻击方法无法对ARM处理器造成影响。 (2)在ARM处理器上,没有一种防御方法可以防御所有的分支预测漏洞,需要多种方法的组合才能完全防御分支预测漏洞攻击。 (3)由于架构的不同,ARM处理器上存在其特有的预测执行漏洞,有必要专门针对ARM处理器进行研究。2.2 ARM处理器分支预测漏洞测评结果
3 防御方法分析和测评
3.1 主要防御方法
3.2 测评结果
4 新型ARM处理器预测执行漏洞
4.1 顺序预测漏洞原理
4.2 顺序预测漏洞攻击方法
4.3 顺序预测漏洞防御方法分析
5 结 论