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指令使验证器无法跟踪寄存器的范围 ,同时可以保证寄存器的值在运行时稳定 �?梢酝ü缦翨PF指令实现:


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