Linux内核eBPF verifier边界计算错误漏洞分析与利用(CVE-2021-31440)

发布时间 2021-05-31

漏洞背景


近日,ZDI官网披露一个Linux内核eBPF verifier边界计算错误漏洞,该漏洞源于eBPF验证器在Linux内核中没有正确计算64位转32位操作的寄存器边界,导致本地攻击者可以利用此缺陷进行内核信息泄露或特权提升,该漏洞编号为CVE-2021-31440。


影响范围与防护措施


(1)影响范围Linux-5.7 ~ Linux- 5.11.15Ubuntu 20.10

(2)防护措施

及时更新升级内核将kernel.unprivileged_bpf_disabled.sysctl设置为1,临时限制普通用户权限 


漏洞原理与调试分析


(1)漏洞原理

该漏洞和CVE-2020-8835,CVE-2020-27194这两个漏洞的原理类似,均是在32位和64位之间进行转换操作时,错误计算了寄存器的约束边界,导致可以绕过验证器检查实现越界读写。缺陷代码出现在kernel/bpf/verifier.c的__reg_combine_64_into_32()函数中,该函数是在commit_id:3f50f132d840中引入的,该功能实现了用64位寄存器上的已知范围来推断该寄存器低32位的范围,但是同样出现了类似的计算错误,该函数实现如下:


1.jpg


行1316,如果smin_value和smax_value都在带符号的32位整数范围内,则将相应地更新32位的带符号范围大小,对于有符号范围来说,这种操作是正确的。接着看,在无符号范围的相应逻辑中,对umin_value和umax_value分别在行1320和行1322进行了检查。这里逻辑不正确,例如设置dreg->umin_value=1,dreg->umax_value=1<<32,即0x100000000,当进行如上操作后,reg->u32_min_value设置为1,这个是正确的,但是reg->u32_max_value却变成了0,高位被截断。这时reg寄存器的低32位范围已经混乱。对于验证器来说是混乱的,但是运行态时,reg的范围是正常的。其实对于有符号边界的情况,已经进行了修改。补丁commit为:b02709587ea3,关键补丁代码如下所示:


2.jpg


而未对无符号边界的情况进行解决。该漏洞补丁中,修改为同时对umin_value和umax_value进行了判断,如下所示:


3.jpg


(2)调试分析


首先将BPF_REG_7寄存器设置为1<<32,即0x10000000,并通过两个连续的NEG指令使验证器无法跟踪寄存器的范围,同时可以保证寄存器的值在运行时不变。可以通过如下BPF指令实现:


4.jpg


执行到LSH指令时,如下所示:


5.jpg


此时BPF_REG_7寄存器的状态如下所示:


6.jpg


执行完LSH后,此时BPF_REG_7寄存器的状态如下图所示:


7.jpg


但是此时umin_value也是0x100000000,还需将umin_value设置成0x1,可以通过如下eBPF指令实现:


8.jpg


断点命中后,调用栈如下所示:


9.jpg


对BPF_JGE和BPF_JGT指令进行处理,这里不是32位指令操作,执行如下代码:


10.jpg


如果R7 >= 0x1,则验证器正确分支上,true_reg->umin_value设置为true_reg->umin_value和true_umin之间的最大值,这里设置成true_umin,为0x1。然后调用__reg_combine_64_into_32()函数更新一下true_reg的范围。如下代码所示:


11


进入该函数后,首先判断有符号范围的情况,如下代码所示:


13.jpg


这里同时判断有符号大小值,结果不为真,不进入if语句,因此不会修改32位的有符号大小值,打印true_reg的状态如下所示:


14.jpg


然后开始判断无符号最小值的情况,结果为真,然后修改32位无符号最小值,如下代码:


15.jpg


 由于这里分开进行判断,可以成功设置reg->u32_min_value为0x1。接下来判断无符号最大值,reg->umax_value为0xffffffffffffffff,大于0xffffffff。因此条件不为真,不修改reg->u32_max_value。最后true_reg的状态如下所示:


16.jpg


将寄存器的umin_value和u32_min_value都设置为0x1。接下来通过如下eBPF指令组合将u32_max_value也设置为0x1。如下所示:


17.jpg


该指令为W7<=0x1,W7为32位寄存器。命中断点后,调用栈如下所示:


18.jpg


如果W7<=0x1,接下来设置正确分支下的true_reg->u32_max_value,如下图所示:


19.jpg


行7200,将true_reg->u32_max_value设置为true_umax,为0x1。此时true_reg的状态如下所示:


20.jpg


然后调用__reg_combine_32_into_64()函数更新true_reg的范围,如下所示:


21.jpg


更新范围后,最后true_reg的状态如下所示:


22.jpg


此时在验证器的视角中,R7寄存器的32位范围是固定值,为常数0x1。接下来通过如下eBPF组合将R7变换成0,如下所示:


23.jpg


首先通过MOV32将R7的64位范围也设置常数0x1。执行完MOV32指令后,在验证器的视角下R7寄存器的状态如下所示:


24.jpg


而在运行时,R7的值为1<<32,即0x100000000,低32为0,即R7的32位范围为常数0,然后通过MUL和ADD两次操作,将R7寄存器的状态转换成在验证器的视角下为0x0,在运行时为0x1,最终便可以实现越界读写。


漏洞复现


在Linux-5.11.0内核版本的特定测试环境中进行漏洞利用测试,成功提权。


25.jpg


参考链接


1.https://www.zerodayinitiative.com/blog/2021/5/26/cve-2021-31440-an-incorrect-bounds-calculation-in-the-linux-kernel-ebpf-verifier
2.https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=10bf4e83167cc68595b85fd73bb91e8f2c086e36
3.https://github.com/torvalds/linux/commit/b02709587ea3d699a608568ee8157d8db4fd8cae
4.https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-31440


启明星辰积极防御实验室(ADLab)


ADLab成立于1999年,是中国安全行业最早成立的攻防技术研究实验室之一,微软MAPP计划核心成员,“黑雀攻击”概念首推者。截止目前,ADLab已通过CVE累计发布安全漏洞近1100个,通过 CNVD/CNNVD累计发布安全漏洞1000余个,持续保持国际网络安全领域一流水准。实验室研究方向涵盖操作系统与应用系统安全研究、智能终端安全研究、物联网智能设备安全研究、Web安全研究、工控系统安全研究、云安全研究。研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。


adlab.jpg