Intel官方对5月15号曝出的CPU侧信道漏洞“ZombieLoad”的详细技术分析(下)

(接上文)

缓冲区覆盖的程序序列

在没有枚举MD_CLEAR功能的处理器上,某些指令序列可用于覆盖受MDS影响的缓冲区。你可以点此,详细查看这些序列。

不同的处理器可能需要不同的序列来覆盖受MDS影响的缓冲区,程序序列的一些要求如下:

1.在支持同步多线程(SMT)的处理器上,同一物理内核上的其他线程应在序列期间暂停,这样它们就不会分配填充缓冲区。这允许当前线程覆盖所有填充缓冲区,特别是,这些暂停的线程不应执行可能错过L1D缓存的任何加载或存储。暂停线程应该对PAUSE指令进行循环,以限制序列期间的交叉线程干扰。

2.对于依赖REP字符串指令的序列,MSR位IA32_MISC_ENABLES [0]必须设置为1,以便启用快速字符串。

缓冲区何时会被覆盖

无论何时切换到以前不信任的程序,都应覆盖存储缓冲区、填充缓冲区和加载端口。如果程序确保任何这些缓冲区中都不存在受保护的数据,则可以避免缓冲区被覆盖。

OS

当从ring0转换到ring3时,OS可以执行VERW指令来覆盖受影响缓冲区中的任何受保护数据。这将覆盖可能属于内核或其他应用程序的缓冲区中的受保护数据,当SMT处于活动状态时,也应在进入C状态之前以及退出C状态和转换为不可信代码之间执行此指令。

Intel ®程序保护扩展(Intel ®SGX)

当进入或退出Intel ®程序保护扩展(Intel ®SGX)安全区(enclave)时,枚举对MD_CLEAR支持的处理器将自动覆盖受影响的数据缓冲区。

虚拟机管理器(VMM)

VMM可以在进入用户VM之前执行VERW指令或L1D_FLUSH命令,这将覆盖可能属于VMM或其他vm的缓冲区中的受保护数据。除了加载枚举MD_CLEAR的微代码更新,在进入用户虚拟机以缓解L1TF之前已经使用L1D_FLUSH命令的VMM可能不需要进行更多更新。

虽然VMM可能只在一个线程上发出L1D_FLUSH来刷新L1D中的数据、填充缓冲区和内核中的所有线程加载端口,但只清除当前线程的存储缓冲区。当另一个线程接下来进入用户端时,可能需要VERW来覆盖属于另一个线程的存储缓冲区。

系统管理模式(SMM)

将系统管理模式(SMM)数据暴露给随后在相同逻辑处理器上进入的程序,可以通过在退出SMM时覆盖缓冲区来缓解这种情况。在枚举MD_CLEAR2的处理器上,处理器将在执行RSM指令时自动覆盖受影响的缓冲区。

进程内的安全域

使用基于语言的安全性的程序可以在不同的信任域之间进行转换,在信任域之间进行转换时,可以使用VERW指令清除缓冲区。

如Deep Dive:Managed Runtime Speculative Execution Side Channel Mitigations 中所讨论的,站点隔离可能是一种更有效的技术,通常用于处理推测执行端通道。

使用同步多线程(SMT)的环境进行缓解

OS

操作系统必须使用两种不同的方法来防止线程使用MDS来推断兄弟线程使用的数据值,第一个(组调度)可防止用户对用户的攻击。当一个线程在另一个线程上以用户模式进入的攻击者执行内核代码时,第二个(同步条目)可以保护内核数据免受攻击。

组调度

当前线程跨越安全域时,操作系统可以防止兄弟线程进入恶意代码。 OS调度程序可以通过确保共享相同物理内核的程序工作载荷彼此相互信任(例如,如果它们位于相同的应用程序定义的安全域中)或确保其他线程处于空闲状态来缓解对兄弟线程的控制。

操作系统可以静态地(例如,通过任务关联或cpuset)在工作加载之间强制执行这种信任关系,或者通过OS中的组调度程序(有时称为内核调度程序)动态地强制执行。组调度程序应该优先选择兄弟内核上具有相同信任域的进程,但前提是没有其他空闲内核可用。这可能会影响内核之间的加载平衡决策。如果来自兼容信任域的进程不可用,则调度程序可能需要使兄弟线程空闲下来。

1.png

没有组调度的系统

2.png

具有组调度的系统

图1显示了一个三核系统,其中Core 2进入来自不同安全域的进程。这些进程将能够使用MDS推断出受保护的数据。图2显示了组调度程序如何通过确保没有内核同时进入来自不同安全域的进程,以缓解进程到进程攻击的可能性。

使用IPI同步ring 0的进入和退出

当当前硬件线程从用户代码(应用程序代码)转换到内核代码(ring0模式)时,OS需要采取行动。这可能是系统调用或中断异步事的一部分,因此可能不允许兄弟线程在用户模式下执行,因为内核代码可能不信任用户代码。在操作系统的简化视图中,我们可以认为每个线程处于以下三种状态之一:

1.闲置状态;

2.Ring 0(内核代码)状态;

3.用户(应用程序代码)状态;

下图显示了保持内核安全情况下,不受恶意应用程序攻击的状态转换。

3.png

线程交会图

上图中的每个节点都显示了共享一个物理内核的两个线程的可能执行状态,从状态1开始,两个线程都处于空闲状态。依据该状态,中断将内核转换到状态2a或2b,具体取决于哪个线程被中断。如果没有要进入的用户任务,则物理内核在完成中断后转换回状态1。如果空闲状态是使用处理器c状态实现的,那么应该在进入受MSBDS影响的处理器的c状态之前执行VERW。

从2a或2b开始,线程可以开始进入用户进程。只要内核上的其他线程保持空闲状态,当从2a转换到3a或2b转换到3b时,就不需要SMT特定的缓解,尽管OS需要通过在转换到3a或3b之前执行VERW来覆盖缓冲区。或者,从2a或2b开始,如果中断唤醒的兄弟线程,则物理内核可以转换到状态4。如果该中断不会导致内核进入用户进程,则物理内核可能会返回2a或2b。

从状态4开始,内核可以转换到状态5并开始在两个线程上执行用户代码。此时操作系统必须确保转换到状态5,以防止第一个输入用户代码的线程对另一个线程的微体系结构缓冲区中的受保护数据执行攻击。操作系统还应该在两个线程上执行VERW,由于内核和用户状态之间的两个线程转换都没有硬件支持,因此操作系统应该使用标准程序技术来同步线程。操作系统还应注意边界,以避免在一个或两个线程转换到用户模式时将受保护的数据加载到微体系结构缓冲区中。注意,内核只应该在进入来自相同安全域的两个用户线程时进入状态5。

内核可以从状态5进入状态6a或6b,因为其中一个线程离开用户模式或者通过状态3a或3b进入。此时,中断将线程从空闲状态唤醒。当处于状态6a或6b时,操作系统应该避免访问任何被认为与用户模式中的兄弟线程相关的受保护的数据。如果处于内核状态的线程需要访问受保护的数据,操作系统应该从状态6a或6b转换到状态4。处于内核状态的线程应该使用处理器间中断(interprocessor interrupt, IPI)来处理内核状态中的两个线程,以便将内核转换为状态4。当内核线程通过进入空闲状态或返回到用户状态,退出内核状态时,可以允许兄弟线程退出IPI服务例程,并在执行VERW之后返回到用户状态本身进入。

禁用同步多线程(SMT)

防止同级线程通过MDS推断数据值的另一种方法是通过BIOS禁用SMT,或者让OS只在其中一个线程上调度工作。

针对Atom和Knight系列处理器的SMT缓解措施

某些受MDS影响的处理器(MDS_NO为0)不需要缓解其他兄弟线程,具体而言,任何不支持SMT的处理器(例如,基于Silvermont和Airmont微体系结构的处理器)都不需要SMT缓解。

基于Knights Landing或Knights Mill微体系结构的处理器不需要组调度或同步退出/进入来缓解来自兄弟线程的MDS攻击,这是因为这些处理器仅受MSBDS的影响,并且存储数据缓冲区仅在进入/退出C状态时,在线程之间共享。在这样的处理器上,应该在进入时覆盖存储缓冲区,以及在退出c状态和过渡到不可信代码之间覆盖存储缓冲区。每个内核中只有四个线程的处理器受到MDS的影响(不要枚举MDS_NO),它们是Knights系列处理器。

虚拟机管理器(VMM)

MDS的缓解与缓解L1TF所需的缓解相似,枚举MDS_CLEAR的处理器增强了L1D_FLUSH命令4,也覆盖了受MDS影响的微体系结构结构。这可以允许VMM通过组调度和使用L1D_FLUSH命令缓解L1TF的影响,从而缓解MDS的影响。VMM缓解可能需要应用于不受L1TF(设置了RDCL_NO)影响但受MDS (MDS_NO是clear)影响的处理器。在这样的处理器上,VMM可以使用VERW而不是L1D_FLUSH命令,使用程序序列实现L1D刷新的VMM应该使用VERW指令覆盖受MDS影响的微体系结构结构。

请注意,即使VMM仅在一个线程上发出L1D_FLUSH来刷新内核中所有线程的数据,也会为当前线程清除存储缓冲区。当下一个线程进入用户端时,可能需要VERW来覆盖属于该线程的存储缓冲区。

Intel ®SGX

Intel SGX安全模型不信任操作系统调度程序,通过这种方式来确保在兄弟线程上进入的程序工作加载彼此信任。对于受跨线程MDS影响的处理器,Intel SGX远程认证反映了BIOS是否启用了SMT。Intel SGX远程认证验证程序可以评估在平台上启用SMT时潜在的跨线程攻击的风险,并决定是否信任平台上的安全区以保护特定的受保护数据。

SMM

SMM是BIOS使用的一种特殊的处理器模式,枚举MD_CLEAR并受MDS影响的处理器将在退出SMM的RSM指令期间自动刷新受影响的微体系结构结构。

SMM程序必须在进入和退出SMM时对所有逻辑处理器进行交合,以确保兄弟逻辑处理器在自动刷新后不会将数据重新加载到微体系结构结构中。目前,大多数SMM程序已经做到了这一点。这确保了在属于SMM的数据位于微体系结构结构中时,非SMM程序不会进入,这种SMM实现不需要对MDS进行任何程序更改。

CPUID枚举

CPUID.(EAX=7H,ECX=0):EDX[MD_CLEAR=10] 列举了对附加功能的支持,这些功能将刷新下面列出的微体系结构。

1.执行(现有的)VERW指令时,其参数是一个内存操作数值;

2.在IA32_FLUSH_CMD MSR中设置L1D_FLUSH命令位;

3.执行RSM指令;

4.进入或退出Intel SGX安全区。

用于覆盖缓冲区的程序序列

对于没有枚举MD_CLEAR功能的处理器,可以使用以下指令序列覆盖受MDS影响的缓冲区。在枚举MD_CLEAR的处理器上,应该使用VERW指令或L1D_FLUSH command4来代替这些程序序列。

程序序列在每个处理器模型上使用最流行的可用内存操作办法,以确保覆盖所有的高阶位。在这些序列中间发生的系统管理中断(SMI)、中断或异常可能导致执行较小的内存访问,而这些访问只覆盖缓冲区的较低位。在这种情况下,当序列完成时,一些缓冲区条目可能被覆盖两次,而只有其他缓冲区条目的低位被覆盖。其中一些序列使用%xmm0覆盖微体系结构缓冲区,可以安全地假设这个值不包含受保护的数据,因为Intel在返回用户模式(可以直接访问%xmm0)之前执行这个序列。虽然覆盖操作通过MDS漏洞使兄弟线程可以看到%xmm0值,但Intel假设组调度确保兄弟线程上的进程在返回用户模式的线程上被进程信任。

请注意,在虚拟化环境中,VMM可能无法为用户操作系统提供有关正在使用的实际物理处理器模型的真实信息。在这些环境中,Intel建议用户操作系统始终使用VERW。

Nehalem,Westmere,Sandy Bridge和Ivy Bridge

下面的序列可以覆盖处理器系列代码Nehalem、Westmere、Sandy Bridge或Ivy Bridge中受影响的数据缓冲区。

static inline void IVB_clear_buf(char *zero_ptr)
{
        __asm__ __volatile__ (
                "lfence                         \n\t"
                "orpd (%0), %%xmm0              \n\t"
                "orpd (%0), %%xmm1              \n\t"
                "mfence                         \n\t"
                "movl $40, %%ecx                \n\t"
                "addq $16, %0                   \n\t"
                "1: movntdq %%xmm0, (%0)        \n\t"
                "addq $16, %0                   \n\t"
                "decl %%ecx                     \n\t"
                "jnz 1b                         \n\t"
                "mfence                         \n\t"
                ::"r" (zero_ptr):"ecx","memory");
}

Haswell和Broadwell

以下序列可以基于Haswell或Broadwell微体系结构覆盖处理器的受影响数据缓冲区:

static inline void BDW_clear_buf(char *dst)
{
        __asm__ __volatile__ (
                "movq %0, %%rdi                 \n\t"
                "movq %0, %%rsi                 \n\t"
                "movl $40, %%ecx                \n\t"
                "1: movntdq %%xmm0, (%0)        \n\t"
                "addq $16, %0                   \n\t"
                "decl %%ecx                     \n\t"
                "jnz 1b                         \n\t"
                "mfence                         \n\t"
                "movl $1536, %%ecx              \n\t"
                "rep movsb                      \n\t"
                "lfence                         \n\t"
                ::"r" (dst):"eax", "ecx", "edi", "esi",
			"cc","memory");
}

Skylake,Kaby Lake和Coffee Lake

对于基于Skylake,Kaby Lake或Coffee Lake微体系结构的处理器,所需的序列取决于启用了哪些向量扩展。

void _do_skl_sse(char *dst, const __m128i *zero_ptr)
{
	__asm__ __volatile__ (
		"lfence\n\t"
		"orpd (%1), %%xmm0\n\t"
		"orpd (%1), %%xmm0\n\t"
		"xorl	%%eax, %%eax\n\t"
		"1:clflushopt 5376(%0,%%rax,8)\n\t"
		"addl	$8, %%eax\n\t"
		"cmpl $8*12, %%eax\n\t"
		"jb 1b\n\t"
		"sfence\n\t"
		"movl	$6144, %%ecx\n\t"
		"xorl	%%eax, %%eax\n\t"
		"rep stosb\n\t"
		"mfence\n\t"
		: "+D" (dst)
		: "r" (zero_ptr)
		: "eax", "ecx", "cc", "memory"
	);
}

如果处理器支持Intel AVX但不支持Intel AVX-512,则可以使用此Intel AVX序列。

void _do_skl_avx(char *dst, const __m256i *zero_ptr)
{
	__asm__ __volatile__ (
		"lfence\n\t"
		"vorpd (%1), %%ymm0, %%ymm0\n\t"
		"vorpd (%1), %%ymm0, %%ymm0\n\t"
		"xorl	%%eax, %%eax\n\t"
		"1:clflushopt 5376(%0,%%rax,8)\n\t"
		"addl	$8, %%eax\n\t"
		"cmpl $8*12, %%eax\n\t"
		"jb 1b\n\t"
		"sfence\n\t"
		"movl	$6144, %%ecx\n\t"
		"xorl	%%eax, %%eax\n\t"
		"rep stosb\n\t"
		"mfence\n\t"
		: "+D" (dst)
		: "r" (zero_ptr)
		: "eax", "ecx", "cc", "memory", "ymm0"
	);
}

如果处理器支持Intel AVX-512,则可以使用此序列。请注意,使用Intel AVX-512操作可能会影响处理器频率。使用VERW和MD_CLEAR支持不会影响处理器频率,因此建议使用。

void _do_skl_avx512(char *dst, const __m512i *zero_ptr)
{
	__asm__ __volatile__ (
		"lfence\n\t"
		"vorpd (%1), %%zmm0, %%zmm0\n\t"
		"vorpd (%1), %%zmm0, %%zmm0\n\t"
		"xorl	%%eax, %%eax\n\t"
		"1:clflushopt 5376(%0,%%rax,8)\n\t"
		"addl	$8, %%eax\n\t"
		"cmpl $8*12, %%eax\n\t"
		"jb 1b\n\t"
		"sfence\n\t"
		"movl	$6144, %%ecx\n\t"
		"xorl	%%eax, %%eax\n\t"
		"rep stosb\n\t"
		"mfence\n\t"
		: "+D" (dst)
		: "r" (zero_ptr)
		: "eax", "ecx", "cc", "memory", "zmm0"
	);
}

Atom(仅限Silvermont和Airmont)

以下序列可以基于Silvermont或Airmont微体系结构覆盖处理器的存储缓冲区,由于Silvermont和Airmont不支持SMT,因此在进入/退出C状态时可能不需要这些序列。

static inline void SLM_clear_sb(char *zero_ptr)
{
       __asm__ __volatile__ (
               "movl $16, %%ecx                \n\t"
               "1: movntdq %%xmm0, (%0)        \n\t"
               "addq $16, %0                   \n\t"
               "decl %%ecx                     \n\t"
               "jnz 1b                         \n\t"
               "mfence                         \n\t"
               ::"r" (zero_ptr):"ecx","memory");
}

Knights Landing和Knights Mill

以下程序序列可以覆盖基于Knights Landing和Knights Mill的处理器的存储缓冲区,当线程被唤醒或进入睡眠状态时,Knights系列处理器重新分区存储缓冲区。程序应该在线程进入休眠状态之前以及线程唤醒和执行不信任代码之间执行此序列。请注意,Knights系列处理器支持用户级MWAIT,当操作系统启用时,可以防止操作系统知道线程何时休眠/唤醒。

Knights程序序列只需要覆盖存储缓冲区,因此不需要线程集合。无论其他线程在做什么,它都可以运行。

void KNL_clear_sb(char *dst)
{
  __asm__ __volatile__ (
       "xorl	%%eax, %%eax\n\t"
       "movl	$16, %%ecx\n\t"
       "cld \n\t"
       "rep stosq\n\t"
       "movl	$128, %%ecx\n\t"
       "rep stosq\n\t"
       "mfence\n\t"
       : "+D" (dst)
       :: "eax", "ecx", "cc", "memory"
       );
}