..

CVE-2020-27194 or another 0-day?

关于 CVE-2020-27194 的详细介绍可以参见 patch 以及发现者的 writeup

在看触发的程序时,有一个点令我很困惑,触发的漏洞寄存器边界条件要求是 [1, 0x600000001]。

漏洞相关的代码是

	if (dst_reg->smin_value < 0 || smin_val < 0) {
				// ...
    } else {
        /* ORing two positives gives a positive, so safe to
         * cast result into s64.
         */
        dst_reg->smin_value = dst_reg->umin_value;
        dst_reg->smax_value = dst_reg->umax_value;
    }

按照作者的说法,会进入 else 分支触发漏洞代码,给 smin_value 和 smax_value 都赋值上 1,从而让 verifier 误认为其是一个常量。

然而华点是什么?拥有 [1, 0x600000001] 边界范围的寄存器,smin_value 应该会小于 0。例如 0xffffffff 在 32bit 情况下是一个负数,根本就不会进入 else 分支!


调试发现此时 verifier 认为 smin_value 为 1,溯源一下发现漏洞点出现在 __reg_combine_64_into_32() 函数之中

if (__reg64_bound_s32(reg->smin_value) {
		reg->s32_min_value = (s32)reg->smin_value;
		reg->s32_max_value = (s32)reg->smax_value;
	}

代码认为如果 smin_value 在 32b 能表示的范围,那么 s32_min_value 就应该等于 smin_value。然而这是不正确的,就如同上面那个 [1, 0x600000001] 边界的寄存器,其 s32_min_value 被错误赋值为了 1。

漏洞!0day!CVE!

然而我是属于 2022 年的人,这个漏洞已经在 patch 中被修复了 ; )

@@ -1314,10 +1312,10 @@ static void __reg_combine_64_into_32(struct bpf_reg_state *reg)
 {
 	__mark_reg32_unbounded(reg);
 
-	if (__reg64_bound_s32(reg->smin_value))
+	if (__reg64_bound_s32(reg->smin_value) && __reg64_bound_s32(reg->smax_value)) {
 		reg->s32_min_value = (s32)reg->smin_value;
-	if (__reg64_bound_s32(reg->smax_value))
 		reg->s32_max_value = (s32)reg->smax_value;
+	}
 	if (__reg64_bound_u32(reg->umin_value))
 		reg->u32_min_value = (u32)reg->umin_value;
 	if (__reg64_bound_u32(reg->umax_value))

确实,当 smin_value 和 smax_value 都在 32bit 可表示范围内时逻辑才成立。


有符号边界这样计算有问题,无符号边界就可以了?查看无符号边界的计算方式

	if (__reg64_bound_u32(reg->umin_value)) {
		reg->u32_min_value = (u32)reg->umin_value;
		reg->u32_max_value = (u32)reg->umax_value;
	}

考虑刚才的例子 [1, 0x600000001],u32_min_value 被计算为了 1,然而正确值应该为 0,u32_max_value 也被计算为了 1,然而正确值应该为 0xffffffff!

漏洞!0day!CVE!

然而这个漏洞也在 patch 中被修复了,而且分配了 CVE-2021-31440,还参加了 pwn2own ;(