Skip to content

CPU 漏洞和缓解措施

漏洞

Spectre

  • 论文 Spectre Attacks: Exploiting Speculative Execution
  • Reading privileged memory with a side-channel
  • Spectre Side Channels
  • 有两个变种:Variant 1 和 Variant 2(Variant 3 是 Meltdown)
  • Variant 1: Bounds Check Bypass
    • CVE-2017-5753
    • 原理:访问数组的时候,通常会有边界检查,这个边界检查是通过条件分支实现的;即使边界检查失败,如果分支预测器预测它会检查成功,就会导致错误路径上的代码被执行,进而产生可以被观察到的副作用
    • 攻击方法:非 root 用户利用 eBPF 在内核态中运行自定义代码,虽然 eBPF 强制做了边界检查,但处理器会执行错误路径,从而泄漏内核态的信息
    • 具体到 Linux 内核中,有一个场景是 swapgs:在切换上下文的时候,需要切换 gs 段的基地址,那么这个基地址应该用内核态的还是用户态的,就要根据具体情况来判断,而这就会引入条件分支:
      if (coming from user space)
          swapgs
      mov %gs:<percpu_offset>, %reg
      mov (%reg), %reg1
      
    • 如果在内核态在分支预测的错误路径上,跳过了 swapgs,直接用用户态的基地址去访问内存,就产生了安全问题
    • 类似的问题还出现在从用户态复制数据的时候:需要调用 access_ok 函数来判断指针是否合法,但这个判断也需要分支预测,可能会在错误路径上产生问题
    • 缓解措施:
      • Bounds Clipping
      • 特定场景下(例如 usercopy 和 swapgs)加入 lfence 指令阻止错误路径上的 load 指令被执行
      • Supervisor-Mode Access Prevention (SMAP)
  • Variant 2: Branch Target Injection (BTI)
    • CVE-2017-5715
    • 原理:CPU 的分支预测器的状态是全局共享的,因此可以在虚拟机内控制分支预测器的状态,当 CPU 控制权回到宿主机时(比如进行一次 hypercall),CPU 会使用虚拟机准备好的分支预测器的状态来进行分支预测,从而推测执行了原本宿主机不会执行的代码,具体方法是欺骗 BTB 或间接分支预测器,注入攻击者指定的分支目的地址,使得 CPU 在预测错误路径上预测执行攻击者指定的代码;类似的方法也可以用于从用户态攻击内核态
    • 用类似的方法,还可以注入 Return Stack Buffer,也就是对 return 返回地址的预测
    • 缓解措施:
      • Retpoline
      • Indirect Branch Restricted Speculation (IBRS)
      • Enhanced Indirect Branch Restricted Speculation (Enhanced IBRS)
      • Single Threaded Indirect Branch Predictors (STIBP)
      • Indirect Branch Prediction Barrier (IBPB)
      • Supervisor-Mode Execution Prevention (SMEP)

Meltdown (Rouge Data Cache Load)

  • 论文 Meltdown: Reading Kernel Memory from User Space
  • Reading privileged memory with a side-channel
  • CVE-2017-5754
  • Rogue Data Cache Load / CVE-2017-5754 / INTEL-SA-00088
  • Meltdown 和 Spectre 一起被提出,其中 Variant 1 和 2 属于 Spectre,Variant 3 就是 Meltdown
  • 允许用户态程序读取完整的内核态地址空间
  • 原理:
    • 硬件为了性能,在验证 load 指令的权限是否正确之前,提前把 load 指令的结果传递给了后续的指令,后续发现 load 指令的的权限错误后,再进行回滚
    • 在用户态的时候,页表中内核态的地址也被映射了,只不过设置了权限位,禁止用户态访问
    • 在用户态访问内核态地址会触发异常,但如果 load 指令的结果通过某种方式带来了副作用,就可以知道 load 的结果是多少
    • 为了利用副作用来得到 load 指令的结果,首先把一个数组从缓存中清空,再把 load 指令的结果移位后作为下标去访问数组,那么副作用就是把对应位置的缓存行加载到缓存当中
    • 最后再测量缓存行的访问时间,从而知道之前哪个缓存行通过副作用被加载到缓存当中,从而得到 load 指令的结果,而这本来是只有在内核态才能访问的数据
    • 由于时间窗口比较小,如果访问的内核态的数据不在 L1 数据缓存中,可能会来不及产生副作用
  • 代码:
    ; rcx = kernel address, rbx = probe array
    mov al, byte [rcx]         ; read one byte from kernel space
    shl rax, 0xc               ; multiply by 4096
    mov rbx, qword [rbx + rax] ; access probe array
    
  • 从内核地址 rcx 读取一个字节,左移 0xc,也就是乘以 4096,再去访问 rbx 指向的数组。那么每个可能的 al 放在不同的页上,避免预取器干扰后续的测量
  • 缓解措施:
    • 软件上:Kernel Page Table Isolation
    • 硬件上:提早对权限的检查,避免以错误权限读出来的数据被用于后续的指令
  • PoC:IAIK/meltdown paboldin/meltdown-exploit,下面分析后一个 PoC:
    • 这个 PoC 先用 root 权限读取 /proc/kallsyms,找到内核符号 linux_proc_banner 的地址,这主要是为了展示,方便拿到内核态地址,实际攻击者是没有 root 权限的
    • 访问 /proc/version,使得 linux_proc_banner 符号在缓存中
    • 在用户态用 Meltdown 读取内核态的 linux_proc_banner 的结果
    • 在 Intel Xeon E5-2603 v4 上关闭 KPTI(内核 cmdline 添加 mitigations=off)后成功复现,成功读出来 %s version %s (d
      cached = 30, uncached = 316, threshold 97
      read ffffffffa2400260 = 25 % (score=999/1000)
      read ffffffffa2400261 = 73 s (score=1000/1000)
      read ffffffffa2400262 = 20   (score=1000/1000)
      read ffffffffa2400263 = 76 v (score=1000/1000)
      read ffffffffa2400264 = 65 e (score=1000/1000)
      read ffffffffa2400265 = 72 r (score=1000/1000)
      read ffffffffa2400266 = 73 s (score=1000/1000)
      read ffffffffa2400267 = 69 i (score=1000/1000)
      read ffffffffa2400268 = 6f o (score=1000/1000)
      read ffffffffa2400269 = 6e n (score=1000/1000)
      read ffffffffa240026a = 20   (score=1000/1000)
      read ffffffffa240026b = 25 % (score=1000/1000)
      read ffffffffa240026c = 73 s (score=1000/1000)
      read ffffffffa240026d = 20   (score=1000/1000)
      read ffffffffa240026e = 28 ( (score=1000/1000)
      read ffffffffa240026f = 64 d (score=1000/1000)
      VULNERABLE
      
    • 继续增加读取的字节数,可以得到完整的 linux_proc_banner 的内容:%s version %s (debian-kernel@lists.debian.org) (gcc-12 (Debian 12.2.0-14) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40) %s
  • Meltdown 的变种:Meltdown variant 3a, Meltdown-CPL-REG, Rogue System Register Read, CVE-2018-3640, lgeek/spec_poc_arm 把 Meltdown 中读取内核态地址改成在用户态读取 System Register,而这本来是在内核态才能读取的

Retbleed (Return Stack Buffer Underflow)

  • Retbleed
  • CVE-2022-29900
  • CVE-2022-29901
  • Return Stack Buffer Underflow
  • 顾名思义,针对的是 Return Stack Buffer,它是一个栈,call 的时候压栈,ret 的时候弹栈,那么如果 ret 的时候,栈已经空了,也就是所谓的 Underflow,此时会有什么样的行为?
  • 在部分处理器上,当 Return Stack Buffer 为空的时候,ret 的预测会交给间接分支预测器去预测,而间接分支预测器在之前 Spectre Variant 2 Branch Target Injection 的时候,就被攻击过了
  • Intel 处理器对 Return Stack Buffer Underflow 的处理方式有如下几种类型:
    • RSB Alternate (RSBA):RSB 为空时,用间接分支预测器预测 ret,会受到其他特权级的影响,此时会有安全问题
    • Restricted RSB Alternate (RRSBA): RSB 为空时,用间接分支预测器预测 ret,如果打开了 eIBRS,则在一个特权级下的预测不会受到其他特权级的影响(例如用户态无法影响到内核态)
    • RRSBA_DIS_S: 可以通过配置 IA32_SPEC_CTRL.RRSBA_DIS_S = 1,使得在 RSB 为空时,不去预测 ret 的目的地址
  • 缓解措施:
    • RSB stuffing/filling
    • RRSBA_DIS_S

Speculative Return Stack Overflow (SRSO/Spec rstack overflow/INCEPTION)

  • Speculative Return Stack Overflow (SRSO)
  • Inception: how a simple XOR can cause a Microarchitectural Stack Overflow
  • TECHNICAL UPDATE REGARDING SPECULATIVE RETURN STACK OVERFLOW
  • CVE-2023-20569
  • 原理:
    • ret 的目的地址由 Return Stack Buffer 来预测,预测的前提是有一个匹配的 call
    • 正常情况下,call 和 ret 是可以配对的,那么 ret 会被预测到 call 的下一条指令的地址上
    • 在 AMD 的一些处理器上,BTB 是可能出现 alias 的,也就是不同的地址会被映射到同一个 BTB Entry 上,并且无法区分
    • 如果两段代码,被映射到同一个 BTB Entry 上,第一段代码有 call,第二段代码没有,假如通过执行第一段代码,让 BTB 记录下第一段代码的 call 指令信息,那么 BTB 在预测第二段代码的时候,会认为第二段代码也有 call,即使实际上并没有
    • 在 BTB 预测第二段代码有 call 的时候,会更新 Return Stack Buffer 的状态,进而影响了后续 ret 指令的预测,即使第二段代码实际上并没有 call 指令
  • 缓解措施:
    • 硬件上,BTB 也要用 full tag 比较,不允许 alias
    • Safe RET
    • Indirect Branch Prediction Barrier (IBPB)

Gather Data Sampling (DOWNFALL)

  • Downfall Attacks
  • 论文 Downfall: Exploiting Speculative Data Gathering
  • Gather Data Sampling
  • CVE-2022-40982
  • 原理:
    • Intel 处理器在实现 SIMD Gather 指令的时候,为了提升性能,会把 Gather 读取的缓存行的数据放在一个临时的 Buffer 当中,而这个 Buffer 是没有隔离的,同一个物理核的两个逻辑核会用同一个
    • 在一个物理核的第一个逻辑核上进行正常的 SIMD Gather 指令,在同一个物理核的第二个逻辑核上也进行一个 SIMD Gather 指令,由于 Buffer 的共享,可能会导致在预测执行路径上,第一个逻辑核的 SIMD Gather 读出来的数据被转发到第二个逻辑核的 SIMD Gather 结果
    • 这个被转发的数据是错误的,之后会被回滚并纠正为正确的数据;但是回滚前,被转发的数据已经传播出去了
    • 于是在第二个逻辑核上通过缓存的侧信道,就可以推测出第一个逻辑核使用 SIMD Gather 读取了什么数据:
      // Step (i): Increase the transient window
      lea addresses_normal, %rdi
      clflush (%rdi)
      mov (%rdi), %rax
      
      // Step (ii): Gather uncacheable memory
      lea addresses_uncacheable, %rsi
      mov $0b1, %rdi
      kmovq %rdi, %k1
      vpxord %zmm1, %zmm1, %zmm1
      vpgatherdd 0(%rsi, %zmm1, 1), %zmm5{%k1}
      
      // Step (iii): Encode (transient) data to cache
      movq %xmm5, %rax
      encode_eax
      
      // Step (iv): Scan the cache
      scan_flush_reload
      
    • 为了扩大预测执行的窗口,使得信息能够通过缓存侧信道传递出来,在上述第二个逻辑核上首先访问不在缓存中的缓存行,再使用 SIMD Gather 访问无法缓存的内存
    • 进一步测试,发现不仅能够泄露第一个逻辑核上使用 SIMD Gather 指令读取的数据,用其他很多 SIMD Load 指令读出来的数据也可以通过在第二个逻辑核上用 SIMD Gather 泄露出来
    • 进一步测试,发现不仅能够泄露同一个物理核的另一个逻辑核的数据,同一个逻辑核内的数据也可以从内核态泄露到用户态
  • 缓解措施:
    • 硬件 Microcode 修复

Branch History Injection/Intra-mode BTI (IMBTI)

  • Branch History Injection/Intra-mode BTI
  • CVE-2022-0001
  • CVE-2022-0002
  • 原理:
    • 用于记录分支历史的 Branch History Buffer(BHB,实际上就是 Path History Register)没有在特权态之间隔离
    • IBRS/eIBRS 隔离了间接分支预测器的状态,但没有隔离 BHB,用户态程序可以向 BHB 注入分支历史来操控内核态的间接分支预测
  • 缓解措施:
    • Retpoline
    • Enhanced Indirect Branch Restricted Speculation (Enhanced IBRS)
    • Supervisor-Mode Execution Prevention (SMEP)
    • BHI_DIS_S
    • IPRED_DIS_S
    • Software BHB Clearing

缓解措施 Mitigations

KASLR

  • KASLR: Kernel Address Space Layout Randomization
  • 随机化内核地址,避免攻击者猜测出内核地址

Kernel Page Table Isolation (KPTI/PTI/KAISER)

  • 又称 KAISER:论文 KASLR is Dead: Long Live KASLR
  • 在用户态的页表里,不要映射整个内核态空间,只映射必须映射的部分,进入内核态后,再切换到具有完整的内核态地址空间的页表
  • 那么在用户态尝试读取内核态地址的时候,由于地址不在 TLB 当中,也就无法读取内存,不会泄漏数据
  • 修复 Meltdown 漏洞

Retpoline

  • Retpoline
  • 把间接分支替换为 call/ret 对,同时把栈上的地址改成实际的目的地址,强迫硬件用 Return Stack Buffer 做预测,但由于覆盖了返回地址,Return Stack Buffer 总是会预测错误,在错误路径上循环执行 pause-lfence-jmp 三条指令,不会被攻击者控制,但会损失性能
  • 针对间接跳转:
    # for indirect jmp:
    # before:
    jmp *%rax
    
    # after:
    call load_label # push the address of capture_ret_spec to the stack
    
    capture_ret_spec:
    pause
    lfence
    jmp capture_ret_spec
    
    load_label:
    mov %rax, (%rsp) # override the return address on the top of the stack
    ret # the return stack buffer predicts to jump to capture_ret_spec
    
  • 针对间接调用:
    # for indirect call:
    # before:
    call *%rax
    
    # after:
    jmp label2
    label0:
        call label1 # push the address of capture_ret_spec to the stack
    
    capture_ret_spec:
        pause
        lfence
        jmp capture_ret_spec
    
    label1:
        mov %rax, (%rsp) # override the return address
        ret
    
    label2:
        call label0 # use extra call to maintain real return address
        # continue execution
    
  • 修复基于间接分支的漏洞:Spectre Variant 2 Branch Target Injection

Supervisor-Mode Execution Prevention (SMEP)

  • Supervisor-Mode Execution Prevention (SMEP) 在内核态下,禁止运行(包括推测运行)用户态地址空间内的代码,这样就缩小了攻击者可利用的代码范围
  • 又称 Intel® OS Guard
  • SMEP 本身不能修复漏洞,但可以给攻击者创造更多限制

Supervisor-Mode Access Prevention (SMAP)

  • Supervisor-Mode Access Prevention (SMAP) 在内核态下,禁止访问(包括推测访问)用户态空间的内存,缩小攻击者可以利用的内存范围;代价是在需要访问用户态空间的内存时,内核需要临时关闭 SMAP
  • SMAP 相当于是把 SMEP 的限制从执行权限扩展到了读写权限
  • SMAP 本身不能修复漏洞,但可以给攻击者创造更多限制

Bounds Clipping

  • 边界检查如果用条件分支实现,可能会在分支预测的错误路径上访问边界外的内存
  • Bounds Clipping 以性能损失为代价,把边界检查的条件分支变成计算,从而避免了错误路径的预测执行
  • Linux 的实现: array_index_nospec
  • 修复 Spectre Variant 1 即 Bounds Check Bypass 漏洞

Indirect Branch Restricted Speculation (IBRS)

  • Indirect Branch Restricted Speculation (IBRS)
  • AMD64 TECHNOLOGY INDIRECT BRANCH CONTROL EXTENSION
  • 从用户态切换到内核态后,在内核态设置 IA32_SPEC_CTRL.IBRS = 1 可以保证内核态后续间接分支的预测不会被用户态影响
  • 只保护了间接分支的预测,不保护 Return Stack Buffer
  • 只避免了用户态对内核态的间接预测器的影响,但不同用户态进程间(包括虚拟机之间)还是会互相影响
  • IBRS 包括了 STIBP 的功能:如果开启了 IBRS,那么同一个物理核的两个逻辑核之间的间接分支预测器也会被隔离
  • 此外 IBRS 还会保证内核态和 Enclave 以及 SMM(System Management Mode)之间的隔离,在 Linux 里称这个功能为 IBRS_FW,表示对固件的保护
  • 修复基于间接分支的漏洞:Spectre Variant 2 Branch Target Injection

Enhanced Indirect Branch Restricted Speculation (Enhanced IBRS/eIBRS)

  • Indirect Branch Restricted Speculation (IBRS) 每次从用户态切换到内核态都要写入一次 IA32_SPEC_CTRL.IBRS,降低性能
  • AMD64 TECHNOLOGY INDIRECT BRANCH CONTROL EXTENSION
  • Enhanced IBRS:打开一次以后,总是启用 IBRS
  • 不再需要每次从用户态切换到内核态去写入 IA32_SPEC_CTRL.IBRS
  • 修复基于间接分支的漏洞:Spectre Variant 2 Branch Target Injection
  • Post-barrier Return Stack Buffer with eIBRS enabled (PBRSB-eIBRS):即使打开了 eIBRS,由于 IBRS 主要针对的是间接分支预测,对于 ret 的保护是不足的,所以需要额外的针对 Return Stack Buffer 的防护

Single Threaded Indirect Branch Predictors (STIBP)

Indirect Branch Prediction Barrier (IBPB)

  • Indirect Branch Predictor Barrier
  • AMD64 TECHNOLOGY INDIRECT BRANCH CONTROL EXTENSION
  • IBPB 是一个预测器的 Barrier,保证 Barrier 之前的指令不会影响 Barrier 之后的指令的间接分支预测
  • 这个 Barrier 通过写入 MSR IA32_PRED_CMD.IBPB = 0 实现,没有引入新的指令
  • 前面提到,IBRS 只考虑了用户态对内核态的影响,没有考虑用户态之间的影响,这可以由 IBPB 来补足
  • 修复基于间接分支的漏洞:Spectre Variant 2 Branch Target Injection

RSB filling/stuffing

  • Retpoline
  • 由于 Return Stack Buffer 在空的时候,会使用被攻击者控制的间接分支预测器来预测目的地址
  • 为了缓解这个问题,提前向 Return Stack Buffer 填充一些地址,避免出现 Underflow 的问题,这就叫 RSB filling/stuffing:
    .rept 16
        call 1f
        pause
        lfence
    
        1:
    .endr
    addq $(8 * 16), %rsp
    

Safe RET

  • Safe RET 和 Retpoline 类似,也是要强迫 ret 被预测到指定的错误路径上,避免被攻击者控制预测的地址
  • 但 Safe RET 要解决的是 Speculative Return Stack Overflow 漏洞中,BTB 出现 alias 的问题
  • 因此 Safe RET 的解决办法是,主动构造 BTB alias,先把攻击者从 BTB 中刷掉,再去进行实际的 ret

Software BHB Clearing

  • Software BHB(Branch History Buffer)Clearing 的目的是防止用用户态控制的 BHB 来进行内核态的间接分支的预测
  • 因此实现上就是在用户态切换到内核态的时候,首先调用足够次数的分支,使得 BHB 中由用户态设置的部分被刷掉,保证用于内核态间接分支预测的 BHB 是安全的

Intel MSR

  • Intel 处理器上很多缓解措施的控制都和两个 MSR 有关:IA32_SPEC_CTRL 和 IA32_PRED_CMD,这里总结一下它们的各个字段:
  • IA32_SPEC_CTRL:
    • [0]: IBRS, Indirect Branch Restricted Speculation,隔离用户态和内核态的间接分支预测
    • [1]: STIBP, Single Thread Indirect Branch Predictor,隔离同一个物理核的两个逻辑核的间接分支预测
    • [2]: SSBD,Speculative Store Bypass Disable,等先前的所有的 store 地址已知后,再预测执行 load
    • [3]: IPRED_DIS_U,Indirect Predictor Disable User,用户态下,等到间接分支的目的地址被实际计算出来后才进行后续的执行
    • [4]: IPRED_DIS_S,Indirect Predictor Disable Supervisor,内核态下,等到间接分支的目的地址被实际计算出来后才进行后续的执行
    • [5]: RRSBA_DIS_U,Restricted Return Stack Buffer Alternate User,用户态下,当 Return Stack Buffer 为空的时候,禁止预测
    • [6]: RRSBA_DIS_S,Restricted Return Stack Buffer Alternate Supervisor,内核态下,当 Return Stack Buffer 为空的时候,禁止预测
    • [7]: PSFD,禁止 Fast Store Forwarding Predictor
    • [8]: DDPD_U,用户态下禁止 Data Dependent Prefetcher
    • [10]: BHI_DIS_S,Branch History Injection Disable Supervisor,隔离用户态和内核态用于分支预测的分支历史
  • IA32_PRED_CMD:
    • [0]: IPBP,Indirect Branch Prediction Barrier,阻止 Barrier 前面的指令影响 Barrier 后面的指令的间接分支预测

TODO

  • Itlb multihit
  • L1tf
  • Mds
  • Mmio stable data
  • Reg file data sampling
  • Spec store bypass
  • Srbds
  • Tsx async abort
  • __user pointer sanitization
  • PTE Inversion
  • Zenbleed
  • untrained return thunk

Comments