From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Qiliang Yuan" <odys.yuan@gmail.com>,
"Daniel Borkmann" <daniel@iogearbox.net>,
"John Fastabend" <john.fastabend@gmail.com>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Song Liu" <song@kernel.org>,
"Yonghong Song" <yonghong.song@linux.dev>,
"Jiri Olsa" <jolsa@kernel.org>,
"Emil Tsalapatis" <emil@etsalapatis.com>,
"Ihor Solodrai" <ihor.solodrai@linux.dev>,
"Shuah Khan" <shuah@kernel.org>
Cc: <bpf@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<linux-kselftest@vger.kernel.org>
Subject: Re: [PATCH bpf-next v2 07/22] bpf: Let the patch list remove instructions
Date: Sat, 31 Oct 2026 05:29:43 +0000 [thread overview]
Message-ID: <DMIRYFU9YEX1.14HBEXD0UQR7H@gmail.com> (raw)
In-Reply-To: <20261030-bpf-verifier-patch-batch-v2-7-55cd43439f7c@gmail.com>
On Fri, Oct 30, 2026 at 04:51 AM Qiliang Yuan <odys.yuan@gmail.com> wrote:
> bpf_patch_image_install(prog, &img);
[...]
> + /* before subprogs are removed, since it looks at them */
> for (i = 0; i < env->func_ptr_cnt; i++) {
> struct bpf_func_ptr *fp = &env->func_ptrs[i];
> + int subprog;
>
> if (fp->xlated_off == BPF_FUNC_PTR_DELETED)
> continue;
> + if (bpf_patch_image_deleted(&img, fp->xlated_off)) {
> + subprog = bpf_find_subprog(env, fp->xlated_off);
> + if (subprog <= 0 ||
Sashiko is right. It's real, and it crashes the kernel.
bpf_patch_image_install() already set prog->len to the new length.
bpf_find_subprog() gets an old offset and compares it with prog->len.
When the prog shrinks enough it doesn't find a live function.
The func ptr is marked as deleted and the slot in .rodata becomes NULL.
It needs two things:
- the first insn of the function goes away as a nop
- this commit removes at least (end of prog - start of function) insns
Plain C hits it:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct st { int a; } g = { .a = 10 };
#define OP(name, expr) \
static __noinline int name(struct st *s) \
{ \
if (!s) \
return bpf_get_prandom_u32(); \
return expr; \
}
OP(op_a, s->a + 2) OP(op_b, s->a + 3) OP(op_c, s->a + 4) OP(op_d, s->a + 5)
OP(op_e, s->a + 6) OP(op_f, s->a + 7) OP(op_g, s->a + 8) OP(op_h, s->a + 9)
static int (* const volatile ops[])(struct st *) = {
op_a, op_b, op_c, op_d, op_e, op_f, op_g, op_h
};
SEC("socket")
int vt_all(struct __sk_buff *skb)
{
int i, sum = 0;
for (i = 0; i < 8; i++)
sum += ops[i](&g);
return sum;
}
char _license[] SEC("license") = "GPL";
The verifier knows that &g is not NULL, so every op starts with a nop.
bpf-next: Return value: 124
this series: BUG: kernel NULL pointer dereference, address: 0000000000000000
RIP: 0010:0x0
That's the shape of any table of ops.
The minimal fix is to do the lookup before the install.
On top of the whole series:
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index a442188647bc..de758ce7afac 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -451,6 +451,28 @@ int bpf_patch_list_commit(struct bpf_verifier_env *env)
}
}
+ /*
+ * Before the new image is installed and subprogs are removed:
+ * bpf_find_subprog() looks at prog->len and at the subprogs.
+ */
+ for (i = 0; i < env->func_ptr_cnt; i++) {
+ struct bpf_func_ptr *fp = &env->func_ptrs[i];
+ int subprog;
+
+ if (fp->xlated_off == BPF_FUNC_PTR_DELETED)
+ continue;
+ if (bpf_patch_image_deleted(&img, fp->xlated_off)) {
+ subprog = bpf_find_subprog(env, fp->xlated_off);
+ if (subprog <= 0 ||
+ img.new_off[env->subprog_info[subprog].start] ==
+ img.new_off[env->subprog_info[subprog + 1].start]) {
+ fp->xlated_off = BPF_FUNC_PTR_DELETED;
+ continue;
+ }
+ }
+ fp->xlated_off = bpf_patch_image_remap(&img, img.new_off, fp->xlated_off);
+ }
+
bpf_patch_image_install(prog, &img);
for (i = 0, k = 0; i < img.cnt; i = end) {
@@ -474,25 +496,6 @@ int bpf_patch_list_commit(struct bpf_verifier_env *env)
env->insn_aux_data_len = img.new_cnt;
data = NULL;
- /* before subprogs are removed, since it looks at them */
- for (i = 0; i < env->func_ptr_cnt; i++) {
- struct bpf_func_ptr *fp = &env->func_ptrs[i];
- int subprog;
-
- if (fp->xlated_off == BPF_FUNC_PTR_DELETED)
- continue;
- if (bpf_patch_image_deleted(&img, fp->xlated_off)) {
- subprog = bpf_find_subprog(env, fp->xlated_off);
- if (subprog <= 0 ||
- img.new_off[env->subprog_info[subprog].start] ==
- img.new_off[env->subprog_info[subprog + 1].start]) {
- fp->xlated_off = BPF_FUNC_PTR_DELETED;
- continue;
- }
- }
- fp->xlated_off = bpf_patch_image_remap(&img, img.new_off, fp->xlated_off);
- }
-
if (img.del)
patch_list_del_subprogs(env, img.new_off);
With it the prog above returns 124.
But that fixes one instance.
The commit changes prog->len, aux data, func ptrs, subprogs, line info and
insn arrays one after another, and helpers read env and prog in between.
So the order of the steps is load-bearing, and nothing says so.
Make each table a function of (old value, new_off[]), with no helper that
looks at env or prog, and install the image as the last step.
Then this class of bugs cannot happen.
Please add a selftest for it: callx through a pointer in .rodata, the callee
starts with a nop, and main loses more insns than the callee has.
All tests in patches 21 and 22 pass on bpf-next without the series,
so they don't tell whether a remap rule of the commit is wrong.
Also patches 4 and 6 don't apply anymore, since arena_scalar is gone.
Other than that the patches look great.
pw-bot: cr
next prev parent reply other threads:[~2026-10-31 5:29 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 11:51 [PATCH bpf-next v2 00/22] bpf: Replace bpf_patch_insn_data() with a list of deferred patches Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 01/22] bpf: Add a list of deferred instruction patches Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 02/22] bpf: Remap the verifier state through the patch list Qiliang Yuan
2026-10-30 12:44 ` bot+bpf-ci
2026-10-30 11:51 ` [PATCH bpf-next v2 03/22] bpf: Apply single-instruction patches of the verifier in place Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 04/22] bpf: Queue the rewrites of bpf_do_misc_fixups() Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 05/22] bpf: Expand a backward may_goto to gotol when patches push it out of range Qiliang Yuan
2026-10-30 12:59 ` bot+bpf-ci
2026-10-30 11:51 ` [PATCH bpf-next v2 06/22] bpf: Drop the constant delta from bpf_do_misc_fixups() Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 07/22] bpf: Let the patch list remove instructions Qiliang Yuan
2026-10-31 5:29 ` Alexei Starovoitov [this message]
2026-10-30 11:51 ` [PATCH bpf-next v2 08/22] bpf: Remove dead code through the patch list Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 09/22] bpf: Strip nops " Qiliang Yuan
2026-10-30 12:45 ` bot+bpf-ci
2026-10-30 11:51 ` [PATCH bpf-next v2 10/22] bpf: Batch the patches of bpf_opt_subreg_zext_lo32_rnd_hi32() Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 11/22] bpf: Allow patching an instruction twice through the patch list Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 12/22] bpf: Teach the patch list to insert in front of an instruction Qiliang Yuan
2026-10-30 12:44 ` bot+bpf-ci
2026-10-30 11:51 ` [PATCH bpf-next v2 13/22] bpf: Allow inserting in front of a queued patch Qiliang Yuan
2026-10-30 11:51 ` [PATCH bpf-next v2 14/22] bpf: Move bpf_convert_ctx_accesses() to the patch list Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 15/22] bpf: Insert the may_goto counter setup through " Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 16/22] bpf: Append the hidden subprog " Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 17/22] bpf: Inline bpf_loop() calls " Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 18/22] bpf: Apply the patch list without a verifier env Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 19/22] bpf: Blind constants through the patch list Qiliang Yuan
2026-10-30 12:44 ` bot+bpf-ci
2026-10-30 11:52 ` [PATCH bpf-next v2 20/22] bpf: Remove the old instruction patching code Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 21/22] selftests/bpf: Test jumps around patches of bpf_do_misc_fixups() Qiliang Yuan
2026-10-30 11:52 ` [PATCH bpf-next v2 22/22] selftests/bpf: Cover a backward may_goto pushed out of range by patches Qiliang Yuan
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=DMIRYFU9YEX1.14HBEXD0UQR7H@gmail.com \
--to=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=odys.yuan@gmail.com \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®