From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: bpf@vger.kernel.org
Cc: Nicholas Carlini <npc@anthropic.com>,
Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Eduard Zingerman <eddyz87@gmail.com>,
Emil Tsalapatis <emil@etsalapatis.com>,
kkd@meta.com, kernel-team@meta.com
Subject: [PATCH bpf v2 2/7] bpf: Preserve packet pointer class displacement in regsafe()
Date: Sat, 5 Sep 2026 10:34:10 +0200 [thread overview]
Message-ID: <20260905083418.3723623-3-memxor@gmail.com> (raw)
In-Reply-To: <20260905083418.3723623-1-memxor@gmail.com>
regsafe() maps packet pointer IDs between states and checks that each
current register range is a subset of the corresponding explored
register range. It does not, however, preserve the displacement between
registers that share a packet pointer ID.
This is unsound because packet range is shared by ID. A bounds check on
one class member updates every member, and a later access can consume the
range through another member. Commit 022ac0750883 ("bpf: use reg->var_off
instead of reg->off for pointers") folded the fixed pointer offset into
r64 and removed the old off equality check, so two individually narrower
registers can prune even when their displacement has changed. The
explored path can then license an out-of-bounds packet access on the
pruned path.
Requiring equal r64 bases would prevent the bug, but would also reject a
safe uniform translation of the whole class. Instead, record the base
translation seen for the first packet pointer in each ID mapping and
require every subsequent member to have the same translation. This keeps
the relative displacement invariant while retaining pruning for uniformly
translated classes. Packet pointers without an ID remain unaffected.
Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers")
Reported-by: Nicholas Carlini <npc@anthropic.com>
Suggested-by: Nicholas Carlini <npc@anthropic.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
include/linux/bpf_verifier.h | 2 ++
kernel/bpf/states.c | 42 ++++++++++++++++++++++++++++++++++--
2 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 36b65797877d..5cf92ce18520 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -854,6 +854,8 @@ struct backtrack_state {
struct bpf_id_pair {
u32 old;
u32 cur;
+ s32 pkt_ptr_delta;
+ bool pkt_ptr_delta_set;
};
struct bpf_idmap {
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 66fb11b6c6a7..2f8f3fe164b0 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -339,6 +339,7 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
if (idmap->cnt < BPF_ID_MAP_SIZE) {
map[idmap->cnt].old = old_id;
map[idmap->cnt].cur = cur_id;
+ map[idmap->cnt].pkt_ptr_delta_set = false;
idmap->cnt++;
return true;
}
@@ -352,6 +353,43 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
return false;
}
+static bool check_pkt_ptr_ids(const struct bpf_reg_state *old,
+ const struct bpf_reg_state *cur,
+ struct bpf_idmap *idmap)
+{
+ struct bpf_id_pair *map = idmap->map;
+ s64 delta;
+ unsigned int i;
+
+ if (!check_ids(old->id, cur->id, idmap))
+ return false;
+ if (!old->id)
+ return true;
+
+ /*
+ * Packet range is shared by all pointers with the same ID. Preserve
+ * their relative displacement, while allowing the whole class to move.
+ * Packet pointer offsets are bounded by BPF_MAX_VAR_OFF, so the delta
+ * between two valid offsets fits in s32.
+ */
+ delta = (s64)(cur->r64.base - old->r64.base);
+ if (delta < S32_MIN || delta > S32_MAX)
+ return false;
+
+ for (i = 0; i < idmap->cnt; i++) {
+ if (map[i].old != old->id)
+ continue;
+ if (!map[i].pkt_ptr_delta_set) {
+ map[i].pkt_ptr_delta = delta;
+ map[i].pkt_ptr_delta_set = true;
+ return true;
+ }
+ return map[i].pkt_ptr_delta == delta;
+ }
+
+ return false;
+}
+
/*
* Compare scalar register IDs for state equivalence.
*
@@ -632,8 +670,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
} else if (rold->range > rcur->range) {
return false;
}
- /* id relations must be preserved */
- if (!check_ids(rold->id, rcur->id, idmap))
+ /* id relations and intra-class displacement must be preserved */
+ if (!check_pkt_ptr_ids(rold, rcur, idmap))
return false;
/* new val must satisfy old val knowledge */
return range_within(rold, rcur) &&
--
2.53.0
next prev parent reply other threads:[~2026-09-05 8:34 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-05 8:34 [PATCH bpf v2 0/7] Misc bug fixes - part 5 Kumar Kartikeya Dwivedi
2026-09-05 8:34 ` [PATCH bpf v2 1/7] bpf: Make post-verification instruction rewrites killable Kumar Kartikeya Dwivedi
2026-09-11 22:56 ` Eduard Zingerman
2026-09-05 8:34 ` Kumar Kartikeya Dwivedi [this message]
2026-09-05 9:25 ` [PATCH bpf v2 2/7] bpf: Preserve packet pointer class displacement in regsafe() bot+bpf-ci
2026-09-05 20:46 ` Alexei Starovoitov
2026-09-06 6:40 ` Eduard Zingerman
2026-09-06 7:04 ` Eduard Zingerman
2026-09-06 15:11 ` Alexei Starovoitov
2026-09-05 8:34 ` [PATCH bpf v2 3/7] selftests/bpf: Test packet pointer class displacement pruning Kumar Kartikeya Dwivedi
2026-09-05 9:10 ` bot+bpf-ci
2026-09-05 20:48 ` Alexei Starovoitov
2026-09-05 8:34 ` [PATCH bpf v2 4/7] bpf: Reject fall-through across subprogram boundaries Kumar Kartikeya Dwivedi
2026-09-05 20:29 ` Alexei Starovoitov
2026-09-05 8:34 ` [PATCH bpf v2 5/7] selftests/bpf: Test poisoned subprogram terminator Kumar Kartikeya Dwivedi
2026-09-05 8:34 ` [PATCH bpf v2 6/7] bpf: Assign lock identity to callback map values Kumar Kartikeya Dwivedi
2026-09-05 9:25 ` bot+bpf-ci
2026-09-12 0:23 ` Eduard Zingerman
2026-09-05 8:34 ` [PATCH bpf v2 7/7] selftests/bpf: Check callback map value lock identity Kumar Kartikeya Dwivedi
2026-09-05 9:10 ` bot+bpf-ci
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=20260905083418.3723623-3-memxor@gmail.com \
--to=memxor@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=kernel-team@meta.com \
--cc=kkd@meta.com \
--cc=npc@anthropic.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox