From: Eduard Zingerman <eddyz87@gmail.com>
To: bpf@vger.kernel.org, ast@kernel.org, andrii@kernel.org
Cc: daniel@iogearbox.net, martin.lau@linux.dev, kernel-team@fb.com,
yonghong.song@linux.dev, eddyz87@gmail.com
Subject: [PATCH bpf-next 1/2] bpf: range_within() must check cnum ranges instead of min/max pairs
Date: Sat, 25 Apr 2026 15:48:23 -0700 [thread overview]
Message-ID: <20260425-cnum-range-within-v1-1-2fdca70cb09d@gmail.com> (raw)
In-Reply-To: <20260425-cnum-range-within-v1-0-2fdca70cb09d@gmail.com>
states.c:range_within() must be updated to properly check if
cnum-based range in an old state is a superset of a range in the cur
state. Currently it makes the decision using min/max accessors:
reg_umin(old) <= reg_umin(cur) <= reg_umax(old)
This is wrong for cnums that cross both UT_MAX/0 and ST_MAX/ST_MIN
boundaries. Consider cnum32{base=0x7FFFFFF0, size=0x80000020},
which represents values [0x7FFFFFF0, ..., U32_MAX, 0, ..., 0x10].
Its projections are u32_min/max=0/U32_MAX, s32_min/max=S32_MIN/MAX.
A register with range [0x100, 0x200] (which lies entirely in the gap
of the wrapping range) would pass the min/max check despite having no
overlap with the actual cnum arc.
This commit replaces min/max comparison with cnum{32,64}_is_subset()
operation. The operation implementation is verified using cbmc model
checker in [1].
[1] https://github.com/eddyz87/cnum-verif/
Fixes: bbc631085503 ("bpf: replace min/max fields with struct cnum{32,64}")
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
include/linux/cnum.h | 2 ++
kernel/bpf/cnum_defs.h | 14 ++++++++++++++
kernel/bpf/states.c | 11 +++--------
3 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/include/linux/cnum.h b/include/linux/cnum.h
index a7259b105b45..49b7d0c7645d 100644
--- a/include/linux/cnum.h
+++ b/include/linux/cnum.h
@@ -48,6 +48,7 @@ bool cnum32_is_const(struct cnum32 cnum);
bool cnum32_is_empty(struct cnum32 cnum);
struct cnum32 cnum32_add(struct cnum32 a, struct cnum32 b);
struct cnum32 cnum32_negate(struct cnum32 a);
+bool cnum32_is_subset(struct cnum32 outer, struct cnum32 inner);
/* Same as cnum32 but for 64-bit ranges */
struct cnum64 {
@@ -73,6 +74,7 @@ bool cnum64_is_const(struct cnum64 cnum);
bool cnum64_is_empty(struct cnum64 cnum);
struct cnum64 cnum64_add(struct cnum64 a, struct cnum64 b);
struct cnum64 cnum64_negate(struct cnum64 a);
+bool cnum64_is_subset(struct cnum64 outer, struct cnum64 inner);
struct cnum32 cnum32_from_cnum64(struct cnum64 cnum);
struct cnum64 cnum64_cnum32_intersect(struct cnum64 a, struct cnum32 b);
diff --git a/kernel/bpf/cnum_defs.h b/kernel/bpf/cnum_defs.h
index 3ebd8f723dbb..1f232138b6e9 100644
--- a/kernel/bpf/cnum_defs.h
+++ b/kernel/bpf/cnum_defs.h
@@ -220,6 +220,20 @@ bool FN(is_const)(struct cnum_t cnum)
return cnum.size == 0;
}
+bool FN(is_subset)(struct cnum_t bigger, struct cnum_t smaller)
+{
+ if (FN(is_empty(smaller)))
+ return true;
+ if (FN(is_empty(bigger)))
+ return false;
+ /* rotate both arcs such that 'bigger' starts at origin, hence does not overflow */
+ smaller.base -= bigger.base;
+ bigger.base = 0;
+ if (FN(urange_overflow)(smaller) && bigger.size < UT_MAX)
+ return false;
+ return smaller.base + smaller.size <= bigger.size;
+}
+
#undef EMPTY
#undef cnum_t
#undef ut
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index a78ae891b743..bd9c22945050 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -2,6 +2,7 @@
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <linux/bpf.h>
#include <linux/bpf_verifier.h>
+#include <linux/cnum.h>
#include <linux/filter.h>
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
@@ -301,14 +302,8 @@ int bpf_update_branch_counts(struct bpf_verifier_env *env, struct bpf_verifier_s
static bool range_within(const struct bpf_reg_state *old,
const struct bpf_reg_state *cur)
{
- return reg_umin(old) <= reg_umin(cur) &&
- reg_umax(old) >= reg_umax(cur) &&
- reg_smin(old) <= reg_smin(cur) &&
- reg_smax(old) >= reg_smax(cur) &&
- reg_u32_min(old) <= reg_u32_min(cur) &&
- reg_u32_max(old) >= reg_u32_max(cur) &&
- reg_s32_min(old) <= reg_s32_min(cur) &&
- reg_s32_max(old) >= reg_s32_max(cur);
+ return cnum64_is_subset(old->r64, cur->r64) &&
+ cnum32_is_subset(old->r32, cur->r32);
}
/* If in the old state two registers had the same id, then they need to have
--
2.53.0
next prev parent reply other threads:[~2026-04-25 22:48 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-25 22:48 [PATCH bpf-next 0/2] bpf: range_within() must check cnum ranges instead of min/max pairs Eduard Zingerman
2026-04-25 22:48 ` Eduard Zingerman [this message]
2026-04-25 23:20 ` [PATCH bpf-next 1/2] " bot+bpf-ci
2026-04-27 17:56 ` Alexei Starovoitov
2026-04-25 22:48 ` [PATCH bpf-next 2/2] selftests/bpf: a test for proper cnums compare in is_state_visited() Eduard Zingerman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260425-cnum-range-within-v1-1-2fdca70cb09d@gmail.com \
--to=eddyz87@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@fb.com \
--cc=martin.lau@linux.dev \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.