购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

8.3 Windows中的侧信道缓解措施

本节简要介绍Windows如何通过各种缓解措施防范侧信道攻击。总的来说,某些侧信道缓解措施是由CPU制造商通过微码(microcode)更新实现的。然而,并非所有这类措施都始终可用,有些缓解措施需要由软件(Windows内核)启用。

8.3.1 KVA影子

内核虚拟地址影子(kernel virtual address shadowing)也称KVA影子(在Linux的世界中称为KPTI,代表内核页表隔离,kernel page table isolation),可在内核与用户页表之间创建清晰的隔离,借此缓解Meltdown攻击。当处理器未以正确的特权级别访问时,预测执行使得CPU能够获取到内核数据,但这要求在转换目标内核页的页表中存在一个有效的页帧编号。Meltdown攻击针对的内核内存通常会使用系统页表中有效的叶项(leaf entry)进行转换,这意味着需要具备监管特权级别(有关页表和虚拟地址转换的介绍请参阅本书卷1第5章)。在启用KVA影子后,系统会为每个进程分配并使用两个顶级页表:

内核页表,用于映射整个进程地址空间(包括内核和用户页)。在Windows中,用户页会以不可执行的方式进行映射,这是为了防止内核代码执行以用户模式分配的内存(这类似于硬件SMEP提供的功能)。

用户页表(又名影子页表),只负责映射用户页以及最少量不包含任何机密信息的内核页,可用于为页表切换、内核栈提供最基本的功能,以及中断、系统调用和其他转换、陷阱的处理。这组内核页也叫过渡(transition)地址空间。

在这个过渡地址空间中,NT内核通常会映射一种名为KPROCESSOR_DESCRIPTOR_ AREA的数据结构,该数据结构被包含在处理器的PRCB中,其中包含需要在用户(或影子)和内核页表之间共享的数据,如处理器的TSS、GDT以及内核模式GS基址的副本。此外,该过渡地址空间还包括NT内核映像“.KVASCODE”节下的所有影子陷阱处理程序。

当启用KVA影子的系统运行非特权用户模式线程(如以非管理员特权级别运行)时,处理器并不会映射任何可能包含机密数据的内核页。因此Meltdown攻击将彻底失效,因为内核页不再有效映射至进程的页表,并且任何以这些页为目标的CPU预测操作都无法继续进行。当用户进程使用系统调用,或当CPU在用户模式进程中执行代码的同时遇到中断时,CPU会在过渡栈上构建一个陷阱帧,并按照上文所述的方式将其同时映射至用户和内核页表。随后CPU会执行影子陷阱处理程序的代码,借此处理中断或系统调用。在处理系统调用时通常还需要切换至内核页表,复制内核栈中的陷阱帧,然后跳转至最初的陷阱处理程序(这意味着需要实现一种妥善的算法,以便刷新TLB中陈旧的项。下文将详细介绍TLB刷新算法)。这样即可在映射了整个地址空间的情况下,执行最初的陷阱处理程序。

初始化

在内核初始化第1阶段的早期,当处理器功能位(feature bit)计算完毕后,NT内核会借助内部例程KiDetectKvaLeakage判断CPU是否会受到Meltdown攻击。该例程会获取处理器信息,并将除Atom(一种有序处理器)外其他所有Intel处理器的内部变量KiKvaLeakage都设置为“1”。

内部变量KiKvaLeakage设置完毕后,系统会通过KiEnableKvaShadowing例程启用KVA影子,并开始准备处理器的TSS和过渡栈。处理器TSS的RSP0(内核)和IST栈会设置为指向相应的过渡栈。随后在基栈中写入一种名为KIST_BASE_FRAME的数据结构,借此让过渡栈(其大小为512字节)做好准备。该数据结构使得过渡栈能够链接至自己的非过渡内核栈(只有在页表切换之后才能访问),如图8-8所示。请注意,常规的非IST内核栈并不需要该数据结构。操作系统可以从CPU的PRCB中获取用户与内核模式切换所需的全部数据。每个线程都有对应的内核栈。当新线程被选中执行后,调度器会将其内核栈链接至处理器的PRCB,以此激活该内核栈。这是内核栈与IST栈的一个重要差异,并且每个处理器中只存在一个IST栈。

图8-8 KVA影子被激活后,CPU任务状态段(TSS)的配置情况

KiEnableKvaShadowing例程还承担一个重要职责:确定适合的TLB刷新算法(下面将详细介绍)。而确定后的结果(全局项或PCID)会存储在全局变量KiKvaShadowMode中。最后,对于非引导处理器,该例程会调用KiShadowProcessorAllocation在影子页表中映射每个处理器的共享数据结构。对于BSP处理器,则会在初始化阶段1的后期,当SYSTEM进程及其影子页表均已成功创建(且IRQL已被降至被动级别)之后再进行映射。只有在这种情况下,影子陷阱处理程序(全局的,且并非每个处理器专用的)才会映射至用户页表。

影子页表

当进程的地址空间创建完成后,内存管理器将使用内部例程MiAllocateProcessShadow分配影子(或用户)页表。新进程的影子页表在创建好后内容为空。随后,内存管理器会将SYSTEM进程的所有内核影子顶级页表项复制到新进程的影子页表中。

借此,操作系统可快速将整个过渡地址空间(位于内核中,被所有用户模式进程共享)映射给新进程。对于SYSTEM进程,影子页表依然为空,正如上一节所述,该页表将由KiShadowProcessorAllocation例程填充,这个例程会使用内存管理器服务将特定的内存块映射至影子页表,并重建整个页面层次结构。

内存管理器只会在特定情况下更新影子页表,并且仅有内核可以写入映射或解除映射。当一个请求需要分配或映射新内存到用户进程地址空间时,可能会遇到特定地址的顶级页表项丢失的情况。在这种情况下,内存管理器会分配整个页表层次结构的所有页面,并将新的顶级PTE存储在内核页表中。然而在启用KVA后,仅这样做还不够,内存管理器还必须在影子页表中写入顶级PTE。否则在陷阱处理程序正确切换页表后,返回用户模式之前,该地址将无法出现在用户映射中。

相比内核页表,内核地址会使用不同的方式映射至过渡地址空间。为防止错误地将与映射至过渡地址空间中的内存块距离太过接近的地址共享出来,内存管理器会始终为被共享的一个或多个PTE重建页表层次结构映射。这也意味着当内核需要在进程的过渡地址空间中映射某些新页面时,都必须在所有进程的影子页表中重复进行该映射(该操作完全由内部例程MiCopyTopLevelMappings负责)。

TLB刷新算法

在x86架构中,切换页表通常会导致刷新当前处理器的TLB(Translation Look-aside Buffer,转译后备缓冲区)。TLB是一种缓存,处理器会用它来快速转译在执行代码或访问数据时所用的虚拟地址。TLB中的有效项可以让处理器无须查询页表链,因此可加快执行速度。在未启用KVA影子的系统中,TLB中用于转译内核地址的项无须显式刷新。在Windows中,内核地址空间在大部分情况下是唯一的,并会被所有进程共享。Intel和AMD采用不同的技术来避免每次切换页表时刷新内核项,例如全局/非全局位和进程上下文标识符(Process-Context Identifier,PCID)。Intel与AMD的架构手册中详细描述了TLB及其刷新方法,本书不再深入讨论。

通过使用CPU的新功能,操作系统可以只刷新用户项,以此确保性能不受影响。但在启用KVA影子的情况下无疑是无法接受这种做法的,因为线程有义务切换页表,即使是在进入或退出内核时。在启用KVA的系统中,Windows会借助一种算法确保只在必要时才明确刷新内核和用户TLB项,进而实现下列两个目标:

在执行线程用户代码时,TLB中不维持任何有效的内核项。否则这些内核项可能被攻击者使用与Meltdown类似的推测技术所利用,进而读取机密的内核数据。

在切换页表时,只刷新最少量的TLB项。这样可确保因启用KVA影子而导致的性能损失处于可接受范围内。

TLB刷新算法主要应用于这三个场景:上下文切换、进入陷阱以及退出陷阱。无论是只支持全局/非全局位,还是在此基础上还能支持PCID的系统,都可以运行该算法。对于只支持全局/非全局位的系统,非KVA影子的配置将有所差异,其中所有内核页面都会标记为“非全局”,而过渡页和用户页会标记为“全局”。进行页表切换时,全局页不会被刷新(系统会更改CR3寄存器的值)。对于支持PCID的系统,则会将内核页标记为PCID 2,并将用户页标记为PCID 1。此时会忽略全局位和非全局位。

在当前执行的线程结束其量程(quantum)时,将会初始化上下文切换。当内核被调度去执行隶属于其他进程地址空间的线程时,TLB算法会保证TLB中的所有用户页均已移出(这意味着对于使用全局/非全局位的系统,需要进行一次彻底的TLB刷新,并且用户页会被标记为全局)。在内核退出陷阱(内核执行完代码返回用户模式)时,算法会保证TLB中的所有内核项已被移出(或作废)。这一点很容易实现,在支持全局/非全局位的处理器上,只需重新加载页表即可迫使处理器将所有非全局页作废;在支持PCID的系统中,用户页表会使用User PCID重新加载,进而让所有陈旧的内核TLB项自动作废。

该策略允许内核进入陷阱,即系统正在执行用户代码时产生了中断,或线程使用了系统调用,此时TLB中的一切都不会作废。上述TLB刷新算法的方案如表8-1所示。

表8-1 KVA影子TLB刷新策略

8.3.2 硬件间接分支控制(IBRS、IBPB、STIBP、SSBD)

处理器制造商也为不同的侧信道攻击设计了硬件层面的缓解措施。这些缓解措施在设计上能够与软件措施配合生效。有关侧信道攻击的硬件缓解措施主要通过下列间接分支控制机制来实现,具体采用何种机制通常是由CPU特殊模块寄存器(MSR)中的一位决定的。

间接分支限制推测(Indirect Branch Restricted Speculation,IBRS): 可在切换至不同安全上下文(用户/内核模式,或VM根/非根)时彻底禁用分支预测器(并刷新分支预测器缓冲区)。如果操作系统在过渡到更高特权的模式后设置了IBRS,那么间接分支预测目标将无法继续被低特权模式下执行的软件所控制。此外,在启用IBRS后,间接分支预测目标将无法被其他逻辑处理器所控制。操作系统通常会将IBRS设置为1,并在返回至较低特权安全上下文之前始终保持该设置。
IBRS的实现取决于CPU制造商:一些CPU会在启用IBRS后彻底禁用分支预测器缓冲区(这是一种禁止行为),而其他CPU可能只会刷新预测器的缓冲区(这是一种刷新行为)。在这些CPU中,IBRS缓解措施的工作方式与IBPB的较为类似,因此这些CPU通常只会实现IBRS。

间接分支预测器屏障(Indirect Branch Predictor Barrier,IBPB) :在设置为“1”后,会刷新分支预测器的内容,以此防止之前执行过的软件控制同一个逻辑处理器上的间接分支预测目标。

单线程间接分支预测器(Single Thread Indirect Branch Predictor,STIBP) :可对同一个物理CPU内核上不同逻辑处理器之间共享的分支预测进行限制。将逻辑处理器的STIBP设置为“1”后,可防止当前正在执行的逻辑处理器的间接分支预测目标被同一个内核中其他逻辑处理器上执行(或曾经执行过)的软件所控制。

预测存储旁路禁止(Speculative Store Bypass Disable,SSBD) :可以让处理器不以预测执行的方式加载,除非所有较旧的存储均处于已知状态。这样即可确保加载操作不会因为同一个逻辑处理器上较旧存储所产生的旁路,而以预测的方式使用陈旧的数据值,从而可防范预测性存储旁路攻击(详见上文“其他侧信道攻击”一节)。

NT内核会使用一种复杂的算法来确定上述间接分支限制机制的值,而这些值也会在上文有关KVA影子介绍中所提到的三个场景中产生相应的变化,这三个场景分别为上下文切换、进入陷阱以及退出陷阱。在兼容的系统中,系统会在始终启用IBRS的情况下运行内核代码(除非启用了Retpoline)。如果没有可用的IBRS(但IBPB和STIBP均可支持),内核将在启用STIBP的情况下运行,并在每次进入陷阱时(使用IBPB)刷新分支预测器缓冲区(这样,分支预测器就不会被用户模式运行的代码或在其他安全上下文中运行的“同胞”线程所影响)。如果CPU支持SSBD,则SSBD会始终在内核模式中启用。

出于性能方面的考虑,用户模式线程在执行时通常并不会启用硬件预测缓解措施,或只启用STIBP(取决于STIBP配对是否启用,详见下一节)。如果需要,则必须通过全局或每个进程的预测执行功能手动启用针对预测性存储旁路攻击的防护。实际上,所有预测缓解措施均可通过全局注册表值HKLM\System\CurrentControlSet\Control\Session Manager\ Memory Management\FeatureSettings加以调整。这是一个32位掩码值,其中的每一位对应一个具体的设置。表8-2总结了不同的功能设置及其含义。

表8-2 功能设置及其对应的值

8.3.3 Retpoline和导入优化

硬件缓解措施会对系统性能产生极大影响,因为在启用这些缓解措施后,CPU的分支预测器会受到限制甚至被彻底禁用。对游戏和关键业务应用程序来说,大幅度的性能下降往往是无法接受的。用于防范Spectre的IBRS(或IBPB)可能是对性能产生最大影响的缓解措施。在内存屏障(memory fence)指令的帮助下,可以在不使用任何硬件缓解措施的情况下防范Spectre的第一个变体,例如x86架构中所用的LFENCE。这些指令会迫使处理器在屏障本身建立完成之前不以预测执行的方式执行任何新操作,仅在屏障建立完成(并且在此之前的所有指令均已退出)后,处理器的流水线才会重新开始执行(并预测)新的操作码(Opcode)。不过Spectre的第二个变体依然需要通过硬件缓解措施来预防,进而会因为IBRS和IBPB导致性能退化。

为了解决这个问题,Google的工程师设计了一种新颖的二进制修改技术,名为Retpoline。Retpoline代码序列如图8-9所示,可将间接分支从预测执行中隔离出来。这样无须执行存在漏洞的间接调用,处理器可以跳转至一个安全控制序列,该序列可动态地修改栈,记录最终的预测,并通过“Return”操作抵达新的目标。

图8-9 x86 CPU的Retpoline代码序列

在Windows中,Retpoline是在NT内核里实现的,这样可通过动态值重定位表(Dynamic Value Relocation Table,DVRT),动态地为内核与外部驱动程序映像应用Retpoline代码序列。当内核映像使用Retpoline编译(通过兼容的编译器)时,编译器会在映像的DVRT里为代码中存在的每个间接分支插入一个项,以此描述其地址和类型。执行该间接分支的操作码会照原样保存在最终的代码中,但会被增加一个大小可变的填充(padding)。DVRT中的项包含NT内核动态修改间接分支的操作码所需的全部信息。这种架构确保了使用Retpoline选项编译的外部驱动程序也可以在老版本操作系统中运行,为此只需跳过DVRT表中这些项的解析操作即可。

注意 DVRT的开发最初是为了支持内核ASLR(Address Space Layout Randomization,地址空间布局随机化,详见卷1第5章)。随后DVRT表通过扩展包含了Retpoline描述符。系统可以识别映像中所包含的DVRT表的版本。

在初始化的阶段1,内核将检测处理器是否会受到Spectre攻击,如果系统可兼容并具备足够可用的硬件缓解措施,就会启用Retpoline并将其应用到NT内核映像和HAL。RtlPerformRetpolineRelocationsOnImage例程会扫描DVRT,将表中每项所描述的间接分支替换为不容易受到预测攻击,且以Retpoline代码序列为目标的直接分支。间接分支最初的目标地址会保存在一个CPU寄存器(AMD和Intel处理器的R10寄存器)中,并通过一条指令覆盖写入由编译器生成的填充。Retpoline代码序列会存储在NT内核映像的RETPOL节中,为该节提供支撑的页面会映射至每个驱动程序映像的结尾处。

启动前,内部例程MiReloadBootLoadedDrivers会将引导驱动程序物理迁移至其他位置,并为每个驱动程序的映像进行必要的修复(包括Retpoline)。所有引导驱动程序、NT内核以及HAL映像都会被Windows加载器(Windows Loader)分配一块连续的虚拟地址空间,该空间不包含相关的控制区域,因此这些空间将不可分页。这意味着为这些映像提供支撑的内存将始终驻留,并且NT内核可以使用同一个RtlPerformRetpolineRelocationsOnImage函数直接在代码中修改每个间接分支。如果启用了HVCI,那么系统必须调用安全内核(Secure Kernel)以应用Retpoline(借助安全调用PERFORM_RETPOLINE_RELOCATIONS)。实际上,在这个场景中,驱动程序的可执行内存会按照第9章介绍的安全内核写入执行限制措施加以保护,不允许任何形式的修改,仅安全内核可以进行修改。

注意 Retpoline和导入优化修复措施是由内核在PatchGuard(也叫内核补丁保护,Kernel Patch Protection,详见本书卷1第7章)初始化并提供一定程度的保护之前对引导驱动程序应用的。对于驱动程序和NT内核本身,修改受保护驱动程序的代码节是一种非法操作。

运行时驱动程序(详见本书卷1第5章)由NT内存管理器负责加载,可创建出由驱动程序的映像文件支撑的节对象(section object)。这意味着为了跟踪内存节中的页面,需要创建一个控制区(包括原型PTE数组)。对于驱动程序节,一些物理页面最初被放入内存中只是为了验证代码的完整性,随后就会被转移至备用表(standby list)中。当这样的节随后被映射并且驱动程序的页面被首次访问时,来自备用表(或来自备份文件)的物理页面会被页面错误处理程序按需进行具体化。Windows会对原型PTE所指向的共享页面应用Retpoline。如果同一节同时也被用户模式的应用程序所映射,内存管理器就会新建一个私有页,并将共享页面中的内容复制到私有页,借此重新恢复Retpoline(以及导入优化)的修复措施。

注意 一些较新的Intel处理器还会对“Return”指令进行预测。此类CPU将无法启用Retpoline,因为无法借此防范Spectre v2。在这种情况下,只能使用硬件缓解措施。增强型IBRS(一种新的硬件缓解措施)解决了IBRS的性能退化问题。

Retpoline位图

在Windows中实现Retpoline的最初设计目标(局限)之一在于需要为混合环境(同时包含兼容和不兼容Retpoline的驱动程序)提供支持,并针对Spectre v2提供整体性系统保护。这意味着不支持Retpoline的驱动程序应在启用IBRS(或在启用STIBP的情况下同时为内核项启用IBPB,详见“硬件间接分支控制”一节)的情况下运行,其他驱动程序则可在不启用任何硬件预测缓解措施的情况下运行(此时可由Retpoline代码序列和内存屏障提供保护)。

为了动态实现与老旧驱动程序的兼容性,在初始化的阶段0过程中,NT内核会分配并初始化一个动态位图,以此跟踪组成整个内核地址空间的每个64 KB内存块。在这种模型中,设置为“1”的位代表64 KB的地址空间块包含可兼容Retpoline的代码,反之则会设置为“0”。随后,NT内核会将代表HAL和NT映像(始终兼容Retpoline)的地址空间对应的位设置为“1”。每次加载新的内核映像后,系统都会尝试为其应用Retpoline。如果能成功应用,那么Retpoline位图中对应的位也会被设置为“1”。

Retpoline代码序列还可进一步加入位图检查功能:每次执行间接分支时,系统会检查最初的调用目标是否位于可兼容Retpoline的模块中。如果检查通过(且相关位被设置为“1”),则系统会执行Retpoline代码序列(见图8-9)并以安全的方式进入目标地址。否则(当Retpoline位图中的位被设置为“0”时)将会初始化Retpoline退出序列。随后,当前CPU的PRCB会设置RUNNING_NON_RETPOLINE_CODE标记(用于上下文切换),IBRS会被启用(或启用STIBP,取决于硬件配置),需要时会发出IBPB和LFENCE,并生成内核事件SPEC_CONTROL。最后,处理器依然能以安全的方式进入目标地址(由硬件缓解措施提供所需的保护能力)。

当线程量程终止且调度器选择新线程后,调度器会将当前处理器的Retpoline状态(由是否出现RUNNING_NON_RETPOLINE_CODE标记来表示)保存在旧线程的KTHREAD数据结构中。通过这种方式,当旧线程被选中再次执行(或发生了进入内核陷阱事件)时,系统就会知道自己需要重新启用所需的硬件预测缓解措施,进而确保系统能够始终获得保护。

导入优化

DVRT中的Retpoline项还描述了以导入函数为目标的间接分支。DVRT会借助导入的控制传输项,使用指向IAT中正确项的索引来描述此类分支(IAT是指Image Import Address Table,即映像导入地址表,这是一种由加载器编译的导入函数指针数组)。当Windows加载器编译了IAT后,其内容通常就不太可能发生变化了(但存在一些罕见的例外情况)。如图8-10所示,其实并不需要将指向导入函数的间接分支转换为Retpoline分支,因为NT内核可以保证两个映像(调用方和被调用方)的虚拟地址足够接近,可直接调用(不超过2 GB的)目标。

图8-10 ExAllocatePool函数不同的间接分支

导入优化(import optimization,在内部通常称为“导入链接”)这项功能可使用Retpoline动态重定向,将指向导入函数的间接调用转换为直接分支。如果使用直接分支将代码执行过程转向至导入函数,则无须应用Retpoline,因为直接分支不会受到预测攻击。NT内核会在应用Retpoline的同时应用导入优化,虽然这两个功能可以单独配置,但为了正常生效,它们都用到了相同的DVRT项。借助导入优化,甚至在不会受到Spectre v2攻击的系统中,Windows也可以进一步获得性能提升(直接分支不需要任何额外的内存访问)。

8.3.4 STIBP配对

在超线程(hyper-thread)系统中,为保护用户模式代码免受Spectre v2攻击,系统至少会在启用了STIBP的情况下运行用户线程。在非超线程系统中则无须这样做:因为先前执行内核模式代码时已经启用了IBRS,此时已经可以防止先前执行的用户模式线程进行预测。如果启用了Retpoline,当跨进程切换线程并且首次从内核陷阱返回时,就已经发出了所需的IBPB。这确保了在执行用户线程代码前,CPU分支预测器缓冲区一定为空。

在超线程系统中启用STIBP会导致性能退化,因此,默认情况下,用户模式线程的STIBP会被禁用,这会导致线程可能受到来自同胞SMT线程的预测攻击。终端用户可以通过USER_STIBP_ALWAYS功能设置,或使用RESTRICT_INDIRECT_BRANCH_PREDICTION这个进程缓解选项为用户线程手动启用STIBP(详见“硬件间接分支控制”一节)。

上述场景并非最理想的。更好的解决方案是通过STIBP配对机制来实现。STIBP配对是由I/O管理器在NT内核初始化的阶段1启用的(使用KeOptimizeSpecCtrlSettings函数),但这需要满足一些条件。系统必须启用超线程,CPU需要支持IBRS和STIBP。此外,只有非嵌套虚拟化环境或禁用Hyper-V的情况下才能支持STIBP配对(详见第9章)。

在STIBP配对场景中,系统会为每个进程分配一个安全域标识符(存储在EPROCESS数据结构中),该标识符由一个64位数字表示。System安全域标识符(等于“0”)只会分配给使用System或完整管理令牌运行的进程。Nonsystem安全域则会在进程创建时(由内部函数PspInitializeProcessSecurit)按照如下规则分配:

如果新建的进程未明确分配新的主令牌,那么它会从创建它的父级进程获得相同的安全域。

如果新进程明确指定了新的主令牌(例如使用CreateProcessAsUser或CreateProcessWithLogon API),则会从内部符号PsNextSecurityDomain开始为新进程生成新的用户安全域ID。随后每生成一个新的域ID,其值都会增加(保证了在系统运行全过程中不会产生冲突的安全域)。

请注意,进程最初创建完毕后,还可以使用NtSetInformationProcess API(以及ProcessAccessToken信息类)分配新的主令牌。为了让该API的操作成功实现,进程需要创建为挂起状态(其中未运行任何线程)。至此,该进程依然具备最初的令牌并处于非冻结状态。新安全域则会按照上文介绍的规则进行分配。

安全域还可以以手动方式分配给属于同一组的不同进程。应用程序可以使用NtSetInformationProcess API以及ProcessCombineSecurityDomainsInformation类,将进程的安全域替换为同一组中其他进程的安全域。该API可接收两个进程句柄,并在两个令牌都被冻结的情况下替换第一个进程的安全域,而这两个进程可以通过PROCESS_VM_ WRITE和PROCESS_VM_OPERATION访问权打开对方。

STIBP配对机制的正常生效离不开安全域。STIBP配对可将逻辑处理器(LP)与其“同胞”链接在一起(两者共享一个物理内核。本节内容中出现的LP和CPU这两个术语可互换)。只有在本地CPU和远程CPU的安全域相同,或者两个LP中有一个闲置时,两个LP才会由STIBP配对算法(实现于内部函数KiUpdateStibpPairing中)进行配对。这些情况下,两个LP都可以在不设置STIBP的情况下运行,并暗地受到预测执行保护(对相同安全上下文中运行的同胞CPU进行此类攻击无法获得任何好处)。

STIBP配对算法实现于KiUpdateStibpPairing函数中,其中包含一个完整的状态机。只有当CPU的PRCB中所存储的配对状态信息变得陈旧时,陷阱退出处理程序才会调用该例程(会在系统退出内核模式开始执行用户模式线程时调用)。LP的配对状态主要会因为如下两个原因变得陈旧:

NT调度器选择了在当前CPU上执行的新线程。如果新线程的安全域不同于旧线程,CPU的PRCB配对状态就会被标记为陈旧。随后STIBP配对算法会重新评估两者的配对状态。

当同胞CPU脱离闲置状态时,它会请求远程CPU重新评估自己的STIBP配对状态。

请注意,当LP在启用STIBP的情况下运行代码时,可防范来自同胞CPU的预测。STIBP配对是基于相反概念开发的:启用STIBP的情况下执行LP时,可保证同胞CPU能够防范来自自己的预测。这意味着当通过上下文切换进入不同的安全域时,完全不需要中断同胞CPU的执行,哪怕对方正在禁用STIBP的情况下运行用户模式代码。

上述场景唯独不适用于这种情况:调度器选择的VP调度线程(在启用根调度器的情况下为虚拟处理器提供支撑,详见第9章)隶属于VMMEM进程。这种情况下,系统会立刻向同胞线程发送IPI以便更新其STIBP配对状态。实际上,运行客户端虚拟机代码的VP调度线程始终可以决定禁用STIBP,导致同胞线程(同样运行于STIBP禁用的情况下)处于不受保护的状态。

实验:查询系统的侧信道缓解状态

Windows会使用原生API NtQuerySystemInformation,通过SystemSpeculationControl- Information和SystemSecureSpeculationControlInformation这两个信息类暴露侧信道缓解信息。很多工具可利用该API向终端用户显示系统的侧信道缓解状态:

由Matt Miller开发并由微软官方提供支持的PowerShell脚本SpeculationControl,这是一个开源工具,已发布至如下GitHub代码库:https://github.com/microsoft/ SpeculationControl。

由亚历克斯·伊奥尼斯库(本书作者之一)开发的SpecuCheck工具,同样已开源并发布至如下GitHub代码库:https://github.com/ionescu007/SpecuCheck。

由安德里亚·阿列维(本书作者之一)开发的SkTool,(在撰写本书时)已被纳入较新的Windows Insider版本中。

上述三个工具都能提供大致相同的结果。但只有SkTool能够显示安全内核中实现的侧信道缓解措施(虚拟机监控程序和安全内核详见第9章)。在这个实验中,我们将了解自己系统中启用了哪些缓解措施。请下载SpecuCheck并打开命令提示符窗口(在搜索框中输入cmd)执行该工具,随后应该能看到类似如下的输出结果:

SpecuCheck v1.1.1    --   Copyright(c) 2018 Alex Ionescu 
https://ionescu007.github.io/SpecuCheck/  --   @aionescu 
--------------------------------------------------------
 
Mitigations for CVE-2017-5754 [rogue data cache load] 
--------------------------------------------------------
[-] Kernel VA Shadowing Enabled:                       yes 
     > Unnecessary due lack of CPU vulnerability:    no 
     > With User Pages Marked Global:                no 
     > With PCID Support:                           yes 
     > With PCID Flushing Optimization (INVPCID):   yes 
 
Mitigations for CVE-2018-3620 [L1 terminal fault] 
[-] L1TF Mitigation Enabled:                           yes 
     > Unnecessary due lack of CPU vulnerability:    no 
     > CPU Microcode Supports Data Cache Flush:     yes 
     > With KVA Shadow and Invalid PTE Bit:         yes 

(为节省版面,上述输出结果已节略。)

此外,也可下载最新的Windows Insider版本并尝试使用SkTool工具。在不添加任何命令行参数的情况下启动该工具后,默认即可显示虚拟机监控程序和安全内核的状态。要查看所有侧信道缓解措施的状态,需要使用/mitigations这个命令行参数来调用该工具: rfbLwEvjIdpVK4anoR17I+lF7rUVoEsf9eBgIaXHVhjcJKswacFxjb/UsdI18j+0

Hypervisor / Secure Kernel / Secure Mitigations Parser Tool 1.0 
Querying Speculation Features... Success! 
   This system supports Secure Speculation Controls. 
 
System Speculation Features. 
   Enabled: 1 
   Hardware support: 1 
   IBRS Present: 1 
   STIBP Present: 1 
   SMEP Enabled: 1 
   Speculative Store Bypass Disable (SSBD) Available: 1 
   Speculative Store Bypass Disable (SSBD) Supported by OS: 1 
   Branch Predictor Buffer (BPB) flushed on Kernel/User transition: 1 
   Retpoline Enabled: 1 
   Import Optimization Enabled: 1 
   SystemGuard (Secure Launch) Enabled: 0 (Capable: 0) 
   SystemGuard SMM Protection (Intel PPAM / AMD SMI monitor) Enabled: 0 
 
Secure system Speculation Features. 
   KVA Shadow supported: 1 
   KVA Shadow enabled: 1 
   KVA Shadow TLB flushing strategy: PCIDs 
   Minimum IBPB Hardware support: 0 
   IBRS Present: 0 (Enhanced IBRS: 0) 
   STIBP Present: 0 
   SSBD Available: 0 (Required: 0) 
   Branch Predictor Buffer (BPB) flushed on Kernel/User transition: 0 
   Branch Predictor Buffer (BPB) flushed on User/Kernel and VTL 1 transition: 0 
   L1TF mitigation: 0 
   Microarchitectural Buffers clearing: 1
点击中间区域
呼出菜单
上一章
目录
下一章
×

打开