Message ID | 20230314203424.4015351-2-xukuohai@huaweicloud.com |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:5915:0:0:0:0:0 with SMTP id v21csp1615669wrd; Tue, 14 Mar 2023 00:37:20 -0700 (PDT) X-Google-Smtp-Source: AK7set8O1e/V2luAo75NzFaM7ntz+SVjhJA8I654IGdJv6N12yl5gnUlI4gsnEQzAz4DLlDuoE3m X-Received: by 2002:a17:90b:1d88:b0:237:99b8:4eef with SMTP id pf8-20020a17090b1d8800b0023799b84eefmr38577345pjb.9.1678779440100; Tue, 14 Mar 2023 00:37:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1678779440; cv=none; d=google.com; s=arc-20160816; b=m6o1wWgvMP+PGoZDIzo70zKrSbNtWWUFHyHgTAW5m/bMeNGF1FQqCet5noHvsno34M JHVBC5qx0RVdTSZar4oEg/xNPjfDyq9jLg+Nb52vtwiWm9NgTqZW6ujuHkRLB67Z8Aa3 vQiTGPYeg+13nJlf3BP1lUEbjB91+eF+9OpqBzDYU06v5JSZ9GKz7VXg2DiTstWe5bcW YcXqA+KvhcPeOCR7q2O/LDpW8pbdG49WN3yOvua8p6M64K0gYTbhnjQpcYLFkeTLbX67 NQT12B8vBK+WvjCJiYbLDz67AzsoPWqkyuG4YkgxmbXIkAjzxZyvRd3cm/jAWokYXQcV 6qLA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=GOMcIvq652GFNLaSpdD/PhQDeWEOMjJdBoYf58np2d4=; b=QEuwm1F8V/U+a2c7HeRm49LqpteVUoWXiN98m0z3FCNSxXz6wzh5+mMOyBL2ErTomD GW/tWbPHXigcwuslhPg95Ja9XtdmtgEIaLCKfLaz4v54uXgGZ1H+gqgno2wjyDl0OPc6 aRQXXUjO3rOSO1Qc2v+amIikn3OcdZS1PUprYKR7OCSbBfC+Q07D/vq/0LIAyp5N2qTG akDNAsUSj0xpyJXWkMjqa87Y3+Sjctr96aboOwQ2KP0/dfrqWOF2Yqq1KMitvYa307un WOB8bw/pWuuMWkzvFBGjtMJiMrNlO5VFY1hgrNWfByA6Saeje2lTSlxdax1ChSdqWN+X yM3Q== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id v22-20020a17090ad59600b0023c1f976f2dsi1822251pju.85.2023.03.14.00.37.05; Tue, 14 Mar 2023 00:37:20 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230227AbjCNHf0 (ORCPT <rfc822;realc9580@gmail.com> + 99 others); Tue, 14 Mar 2023 03:35:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49886 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230091AbjCNHfX (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Tue, 14 Mar 2023 03:35:23 -0400 Received: from dggsgout11.his.huawei.com (dggsgout11.his.huawei.com [45.249.212.51]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5084B888A1; Tue, 14 Mar 2023 00:35:21 -0700 (PDT) Received: from mail02.huawei.com (unknown [172.30.67.143]) by dggsgout11.his.huawei.com (SkyGuard) with ESMTP id 4PbQKd0T2pz4f3l8d; Tue, 14 Mar 2023 15:35:17 +0800 (CST) Received: from k01.huawei.com (unknown [10.67.174.197]) by APP4 (Coremail) with SMTP id gCh0CgBnF6utIxBk_rhhFQ--.63403S3; Tue, 14 Mar 2023 15:35:18 +0800 (CST) From: Xu Kuohai <xukuohai@huaweicloud.com> To: bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Alexei Starovoitov <ast@kernel.org>, Daniel Borkmann <daniel@iogearbox.net>, John Fastabend <john.fastabend@gmail.com>, Andrii Nakryiko <andrii@kernel.org>, Martin KaFai Lau <martin.lau@linux.dev>, Song Liu <song@kernel.org>, Yonghong Song <yhs@fb.com>, KP Singh <kpsingh@kernel.org>, Stanislav Fomichev <sdf@google.com>, Hao Luo <haoluo@google.com>, Jiri Olsa <jolsa@kernel.org>, Mykola Lysenko <mykolal@fb.com>, Shuah Khan <shuah@kernel.org> Subject: [PATCH bpf-next v2 1/2] bpf: Fix a umin > umax reg bound error Date: Tue, 14 Mar 2023 16:34:23 -0400 Message-Id: <20230314203424.4015351-2-xukuohai@huaweicloud.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230314203424.4015351-1-xukuohai@huaweicloud.com> References: <20230314203424.4015351-1-xukuohai@huaweicloud.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-CM-TRANSID: gCh0CgBnF6utIxBk_rhhFQ--.63403S3 X-Coremail-Antispam: 1UD129KBjvJXoW3Gw15XF15tw4DGFyUXFy3XFb_yoWDXr1Dpr W3Gr1jgF4kWay8Zr4ktwsrXrZ5Ar18Ja4kCr9Ykry8tr13Wr9IyrnrKrWUtFyxAry0qa1x Jw1DZayq9w4UtFUanT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPab4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M280x2IEY4vEnII2IxkI6r4Y6ry7M2 8IrcIa0xkI8VA2jI8067AKxVWUGwA2048vs2IY020Ec7CjxVAFwI0_Gr0_Xr1l8cAvFVAK 0II2c7xJM28CjxkF64kEwVA0rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWDJVCq3wA2z4 x0Y4vE2Ix0cI8IcVCY1x0267AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_GcCE3s1l 84ACjcxK6I8E87Iv6xkF7I0E14v26rxl6s0DM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I 8CrVACY4xI64kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AK xVWUJVW8JwAm72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxV A2Y2ka0xkIwI1l42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAq x4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r4a6r W5MIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Jr0_JF4lIxAIcVC0I7IYx2IY6xkF 7I0E14v26r4j6F4UMIIF0xvE42xK8VAvwI8IcIk0rVWUCVW8JwCI42IY6I8E87Iv67AKxV WUJVW8JwCI42IY6I8E87Iv6xkF7I0E14v26r4j6r4UJbIYCTnIWIevJa73UjIFyTuYvjxU s18BUUUUU X-CM-SenderInfo: 50xn30hkdlqx5xdzvxpfor3voofrz/ X-CFilter-Loop: Reflected X-Spam-Status: No, score=1.3 required=5.0 tests=BAYES_00,DATE_IN_FUTURE_12_24, SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=3.4.6 X-Spam-Level: * X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1760327830051555284?= X-GMAIL-MSGID: =?utf-8?q?1760327830051555284?= |
Series |
bpf: Fix a umin > umax reg bound error
|
|
Commit Message
Xu Kuohai
March 14, 2023, 8:34 p.m. UTC
From: Xu Kuohai <xukuohai@huawei.com> After commit 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking"), the following bpf prog is rejected: 0: (61) r2 = *(u32 *)(r1 +0) ; R2_w=pkt(off=0,r=0,imm=0) 1: (61) r3 = *(u32 *)(r1 +4) ; R3_w=pkt_end(off=0,imm=0) 2: (bf) r1 = r2 3: (07) r1 += 1 4: (2d) if r1 > r3 goto pc+8 5: (71) r1 = *(u8 *)(r2 +0) ; R1_w=scalar(umax=255,var_off=(0x0; 0xff)) 6: (18) r0 = 0x7fffffffffffff10 8: (0f) r1 += r0 ; R1_w=scalar(umin=0x7fffffffffffff10,umax=0x800000000000000f) 9: (18) r0 = 0x8000000000000000 11: (07) r0 += 1 12: (ad) if r0 < r1 goto pc-2 13: (b7) r0 = 0 14: (95) exit And the verifier log says: [...] from 12 to 11: R0_w=-9223372036854775794 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) 11: (07) r0 += 1 ; R0_w=-9223372036854775793 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) 13: safe from 12 to 11: R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) 11: (07) r0 += 1 ; R0_w=-9223372036854775792 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775792 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) 13: safe [...] What can be seen here is that r1->umin grows blindly and becomes bigger than r1->umax. The reason is because the loop does not terminate, when r0 increases to r1->umax_value, the following code in reg_set_min_max() sets r1->umin_value to r1->umax_value + 1 blindly: case BPF_JGT: { if (is_jmp32) { [...] } else { u64 false_umax = opcode == BPF_JGT ? val : val - 1; u64 true_umin = opcode == BPF_JGT ? val + 1 : val; false_reg->umax_value = min(false_reg->umax_value, false_umax); true_reg->umin_value = max(true_reg->umin_value, true_umin); } break; } Why the loop does not terminate is because tnum_is_const(src_reg->var_off) always returns false, causing is_branch_taken() to be skipped: if (src_reg->type == SCALAR_VALUE && !is_jmp32 && tnum_is_const(src_reg->var_off)) { pred = is_branch_taken(dst_reg, // could not reach here src_reg->var_off.value, opcode, is_jmp32); } Why tnum_is_const(src_reg->var_off) always returns false is because r1->umin_value starts increasing from 0x7fffffffffffff10, always bigger than U32_MAX, causing the __reg_combine_64_into_32() to mark the lower 32 bits unbounded, i.e. not a constant. To fix it: 1. avoid increasing reg lower bound to a value bigger than the upper bound, or decreasing reg upper bound to a value smaller than the lower bound. 2. set 32-bit min/max values to the lower 32 bits of the 64-bit min/max values when the 64-bit min/max values are equal. Fixes: 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking") Signed-off-by: Xu Kuohai <xukuohai@huawei.com> --- kernel/bpf/verifier.c | 143 +++++++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 50 deletions(-)
Comments
On 3/14/23 9:34 PM, Xu Kuohai wrote: > From: Xu Kuohai <xukuohai@huawei.com> > > After commit 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking"), > the following bpf prog is rejected: > > 0: (61) r2 = *(u32 *)(r1 +0) ; R2_w=pkt(off=0,r=0,imm=0) > 1: (61) r3 = *(u32 *)(r1 +4) ; R3_w=pkt_end(off=0,imm=0) > 2: (bf) r1 = r2 > 3: (07) r1 += 1 > 4: (2d) if r1 > r3 goto pc+8 > 5: (71) r1 = *(u8 *)(r2 +0) ; R1_w=scalar(umax=255,var_off=(0x0; 0xff)) > 6: (18) r0 = 0x7fffffffffffff10 > 8: (0f) r1 += r0 ; R1_w=scalar(umin=0x7fffffffffffff10,umax=0x800000000000000f) > 9: (18) r0 = 0x8000000000000000 > 11: (07) r0 += 1 > 12: (ad) if r0 < r1 goto pc-2 > 13: (b7) r0 = 0 > 14: (95) exit > > And the verifier log says: > > [...] > > from 12 to 11: R0_w=-9223372036854775794 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) > 11: (07) r0 += 1 ; R0_w=-9223372036854775793 > 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) > 13: safe > > from 12 to 11: R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) > 11: (07) r0 += 1 ; R0_w=-9223372036854775792 > 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775792 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) > 13: safe > > [...] > > What can be seen here is that r1->umin grows blindly and becomes bigger > than r1->umax. The reason is because the loop does not terminate, when > r0 increases to r1->umax_value, the following code in reg_set_min_max() > sets r1->umin_value to r1->umax_value + 1 blindly: > > case BPF_JGT: > { > if (is_jmp32) { > [...] > } else { > u64 false_umax = opcode == BPF_JGT ? val : val - 1; > u64 true_umin = opcode == BPF_JGT ? val + 1 : val; > > false_reg->umax_value = min(false_reg->umax_value, false_umax); > true_reg->umin_value = max(true_reg->umin_value, true_umin); > } > break; > } > > Why the loop does not terminate is because tnum_is_const(src_reg->var_off) > always returns false, causing is_branch_taken() to be skipped: > > if (src_reg->type == SCALAR_VALUE && > !is_jmp32 && tnum_is_const(src_reg->var_off)) { > pred = is_branch_taken(dst_reg, // could not reach here > src_reg->var_off.value, > opcode, > is_jmp32); > } > > Why tnum_is_const(src_reg->var_off) always returns false is because > r1->umin_value starts increasing from 0x7fffffffffffff10, always bigger > than U32_MAX, causing the __reg_combine_64_into_32() to mark the lower > 32 bits unbounded, i.e. not a constant. > > To fix it: > 1. avoid increasing reg lower bound to a value bigger than the upper bound, > or decreasing reg upper bound to a value smaller than the lower bound. > 2. set 32-bit min/max values to the lower 32 bits of the 64-bit min/max values > when the 64-bit min/max values are equal. Should both these be separate patches, meaning are both of them strictly required as one logical entity or not? From your description it's not really clear wrt reg_{inc,dec}_{u32,u64}_{min,max} and if this is mainly defensive or required. Also, while you describe to some degree how we get here, there is no analysis on why your proposed changes are safe. If you want to make the verifier less conservative to start accepting such progs, can you then elaborate on the latter? > Fixes: 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking") > Signed-off-by: Xu Kuohai <xukuohai@huawei.com> > --- > kernel/bpf/verifier.c | 143 +++++++++++++++++++++++++++--------------- > 1 file changed, 93 insertions(+), 50 deletions(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 2bbd89279070..b775b50353d6 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -2223,14 +2223,21 @@ static bool __reg64_bound_u32(u64 a) > > static void __reg_combine_64_into_32(struct bpf_reg_state *reg) > { > + s64 smin = reg->smin_value; > + s64 smax = reg->smax_value; > + u64 umin = reg->umin_value; > + u64 umax = reg->umax_value; > + > __mark_reg32_unbounded(reg); > - if (__reg64_bound_s32(reg->smin_value) && __reg64_bound_s32(reg->smax_value)) { > - reg->s32_min_value = (s32)reg->smin_value; > - reg->s32_max_value = (s32)reg->smax_value; > + if ((__reg64_bound_s32(smin) && __reg64_bound_s32(smax)) || > + smin == smax) { Did you look into debugging reg_bounds_sync()? Assumption for constant register is register_is_const() and it's explicitly looking at var_off. > + reg->s32_min_value = (s32)smin; > + reg->s32_max_value = (s32)smax; > } > - if (__reg64_bound_u32(reg->umin_value) && __reg64_bound_u32(reg->umax_value)) { > - reg->u32_min_value = (u32)reg->umin_value; > - reg->u32_max_value = (u32)reg->umax_value; > + if ((__reg64_bound_u32(umin) && __reg64_bound_u32(umax)) || > + umin == umax) { > + reg->u32_min_value = (u32)umin; > + reg->u32_max_value = (u32)umax; > } > reg_bounds_sync(reg); > } > @@ -12828,6 +12835,62 @@ static int is_pkt_ptr_branch_taken(struct bpf_reg_state *dst_reg, > return -1; > } > > +static void reg_inc_u32_min(struct bpf_reg_state *reg, u32 val) > +{ > + reg->u32_min_value = max(reg->u32_min_value, val); > + if (reg->u32_min_value > reg->u32_max_value) > + reg->u32_min_value = reg->u32_max_value; > +} > + > +static void reg_dec_u32_max(struct bpf_reg_state *reg, u32 val) > +{ > + reg->u32_max_value = min(reg->u32_max_value, val); > + if (reg->u32_max_value < reg->u32_min_value) > + reg->u32_max_value = reg->u32_min_value; > +} > + > +static void reg_inc_s32_min(struct bpf_reg_state *reg, s32 val) > +{ > + reg->s32_min_value = max(reg->s32_min_value, val); > + if (reg->s32_min_value > reg->s32_max_value) > + reg->s32_min_value = reg->s32_max_value; > +} > + > +static void reg_dec_s32_max(struct bpf_reg_state *reg, s32 val) > +{ > + reg->s32_max_value = min(reg->s32_max_value, val); > + if (reg->s32_max_value < reg->s32_min_value) > + reg->s32_max_value = reg->s32_min_value; > +} > + > +static void reg_inc_u64_min(struct bpf_reg_state *reg, u64 val) > +{ > + reg->umin_value = max(reg->umin_value, val); > + if (reg->umin_value > reg->umax_value) > + reg->umin_value = reg->umax_value; > +} > + > +static void reg_dec_u64_max(struct bpf_reg_state *reg, u64 val) > +{ > + reg->umax_value = min(reg->umax_value, val); > + if (reg->umax_value < reg->umin_value) > + reg->umax_value = reg->umin_value; > +} > + > +static void reg_inc_s64_min(struct bpf_reg_state *reg, s64 val) > +{ > + reg->smin_value = max(reg->smin_value, val); > + if (reg->smin_value > reg->smax_value) > + reg->smin_value = reg->smax_value; > +} > + > +static void reg_dec_s64_max(struct bpf_reg_state *reg, s64 val) > +{ > + reg->smax_value = min(reg->smax_value, val); > + if (reg->smax_value < reg->smin_value) > + reg->smax_value = reg->smin_value; > +} All this feels more like a workaround and papering over the issue. > /* Adjusts the register min/max values in the case that the dst_reg is the > * variable register that we are working on, and src_reg is a constant or we're > * simply doing a BPF_K check. > @@ -12898,76 +12961,56 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg, > case BPF_JGE: > case BPF_JGT: > { > - if (is_jmp32) { > - u32 false_umax = opcode == BPF_JGT ? val32 : val32 - 1; > - u32 true_umin = opcode == BPF_JGT ? val32 + 1 : val32; > + bool neq = (opcode == BPF_JGT); > > - false_reg->u32_max_value = min(false_reg->u32_max_value, > - false_umax); > - true_reg->u32_min_value = max(true_reg->u32_min_value, > - true_umin); > + if (is_jmp32) { > + reg_dec_u32_max(false_reg, neq ? val32 : val32 - 1); > + reg_inc_u32_min(true_reg, neq ? val32 + 1 : val32); > } else { > - u64 false_umax = opcode == BPF_JGT ? val : val - 1; > - u64 true_umin = opcode == BPF_JGT ? val + 1 : val; > - > - false_reg->umax_value = min(false_reg->umax_value, false_umax); > - true_reg->umin_value = max(true_reg->umin_value, true_umin); > + reg_dec_u64_max(false_reg, neq ? val : val - 1); > + reg_inc_u64_min(true_reg, neq ? val + 1 : val);
On 3/17/23 11:24 PM, Daniel Borkmann wrote: > On 3/14/23 9:34 PM, Xu Kuohai wrote: >> From: Xu Kuohai <xukuohai@huawei.com> >> >> After commit 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking"), >> the following bpf prog is rejected: >> >> 0: (61) r2 = *(u32 *)(r1 +0) ; R2_w=pkt(off=0,r=0,imm=0) >> 1: (61) r3 = *(u32 *)(r1 +4) ; R3_w=pkt_end(off=0,imm=0) >> 2: (bf) r1 = r2 >> 3: (07) r1 += 1 >> 4: (2d) if r1 > r3 goto pc+8 >> 5: (71) r1 = *(u8 *)(r2 +0) ; R1_w=scalar(umax=255,var_off=(0x0; 0xff)) >> 6: (18) r0 = 0x7fffffffffffff10 >> 8: (0f) r1 += r0 ; R1_w=scalar(umin=0x7fffffffffffff10,umax=0x800000000000000f) >> 9: (18) r0 = 0x8000000000000000 >> 11: (07) r0 += 1 >> 12: (ad) if r0 < r1 goto pc-2 >> 13: (b7) r0 = 0 >> 14: (95) exit >> >> And the verifier log says: >> >> [...] >> >> from 12 to 11: R0_w=-9223372036854775794 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 11: (07) r0 += 1 ; R0_w=-9223372036854775793 >> 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 13: safe >> >> from 12 to 11: R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 11: (07) r0 += 1 ; R0_w=-9223372036854775792 >> 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775792 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 13: safe >> >> [...] >> >> What can be seen here is that r1->umin grows blindly and becomes bigger >> than r1->umax. The reason is because the loop does not terminate, when >> r0 increases to r1->umax_value, the following code in reg_set_min_max() >> sets r1->umin_value to r1->umax_value + 1 blindly: >> >> case BPF_JGT: >> { >> if (is_jmp32) { >> [...] >> } else { >> u64 false_umax = opcode == BPF_JGT ? val : val - 1; >> u64 true_umin = opcode == BPF_JGT ? val + 1 : val; >> >> false_reg->umax_value = min(false_reg->umax_value, false_umax); >> true_reg->umin_value = max(true_reg->umin_value, true_umin); >> } >> break; >> } >> >> Why the loop does not terminate is because tnum_is_const(src_reg->var_off) >> always returns false, causing is_branch_taken() to be skipped: >> >> if (src_reg->type == SCALAR_VALUE && >> !is_jmp32 && tnum_is_const(src_reg->var_off)) { >> pred = is_branch_taken(dst_reg, // could not reach here >> src_reg->var_off.value, >> opcode, >> is_jmp32); >> } >> >> Why tnum_is_const(src_reg->var_off) always returns false is because >> r1->umin_value starts increasing from 0x7fffffffffffff10, always bigger >> than U32_MAX, causing the __reg_combine_64_into_32() to mark the lower >> 32 bits unbounded, i.e. not a constant. >> >> To fix it: >> 1. avoid increasing reg lower bound to a value bigger than the upper bound, >> or decreasing reg upper bound to a value smaller than the lower bound. >> 2. set 32-bit min/max values to the lower 32 bits of the 64-bit min/max values >> when the 64-bit min/max values are equal. > > Should both these be separate patches, meaning are both of them strictly > required as one logical entity or not? From your description it's not really > clear wrt reg_{inc,dec}_{u32,u64}_{min,max} and if this is mainly defensive > or required. Fyi, I'm working on the below draft patch which passes all of test_verifier and your test cases as well from patch 2. Will cook a proper patch once I'm through with further analysis: diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index d517d13878cf..8bef2ed89e87 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1823,7 +1823,7 @@ static void __reg_bound_offset(struct bpf_reg_state *reg) struct tnum var64_off = tnum_intersect(reg->var_off, tnum_range(reg->umin_value, reg->umax_value)); - struct tnum var32_off = tnum_intersect(tnum_subreg(reg->var_off), + struct tnum var32_off = tnum_intersect(tnum_subreg(var64_off), tnum_range(reg->u32_min_value, reg->u32_max_value));
On 3/18/2023 6:24 AM, Daniel Borkmann wrote: > On 3/14/23 9:34 PM, Xu Kuohai wrote: >> From: Xu Kuohai <xukuohai@huawei.com> >> >> After commit 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking"), >> the following bpf prog is rejected: >> >> 0: (61) r2 = *(u32 *)(r1 +0) ; R2_w=pkt(off=0,r=0,imm=0) >> 1: (61) r3 = *(u32 *)(r1 +4) ; R3_w=pkt_end(off=0,imm=0) >> 2: (bf) r1 = r2 >> 3: (07) r1 += 1 >> 4: (2d) if r1 > r3 goto pc+8 >> 5: (71) r1 = *(u8 *)(r2 +0) ; R1_w=scalar(umax=255,var_off=(0x0; 0xff)) >> 6: (18) r0 = 0x7fffffffffffff10 >> 8: (0f) r1 += r0 ; R1_w=scalar(umin=0x7fffffffffffff10,umax=0x800000000000000f) >> 9: (18) r0 = 0x8000000000000000 >> 11: (07) r0 += 1 >> 12: (ad) if r0 < r1 goto pc-2 >> 13: (b7) r0 = 0 >> 14: (95) exit >> >> And the verifier log says: >> >> [...] >> >> from 12 to 11: R0_w=-9223372036854775794 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 11: (07) r0 += 1 ; R0_w=-9223372036854775793 >> 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 13: safe >> >> from 12 to 11: R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 11: (07) r0 += 1 ; R0_w=-9223372036854775792 >> 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775792 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >> 13: safe >> >> [...] >> >> What can be seen here is that r1->umin grows blindly and becomes bigger >> than r1->umax. The reason is because the loop does not terminate, when >> r0 increases to r1->umax_value, the following code in reg_set_min_max() >> sets r1->umin_value to r1->umax_value + 1 blindly: >> >> case BPF_JGT: >> { >> if (is_jmp32) { >> [...] >> } else { >> u64 false_umax = opcode == BPF_JGT ? val : val - 1; >> u64 true_umin = opcode == BPF_JGT ? val + 1 : val; >> >> false_reg->umax_value = min(false_reg->umax_value, false_umax); >> true_reg->umin_value = max(true_reg->umin_value, true_umin); >> } >> break; >> } >> >> Why the loop does not terminate is because tnum_is_const(src_reg->var_off) >> always returns false, causing is_branch_taken() to be skipped: >> >> if (src_reg->type == SCALAR_VALUE && >> !is_jmp32 && tnum_is_const(src_reg->var_off)) { >> pred = is_branch_taken(dst_reg, // could not reach here >> src_reg->var_off.value, >> opcode, >> is_jmp32); >> } >> >> Why tnum_is_const(src_reg->var_off) always returns false is because >> r1->umin_value starts increasing from 0x7fffffffffffff10, always bigger >> than U32_MAX, causing the __reg_combine_64_into_32() to mark the lower >> 32 bits unbounded, i.e. not a constant. >> >> To fix it: >> 1. avoid increasing reg lower bound to a value bigger than the upper bound, >> or decreasing reg upper bound to a value smaller than the lower bound. >> 2. set 32-bit min/max values to the lower 32 bits of the 64-bit min/max values >> when the 64-bit min/max values are equal. > > Should both these be separate patches, meaning are both of them strictly > required as one logical entity or not? From your description it's not really > clear wrt reg_{inc,dec}_{u32,u64}_{min,max} and if this is mainly defensive > or required. > It's defensive, not required. It was added to address "umin > umax" concerns received in v1. Maybe I misunderstood something. > Also, while you describe to some degree how we get here, there is no analysis > on why your proposed changes are safe. If you want to make the verifier less > conservative to start accepting such progs, can you then elaborate on the latter? > I have some discussion below and hope it makes some sense. >> Fixes: 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking") >> Signed-off-by: Xu Kuohai <xukuohai@huawei.com> >> --- >> kernel/bpf/verifier.c | 143 +++++++++++++++++++++++++++--------------- >> 1 file changed, 93 insertions(+), 50 deletions(-) >> >> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c >> index 2bbd89279070..b775b50353d6 100644 >> --- a/kernel/bpf/verifier.c >> +++ b/kernel/bpf/verifier.c >> @@ -2223,14 +2223,21 @@ static bool __reg64_bound_u32(u64 a) >> static void __reg_combine_64_into_32(struct bpf_reg_state *reg) >> { >> + s64 smin = reg->smin_value; >> + s64 smax = reg->smax_value; >> + u64 umin = reg->umin_value; >> + u64 umax = reg->umax_value; >> + >> __mark_reg32_unbounded(reg); >> - if (__reg64_bound_s32(reg->smin_value) && __reg64_bound_s32(reg->smax_value)) { >> - reg->s32_min_value = (s32)reg->smin_value; >> - reg->s32_max_value = (s32)reg->smax_value; >> + if ((__reg64_bound_s32(smin) && __reg64_bound_s32(smax)) || >> + smin == smax) { > > Did you look into debugging reg_bounds_sync()? Assumption for constant > register is register_is_const() and it's explicitly looking at var_off. > I checked this function and saw that the low 32 bits and high 32 bits of var_off are updated separately, and the new low/high 32 bits are the intersection of the old low/high 32 bits and the new [u32_min, u32_max] or [umin, umax] range. So as the range [u32_min, u32_max] or [umin, umax] converges, the low/high 32 bits converge as well. This converge process is reasonable. The lower 32 bits of var_off do not converge to constant is because the range [u32_min, u32_max] does not converge to constant. IIUC, what _reg_combine_64_into_32 does before calling reg_bounds_sync is find the value range of y when the 64-bit variable x changes from smin to smax for function y = (s32)x, and the value range of y when x changes from umin to umax for function y = (u32)x. So when the value range of x is a constant, the value range of y is naturally also a constant, that is, it's reasonable to infer u32_min == u32_max from the 64-bit umin == umax. In fact, the range of y could be found from function graph directly. 1. function graph for y = (u32)x: ^ y = (u32)x | U32_MAX + _ _ | _/ | _| | | _| | _| | | _| | _| | | _| | _| | | _| | _| | | _| | _| | | _| | _| | | _| | _| | | | | | | 0 +-------------------+-------------------+----> x/u64 |<---U32_MAX + 1--->|<---U32_MAX + 1--->| 1A) if umin == umax, then u32_min = (u32)umin, u32_max = (u32)umax: ^ y = (u32)x | U32_MAX + _ _ | _/ | _| | | _| | _| | | _| | _| | u32_min == u32_max | _ _ _ _ _| | _| | | _| | _| | | _| | | _| | | _| | | _| | | _| | | _| | | | | | | | 0 +---------+---------+-------------------+----> x/u64 umin == umax |<---U32_MAX + 1--->|<---U32_MAX + 1--->| 1B) if umax - umin <= U32_MAX and (u32)umin < (u32)umax, then u32_min = (u32)umin, u32_max = (u32)umax: ^ y = (u32)x | U32_MAX + _ _ u32_max = (u32)umax | _ _ _ _ _ _ _ _/ | _| | | _| | _| | | _| | | _| | u32_min = (u32)umin | _ _ _ _ _| | | _| | | _| | | _| | | _| | | | _| | | _| | | | _| | | _| | | | _| | | | | | | | | 0 +---------+-----+---+-------------------+----> x/u64 umin umax |<---U32_MAX + 1--->|<---U32_MAX + 1--->| 1C) if umax - umin <= U32_MAX and (u32)umin > (u32)umax, then u32_min = U32_MIN, u32_max = U32_MAX: ^ y = (u32)x | u32_max = 32_MAX + _ _ | _/ | _| | | _| | _| | | _| | _| | (u32)umin | _ _ _ _ _| | _| | | _| | _| | | _| | | _| | (u32)umax | _ _| _ |_ _ _ _ _|_ _ _| | | _| | | _| | | | | | | | | | u32_min = U32_MIN 0 +---------+---------+-----+-------------+----> x/u64 umin umax |<---U32_MAX + 1--->|<---U32_MAX + 1--->| 1D) if umax - umin > U32_MAX, then u32_min = U32_MIN, u32_max = U32_MAX: ^ y = (u32)x | u32_max = 32_MAX + _ _ | _/ | _| | (u32)umax | _ _ _ _ _ _ _|_ _|_ _ _ _ _ _ _ _| | | _| | _| | (u32)umin | _ _ _ _ _| | _| | | | _| | _| | | | _| | | _| | | | _| | | _| | | | _| | | _| | | | | | | | | | u32_min = U32_MIN 0 +---------+---------+-------------+-----+----> x/u64 umin umax |<---U32_MAX + 1--->|<---U32_MAX + 1--->| 2. function graph for y = (s32)x: ^ y = (s32)x | S32_MAX | _ _ _ _ _ _ | _| | _| | | _| | _| | | _| | _| | | _| | _| | --+---------0----------+---------+----------+---> x/s64 | _| | _| | _| | | _| | _| | | _| | _| | | _| |_| _ _ _ | S32_MIN |_| | | |<--- U32_MAX + 1--->|<--- U32_MAX + 1--->| 2A) if smin == smax, then s32_min = (s32)smin, s32_max = (s32)smax: ^ y = (s32)x | S32_MAX | _ _ _ _ _ _ | _| | _| | | _| | _| | s32_min == s32_max|__ _| | _| | | _| | | _| | --+---------0----+-----+---------+----------+---> x/s64 | _| smin | _| | _| | smax | _| | _| | | _| | _| | | _| |_| _ _ _ | S32_MIN |_| | | |<--- U32_MAX + 1--->|<--- U32_MAX + 1--->| 2B) if smax - smin <= U32_MAX and (s32)smin < (s32)smax, then s32_min = (u32)smin, s32_max = (s32)smax: ^ y = (s32)x | S32_MAX | _ _ _ _ _ _ s32_max | _ _ _ _| | _| | | _| | _| | | _| | | _| | | _| | | _| | --+---+-----0------+---+---------+----------+---> x/s64 | smin _| smax | _| | | _| | | _| | |_|_ _| s32_min | _| | _| | | _| |_| _ _ _ | S32_MIN |_| | | |<--- U32_MAX + 1--->|<--- U32_MAX + 1--->| 2C) if smax - smin <= U32_MAX and (s32)smin > (s32)smax, then s32_min = S32_MIN, s32_max = S32_MAX: ^ y = (s32)x | s32_max = S32_MAX | _ _ _ _ _ _ | _| | _| | (s32)smin |_ _ _| | _| | | _| | _| | | _| | | smax _| | --+---------0----+-----+-----+---+----------+---> x/s64 | smin | | _| (s32)smax | _ _ _ _ _|_ _ _|_| | | _| | | _| s32_min = S32_MIN | _ _ _ _ _|_| | | |<--- U32_MAX + 1--->|<--- U32_MAX + 1--->| 2D) if smax - smin > U32_MAX, then s32_min = S32_MIN, s32_max = S32_MAX: ^ y = (s32)x | s32_max = S32_MAX | _ _ _ _ _ _ (s32)smax | _ _ _ _|_|_ _ _ _ _ _ _ _ _| | | _| | _| | (s32)smin | _ _| | _| | | | _| | _| | | --+---------0--+-------+---------+------+---+---> x/s64 | smin | _| smax | | _| | | _| | | _| s32_min = S32_MIN | _ _ _ _ _|_| | | |<--- U32_MAX + 1--->|<--- U32_MAX + 1--->| >> + reg->s32_min_value = (s32)smin; >> + reg->s32_max_value = (s32)smax; >> } >> - if (__reg64_bound_u32(reg->umin_value) && __reg64_bound_u32(reg->umax_value)) { >> - reg->u32_min_value = (u32)reg->umin_value; >> - reg->u32_max_value = (u32)reg->umax_value; >> + if ((__reg64_bound_u32(umin) && __reg64_bound_u32(umax)) || >> + umin == umax) { >> + reg->u32_min_value = (u32)umin; >> + reg->u32_max_value = (u32)umax; >> } >> reg_bounds_sync(reg); >> } >> @@ -12828,6 +12835,62 @@ static int is_pkt_ptr_branch_taken(struct bpf_reg_state *dst_reg, >> return -1; >> } >> +static void reg_inc_u32_min(struct bpf_reg_state *reg, u32 val) >> +{ >> + reg->u32_min_value = max(reg->u32_min_value, val); >> + if (reg->u32_min_value > reg->u32_max_value) >> + reg->u32_min_value = reg->u32_max_value; >> +} >> + >> +static void reg_dec_u32_max(struct bpf_reg_state *reg, u32 val) >> +{ >> + reg->u32_max_value = min(reg->u32_max_value, val); >> + if (reg->u32_max_value < reg->u32_min_value) >> + reg->u32_max_value = reg->u32_min_value; >> +} >> + >> +static void reg_inc_s32_min(struct bpf_reg_state *reg, s32 val) >> +{ >> + reg->s32_min_value = max(reg->s32_min_value, val); >> + if (reg->s32_min_value > reg->s32_max_value) >> + reg->s32_min_value = reg->s32_max_value; >> +} >> + >> +static void reg_dec_s32_max(struct bpf_reg_state *reg, s32 val) >> +{ >> + reg->s32_max_value = min(reg->s32_max_value, val); >> + if (reg->s32_max_value < reg->s32_min_value) >> + reg->s32_max_value = reg->s32_min_value; >> +} >> + >> +static void reg_inc_u64_min(struct bpf_reg_state *reg, u64 val) >> +{ >> + reg->umin_value = max(reg->umin_value, val); >> + if (reg->umin_value > reg->umax_value) >> + reg->umin_value = reg->umax_value; >> +} >> + >> +static void reg_dec_u64_max(struct bpf_reg_state *reg, u64 val) >> +{ >> + reg->umax_value = min(reg->umax_value, val); >> + if (reg->umax_value < reg->umin_value) >> + reg->umax_value = reg->umin_value; >> +} >> + >> +static void reg_inc_s64_min(struct bpf_reg_state *reg, s64 val) >> +{ >> + reg->smin_value = max(reg->smin_value, val); >> + if (reg->smin_value > reg->smax_value) >> + reg->smin_value = reg->smax_value; >> +} >> + >> +static void reg_dec_s64_max(struct bpf_reg_state *reg, s64 val) >> +{ >> + reg->smax_value = min(reg->smax_value, val); >> + if (reg->smax_value < reg->smin_value) >> + reg->smax_value = reg->smin_value; >> +} > > All this feels more like a workaround and papering over the issue. > Agree, making umin > umax disappear from error log does not fix the issue, and may make debugging more difficult. >> /* Adjusts the register min/max values in the case that the dst_reg is the >> * variable register that we are working on, and src_reg is a constant or we're >> * simply doing a BPF_K check. >> @@ -12898,76 +12961,56 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg, >> case BPF_JGE: >> case BPF_JGT: >> { >> - if (is_jmp32) { >> - u32 false_umax = opcode == BPF_JGT ? val32 : val32 - 1; >> - u32 true_umin = opcode == BPF_JGT ? val32 + 1 : val32; >> + bool neq = (opcode == BPF_JGT); >> - false_reg->u32_max_value = min(false_reg->u32_max_value, >> - false_umax); >> - true_reg->u32_min_value = max(true_reg->u32_min_value, >> - true_umin); >> + if (is_jmp32) { >> + reg_dec_u32_max(false_reg, neq ? val32 : val32 - 1); >> + reg_inc_u32_min(true_reg, neq ? val32 + 1 : val32); >> } else { >> - u64 false_umax = opcode == BPF_JGT ? val : val - 1; >> - u64 true_umin = opcode == BPF_JGT ? val + 1 : val; >> - >> - false_reg->umax_value = min(false_reg->umax_value, false_umax); >> - true_reg->umin_value = max(true_reg->umin_value, true_umin); >> + reg_dec_u64_max(false_reg, neq ? val : val - 1); >> + reg_inc_u64_min(true_reg, neq ? val + 1 : val); > .
On 3/21/2023 12:42 AM, Daniel Borkmann wrote: > On 3/17/23 11:24 PM, Daniel Borkmann wrote: >> On 3/14/23 9:34 PM, Xu Kuohai wrote: >>> From: Xu Kuohai <xukuohai@huawei.com> >>> >>> After commit 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking"), >>> the following bpf prog is rejected: >>> >>> 0: (61) r2 = *(u32 *)(r1 +0) ; R2_w=pkt(off=0,r=0,imm=0) >>> 1: (61) r3 = *(u32 *)(r1 +4) ; R3_w=pkt_end(off=0,imm=0) >>> 2: (bf) r1 = r2 >>> 3: (07) r1 += 1 >>> 4: (2d) if r1 > r3 goto pc+8 >>> 5: (71) r1 = *(u8 *)(r2 +0) ; R1_w=scalar(umax=255,var_off=(0x0; 0xff)) >>> 6: (18) r0 = 0x7fffffffffffff10 >>> 8: (0f) r1 += r0 ; R1_w=scalar(umin=0x7fffffffffffff10,umax=0x800000000000000f) >>> 9: (18) r0 = 0x8000000000000000 >>> 11: (07) r0 += 1 >>> 12: (ad) if r0 < r1 goto pc-2 >>> 13: (b7) r0 = 0 >>> 14: (95) exit >>> >>> And the verifier log says: >>> >>> [...] >>> >>> from 12 to 11: R0_w=-9223372036854775794 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >>> 11: (07) r0 += 1 ; R0_w=-9223372036854775793 >>> 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775823,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >>> 13: safe >>> >>> from 12 to 11: R0_w=-9223372036854775793 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >>> 11: (07) r0 += 1 ; R0_w=-9223372036854775792 >>> 12: (ad) if r0 < r1 goto pc-2 ; R0_w=-9223372036854775792 R1=scalar(umin=9223372036854775824,umax=9223372036854775823,var_off=(0x8000000000000000; 0xffffffff)) >>> 13: safe >>> >>> [...] >>> >>> What can be seen here is that r1->umin grows blindly and becomes bigger >>> than r1->umax. The reason is because the loop does not terminate, when >>> r0 increases to r1->umax_value, the following code in reg_set_min_max() >>> sets r1->umin_value to r1->umax_value + 1 blindly: >>> >>> case BPF_JGT: >>> { >>> if (is_jmp32) { >>> [...] >>> } else { >>> u64 false_umax = opcode == BPF_JGT ? val : val - 1; >>> u64 true_umin = opcode == BPF_JGT ? val + 1 : val; >>> >>> false_reg->umax_value = min(false_reg->umax_value, false_umax); >>> true_reg->umin_value = max(true_reg->umin_value, true_umin); >>> } >>> break; >>> } >>> >>> Why the loop does not terminate is because tnum_is_const(src_reg->var_off) >>> always returns false, causing is_branch_taken() to be skipped: >>> >>> if (src_reg->type == SCALAR_VALUE && >>> !is_jmp32 && tnum_is_const(src_reg->var_off)) { >>> pred = is_branch_taken(dst_reg, // could not reach here >>> src_reg->var_off.value, >>> opcode, >>> is_jmp32); >>> } >>> >>> Why tnum_is_const(src_reg->var_off) always returns false is because >>> r1->umin_value starts increasing from 0x7fffffffffffff10, always bigger >>> than U32_MAX, causing the __reg_combine_64_into_32() to mark the lower >>> 32 bits unbounded, i.e. not a constant. >>> >>> To fix it: >>> 1. avoid increasing reg lower bound to a value bigger than the upper bound, >>> or decreasing reg upper bound to a value smaller than the lower bound. >>> 2. set 32-bit min/max values to the lower 32 bits of the 64-bit min/max values >>> when the 64-bit min/max values are equal. >> >> Should both these be separate patches, meaning are both of them strictly >> required as one logical entity or not? From your description it's not really >> clear wrt reg_{inc,dec}_{u32,u64}_{min,max} and if this is mainly defensive >> or required. > > Fyi, I'm working on the below draft patch which passes all of test_verifier and > your test cases as well from patch 2. Will cook a proper patch once I'm through > with further analysis: > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index d517d13878cf..8bef2ed89e87 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -1823,7 +1823,7 @@ static void __reg_bound_offset(struct bpf_reg_state *reg) > struct tnum var64_off = tnum_intersect(reg->var_off, > tnum_range(reg->umin_value, > reg->umax_value)); > - struct tnum var32_off = tnum_intersect(tnum_subreg(reg->var_off), > + struct tnum var32_off = tnum_intersect(tnum_subreg(var64_off), > tnum_range(reg->u32_min_value, > reg->u32_max_value)); > . [forget to reply to the list, resend] Thanks for the patch, it works for me. But as replied in the other mail, it seems more reasonable to converge var32_off to constant by converging [u32_min_value, u32_max_value] to constant.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2bbd89279070..b775b50353d6 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2223,14 +2223,21 @@ static bool __reg64_bound_u32(u64 a) static void __reg_combine_64_into_32(struct bpf_reg_state *reg) { + s64 smin = reg->smin_value; + s64 smax = reg->smax_value; + u64 umin = reg->umin_value; + u64 umax = reg->umax_value; + __mark_reg32_unbounded(reg); - if (__reg64_bound_s32(reg->smin_value) && __reg64_bound_s32(reg->smax_value)) { - reg->s32_min_value = (s32)reg->smin_value; - reg->s32_max_value = (s32)reg->smax_value; + if ((__reg64_bound_s32(smin) && __reg64_bound_s32(smax)) || + smin == smax) { + reg->s32_min_value = (s32)smin; + reg->s32_max_value = (s32)smax; } - if (__reg64_bound_u32(reg->umin_value) && __reg64_bound_u32(reg->umax_value)) { - reg->u32_min_value = (u32)reg->umin_value; - reg->u32_max_value = (u32)reg->umax_value; + if ((__reg64_bound_u32(umin) && __reg64_bound_u32(umax)) || + umin == umax) { + reg->u32_min_value = (u32)umin; + reg->u32_max_value = (u32)umax; } reg_bounds_sync(reg); } @@ -12828,6 +12835,62 @@ static int is_pkt_ptr_branch_taken(struct bpf_reg_state *dst_reg, return -1; } +static void reg_inc_u32_min(struct bpf_reg_state *reg, u32 val) +{ + reg->u32_min_value = max(reg->u32_min_value, val); + if (reg->u32_min_value > reg->u32_max_value) + reg->u32_min_value = reg->u32_max_value; +} + +static void reg_dec_u32_max(struct bpf_reg_state *reg, u32 val) +{ + reg->u32_max_value = min(reg->u32_max_value, val); + if (reg->u32_max_value < reg->u32_min_value) + reg->u32_max_value = reg->u32_min_value; +} + +static void reg_inc_s32_min(struct bpf_reg_state *reg, s32 val) +{ + reg->s32_min_value = max(reg->s32_min_value, val); + if (reg->s32_min_value > reg->s32_max_value) + reg->s32_min_value = reg->s32_max_value; +} + +static void reg_dec_s32_max(struct bpf_reg_state *reg, s32 val) +{ + reg->s32_max_value = min(reg->s32_max_value, val); + if (reg->s32_max_value < reg->s32_min_value) + reg->s32_max_value = reg->s32_min_value; +} + +static void reg_inc_u64_min(struct bpf_reg_state *reg, u64 val) +{ + reg->umin_value = max(reg->umin_value, val); + if (reg->umin_value > reg->umax_value) + reg->umin_value = reg->umax_value; +} + +static void reg_dec_u64_max(struct bpf_reg_state *reg, u64 val) +{ + reg->umax_value = min(reg->umax_value, val); + if (reg->umax_value < reg->umin_value) + reg->umax_value = reg->umin_value; +} + +static void reg_inc_s64_min(struct bpf_reg_state *reg, s64 val) +{ + reg->smin_value = max(reg->smin_value, val); + if (reg->smin_value > reg->smax_value) + reg->smin_value = reg->smax_value; +} + +static void reg_dec_s64_max(struct bpf_reg_state *reg, s64 val) +{ + reg->smax_value = min(reg->smax_value, val); + if (reg->smax_value < reg->smin_value) + reg->smax_value = reg->smin_value; +} + /* Adjusts the register min/max values in the case that the dst_reg is the * variable register that we are working on, and src_reg is a constant or we're * simply doing a BPF_K check. @@ -12898,76 +12961,56 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg, case BPF_JGE: case BPF_JGT: { - if (is_jmp32) { - u32 false_umax = opcode == BPF_JGT ? val32 : val32 - 1; - u32 true_umin = opcode == BPF_JGT ? val32 + 1 : val32; + bool neq = (opcode == BPF_JGT); - false_reg->u32_max_value = min(false_reg->u32_max_value, - false_umax); - true_reg->u32_min_value = max(true_reg->u32_min_value, - true_umin); + if (is_jmp32) { + reg_dec_u32_max(false_reg, neq ? val32 : val32 - 1); + reg_inc_u32_min(true_reg, neq ? val32 + 1 : val32); } else { - u64 false_umax = opcode == BPF_JGT ? val : val - 1; - u64 true_umin = opcode == BPF_JGT ? val + 1 : val; - - false_reg->umax_value = min(false_reg->umax_value, false_umax); - true_reg->umin_value = max(true_reg->umin_value, true_umin); + reg_dec_u64_max(false_reg, neq ? val : val - 1); + reg_inc_u64_min(true_reg, neq ? val + 1 : val); } break; } case BPF_JSGE: case BPF_JSGT: { - if (is_jmp32) { - s32 false_smax = opcode == BPF_JSGT ? sval32 : sval32 - 1; - s32 true_smin = opcode == BPF_JSGT ? sval32 + 1 : sval32; + bool neq = (opcode == BPF_JSGT); - false_reg->s32_max_value = min(false_reg->s32_max_value, false_smax); - true_reg->s32_min_value = max(true_reg->s32_min_value, true_smin); + if (is_jmp32) { + reg_dec_s32_max(false_reg, neq ? sval32 : sval32 - 1); + reg_inc_s32_min(true_reg, neq ? sval32 + 1 : sval32); } else { - s64 false_smax = opcode == BPF_JSGT ? sval : sval - 1; - s64 true_smin = opcode == BPF_JSGT ? sval + 1 : sval; - - false_reg->smax_value = min(false_reg->smax_value, false_smax); - true_reg->smin_value = max(true_reg->smin_value, true_smin); + reg_dec_s64_max(false_reg, neq ? sval : sval - 1); + reg_inc_s64_min(true_reg, neq ? sval + 1 : sval); } break; } case BPF_JLE: case BPF_JLT: { - if (is_jmp32) { - u32 false_umin = opcode == BPF_JLT ? val32 : val32 + 1; - u32 true_umax = opcode == BPF_JLT ? val32 - 1 : val32; + bool neq = (opcode == BPF_JLT); - false_reg->u32_min_value = max(false_reg->u32_min_value, - false_umin); - true_reg->u32_max_value = min(true_reg->u32_max_value, - true_umax); + if (is_jmp32) { + reg_inc_u32_min(false_reg, neq ? val32 : val32 + 1); + reg_dec_u32_max(true_reg, neq ? val32 - 1 : val32); } else { - u64 false_umin = opcode == BPF_JLT ? val : val + 1; - u64 true_umax = opcode == BPF_JLT ? val - 1 : val; - - false_reg->umin_value = max(false_reg->umin_value, false_umin); - true_reg->umax_value = min(true_reg->umax_value, true_umax); + reg_inc_u64_min(false_reg, neq ? val : val + 1); + reg_dec_u64_max(true_reg, neq ? val - 1 : val); } break; } case BPF_JSLE: case BPF_JSLT: { - if (is_jmp32) { - s32 false_smin = opcode == BPF_JSLT ? sval32 : sval32 + 1; - s32 true_smax = opcode == BPF_JSLT ? sval32 - 1 : sval32; + bool neq = (opcode == BPF_JSLT); - false_reg->s32_min_value = max(false_reg->s32_min_value, false_smin); - true_reg->s32_max_value = min(true_reg->s32_max_value, true_smax); + if (is_jmp32) { + reg_inc_s32_min(false_reg, neq ? sval32 : sval32 + 1); + reg_dec_s32_max(true_reg, neq ? sval32 - 1 : sval32); } else { - s64 false_smin = opcode == BPF_JSLT ? sval : sval + 1; - s64 true_smax = opcode == BPF_JSLT ? sval - 1 : sval; - - false_reg->smin_value = max(false_reg->smin_value, false_smin); - true_reg->smax_value = min(true_reg->smax_value, true_smax); + reg_inc_s64_min(false_reg, neq ? sval : sval + 1); + reg_dec_s64_max(true_reg, neq ? sval - 1 : sval); } break; }