记一次x86 kvm虚机缺失 tlb flush 引发的 CVE 漏洞

本文聚焦Linux 5.5版本以下,x86 intel芯片KVM虚机环境中因TLB flush缺失触发的CVE-2019-3016漏洞。介绍了延迟远程TLB flush机制及原理,分析了TLB flush缺失原因及漏洞触发场景,还阐述了社区给出的修复补丁及作用。

1 背景介绍

linux 5.5 版本以下才会触发,之后的版本已经修复该问题。

触发现象:
在x86 intel 芯片上的 kvm 虚机环境中:当有多个进程或者多个线程在频繁或者多次执行 malloc,write,read,free 操作时,有概率触发任务程序崩溃产生 core dump 或者程序 abort。

该问题触发对应 CVE-2019-3016,并且社区给出了对应的修复 patch:Merge branch ‘cve-2019-3016’ into kvm-next-5.6
首先描述一下该 CVE 漏洞原文:

上述崩溃触发原因是缺少 TLB flush,这可能使运行 KVM 客户机中的进程访问到它不应该访问的客户机中的内存位置。
(解释一下上面的现象:对于一个正常程序来说,如果触发该问题,那么可能读取到的数据并不是原来程序所期望的数据,如原来内存中保存的是一个可访问的指针,在问题触发后,数据不再是可访问的指针,而是一个非法地址,当程序按照原有意图访问指针内容时,则有可能触发 data abort,导致程序被操作系统 kill,并产生 core dump。如果程序内部有一些 assert,比如 glibc 库中检测相关元数据,如果不是原有数据则可能调用 abort() 退出程序。
这是一些正常情况,如果该程序故意利用该漏洞,那么极有可能构建出特定环境,访问到其他程序使用的内存数据,从而执行一些恶意操作,因此该问题被定义为 CVE 漏洞)
这个问题仅限于 host 内核 4.10 以后版本,客户机运行 4.16 以后内核,并且客户机启用半虚拟化支持。该问题主要影响 AMD 处理器,但不能排除 intel cpu 也有该问题(因为从触发原理上来看,intel 也会触发)。

再介绍一下修复补丁的内容:

当远程 vcpu (这里指从当前 vcpu 角度来看,处于其他物理 cpu 的 vcpu 被称为远程 vcpu)不运行时,KVM 管理程序可以为客户机提供延迟远程 tlb flush 能力(该能力用于提高vcpu 运行性能)。当使用此特性时,tlb flush 只会在远程 vcpu 计划再次运行(再次运行指调度到该 vcpu 运行)时发生,这操作可以避免不必要的和昂贵的 ipi 操作(提供性能的关键就是避免 ipi 操作,后续介绍原理)。
在某些情况下,当客户机发起远程延迟 tlb flush 操作时,管理机可能会错过该请求。也有可能客户机错误地认为它已经将远程 vcpu 标记为需要刷新,而实际上该请求已经被管理机意外清除。
在这两种情况下,这都将导致 vcpu 中出现无效的 tlb 转换,可能允许访问客户机地址空间中不应该访问的内存位置。

上面的描述可能很抽象,接下来先详细了解下什么是延迟远程 tlb flush 操作,以及何时会执行远程 tlb flush 操作。

2 vcpu 延迟远程 tlb flush 机制及原理

TLB 全称是 Translation Lookaside Buffer。在开启 mmu 处理器上,当处理器取指或者访问内存时都需要进行地址翻译,即把虚拟地址翻译成物理地址。而翻译过程对于 cpu 来说是一个漫长的过程,需要经历从 pgd 到几个 level 的 translation table,最终找到实际的物理页,从而产生性能开销。为了提升性能,mmu 新增了 tlb 单元,同 cache 类似,tlb 把翻译后缓存条目保存在高速缓存中,当访问地址时,首先从 tlb cache 中寻找翻译后的条目使用,从而避免了翻译过程。当程序修改了地址映射关系,访问属性等页表时,则对应的 tlb 条目需要刷新或者无效化,以便后续能正确访问更新后的映射关系。

tlb 一致性问题:
tlb 也是一种 cache,因此当在多处理器系统中,tlb 条目的副本可能缓存在不同处理器的缓存中,因此存在一致性问题,一般而言处理器不维护该一致性(尤其是 x86,arm64 可以通过 inner share 属性来保证 tlb 缓存在不同处理器之间的一致性)。
因此一旦修改了页表属性或者映射关系,需要操作系统将所有使用该页表的 cpu 对应的 tlb cache 进行 tlb flush 操作,使其缓存 tlb 无效化,并在再次访问修改后的地址时重新进行地址翻译工作。

介绍了上面两个概念,接下来看看操作系统是如何来执行 tlb flush 的。针对该问题,我们不讨论内核自身的 tlb flush,主要讨论用户程序对内核发起的相关请求。

当用户程序执行 malloc,free,brk,mmap。munmap,mprotoct等申请内存,释放内存,修改映射属性等操作时将会触发 tlb flush 路径。接下来以 munmap 系统调用为例:
当用户程序使用 free 释放内存时,对于 glibc 在一定条件下会执行 munmap 释放一大块内存(这里不展开),最终会通过 sys_munmap 系统调用接触相关内存映射,arm64/x86 有如下路径:

SYSCALL_DEFINE2(munmap, unsigned long, addr, size_t, len)
  -> do_munmap
    -> unmap_region
      -> unmap_vmas
      -> free_pgtables
      -> tlb_finish_mmu
        -> arch_tlb_finish_mmu(tlb, start, end, force);
          -> tlb_flush_mmu
            -> tlb_flush_mmu_tlbonly
              -> tlb_flush
                -> __flush_tlb_range (arm64)
                -> flush_tlb_mm_range (x86)

可以看到相关任务在解除某段地址映射最后会调用 tlb_flush 来刷新相关缓存 tlb 以此来保证一致性。
对于 arm64 有如下代码:

static inline void __flush_tlb_range(struct vm_area_struct *vma,
				     unsigned long start, unsigned long end,
				     unsigned long stride, bool last_level)
{
   
   
...
  	start = __TLBI_VADDR(start, asid);
	end = __TLBI_VADDR(end, asid);

	dsb(ishst);
	for (addr = start; addr < end; addr += stride) {
   
   
		if (last_level) {
   
   
			__tlbi(vale1is, addr);
			__tlbi_user(vale1is, addr);
		} else {
   
   
			__tlbi(vae1is, addr);
			__tlbi_user(vae1is, addr);
		}
	}
	dsb(ish);
}

#define __TLBI_0(op, arg) asm ("tlbi " #op "\n"				       \
		   ALTERNATIVE("nop\n			nop",		       \
			       "dsb ish\n		tlbi " #op,	       \
			       ARM64_WORKAROUND_REPEAT_TLBI,		       \
			       CONFIG_QCOM_FALKOR_ERRATUM_1009)		       \
			    : : )

#define __TLBI_1(op, arg) asm ("tlbi " #op ", %0\n"			       \
		   ALTERNATIVE("nop\n			nop",		       \
			       "dsb ish\n		tlbi " #op ", %0",     \
			       ARM64_WORKAROUND_REPEAT_TLBI,		       \
			       CONFIG_QCOM_FALKOR_ERRATUM_1009)		       \
			    : : "r" (arg))

#define __TLBI_N(op, arg, n, ...) __TLBI_##n(op, arg)

#define __tlbi(op, ...)		__TLBI_N(op, ##__VA_ARGS__, 1, 0)

arm64 最终使用 tlbi op 来无效化相关 tlb 缓存,其中/opvale1is,is 代表 inner shareable,这里意思就是无效化内部可共享相关的所有 tlb 缓存。换句话说就是,不仅是本地 cpu 对应的 tlb 缓存需要无效化,其他 cpu 对应的只要是 inner shareable 属性页表也会有 tlb 无效化操作,这是由硬件来保证的。如果op不带is则只会无效化本地 cpu 的 tlb 缓存。

再来看一下对于一个 x86 的代码,x86的flush_tlb_mm_range分为 host os 和 guest os 实现:

void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start,
				unsigned long end, unsigned long vmflag)
{
   
   
...
	if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) {
   
   
		VM_WARN_ON(irqs_disabled());
		local_irq_disable();
		flush_tlb_func_local(&info, TLB_LOCAL_MM_SHOOTDOWN);
		local_irq_enable();
	}

	if (cpumask_any_but(mm_cpumask(mm
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值