* [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier
2026-03-02 10:27 [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Xu Kuohai
@ 2026-03-02 10:27 ` Xu Kuohai
2026-03-02 10:46 ` bot+bpf-ci
2026-03-04 6:05 ` Eduard Zingerman
2026-03-02 10:27 ` [PATCH bpf-next v5 2/5] bpf: Pass bpf_verifier_env to jit Xu Kuohai
` (4 subsequent siblings)
5 siblings, 2 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-02 10:27 UTC (permalink / raw)
To: bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
From: Xu Kuohai <xukuohai@huawei.com>
JIT for single-subprog programs is done after the verification stage. This
prevents the JIT stage from accessing the verifier's internal datas, like
env->insn_aux_data. So move it to the verifier. After the movement, all bpf
progs loaded with bpf_prog_load() are JITed in the verifier. The JIT in
bpf_prog_select_runtime() is preserved for bpf_migrate_filter() and test
cases.
Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
---
include/linux/filter.h | 2 ++
kernel/bpf/core.c | 51 +++++++++++++++++++++++++++---------------
kernel/bpf/syscall.c | 2 +-
kernel/bpf/verifier.c | 7 +++++-
4 files changed, 42 insertions(+), 20 deletions(-)
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 44d7ae95ddbc..632c03e126d9 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1108,6 +1108,8 @@ static inline int sk_filter_reason(struct sock *sk, struct sk_buff *skb,
return sk_filter_trim_cap(sk, skb, 1, reason);
}
+struct bpf_prog *bpf_prog_select_jit(struct bpf_prog *fp, int *err);
+struct bpf_prog *__bpf_prog_select_runtime(struct bpf_prog *fp, bool jit_attempted, int *err);
struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err);
void bpf_prog_free(struct bpf_prog *fp);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 229c74f3d6ae..00be578a438d 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -2505,18 +2505,18 @@ static bool bpf_prog_select_interpreter(struct bpf_prog *fp)
return select_interpreter;
}
-/**
- * bpf_prog_select_runtime - select exec runtime for BPF program
- * @fp: bpf_prog populated with BPF program
- * @err: pointer to error variable
- *
- * Try to JIT eBPF program, if JIT is not available, use interpreter.
- * The BPF program will be executed via bpf_prog_run() function.
- *
- * Return: the &fp argument along with &err set to 0 for success or
- * a negative errno code on failure
- */
-struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
+struct bpf_prog *bpf_prog_select_jit(struct bpf_prog *fp, int *err)
+{
+ *err = bpf_prog_alloc_jited_linfo(fp);
+ if (*err)
+ return fp;
+
+ fp = bpf_int_jit_compile(fp);
+ bpf_prog_jit_attempt_done(fp);
+ return fp;
+}
+
+struct bpf_prog *__bpf_prog_select_runtime(struct bpf_prog *fp, bool jit_attempted, int *err)
{
/* In case of BPF to BPF calls, verifier did all the prep
* work with regards to JITing, etc.
@@ -2540,12 +2540,11 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
* be JITed, but falls back to the interpreter.
*/
if (!bpf_prog_is_offloaded(fp->aux)) {
- *err = bpf_prog_alloc_jited_linfo(fp);
- if (*err)
- return fp;
-
- fp = bpf_int_jit_compile(fp);
- bpf_prog_jit_attempt_done(fp);
+ if (!jit_attempted) {
+ fp = bpf_prog_select_jit(fp, err);
+ if (*err)
+ return fp;
+ }
if (!fp->jited && jit_needed) {
*err = -ENOTSUPP;
return fp;
@@ -2570,6 +2569,22 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
return fp;
}
+
+/**
+ * bpf_prog_select_runtime - select exec runtime for BPF program
+ * @fp: bpf_prog populated with BPF program
+ * @err: pointer to error variable
+ *
+ * Try to JIT eBPF program, if JIT is not available, use interpreter.
+ * The BPF program will be executed via bpf_prog_run() function.
+ *
+ * Return: the &fp argument along with &err set to 0 for success or
+ * a negative errno code on failure
+ */
+struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
+{
+ return __bpf_prog_select_runtime(fp, false, err);
+}
EXPORT_SYMBOL_GPL(bpf_prog_select_runtime);
static unsigned int __bpf_prog_ret1(const void *ctx,
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 274039e36465..d6982107ba80 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3090,7 +3090,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
if (err < 0)
goto free_used_maps;
- prog = bpf_prog_select_runtime(prog, &err);
+ prog = __bpf_prog_select_runtime(prog, true, &err);
if (err < 0)
goto free_used_maps;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fc4ccd1de569..ab2bc0850770 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -26086,6 +26086,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
convert_pseudo_ld_imm64(env);
}
+ /* constants blinding in the JIT may increase prog->len */
+ len = env->prog->len;
+ if (env->subprog_cnt == 1)
+ env->prog = bpf_prog_select_jit(env->prog, &ret);
+
adjust_btf_func(env);
err_release_maps:
@@ -26111,7 +26116,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
err_unlock:
if (!is_priv)
mutex_unlock(&bpf_verifier_lock);
- clear_insn_aux_data(env, 0, env->prog->len);
+ clear_insn_aux_data(env, 0, len);
vfree(env->insn_aux_data);
err_free_env:
bpf_stack_liveness_free(env);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier
2026-03-02 10:27 ` [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier Xu Kuohai
@ 2026-03-02 10:46 ` bot+bpf-ci
2026-03-03 2:28 ` Xu Kuohai
2026-03-04 6:05 ` Eduard Zingerman
1 sibling, 1 reply; 20+ messages in thread
From: bot+bpf-ci @ 2026-03-02 10:46 UTC (permalink / raw)
To: xukuohai, bpf, linux-kernel, linux-arm-kernel
Cc: ast, daniel, andrii, martin.lau, eddyz87, yonghong.song, puranjay,
a.s.protopopov, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2674 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index fc4ccd1de569..ab2bc0850770 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -26086,6 +26086,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
> convert_pseudo_ld_imm64(env);
> }
>
> + /* constants blinding in the JIT may increase prog->len */
> + len = env->prog->len;
> + if (env->subprog_cnt == 1)
> + env->prog = bpf_prog_select_jit(env->prog, &ret);
> +
> adjust_btf_func(env);
>
> err_release_maps:
[ ... ]
> @@ -26111,7 +26116,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
> err_unlock:
> if (!is_priv)
> mutex_unlock(&bpf_verifier_lock);
> - clear_insn_aux_data(env, 0, env->prog->len);
> + clear_insn_aux_data(env, 0, len);
> vfree(env->insn_aux_data);
Can the len variable be stale when reaching clear_insn_aux_data()
through an error path?
At the top of bpf_check(), len is first assigned as:
len = (*prog)->len;
This original value is used to allocate insn_aux_data. Later,
instruction rewriting passes like convert_ctx_accesses() and
do_misc_fixups() can change env->prog->len and resize
insn_aux_data to match via bpf_patch_insn_data().
The second assignment added by this patch:
len = env->prog->len;
is only reached on the success path, after all instruction
rewriting and after the maps/btfs allocation. If an error occurs
between the rewriting passes and this reassignment, for example
bpf_vlog_finalize() returning an error, copy_to_bpfptr_offset()
failing, or kmalloc for used_maps/used_btfs failing, the code
jumps to err_release_maps and eventually reaches
clear_insn_aux_data(env, 0, len) with the original program length
rather than the current post-rewriting length.
If rewriting increased env->prog->len (the common case for
programs with context accesses), the iteration stops short of
the actual insn_aux_data size, potentially leaking jt fields in
the unvisited entries.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/22571297111
AI-authorship-score: low
AI-authorship-explanation: Standard kernel refactoring by a known BPF contributor with non-native English patterns in the commit message.
issues-found: 1
issue-severity-score: low
issue-severity-explanation: Stale len variable in bpf_check error paths can cause insn_aux_data jt field memory leaks when instruction rewriting changed prog->len before an error between rewriting and the len reassignment.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier
2026-03-02 10:46 ` bot+bpf-ci
@ 2026-03-03 2:28 ` Xu Kuohai
0 siblings, 0 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-03 2:28 UTC (permalink / raw)
To: bot+bpf-ci, xukuohai, bpf, linux-kernel, linux-arm-kernel
Cc: ast, daniel, andrii, martin.lau, eddyz87, yonghong.song, puranjay,
a.s.protopopov, martin.lau, clm, ihor.solodrai
On 3/2/2026 6:46 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index fc4ccd1de569..ab2bc0850770 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -26086,6 +26086,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
>> convert_pseudo_ld_imm64(env);
>> }
>>
>> + /* constants blinding in the JIT may increase prog->len */
>> + len = env->prog->len;
>> + if (env->subprog_cnt == 1)
>> + env->prog = bpf_prog_select_jit(env->prog, &ret);
>> +
>> adjust_btf_func(env);
>>
>> err_release_maps:
>
> [ ... ]
>
>> @@ -26111,7 +26116,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
>> err_unlock:
>> if (!is_priv)
>> mutex_unlock(&bpf_verifier_lock);
>> - clear_insn_aux_data(env, 0, env->prog->len);
>> + clear_insn_aux_data(env, 0, len);
>> vfree(env->insn_aux_data);
>
> Can the len variable be stale when reaching clear_insn_aux_data()
> through an error path?
>
> At the top of bpf_check(), len is first assigned as:
>
> len = (*prog)->len;
>
> This original value is used to allocate insn_aux_data. Later,
> instruction rewriting passes like convert_ctx_accesses() and
> do_misc_fixups() can change env->prog->len and resize
> insn_aux_data to match via bpf_patch_insn_data().
>
> The second assignment added by this patch:
>
> len = env->prog->len;
>
> is only reached on the success path, after all instruction
> rewriting and after the maps/btfs allocation. If an error occurs
> between the rewriting passes and this reassignment, for example
> bpf_vlog_finalize() returning an error, copy_to_bpfptr_offset()
> failing, or kmalloc for used_maps/used_btfs failing, the code
> jumps to err_release_maps and eventually reaches
> clear_insn_aux_data(env, 0, len) with the original program length
> rather than the current post-rewriting length.
>
> If rewriting increased env->prog->len (the common case for
> programs with context accesses), the iteration stops short of
> the actual insn_aux_data size, potentially leaking jt fields in
> the unvisited entries.
>
Right, len is stale in the error path. I'll fix it in the next version.
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/22571297111
>
> AI-authorship-score: low
> AI-authorship-explanation: Standard kernel refactoring by a known BPF contributor with non-native English patterns in the commit message.
> issues-found: 1
> issue-severity-score: low
> issue-severity-explanation: Stale len variable in bpf_check error paths can cause insn_aux_data jt field memory leaks when instruction rewriting changed prog->len before an error between rewriting and the len reassignment.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier
2026-03-02 10:27 ` [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier Xu Kuohai
2026-03-02 10:46 ` bot+bpf-ci
@ 2026-03-04 6:05 ` Eduard Zingerman
2026-03-04 12:22 ` Xu Kuohai
1 sibling, 1 reply; 20+ messages in thread
From: Eduard Zingerman @ 2026-03-04 6:05 UTC (permalink / raw)
To: Xu Kuohai, bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Yonghong Song, Puranjay Mohan, Anton Protopopov
On Mon, 2026-03-02 at 18:27 +0800, Xu Kuohai wrote:
[...]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index fc4ccd1de569..ab2bc0850770 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -26086,6 +26086,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
> convert_pseudo_ld_imm64(env);
> }
>
> + /* constants blinding in the JIT may increase prog->len */
> + len = env->prog->len;
> + if (env->subprog_cnt == 1)
> + env->prog = bpf_prog_select_jit(env->prog, &ret);
> +
I probably miss something important, but would it be possible to put a
call to __bpf_prog_select_runtime() here and remove it from syscall.c:bpf_prog_load()?
Thus avoiding the need for `jit_attempted` parameter.
[...]
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier
2026-03-04 6:05 ` Eduard Zingerman
@ 2026-03-04 12:22 ` Xu Kuohai
0 siblings, 0 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-04 12:22 UTC (permalink / raw)
To: Eduard Zingerman, bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Yonghong Song, Puranjay Mohan, Anton Protopopov
On 3/4/2026 2:05 PM, Eduard Zingerman wrote:
> On Mon, 2026-03-02 at 18:27 +0800, Xu Kuohai wrote:
>
> [...]
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index fc4ccd1de569..ab2bc0850770 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -26086,6 +26086,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
>> convert_pseudo_ld_imm64(env);
>> }
>>
>> + /* constants blinding in the JIT may increase prog->len */
>> + len = env->prog->len;
>> + if (env->subprog_cnt == 1)
>> + env->prog = bpf_prog_select_jit(env->prog, &ret);
>> +
>
> I probably miss something important, but would it be possible to put a
> call to __bpf_prog_select_runtime() here and remove it from syscall.c:bpf_prog_load()?
> Thus avoiding the need for `jit_attempted` parameter.
>
Makes sense. In fact, at first I planned to move the whole thing to verifier,
but since this series is just fixing a JIT issue, I ultimately decided to only
move the JIT code to avoid unnecessary changes.
> [...]
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v5 2/5] bpf: Pass bpf_verifier_env to jit
2026-03-02 10:27 [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Xu Kuohai
2026-03-02 10:27 ` [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier Xu Kuohai
@ 2026-03-02 10:27 ` Xu Kuohai
2026-03-02 10:27 ` [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets Xu Kuohai
` (3 subsequent siblings)
5 siblings, 0 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-02 10:27 UTC (permalink / raw)
To: bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
From: Xu Kuohai <xukuohai@huawei.com>
Pass bpf_verifier_env to bpf_int_jit_compile() and bpf_jit_blind_constants().
The follow-up patch will use env->insn_aux_data in the JIT stage to detect
indirect jump targets.
Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
---
arch/arc/net/bpf_jit_core.c | 19 ++++++++++---------
arch/arm/net/bpf_jit_32.c | 4 ++--
arch/arm64/net/bpf_jit_comp.c | 4 ++--
arch/loongarch/net/bpf_jit.c | 4 ++--
arch/mips/net/bpf_jit_comp.c | 4 ++--
arch/parisc/net/bpf_jit_core.c | 4 ++--
arch/powerpc/net/bpf_jit_comp.c | 4 ++--
arch/riscv/net/bpf_jit_core.c | 4 ++--
arch/s390/net/bpf_jit_comp.c | 4 ++--
arch/sparc/net/bpf_jit_comp_64.c | 4 ++--
arch/x86/net/bpf_jit_comp.c | 4 ++--
arch/x86/net/bpf_jit_comp32.c | 4 ++--
include/linux/filter.h | 6 +++---
kernel/bpf/core.c | 10 +++++-----
kernel/bpf/verifier.c | 6 +++---
15 files changed, 43 insertions(+), 42 deletions(-)
diff --git a/arch/arc/net/bpf_jit_core.c b/arch/arc/net/bpf_jit_core.c
index 1421eeced0f5..076aaf52cb80 100644
--- a/arch/arc/net/bpf_jit_core.c
+++ b/arch/arc/net/bpf_jit_core.c
@@ -157,14 +157,15 @@ static void jit_dump(const struct jit_context *ctx)
}
/* Initialise the context so there's no garbage. */
-static int jit_ctx_init(struct jit_context *ctx, struct bpf_prog *prog)
+static int jit_ctx_init(struct jit_context *ctx, struct bpf_verifier_env *env,
+ struct bpf_prog *prog)
{
memset(ctx, 0, sizeof(*ctx));
ctx->orig_prog = prog;
/* If constant blinding was requested but failed, scram. */
- ctx->prog = bpf_jit_blind_constants(prog);
+ ctx->prog = bpf_jit_blind_constants(env, prog);
if (IS_ERR(ctx->prog))
return PTR_ERR(ctx->prog);
ctx->blinded = (ctx->prog != ctx->orig_prog);
@@ -1335,7 +1336,7 @@ static int jit_patch_relocations(struct jit_context *ctx)
* to get the necessary data for the real compilation phase,
* jit_compile().
*/
-static struct bpf_prog *do_normal_pass(struct bpf_prog *prog)
+static struct bpf_prog *do_normal_pass(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct jit_context ctx;
@@ -1343,7 +1344,7 @@ static struct bpf_prog *do_normal_pass(struct bpf_prog *prog)
if (!prog->jit_requested)
return prog;
- if (jit_ctx_init(&ctx, prog)) {
+ if (jit_ctx_init(&ctx, env, prog)) {
jit_ctx_cleanup(&ctx);
return prog;
}
@@ -1374,7 +1375,7 @@ static struct bpf_prog *do_normal_pass(struct bpf_prog *prog)
* again to get the newly translated addresses in order to resolve
* the "call"s.
*/
-static struct bpf_prog *do_extra_pass(struct bpf_prog *prog)
+static struct bpf_prog *do_extra_pass(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct jit_context ctx;
@@ -1382,7 +1383,7 @@ static struct bpf_prog *do_extra_pass(struct bpf_prog *prog)
if (check_jit_context(prog))
return prog;
- if (jit_ctx_init(&ctx, prog)) {
+ if (jit_ctx_init(&ctx, env, prog)) {
jit_ctx_cleanup(&ctx);
return prog;
}
@@ -1411,15 +1412,15 @@ static struct bpf_prog *do_extra_pass(struct bpf_prog *prog)
* (re)locations involved that their addresses are not known
* during the first run.
*/
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
vm_dump(prog);
/* Was this program already translated? */
if (!prog->jited)
- return do_normal_pass(prog);
+ return do_normal_pass(env, prog);
else
- return do_extra_pass(prog);
+ return do_extra_pass(env, prog);
return prog;
}
diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c
index deeb8f292454..9c07cbf1dbfc 100644
--- a/arch/arm/net/bpf_jit_32.c
+++ b/arch/arm/net/bpf_jit_32.c
@@ -2142,7 +2142,7 @@ bool bpf_jit_needs_zext(void)
return true;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_prog *tmp, *orig_prog = prog;
struct bpf_binary_header *header;
@@ -2162,7 +2162,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
* then we must fall back to the interpreter. Otherwise, we save
* the new JITed code.
*/
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
if (IS_ERR(tmp))
return orig_prog;
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index adf84962d579..823246c7ff5d 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -2006,7 +2006,7 @@ struct arm64_jit_data {
struct jit_ctx ctx;
};
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
int image_size, prog_size, extable_size, extable_align, extable_offset;
struct bpf_prog *tmp, *orig_prog = prog;
@@ -2027,7 +2027,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
/* If blinding was requested and we failed during blinding,
* we must fall back to the interpreter.
*/
diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c
index 3bd89f55960d..b578b176ef01 100644
--- a/arch/loongarch/net/bpf_jit.c
+++ b/arch/loongarch/net/bpf_jit.c
@@ -1909,7 +1909,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
return ret < 0 ? ret : ret * LOONGARCH_INSN_SIZE;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
bool tmp_blinded = false, extra_pass = false;
u8 *image_ptr, *ro_image_ptr;
@@ -1927,7 +1927,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
/*
* If blinding was requested and we failed during blinding,
* we must fall back to the interpreter. Otherwise, we save
diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c
index e355dfca4400..faf0ba098a86 100644
--- a/arch/mips/net/bpf_jit_comp.c
+++ b/arch/mips/net/bpf_jit_comp.c
@@ -909,7 +909,7 @@ bool bpf_jit_needs_zext(void)
return true;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_prog *tmp, *orig_prog = prog;
struct bpf_binary_header *header = NULL;
@@ -931,7 +931,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
* then we must fall back to the interpreter. Otherwise, we save
* the new JITed code.
*/
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
if (IS_ERR(tmp))
return orig_prog;
if (tmp != prog) {
diff --git a/arch/parisc/net/bpf_jit_core.c b/arch/parisc/net/bpf_jit_core.c
index a5eb6b51e27a..e85b6e336b19 100644
--- a/arch/parisc/net/bpf_jit_core.c
+++ b/arch/parisc/net/bpf_jit_core.c
@@ -41,7 +41,7 @@ bool bpf_jit_needs_zext(void)
return true;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
unsigned int prog_size = 0, extable_size = 0;
bool tmp_blinded = false, extra_pass = false;
@@ -53,7 +53,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
if (IS_ERR(tmp))
return orig_prog;
if (tmp != prog) {
diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c
index 52162e4a7f84..fb77e8beb161 100644
--- a/arch/powerpc/net/bpf_jit_comp.c
+++ b/arch/powerpc/net/bpf_jit_comp.c
@@ -129,7 +129,7 @@ bool bpf_jit_needs_zext(void)
return true;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *fp)
{
u32 proglen;
u32 alloclen;
@@ -154,7 +154,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp)
if (!fp->jit_requested)
return org_fp;
- tmp_fp = bpf_jit_blind_constants(org_fp);
+ tmp_fp = bpf_jit_blind_constants(env, org_fp);
if (IS_ERR(tmp_fp))
return org_fp;
diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c
index b3581e926436..ce157319459f 100644
--- a/arch/riscv/net/bpf_jit_core.c
+++ b/arch/riscv/net/bpf_jit_core.c
@@ -41,7 +41,7 @@ bool bpf_jit_needs_zext(void)
return true;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
unsigned int prog_size = 0, extable_size = 0;
bool tmp_blinded = false, extra_pass = false;
@@ -53,7 +53,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
if (IS_ERR(tmp))
return orig_prog;
if (tmp != prog) {
diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c
index 211226748662..84aabfc8a9d6 100644
--- a/arch/s390/net/bpf_jit_comp.c
+++ b/arch/s390/net/bpf_jit_comp.c
@@ -2303,7 +2303,7 @@ static struct bpf_binary_header *bpf_jit_alloc(struct bpf_jit *jit,
/*
* Compile eBPF program "fp"
*/
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *fp)
{
struct bpf_prog *tmp, *orig_fp = fp;
struct bpf_binary_header *header;
@@ -2316,7 +2316,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp)
if (!fp->jit_requested)
return orig_fp;
- tmp = bpf_jit_blind_constants(fp);
+ tmp = bpf_jit_blind_constants(env, fp);
/*
* If blinding was requested and we failed during blinding,
* we must fall back to the interpreter.
diff --git a/arch/sparc/net/bpf_jit_comp_64.c b/arch/sparc/net/bpf_jit_comp_64.c
index b23d1c645ae5..55da61ca2967 100644
--- a/arch/sparc/net/bpf_jit_comp_64.c
+++ b/arch/sparc/net/bpf_jit_comp_64.c
@@ -1477,7 +1477,7 @@ struct sparc64_jit_data {
struct jit_ctx ctx;
};
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_prog *tmp, *orig_prog = prog;
struct sparc64_jit_data *jit_data;
@@ -1492,7 +1492,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
/* If blinding was requested and we failed during blinding,
* we must fall back to the interpreter.
*/
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 8f10080e6fe3..43beacaed56d 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3722,7 +3722,7 @@ struct x64_jit_data {
#define MAX_PASSES 20
#define PADDING_PASSES (MAX_PASSES - 5)
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_binary_header *rw_header = NULL;
struct bpf_binary_header *header = NULL;
@@ -3744,7 +3744,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
/*
* If blinding was requested and we failed during blinding,
* we must fall back to the interpreter.
diff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c
index dda423025c3d..957f7aa951ba 100644
--- a/arch/x86/net/bpf_jit_comp32.c
+++ b/arch/x86/net/bpf_jit_comp32.c
@@ -2518,7 +2518,7 @@ bool bpf_jit_needs_zext(void)
return true;
}
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_binary_header *header = NULL;
struct bpf_prog *tmp, *orig_prog = prog;
@@ -2533,7 +2533,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
if (!prog->jit_requested)
return orig_prog;
- tmp = bpf_jit_blind_constants(prog);
+ tmp = bpf_jit_blind_constants(env, prog);
/*
* If blinding was requested and we failed during blinding,
* we must fall back to the interpreter.
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 632c03e126d9..8b5e9ac9eee4 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1108,7 +1108,7 @@ static inline int sk_filter_reason(struct sock *sk, struct sk_buff *skb,
return sk_filter_trim_cap(sk, skb, 1, reason);
}
-struct bpf_prog *bpf_prog_select_jit(struct bpf_prog *fp, int *err);
+struct bpf_prog *bpf_prog_select_jit(struct bpf_verifier_env *env, struct bpf_prog *fp, int *err);
struct bpf_prog *__bpf_prog_select_runtime(struct bpf_prog *fp, bool jit_attempted, int *err);
struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err);
void bpf_prog_free(struct bpf_prog *fp);
@@ -1155,7 +1155,7 @@ u64 __bpf_call_base(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);
((u64 (*)(u64, u64, u64, u64, u64, const struct bpf_insn *)) \
(void *)__bpf_call_base)
-struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog);
+struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog);
void bpf_jit_compile(struct bpf_prog *prog);
bool bpf_jit_needs_zext(void);
bool bpf_jit_inlines_helper_call(s32 imm);
@@ -1312,7 +1312,7 @@ int bpf_jit_get_func_addr(const struct bpf_prog *prog,
const char *bpf_jit_get_prog_name(struct bpf_prog *prog);
-struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *fp);
+struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog);
void bpf_jit_prog_release_other(struct bpf_prog *fp, struct bpf_prog *fp_other);
static inline void bpf_jit_dump(unsigned int flen, unsigned int proglen,
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 00be578a438d..7702c232c62e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1486,7 +1486,7 @@ static void adjust_insn_arrays(struct bpf_prog *prog, u32 off, u32 len)
#endif
}
-struct bpf_prog *bpf_jit_blind_constants(struct bpf_prog *prog)
+struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_insn insn_buff[16], aux[2];
struct bpf_prog *clone, *tmp;
@@ -2505,13 +2505,13 @@ static bool bpf_prog_select_interpreter(struct bpf_prog *fp)
return select_interpreter;
}
-struct bpf_prog *bpf_prog_select_jit(struct bpf_prog *fp, int *err)
+struct bpf_prog *bpf_prog_select_jit(struct bpf_verifier_env *env, struct bpf_prog *fp, int *err)
{
*err = bpf_prog_alloc_jited_linfo(fp);
if (*err)
return fp;
- fp = bpf_int_jit_compile(fp);
+ fp = bpf_int_jit_compile(env, fp);
bpf_prog_jit_attempt_done(fp);
return fp;
}
@@ -2541,7 +2541,7 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_prog *fp, bool jit_attempt
*/
if (!bpf_prog_is_offloaded(fp->aux)) {
if (!jit_attempted) {
- fp = bpf_prog_select_jit(fp, err);
+ fp = bpf_prog_select_jit(NULL, fp, err);
if (*err)
return fp;
}
@@ -3072,7 +3072,7 @@ const struct bpf_func_proto bpf_tail_call_proto = {
* It is encouraged to implement bpf_int_jit_compile() instead, so that
* eBPF and implicitly also cBPF can get JITed!
*/
-struct bpf_prog * __weak bpf_int_jit_compile(struct bpf_prog *prog)
+struct bpf_prog * __weak bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
return prog;
}
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ab2bc0850770..1d2d42078ddf 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -22844,7 +22844,7 @@ static int jit_subprogs(struct bpf_verifier_env *env)
* all instruction adjustments should be accumulated
*/
old_len = func[i]->len;
- func[i] = bpf_int_jit_compile(func[i]);
+ func[i] = bpf_int_jit_compile(env, func[i]);
subprog_start_adjustment += func[i]->len - old_len;
if (!func[i]->jited) {
@@ -22890,7 +22890,7 @@ static int jit_subprogs(struct bpf_verifier_env *env)
}
for (i = 0; i < env->subprog_cnt; i++) {
old_bpf_func = func[i]->bpf_func;
- tmp = bpf_int_jit_compile(func[i]);
+ tmp = bpf_int_jit_compile(env, func[i]);
if (tmp != func[i] || func[i]->bpf_func != old_bpf_func) {
verbose(env, "JIT doesn't support bpf-to-bpf calls\n");
err = -ENOTSUPP;
@@ -26089,7 +26089,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
/* constants blinding in the JIT may increase prog->len */
len = env->prog->len;
if (env->subprog_cnt == 1)
- env->prog = bpf_prog_select_jit(env->prog, &ret);
+ env->prog = bpf_prog_select_jit(env, env->prog, &ret);
adjust_btf_func(env);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets
2026-03-02 10:27 [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Xu Kuohai
2026-03-02 10:27 ` [PATCH bpf-next v5 1/5] bpf: Move JIT for single-subprog programs to verifier Xu Kuohai
2026-03-02 10:27 ` [PATCH bpf-next v5 2/5] bpf: Pass bpf_verifier_env to jit Xu Kuohai
@ 2026-03-02 10:27 ` Xu Kuohai
2026-03-03 17:19 ` Alexei Starovoitov
2026-03-02 10:27 ` [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for " Xu Kuohai
` (2 subsequent siblings)
5 siblings, 1 reply; 20+ messages in thread
From: Xu Kuohai @ 2026-03-02 10:27 UTC (permalink / raw)
To: bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
From: Xu Kuohai <xukuohai@huawei.com>
Introduce helper bpf_insn_is_indirect_target to determine whether a BPF
instruction is an indirect jump target. This helper will be used by
follow-up patches to decide where to emit indirect landing pad instructions.
Add a new flag to struct bpf_insn_aux_data to mark instructions that are
indirect jump targets. The BPF verifier sets this flag, and the helper
checks it to determine whether an instruction is an indirect jump target.
Also add a new field to struct bpf_insn_aux_data to track the instruction
final index in the bpf prog, as the instructions may be rewritten by
constant blinding in the JIT stage. This field is used as a binary search
key to find the corresponding insn_aux_data for a given instruction.
Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
---
include/linux/bpf.h | 2 ++
include/linux/bpf_verifier.h | 10 ++++++----
kernel/bpf/core.c | 38 +++++++++++++++++++++++++++++++++---
kernel/bpf/verifier.c | 13 +++++++++++-
4 files changed, 55 insertions(+), 8 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 05b34a6355b0..90760e250865 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1541,6 +1541,8 @@ bool bpf_has_frame_pointer(unsigned long ip);
int bpf_jit_charge_modmem(u32 size);
void bpf_jit_uncharge_modmem(u32 size);
bool bpf_prog_has_trampoline(const struct bpf_prog *prog);
+bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog,
+ int insn_idx);
#else
static inline int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
struct bpf_trampoline *tr,
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index c1e30096ea7b..f8f70e5414f0 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -577,16 +577,18 @@ struct bpf_insn_aux_data {
/* below fields are initialized once */
unsigned int orig_idx; /* original instruction index */
- bool jmp_point;
- bool prune_point;
+ unsigned int final_idx; /* final instruction index */
+ u32 jmp_point:1;
+ u32 prune_point:1;
/* ensure we check state equivalence and save state checkpoint and
* this instruction, regardless of any heuristics
*/
- bool force_checkpoint;
+ u32 force_checkpoint:1;
/* true if instruction is a call to a helper function that
* accepts callback function as a parameter.
*/
- bool calls_callback;
+ u32 calls_callback:1;
+ u32 indirect_target:1; /* if it is an indirect jump target */
/*
* CFG strongly connected component this instruction belongs to,
* zero if it is a singleton SCC.
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 7702c232c62e..9a760cf43d68 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1486,13 +1486,41 @@ static void adjust_insn_arrays(struct bpf_prog *prog, u32 off, u32 len)
#endif
}
+static int bpf_insn_aux_cmp_by_insn_idx(const void *a, const void *b)
+{
+ int insn_idx = *(int *)a;
+ int final_idx = ((const struct bpf_insn_aux_data *)b)->final_idx;
+
+ return insn_idx - final_idx;
+}
+
+bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog,
+ int insn_idx)
+{
+ struct bpf_insn_aux_data *insn_aux;
+ int func_idx, subprog_start, subprog_end;
+
+ if (!env)
+ return false;
+
+ func_idx = prog->aux->func_idx;
+ subprog_start = env->subprog_info[func_idx].start;
+ subprog_end = env->subprog_info[func_idx + 1].start;
+
+ insn_aux = bsearch(&insn_idx, &env->insn_aux_data[subprog_start],
+ subprog_end - subprog_start,
+ sizeof(struct bpf_insn_aux_data), bpf_insn_aux_cmp_by_insn_idx);
+
+ return insn_aux && insn_aux->indirect_target;
+}
+
struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog)
{
struct bpf_insn insn_buff[16], aux[2];
struct bpf_prog *clone, *tmp;
- int insn_delta, insn_cnt;
+ int insn_delta, insn_cnt, subprog_start;
struct bpf_insn *insn;
- int i, rewritten;
+ int i, j, rewritten;
if (!prog->blinding_requested || prog->blinded)
return prog;
@@ -1503,8 +1531,10 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
insn_cnt = clone->len;
insn = clone->insnsi;
+ subprog_start = env->subprog_info[prog->aux->func_idx].start;
- for (i = 0; i < insn_cnt; i++, insn++) {
+ for (i = 0, j = 0; i < insn_cnt; i++, j++, insn++) {
+ env->insn_aux_data[subprog_start + j].final_idx = i;
if (bpf_pseudo_func(insn)) {
/* ld_imm64 with an address of bpf subprog is not
* a user controlled constant. Don't randomize it,
@@ -1512,6 +1542,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
*/
insn++;
i++;
+ j++;
+ env->insn_aux_data[subprog_start + j].final_idx = i;
continue;
}
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1d2d42078ddf..5f08d521e58a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3971,6 +3971,11 @@ static bool is_jmp_point(struct bpf_verifier_env *env, int insn_idx)
return env->insn_aux_data[insn_idx].jmp_point;
}
+static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
+{
+ env->insn_aux_data[idx].indirect_target = true;
+}
+
#define LR_FRAMENO_BITS 3
#define LR_SPI_BITS 6
#define LR_ENTRY_BITS (LR_SPI_BITS + LR_FRAMENO_BITS + 1)
@@ -20943,12 +20948,14 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
}
for (i = 0; i < n - 1; i++) {
+ mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
env->insn_idx, env->cur_state->speculative);
if (IS_ERR(other_branch))
return PTR_ERR(other_branch);
}
env->insn_idx = env->gotox_tmp_buf->items[n-1];
+ mark_indirect_target(env, env->insn_idx);
return 0;
}
@@ -22817,6 +22824,7 @@ static int jit_subprogs(struct bpf_verifier_env *env)
num_exentries = 0;
insn = func[i]->insnsi;
for (j = 0; j < func[i]->len; j++, insn++) {
+ env->insn_aux_data[subprog_start + j].final_idx = j;
if (BPF_CLASS(insn->code) == BPF_LDX &&
(BPF_MODE(insn->code) == BPF_PROBE_MEM ||
BPF_MODE(insn->code) == BPF_PROBE_MEM32 ||
@@ -26088,8 +26096,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
/* constants blinding in the JIT may increase prog->len */
len = env->prog->len;
- if (env->subprog_cnt == 1)
+ if (env->subprog_cnt == 1) {
+ for (i = 0; i < len; i++)
+ env->insn_aux_data[i].final_idx = i;
env->prog = bpf_prog_select_jit(env, env->prog, &ret);
+ }
adjust_btf_func(env);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets
2026-03-02 10:27 ` [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets Xu Kuohai
@ 2026-03-03 17:19 ` Alexei Starovoitov
2026-03-04 12:45 ` Xu Kuohai
0 siblings, 1 reply; 20+ messages in thread
From: Alexei Starovoitov @ 2026-03-03 17:19 UTC (permalink / raw)
To: Xu Kuohai
Cc: bpf, LKML, linux-arm-kernel, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Yonghong Song, Puranjay Mohan, Anton Protopopov
On Mon, Mar 2, 2026 at 2:02 AM Xu Kuohai <xukuohai@huaweicloud.com> wrote:
>
> From: Xu Kuohai <xukuohai@huawei.com>
>
> Introduce helper bpf_insn_is_indirect_target to determine whether a BPF
> instruction is an indirect jump target. This helper will be used by
> follow-up patches to decide where to emit indirect landing pad instructions.
>
> Add a new flag to struct bpf_insn_aux_data to mark instructions that are
> indirect jump targets. The BPF verifier sets this flag, and the helper
> checks it to determine whether an instruction is an indirect jump target.
>
> Also add a new field to struct bpf_insn_aux_data to track the instruction
> final index in the bpf prog, as the instructions may be rewritten by
> constant blinding in the JIT stage. This field is used as a binary search
> key to find the corresponding insn_aux_data for a given instruction.
>
> Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
> ---
> include/linux/bpf.h | 2 ++
> include/linux/bpf_verifier.h | 10 ++++++----
> kernel/bpf/core.c | 38 +++++++++++++++++++++++++++++++++---
> kernel/bpf/verifier.c | 13 +++++++++++-
> 4 files changed, 55 insertions(+), 8 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 05b34a6355b0..90760e250865 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1541,6 +1541,8 @@ bool bpf_has_frame_pointer(unsigned long ip);
> int bpf_jit_charge_modmem(u32 size);
> void bpf_jit_uncharge_modmem(u32 size);
> bool bpf_prog_has_trampoline(const struct bpf_prog *prog);
> +bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog,
> + int insn_idx);
> #else
> static inline int bpf_trampoline_link_prog(struct bpf_tramp_link *link,
> struct bpf_trampoline *tr,
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index c1e30096ea7b..f8f70e5414f0 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -577,16 +577,18 @@ struct bpf_insn_aux_data {
>
> /* below fields are initialized once */
> unsigned int orig_idx; /* original instruction index */
> - bool jmp_point;
> - bool prune_point;
> + unsigned int final_idx; /* final instruction index */
> + u32 jmp_point:1;
> + u32 prune_point:1;
> /* ensure we check state equivalence and save state checkpoint and
> * this instruction, regardless of any heuristics
> */
> - bool force_checkpoint;
> + u32 force_checkpoint:1;
> /* true if instruction is a call to a helper function that
> * accepts callback function as a parameter.
> */
> - bool calls_callback;
> + u32 calls_callback:1;
> + u32 indirect_target:1; /* if it is an indirect jump target */
> /*
> * CFG strongly connected component this instruction belongs to,
> * zero if it is a singleton SCC.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 7702c232c62e..9a760cf43d68 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -1486,13 +1486,41 @@ static void adjust_insn_arrays(struct bpf_prog *prog, u32 off, u32 len)
> #endif
> }
>
> +static int bpf_insn_aux_cmp_by_insn_idx(const void *a, const void *b)
> +{
> + int insn_idx = *(int *)a;
> + int final_idx = ((const struct bpf_insn_aux_data *)b)->final_idx;
> +
> + return insn_idx - final_idx;
> +}
> +
> +bool bpf_insn_is_indirect_target(const struct bpf_verifier_env *env, const struct bpf_prog *prog,
> + int insn_idx)
> +{
> + struct bpf_insn_aux_data *insn_aux;
> + int func_idx, subprog_start, subprog_end;
> +
> + if (!env)
> + return false;
> +
> + func_idx = prog->aux->func_idx;
> + subprog_start = env->subprog_info[func_idx].start;
> + subprog_end = env->subprog_info[func_idx + 1].start;
> +
> + insn_aux = bsearch(&insn_idx, &env->insn_aux_data[subprog_start],
> + subprog_end - subprog_start,
> + sizeof(struct bpf_insn_aux_data), bpf_insn_aux_cmp_by_insn_idx);
> +
> + return insn_aux && insn_aux->indirect_target;
> +}
> +
> struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bpf_prog *prog)
> {
> struct bpf_insn insn_buff[16], aux[2];
> struct bpf_prog *clone, *tmp;
> - int insn_delta, insn_cnt;
> + int insn_delta, insn_cnt, subprog_start;
> struct bpf_insn *insn;
> - int i, rewritten;
> + int i, j, rewritten;
>
> if (!prog->blinding_requested || prog->blinded)
> return prog;
> @@ -1503,8 +1531,10 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
>
> insn_cnt = clone->len;
> insn = clone->insnsi;
> + subprog_start = env->subprog_info[prog->aux->func_idx].start;
>
> - for (i = 0; i < insn_cnt; i++, insn++) {
> + for (i = 0, j = 0; i < insn_cnt; i++, j++, insn++) {
> + env->insn_aux_data[subprog_start + j].final_idx = i;
> if (bpf_pseudo_func(insn)) {
> /* ld_imm64 with an address of bpf subprog is not
> * a user controlled constant. Don't randomize it,
> @@ -1512,6 +1542,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
> */
> insn++;
> i++;
> + j++;
> + env->insn_aux_data[subprog_start + j].final_idx = i;
You're adding final_idx because bpf_jit_blind_constants()
doesn't call adjust_insn_aux_data() ?
imo that's an ugly workaround. Just call adjust_insn_aux_data().
And in the future please mention such design decisions in the commit log,
so that reviewers don't need to reverse engineer your thought process.
pw-bot: cr
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets
2026-03-03 17:19 ` Alexei Starovoitov
@ 2026-03-04 12:45 ` Xu Kuohai
2026-03-04 15:37 ` Alexei Starovoitov
0 siblings, 1 reply; 20+ messages in thread
From: Xu Kuohai @ 2026-03-04 12:45 UTC (permalink / raw)
To: Alexei Starovoitov, Xu Kuohai
Cc: bpf, LKML, linux-arm-kernel, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Yonghong Song, Puranjay Mohan, Anton Protopopov
On 3/4/2026 1:19 AM, Alexei Starovoitov wrote:
[...]
>> - for (i = 0; i < insn_cnt; i++, insn++) {
>> + for (i = 0, j = 0; i < insn_cnt; i++, j++, insn++) {
>> + env->insn_aux_data[subprog_start + j].final_idx = i;
>> if (bpf_pseudo_func(insn)) {
>> /* ld_imm64 with an address of bpf subprog is not
>> * a user controlled constant. Don't randomize it,
>> @@ -1512,6 +1542,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
>> */
>> insn++;
>> i++;
>> + j++;
>> + env->insn_aux_data[subprog_start + j].final_idx = i;
>
> You're adding final_idx because bpf_jit_blind_constants()
> doesn't call adjust_insn_aux_data() ?
>
Yes, I added final_idx because insn_aux is not updated here.
> imo that's an ugly workaround. Just call adjust_insn_aux_data().
>
If we adjust the env->insn_aux_data here, should we also adjust the global
env->prog->insnsi array? I think env->insn_aux_data should remain consistent
with the global env->prog->insnsi array. Since constant blinding only rewrites
the subprog's private instruction array, updating the env->insn_aux_data
causes a mismatch with the global state.
> And in the future please mention such design decisions in the commit log,
> so that reviewers don't need to reverse engineer your thought process.
>
Sorry for the lack of clarity. I’ll make an effort to clarify things more
clearly in the future.
> pw-bot: cr
>
>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets
2026-03-04 12:45 ` Xu Kuohai
@ 2026-03-04 15:37 ` Alexei Starovoitov
2026-03-05 3:47 ` Xu Kuohai
0 siblings, 1 reply; 20+ messages in thread
From: Alexei Starovoitov @ 2026-03-04 15:37 UTC (permalink / raw)
To: Xu Kuohai
Cc: bpf, LKML, linux-arm-kernel, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Yonghong Song, Puranjay Mohan, Anton Protopopov
On Wed, Mar 4, 2026 at 4:46 AM Xu Kuohai <xukuohai@huaweicloud.com> wrote:
>
> On 3/4/2026 1:19 AM, Alexei Starovoitov wrote:
>
> [...]
>
> >> - for (i = 0; i < insn_cnt; i++, insn++) {
> >> + for (i = 0, j = 0; i < insn_cnt; i++, j++, insn++) {
> >> + env->insn_aux_data[subprog_start + j].final_idx = i;
> >> if (bpf_pseudo_func(insn)) {
> >> /* ld_imm64 with an address of bpf subprog is not
> >> * a user controlled constant. Don't randomize it,
> >> @@ -1512,6 +1542,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
> >> */
> >> insn++;
> >> i++;
> >> + j++;
> >> + env->insn_aux_data[subprog_start + j].final_idx = i;
> >
> > You're adding final_idx because bpf_jit_blind_constants()
> > doesn't call adjust_insn_aux_data() ?
> >
>
> Yes, I added final_idx because insn_aux is not updated here.
>
> > imo that's an ugly workaround. Just call adjust_insn_aux_data().
> >
>
> If we adjust the env->insn_aux_data here, should we also adjust the global
> env->prog->insnsi array? I think env->insn_aux_data should remain consistent
> with the global env->prog->insnsi array. Since constant blinding only rewrites
> the subprog's private instruction array, updating the env->insn_aux_data
> causes a mismatch with the global state.
yes, and subprog starts, and pokes that bpf_patch_insn_data() do.
blinding was implemented long before that, so it was never updated.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets
2026-03-04 15:37 ` Alexei Starovoitov
@ 2026-03-05 3:47 ` Xu Kuohai
2026-03-05 3:56 ` Alexei Starovoitov
0 siblings, 1 reply; 20+ messages in thread
From: Xu Kuohai @ 2026-03-05 3:47 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: bpf, LKML, linux-arm-kernel, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Yonghong Song, Puranjay Mohan, Anton Protopopov
On 3/4/2026 11:37 PM, Alexei Starovoitov wrote:
> On Wed, Mar 4, 2026 at 4:46 AM Xu Kuohai <xukuohai@huaweicloud.com> wrote:
>>
>> On 3/4/2026 1:19 AM, Alexei Starovoitov wrote:
>>
>> [...]
>>
>>>> - for (i = 0; i < insn_cnt; i++, insn++) {
>>>> + for (i = 0, j = 0; i < insn_cnt; i++, j++, insn++) {
>>>> + env->insn_aux_data[subprog_start + j].final_idx = i;
>>>> if (bpf_pseudo_func(insn)) {
>>>> /* ld_imm64 with an address of bpf subprog is not
>>>> * a user controlled constant. Don't randomize it,
>>>> @@ -1512,6 +1542,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
>>>> */
>>>> insn++;
>>>> i++;
>>>> + j++;
>>>> + env->insn_aux_data[subprog_start + j].final_idx = i;
>>>
>>> You're adding final_idx because bpf_jit_blind_constants()
>>> doesn't call adjust_insn_aux_data() ?
>>>
>>
>> Yes, I added final_idx because insn_aux is not updated here.
>>
>>> imo that's an ugly workaround. Just call adjust_insn_aux_data().
>>>
>>
>> If we adjust the env->insn_aux_data here, should we also adjust the global
>> env->prog->insnsi array? I think env->insn_aux_data should remain consistent
>> with the global env->prog->insnsi array. Since constant blinding only rewrites
>> the subprog's private instruction array, updating the env->insn_aux_data
>> causes a mismatch with the global state.
>
> yes, and subprog starts, and pokes that bpf_patch_insn_data() do.
>
> blinding was implemented long before that, so it was never updated.
I see. Since env->prog->insnsi is rewritten by blind_constants now, would it
make sense to move constant blinding to the beginning of jit_subprogs, just
before the global instruction array is split into subprog copies?
This would eliminate the need to invoke constant blinding per subprog from
the arch-specific JIT, simplifying the overall flow.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets
2026-03-05 3:47 ` Xu Kuohai
@ 2026-03-05 3:56 ` Alexei Starovoitov
0 siblings, 0 replies; 20+ messages in thread
From: Alexei Starovoitov @ 2026-03-05 3:56 UTC (permalink / raw)
To: Xu Kuohai
Cc: bpf, LKML, linux-arm-kernel, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Yonghong Song, Puranjay Mohan, Anton Protopopov
On Wed, Mar 4, 2026 at 7:47 PM Xu Kuohai <xukuohai@huaweicloud.com> wrote:
>
> On 3/4/2026 11:37 PM, Alexei Starovoitov wrote:
> > On Wed, Mar 4, 2026 at 4:46 AM Xu Kuohai <xukuohai@huaweicloud.com> wrote:
> >>
> >> On 3/4/2026 1:19 AM, Alexei Starovoitov wrote:
> >>
> >> [...]
> >>
> >>>> - for (i = 0; i < insn_cnt; i++, insn++) {
> >>>> + for (i = 0, j = 0; i < insn_cnt; i++, j++, insn++) {
> >>>> + env->insn_aux_data[subprog_start + j].final_idx = i;
> >>>> if (bpf_pseudo_func(insn)) {
> >>>> /* ld_imm64 with an address of bpf subprog is not
> >>>> * a user controlled constant. Don't randomize it,
> >>>> @@ -1512,6 +1542,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
> >>>> */
> >>>> insn++;
> >>>> i++;
> >>>> + j++;
> >>>> + env->insn_aux_data[subprog_start + j].final_idx = i;
> >>>
> >>> You're adding final_idx because bpf_jit_blind_constants()
> >>> doesn't call adjust_insn_aux_data() ?
> >>>
> >>
> >> Yes, I added final_idx because insn_aux is not updated here.
> >>
> >>> imo that's an ugly workaround. Just call adjust_insn_aux_data().
> >>>
> >>
> >> If we adjust the env->insn_aux_data here, should we also adjust the global
> >> env->prog->insnsi array? I think env->insn_aux_data should remain consistent
> >> with the global env->prog->insnsi array. Since constant blinding only rewrites
> >> the subprog's private instruction array, updating the env->insn_aux_data
> >> causes a mismatch with the global state.
> >
> > yes, and subprog starts, and pokes that bpf_patch_insn_data() do.
> >
> > blinding was implemented long before that, so it was never updated.
>
> I see. Since env->prog->insnsi is rewritten by blind_constants now, would it
> make sense to move constant blinding to the beginning of jit_subprogs, just
> before the global instruction array is split into subprog copies?
>
> This would eliminate the need to invoke constant blinding per subprog from
> the arch-specific JIT, simplifying the overall flow.
Makes sense to me.
Since we're touching all JITS, lets remove bpf_jit_blind_constants()
call from the too and do it from generic code.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for indirect jump targets
2026-03-02 10:27 [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Xu Kuohai
` (2 preceding siblings ...)
2026-03-02 10:27 ` [PATCH bpf-next v5 3/5] bpf: Add helper to detect indirect jump targets Xu Kuohai
@ 2026-03-02 10:27 ` Xu Kuohai
2026-03-04 6:23 ` Eduard Zingerman
2026-03-05 6:38 ` kernel test robot
2026-03-02 10:27 ` [PATCH bpf-next v5 5/5] bpf, arm64: Emit BTI for indirect jump target Xu Kuohai
2026-03-03 16:29 ` [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Alexis Lothoré
5 siblings, 2 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-02 10:27 UTC (permalink / raw)
To: bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
From: Xu Kuohai <xukuohai@huawei.com>
On CPUs that support CET/IBT, the indirect jump selftest triggers
a kernel panic because the indirect jump targets lack ENDBR
instructions.
To fix it, emit an ENDBR instruction to each indirect jump target. Since
the ENDBR instruction shifts the position of original jited instructions,
fix the instruction address calculation wherever the addresses are used.
For reference, below is a sample panic log.
Missing ENDBR: bpf_prog_2e5f1c71c13ac3e0_big_jump_table+0x97/0xe1
------------[ cut here ]------------
kernel BUG at arch/x86/kernel/cet.c:133!
Oops: invalid opcode: 0000 [#1] SMP NOPTI
...
? 0xffffffffc00fb258
? bpf_prog_2e5f1c71c13ac3e0_big_jump_table+0x97/0xe1
bpf_prog_test_run_syscall+0x110/0x2f0
? fdget+0xba/0xe0
__sys_bpf+0xe4b/0x2590
? __kmalloc_node_track_caller_noprof+0x1c7/0x680
? bpf_prog_test_run_syscall+0x215/0x2f0
__x64_sys_bpf+0x21/0x30
do_syscall_64+0x85/0x620
? bpf_prog_test_run_syscall+0x1e2/0x2f0
Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps")
Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
---
arch/x86/net/bpf_jit_comp.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 43beacaed56d..7a2fa828558a 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1658,8 +1658,8 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,
return 0;
}
-static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image,
- int oldproglen, struct jit_context *ctx, bool jmp_padding)
+static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
+ u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
{
bool tail_call_reachable = bpf_prog->aux->tail_call_reachable;
struct bpf_insn *insn = bpf_prog->insnsi;
@@ -1743,6 +1743,9 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image
dst_reg = X86_REG_R9;
}
+ if (bpf_insn_is_indirect_target(env, bpf_prog, i - 1))
+ EMIT_ENDBR();
+
switch (insn->code) {
/* ALU */
case BPF_ALU | BPF_ADD | BPF_X:
@@ -2449,7 +2452,7 @@ st: if (is_imm8(insn->off))
/* call */
case BPF_JMP | BPF_CALL: {
- u8 *ip = image + addrs[i - 1];
+ u8 *ip = image + addrs[i - 1] + (prog - temp);
func = (u8 *) __bpf_call_base + imm32;
if (src_reg == BPF_PSEUDO_CALL && tail_call_reachable) {
@@ -2474,7 +2477,8 @@ st: if (is_imm8(insn->off))
if (imm32)
emit_bpf_tail_call_direct(bpf_prog,
&bpf_prog->aux->poke_tab[imm32 - 1],
- &prog, image + addrs[i - 1],
+ &prog,
+ image + addrs[i - 1] + (prog - temp),
callee_regs_used,
stack_depth,
ctx);
@@ -2483,7 +2487,7 @@ st: if (is_imm8(insn->off))
&prog,
callee_regs_used,
stack_depth,
- image + addrs[i - 1],
+ image + addrs[i - 1] + (prog - temp),
ctx);
break;
@@ -2648,7 +2652,8 @@ st: if (is_imm8(insn->off))
break;
case BPF_JMP | BPF_JA | BPF_X:
- emit_indirect_jump(&prog, insn->dst_reg, image + addrs[i - 1]);
+ emit_indirect_jump(&prog, insn->dst_reg,
+ image + addrs[i - 1] + (prog - temp));
break;
case BPF_JMP | BPF_JA:
case BPF_JMP32 | BPF_JA:
@@ -2738,7 +2743,7 @@ st: if (is_imm8(insn->off))
ctx->cleanup_addr = proglen;
if (bpf_prog_was_classic(bpf_prog) &&
!ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN)) {
- u8 *ip = image + addrs[i - 1];
+ u8 *ip = image + addrs[i - 1] + (prog - temp);
if (emit_spectre_bhb_barrier(&prog, ip, bpf_prog))
return -EINVAL;
@@ -3820,7 +3825,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
for (pass = 0; pass < MAX_PASSES || image; pass++) {
if (!padding && pass >= PADDING_PASSES)
padding = true;
- proglen = do_jit(prog, addrs, image, rw_image, oldproglen, &ctx, padding);
+ proglen = do_jit(env, prog, addrs, image, rw_image, oldproglen, &ctx, padding);
if (proglen <= 0) {
out_image:
image = NULL;
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for indirect jump targets
2026-03-02 10:27 ` [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for " Xu Kuohai
@ 2026-03-04 6:23 ` Eduard Zingerman
2026-03-04 13:03 ` Xu Kuohai
2026-03-05 6:38 ` kernel test robot
1 sibling, 1 reply; 20+ messages in thread
From: Eduard Zingerman @ 2026-03-04 6:23 UTC (permalink / raw)
To: Xu Kuohai, bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Yonghong Song, Puranjay Mohan, Anton Protopopov
On Mon, 2026-03-02 at 18:27 +0800, Xu Kuohai wrote:
[...]
> @@ -2449,7 +2452,7 @@ st: if (is_imm8(insn->off))
>
> /* call */
> case BPF_JMP | BPF_CALL: {
> - u8 *ip = image + addrs[i - 1];
> + u8 *ip = image + addrs[i - 1] + (prog - temp);
^^^^^^^^^^^^^
Could you please comment a bit why this addend is needed?
>
> func = (u8 *) __bpf_call_base + imm32;
> if (src_reg == BPF_PSEUDO_CALL && tail_call_reachable) {
[...]
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for indirect jump targets
2026-03-04 6:23 ` Eduard Zingerman
@ 2026-03-04 13:03 ` Xu Kuohai
0 siblings, 0 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-04 13:03 UTC (permalink / raw)
To: Eduard Zingerman, Xu Kuohai, bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Yonghong Song, Puranjay Mohan, Anton Protopopov
On 3/4/2026 2:23 PM, Eduard Zingerman wrote:
> On Mon, 2026-03-02 at 18:27 +0800, Xu Kuohai wrote:
>
> [...]
>
>> @@ -2449,7 +2452,7 @@ st: if (is_imm8(insn->off))
>>
>> /* call */
>> case BPF_JMP | BPF_CALL: {
>> - u8 *ip = image + addrs[i - 1];
>> + u8 *ip = image + addrs[i - 1] + (prog - temp);
> ^^^^^^^^^^^^^
> Could you please comment a bit why this addend is needed?
>>
The temp buffer holds the JITed x86 instruction for each BPF instruction, and
prog variable points to the position in temp buffer where the next JITed x86
instruction will be written.
Since there may already be an ENDBR instruction in the temp buffer, which is
written when the current BPF instruction is an indirect jump target, we need to
add addend when computing the actual address of the JITed x86 instruction.
>> func = (u8 *) __bpf_call_base + imm32;
>> if (src_reg == BPF_PSEUDO_CALL && tail_call_reachable) {
>
> [...]
>
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for indirect jump targets
2026-03-02 10:27 ` [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for " Xu Kuohai
2026-03-04 6:23 ` Eduard Zingerman
@ 2026-03-05 6:38 ` kernel test robot
1 sibling, 0 replies; 20+ messages in thread
From: kernel test robot @ 2026-03-05 6:38 UTC (permalink / raw)
To: Xu Kuohai, bpf, linux-kernel, linux-arm-kernel
Cc: oe-kbuild-all, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Yonghong Song, Puranjay Mohan, Anton Protopopov
Hi Xu,
kernel test robot noticed the following build warnings:
[auto build test WARNING on bpf-next/master]
url: https://github.com/intel-lab-lkp/linux/commits/Xu-Kuohai/bpf-Move-JIT-for-single-subprog-programs-to-verifier/20260302-181031
base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master
patch link: https://lore.kernel.org/r/20260302102726.1126019-5-xukuohai%40huaweicloud.com
patch subject: [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for indirect jump targets
config: x86_64-buildonly-randconfig-001-20260305 (https://download.01.org/0day-ci/archive/20260305/202603051414.AAMjmOHv-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260305/202603051414.AAMjmOHv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603051414.AAMjmOHv-lkp@intel.com/
All warnings (new ones prefixed by >>):
arch/x86/net/bpf_jit_comp.c: In function 'do_jit':
>> arch/x86/net/bpf_jit_comp.c:1747:37: warning: suggest braces around empty body in an 'if' statement [-Wempty-body]
1747 | EMIT_ENDBR();
| ^
vim +/if +1747 arch/x86/net/bpf_jit_comp.c
1660
1661 static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
1662 u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
1663 {
1664 bool tail_call_reachable = bpf_prog->aux->tail_call_reachable;
1665 struct bpf_insn *insn = bpf_prog->insnsi;
1666 bool callee_regs_used[4] = {};
1667 int insn_cnt = bpf_prog->len;
1668 bool seen_exit = false;
1669 u8 temp[BPF_MAX_INSN_SIZE + BPF_INSN_SAFETY];
1670 void __percpu *priv_frame_ptr = NULL;
1671 u64 arena_vm_start, user_vm_start;
1672 void __percpu *priv_stack_ptr;
1673 int i, excnt = 0;
1674 int ilen, proglen = 0;
1675 u8 *prog = temp;
1676 u32 stack_depth;
1677 int err;
1678
1679 stack_depth = bpf_prog->aux->stack_depth;
1680 priv_stack_ptr = bpf_prog->aux->priv_stack_ptr;
1681 if (priv_stack_ptr) {
1682 priv_frame_ptr = priv_stack_ptr + PRIV_STACK_GUARD_SZ + round_up(stack_depth, 8);
1683 stack_depth = 0;
1684 }
1685
1686 arena_vm_start = bpf_arena_get_kern_vm_start(bpf_prog->aux->arena);
1687 user_vm_start = bpf_arena_get_user_vm_start(bpf_prog->aux->arena);
1688
1689 detect_reg_usage(insn, insn_cnt, callee_regs_used);
1690
1691 emit_prologue(&prog, image, stack_depth,
1692 bpf_prog_was_classic(bpf_prog), tail_call_reachable,
1693 bpf_is_subprog(bpf_prog), bpf_prog->aux->exception_cb);
1694
1695 bpf_prog->aux->ksym.fp_start = prog - temp;
1696
1697 /* Exception callback will clobber callee regs for its own use, and
1698 * restore the original callee regs from main prog's stack frame.
1699 */
1700 if (bpf_prog->aux->exception_boundary) {
1701 /* We also need to save r12, which is not mapped to any BPF
1702 * register, as we throw after entry into the kernel, which may
1703 * overwrite r12.
1704 */
1705 push_r12(&prog);
1706 push_callee_regs(&prog, all_callee_regs_used);
1707 } else {
1708 if (arena_vm_start)
1709 push_r12(&prog);
1710 push_callee_regs(&prog, callee_regs_used);
1711 }
1712 if (arena_vm_start)
1713 emit_mov_imm64(&prog, X86_REG_R12,
1714 arena_vm_start >> 32, (u32) arena_vm_start);
1715
1716 if (priv_frame_ptr)
1717 emit_priv_frame_ptr(&prog, priv_frame_ptr);
1718
1719 ilen = prog - temp;
1720 if (rw_image)
1721 memcpy(rw_image + proglen, temp, ilen);
1722 proglen += ilen;
1723 addrs[0] = proglen;
1724 prog = temp;
1725
1726 for (i = 1; i <= insn_cnt; i++, insn++) {
1727 const s32 imm32 = insn->imm;
1728 u32 dst_reg = insn->dst_reg;
1729 u32 src_reg = insn->src_reg;
1730 u8 b2 = 0, b3 = 0;
1731 u8 *start_of_ldx;
1732 s64 jmp_offset;
1733 s16 insn_off;
1734 u8 jmp_cond;
1735 u8 *func;
1736 int nops;
1737
1738 if (priv_frame_ptr) {
1739 if (src_reg == BPF_REG_FP)
1740 src_reg = X86_REG_R9;
1741
1742 if (dst_reg == BPF_REG_FP)
1743 dst_reg = X86_REG_R9;
1744 }
1745
1746 if (bpf_insn_is_indirect_target(env, bpf_prog, i - 1))
> 1747 EMIT_ENDBR();
1748
1749 switch (insn->code) {
1750 /* ALU */
1751 case BPF_ALU | BPF_ADD | BPF_X:
1752 case BPF_ALU | BPF_SUB | BPF_X:
1753 case BPF_ALU | BPF_AND | BPF_X:
1754 case BPF_ALU | BPF_OR | BPF_X:
1755 case BPF_ALU | BPF_XOR | BPF_X:
1756 case BPF_ALU64 | BPF_ADD | BPF_X:
1757 case BPF_ALU64 | BPF_SUB | BPF_X:
1758 case BPF_ALU64 | BPF_AND | BPF_X:
1759 case BPF_ALU64 | BPF_OR | BPF_X:
1760 case BPF_ALU64 | BPF_XOR | BPF_X:
1761 maybe_emit_mod(&prog, dst_reg, src_reg,
1762 BPF_CLASS(insn->code) == BPF_ALU64);
1763 b2 = simple_alu_opcodes[BPF_OP(insn->code)];
1764 EMIT2(b2, add_2reg(0xC0, dst_reg, src_reg));
1765 break;
1766
1767 case BPF_ALU64 | BPF_MOV | BPF_X:
1768 if (insn_is_cast_user(insn)) {
1769 if (dst_reg != src_reg)
1770 /* 32-bit mov */
1771 emit_mov_reg(&prog, false, dst_reg, src_reg);
1772 /* shl dst_reg, 32 */
1773 maybe_emit_1mod(&prog, dst_reg, true);
1774 EMIT3(0xC1, add_1reg(0xE0, dst_reg), 32);
1775
1776 /* or dst_reg, user_vm_start */
1777 maybe_emit_1mod(&prog, dst_reg, true);
1778 if (is_axreg(dst_reg))
1779 EMIT1_off32(0x0D, user_vm_start >> 32);
1780 else
1781 EMIT2_off32(0x81, add_1reg(0xC8, dst_reg), user_vm_start >> 32);
1782
1783 /* rol dst_reg, 32 */
1784 maybe_emit_1mod(&prog, dst_reg, true);
1785 EMIT3(0xC1, add_1reg(0xC0, dst_reg), 32);
1786
1787 /* xor r11, r11 */
1788 EMIT3(0x4D, 0x31, 0xDB);
1789
1790 /* test dst_reg32, dst_reg32; check if lower 32-bit are zero */
1791 maybe_emit_mod(&prog, dst_reg, dst_reg, false);
1792 EMIT2(0x85, add_2reg(0xC0, dst_reg, dst_reg));
1793
1794 /* cmove r11, dst_reg; if so, set dst_reg to zero */
1795 /* WARNING: Intel swapped src/dst register encoding in CMOVcc !!! */
1796 maybe_emit_mod(&prog, AUX_REG, dst_reg, true);
1797 EMIT3(0x0F, 0x44, add_2reg(0xC0, AUX_REG, dst_reg));
1798 break;
1799 } else if (insn_is_mov_percpu_addr(insn)) {
1800 /* mov <dst>, <src> (if necessary) */
1801 EMIT_mov(dst_reg, src_reg);
1802 #ifdef CONFIG_SMP
1803 /* add <dst>, gs:[<off>] */
1804 EMIT2(0x65, add_1mod(0x48, dst_reg));
1805 EMIT3(0x03, add_2reg(0x04, 0, dst_reg), 0x25);
1806 EMIT((u32)(unsigned long)&this_cpu_off, 4);
1807 #endif
1808 break;
1809 }
1810 fallthrough;
1811 case BPF_ALU | BPF_MOV | BPF_X:
1812 if (insn->off == 0)
1813 emit_mov_reg(&prog,
1814 BPF_CLASS(insn->code) == BPF_ALU64,
1815 dst_reg, src_reg);
1816 else
1817 emit_movsx_reg(&prog, insn->off,
1818 BPF_CLASS(insn->code) == BPF_ALU64,
1819 dst_reg, src_reg);
1820 break;
1821
1822 /* neg dst */
1823 case BPF_ALU | BPF_NEG:
1824 case BPF_ALU64 | BPF_NEG:
1825 maybe_emit_1mod(&prog, dst_reg,
1826 BPF_CLASS(insn->code) == BPF_ALU64);
1827 EMIT2(0xF7, add_1reg(0xD8, dst_reg));
1828 break;
1829
1830 case BPF_ALU | BPF_ADD | BPF_K:
1831 case BPF_ALU | BPF_SUB | BPF_K:
1832 case BPF_ALU | BPF_AND | BPF_K:
1833 case BPF_ALU | BPF_OR | BPF_K:
1834 case BPF_ALU | BPF_XOR | BPF_K:
1835 case BPF_ALU64 | BPF_ADD | BPF_K:
1836 case BPF_ALU64 | BPF_SUB | BPF_K:
1837 case BPF_ALU64 | BPF_AND | BPF_K:
1838 case BPF_ALU64 | BPF_OR | BPF_K:
1839 case BPF_ALU64 | BPF_XOR | BPF_K:
1840 maybe_emit_1mod(&prog, dst_reg,
1841 BPF_CLASS(insn->code) == BPF_ALU64);
1842
1843 /*
1844 * b3 holds 'normal' opcode, b2 short form only valid
1845 * in case dst is eax/rax.
1846 */
1847 switch (BPF_OP(insn->code)) {
1848 case BPF_ADD:
1849 b3 = 0xC0;
1850 b2 = 0x05;
1851 break;
1852 case BPF_SUB:
1853 b3 = 0xE8;
1854 b2 = 0x2D;
1855 break;
1856 case BPF_AND:
1857 b3 = 0xE0;
1858 b2 = 0x25;
1859 break;
1860 case BPF_OR:
1861 b3 = 0xC8;
1862 b2 = 0x0D;
1863 break;
1864 case BPF_XOR:
1865 b3 = 0xF0;
1866 b2 = 0x35;
1867 break;
1868 }
1869
1870 if (is_imm8(imm32))
1871 EMIT3(0x83, add_1reg(b3, dst_reg), imm32);
1872 else if (is_axreg(dst_reg))
1873 EMIT1_off32(b2, imm32);
1874 else
1875 EMIT2_off32(0x81, add_1reg(b3, dst_reg), imm32);
1876 break;
1877
1878 case BPF_ALU64 | BPF_MOV | BPF_K:
1879 case BPF_ALU | BPF_MOV | BPF_K:
1880 emit_mov_imm32(&prog, BPF_CLASS(insn->code) == BPF_ALU64,
1881 dst_reg, imm32);
1882 break;
1883
1884 case BPF_LD | BPF_IMM | BPF_DW:
1885 emit_mov_imm64(&prog, dst_reg, insn[1].imm, insn[0].imm);
1886 insn++;
1887 i++;
1888 break;
1889
1890 /* dst %= src, dst /= src, dst %= imm32, dst /= imm32 */
1891 case BPF_ALU | BPF_MOD | BPF_X:
1892 case BPF_ALU | BPF_DIV | BPF_X:
1893 case BPF_ALU | BPF_MOD | BPF_K:
1894 case BPF_ALU | BPF_DIV | BPF_K:
1895 case BPF_ALU64 | BPF_MOD | BPF_X:
1896 case BPF_ALU64 | BPF_DIV | BPF_X:
1897 case BPF_ALU64 | BPF_MOD | BPF_K:
1898 case BPF_ALU64 | BPF_DIV | BPF_K: {
1899 bool is64 = BPF_CLASS(insn->code) == BPF_ALU64;
1900
1901 if (dst_reg != BPF_REG_0)
1902 EMIT1(0x50); /* push rax */
1903 if (dst_reg != BPF_REG_3)
1904 EMIT1(0x52); /* push rdx */
1905
1906 if (BPF_SRC(insn->code) == BPF_X) {
1907 if (src_reg == BPF_REG_0 ||
1908 src_reg == BPF_REG_3) {
1909 /* mov r11, src_reg */
1910 EMIT_mov(AUX_REG, src_reg);
1911 src_reg = AUX_REG;
1912 }
1913 } else {
1914 /* mov r11, imm32 */
1915 EMIT3_off32(0x49, 0xC7, 0xC3, imm32);
1916 src_reg = AUX_REG;
1917 }
1918
1919 if (dst_reg != BPF_REG_0)
1920 /* mov rax, dst_reg */
1921 emit_mov_reg(&prog, is64, BPF_REG_0, dst_reg);
1922
1923 if (insn->off == 0) {
1924 /*
1925 * xor edx, edx
1926 * equivalent to 'xor rdx, rdx', but one byte less
1927 */
1928 EMIT2(0x31, 0xd2);
1929
1930 /* div src_reg */
1931 maybe_emit_1mod(&prog, src_reg, is64);
1932 EMIT2(0xF7, add_1reg(0xF0, src_reg));
1933 } else {
1934 if (BPF_CLASS(insn->code) == BPF_ALU)
1935 EMIT1(0x99); /* cdq */
1936 else
1937 EMIT2(0x48, 0x99); /* cqo */
1938
1939 /* idiv src_reg */
1940 maybe_emit_1mod(&prog, src_reg, is64);
1941 EMIT2(0xF7, add_1reg(0xF8, src_reg));
1942 }
1943
1944 if (BPF_OP(insn->code) == BPF_MOD &&
1945 dst_reg != BPF_REG_3)
1946 /* mov dst_reg, rdx */
1947 emit_mov_reg(&prog, is64, dst_reg, BPF_REG_3);
1948 else if (BPF_OP(insn->code) == BPF_DIV &&
1949 dst_reg != BPF_REG_0)
1950 /* mov dst_reg, rax */
1951 emit_mov_reg(&prog, is64, dst_reg, BPF_REG_0);
1952
1953 if (dst_reg != BPF_REG_3)
1954 EMIT1(0x5A); /* pop rdx */
1955 if (dst_reg != BPF_REG_0)
1956 EMIT1(0x58); /* pop rax */
1957 break;
1958 }
1959
1960 case BPF_ALU | BPF_MUL | BPF_K:
1961 case BPF_ALU64 | BPF_MUL | BPF_K:
1962 maybe_emit_mod(&prog, dst_reg, dst_reg,
1963 BPF_CLASS(insn->code) == BPF_ALU64);
1964
1965 if (is_imm8(imm32))
1966 /* imul dst_reg, dst_reg, imm8 */
1967 EMIT3(0x6B, add_2reg(0xC0, dst_reg, dst_reg),
1968 imm32);
1969 else
1970 /* imul dst_reg, dst_reg, imm32 */
1971 EMIT2_off32(0x69,
1972 add_2reg(0xC0, dst_reg, dst_reg),
1973 imm32);
1974 break;
1975
1976 case BPF_ALU | BPF_MUL | BPF_X:
1977 case BPF_ALU64 | BPF_MUL | BPF_X:
1978 maybe_emit_mod(&prog, src_reg, dst_reg,
1979 BPF_CLASS(insn->code) == BPF_ALU64);
1980
1981 /* imul dst_reg, src_reg */
1982 EMIT3(0x0F, 0xAF, add_2reg(0xC0, src_reg, dst_reg));
1983 break;
1984
1985 /* Shifts */
1986 case BPF_ALU | BPF_LSH | BPF_K:
1987 case BPF_ALU | BPF_RSH | BPF_K:
1988 case BPF_ALU | BPF_ARSH | BPF_K:
1989 case BPF_ALU64 | BPF_LSH | BPF_K:
1990 case BPF_ALU64 | BPF_RSH | BPF_K:
1991 case BPF_ALU64 | BPF_ARSH | BPF_K:
1992 maybe_emit_1mod(&prog, dst_reg,
1993 BPF_CLASS(insn->code) == BPF_ALU64);
1994
1995 b3 = simple_alu_opcodes[BPF_OP(insn->code)];
1996 if (imm32 == 1)
1997 EMIT2(0xD1, add_1reg(b3, dst_reg));
1998 else
1999 EMIT3(0xC1, add_1reg(b3, dst_reg), imm32);
2000 break;
2001
2002 case BPF_ALU | BPF_LSH | BPF_X:
2003 case BPF_ALU | BPF_RSH | BPF_X:
2004 case BPF_ALU | BPF_ARSH | BPF_X:
2005 case BPF_ALU64 | BPF_LSH | BPF_X:
2006 case BPF_ALU64 | BPF_RSH | BPF_X:
2007 case BPF_ALU64 | BPF_ARSH | BPF_X:
2008 /* BMI2 shifts aren't better when shift count is already in rcx */
2009 if (boot_cpu_has(X86_FEATURE_BMI2) && src_reg != BPF_REG_4) {
2010 /* shrx/sarx/shlx dst_reg, dst_reg, src_reg */
2011 bool w = (BPF_CLASS(insn->code) == BPF_ALU64);
2012 u8 op;
2013
2014 switch (BPF_OP(insn->code)) {
2015 case BPF_LSH:
2016 op = 1; /* prefix 0x66 */
2017 break;
2018 case BPF_RSH:
2019 op = 3; /* prefix 0xf2 */
2020 break;
2021 case BPF_ARSH:
2022 op = 2; /* prefix 0xf3 */
2023 break;
2024 }
2025
2026 emit_shiftx(&prog, dst_reg, src_reg, w, op);
2027
2028 break;
2029 }
2030
2031 if (src_reg != BPF_REG_4) { /* common case */
2032 /* Check for bad case when dst_reg == rcx */
2033 if (dst_reg == BPF_REG_4) {
2034 /* mov r11, dst_reg */
2035 EMIT_mov(AUX_REG, dst_reg);
2036 dst_reg = AUX_REG;
2037 } else {
2038 EMIT1(0x51); /* push rcx */
2039 }
2040 /* mov rcx, src_reg */
2041 EMIT_mov(BPF_REG_4, src_reg);
2042 }
2043
2044 /* shl %rax, %cl | shr %rax, %cl | sar %rax, %cl */
2045 maybe_emit_1mod(&prog, dst_reg,
2046 BPF_CLASS(insn->code) == BPF_ALU64);
2047
2048 b3 = simple_alu_opcodes[BPF_OP(insn->code)];
2049 EMIT2(0xD3, add_1reg(b3, dst_reg));
2050
2051 if (src_reg != BPF_REG_4) {
2052 if (insn->dst_reg == BPF_REG_4)
2053 /* mov dst_reg, r11 */
2054 EMIT_mov(insn->dst_reg, AUX_REG);
2055 else
2056 EMIT1(0x59); /* pop rcx */
2057 }
2058
2059 break;
2060
2061 case BPF_ALU | BPF_END | BPF_FROM_BE:
2062 case BPF_ALU64 | BPF_END | BPF_FROM_LE:
2063 switch (imm32) {
2064 case 16:
2065 /* Emit 'ror %ax, 8' to swap lower 2 bytes */
2066 EMIT1(0x66);
2067 if (is_ereg(dst_reg))
2068 EMIT1(0x41);
2069 EMIT3(0xC1, add_1reg(0xC8, dst_reg), 8);
2070
2071 /* Emit 'movzwl eax, ax' */
2072 if (is_ereg(dst_reg))
2073 EMIT3(0x45, 0x0F, 0xB7);
2074 else
2075 EMIT2(0x0F, 0xB7);
2076 EMIT1(add_2reg(0xC0, dst_reg, dst_reg));
2077 break;
2078 case 32:
2079 /* Emit 'bswap eax' to swap lower 4 bytes */
2080 if (is_ereg(dst_reg))
2081 EMIT2(0x41, 0x0F);
2082 else
2083 EMIT1(0x0F);
2084 EMIT1(add_1reg(0xC8, dst_reg));
2085 break;
2086 case 64:
2087 /* Emit 'bswap rax' to swap 8 bytes */
2088 EMIT3(add_1mod(0x48, dst_reg), 0x0F,
2089 add_1reg(0xC8, dst_reg));
2090 break;
2091 }
2092 break;
2093
2094 case BPF_ALU | BPF_END | BPF_FROM_LE:
2095 switch (imm32) {
2096 case 16:
2097 /*
2098 * Emit 'movzwl eax, ax' to zero extend 16-bit
2099 * into 64 bit
2100 */
2101 if (is_ereg(dst_reg))
2102 EMIT3(0x45, 0x0F, 0xB7);
2103 else
2104 EMIT2(0x0F, 0xB7);
2105 EMIT1(add_2reg(0xC0, dst_reg, dst_reg));
2106 break;
2107 case 32:
2108 /* Emit 'mov eax, eax' to clear upper 32-bits */
2109 if (is_ereg(dst_reg))
2110 EMIT1(0x45);
2111 EMIT2(0x89, add_2reg(0xC0, dst_reg, dst_reg));
2112 break;
2113 case 64:
2114 /* nop */
2115 break;
2116 }
2117 break;
2118
2119 /* speculation barrier */
2120 case BPF_ST | BPF_NOSPEC:
2121 EMIT_LFENCE();
2122 break;
2123
2124 /* ST: *(u8*)(dst_reg + off) = imm */
2125 case BPF_ST | BPF_MEM | BPF_B:
2126 if (is_ereg(dst_reg))
2127 EMIT2(0x41, 0xC6);
2128 else
2129 EMIT1(0xC6);
2130 goto st;
2131 case BPF_ST | BPF_MEM | BPF_H:
2132 if (is_ereg(dst_reg))
2133 EMIT3(0x66, 0x41, 0xC7);
2134 else
2135 EMIT2(0x66, 0xC7);
2136 goto st;
2137 case BPF_ST | BPF_MEM | BPF_W:
2138 if (is_ereg(dst_reg))
2139 EMIT2(0x41, 0xC7);
2140 else
2141 EMIT1(0xC7);
2142 goto st;
2143 case BPF_ST | BPF_MEM | BPF_DW:
2144 EMIT2(add_1mod(0x48, dst_reg), 0xC7);
2145
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v5 5/5] bpf, arm64: Emit BTI for indirect jump target
2026-03-02 10:27 [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Xu Kuohai
` (3 preceding siblings ...)
2026-03-02 10:27 ` [PATCH bpf-next v5 4/5] bpf, x86: Emit ENDBR for " Xu Kuohai
@ 2026-03-02 10:27 ` Xu Kuohai
2026-03-03 16:29 ` [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Alexis Lothoré
5 siblings, 0 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-02 10:27 UTC (permalink / raw)
To: bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
From: Xu Kuohai <xukuohai@huawei.com>
On CPUs that support BTI, the indirect jump selftest triggers a kernel
panic because there is no BTI instructions at the indirect jump targets.
Fix it by emitting a BTI instruction for each indirect jump target.
For reference, below is a sample panic log.
Internal error: Oops - BTI: 0000000036000003 [#1] SMP
...
Call trace:
bpf_prog_2e5f1c71c13ac3e0_big_jump_table+0x54/0xf8 (P)
bpf_prog_run_pin_on_cpu+0x140/0x468
bpf_prog_test_run_syscall+0x280/0x3b8
bpf_prog_test_run+0x22c/0x2c0
Fixes: f4a66cf1cb14 ("bpf: arm64: Add support for indirect jumps")
Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
---
arch/arm64/net/bpf_jit_comp.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index 823246c7ff5d..127e099d3d3a 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1198,8 +1198,8 @@ static int add_exception_handler(const struct bpf_insn *insn,
* >0 - successfully JITed a 16-byte eBPF instruction.
* <0 - failed to JIT.
*/
-static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx,
- bool extra_pass)
+static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn *insn,
+ struct jit_ctx *ctx, bool extra_pass)
{
const u8 code = insn->code;
u8 dst = bpf2a64[insn->dst_reg];
@@ -1224,6 +1224,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx,
int ret;
bool sign_extend;
+ if (bpf_insn_is_indirect_target(env, ctx->prog, i))
+ emit_bti(A64_BTI_J, ctx);
+
switch (code) {
/* dst = src */
case BPF_ALU | BPF_MOV | BPF_X:
@@ -1899,7 +1902,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx,
return 0;
}
-static int build_body(struct jit_ctx *ctx, bool extra_pass)
+static int build_body(struct bpf_verifier_env *env, struct jit_ctx *ctx, bool extra_pass)
{
const struct bpf_prog *prog = ctx->prog;
int i;
@@ -1918,7 +1921,7 @@ static int build_body(struct jit_ctx *ctx, bool extra_pass)
int ret;
ctx->offset[i] = ctx->idx;
- ret = build_insn(insn, ctx, extra_pass);
+ ret = build_insn(env, insn, ctx, extra_pass);
if (ret > 0) {
i++;
ctx->offset[i] = ctx->idx;
@@ -2100,7 +2103,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
goto out_off;
}
- if (build_body(&ctx, extra_pass)) {
+ if (build_body(env, &ctx, extra_pass)) {
prog = orig_prog;
goto out_off;
}
@@ -2152,7 +2155,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
/* Dont write body instructions to memory for now */
ctx.write = false;
- if (build_body(&ctx, extra_pass)) {
+ if (build_body(env, &ctx, extra_pass)) {
prog = orig_prog;
goto out_free_hdr;
}
@@ -2163,7 +2166,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
ctx.write = true;
/* Pass 3: Adjust jump offset and write final image */
- if (build_body(&ctx, extra_pass) ||
+ if (build_body(env, &ctx, extra_pass) ||
WARN_ON_ONCE(ctx.idx != ctx.epilogue_offset)) {
prog = orig_prog;
goto out_free_hdr;
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets
2026-03-02 10:27 [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Xu Kuohai
` (4 preceding siblings ...)
2026-03-02 10:27 ` [PATCH bpf-next v5 5/5] bpf, arm64: Emit BTI for indirect jump target Xu Kuohai
@ 2026-03-03 16:29 ` Alexis Lothoré
2026-03-04 12:11 ` Xu Kuohai
5 siblings, 1 reply; 20+ messages in thread
From: Alexis Lothoré @ 2026-03-03 16:29 UTC (permalink / raw)
To: Xu Kuohai, bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
Hi Xu,
On Mon Mar 2, 2026 at 11:27 AM CET, Xu Kuohai wrote:
> On x86 CPUs with CET/IBT and arm64 CPUs with BTI, missing landing pad instructions
> at indirect jump targets triggers kernel panic. So emit ENDBR instructions for
> indirect jump targets on x86 and BTI on arm64. Indirect jump targets are identified
> based on the insn_aux_data created by the verifier.
>
> v5:
> - Switch to pass env to JIT directly to get rid of coping private insn_aux_data for
> each prog
Nice, thanks for this. As discussed in [1], I'm planning to reuse this
in my KASAN work to pass info to JIT comp about ldx/stx instructions
(especially, whether those insn are accessing prog stack or elsewhere).
So far I've been using a small POC aiming to do the same kind of thing,
but your series made me aware of a few points I missed, like the offset
induced by constant blinding. I'll keep my work rebased on top of your
series, and track it until it is merged.
Thanks
Alexis
[1] https://lore.kernel.org/bpf/CAADnVQLX7RSnOqQuU32Cgq-e0MVqyeNrtCQSBbk0W2xGkE-ZNw@mail.gmail.com/
> v4: https://lore.kernel.org/all/20260114093914.2403982-1-xukuohai@huaweicloud.com/
> - Switch to the approach proposed by Eduard, using insn_aux_data to indentify indirect
> jump targets, and emit ENDBR on x86
>
> v3: https://lore.kernel.org/bpf/20251227081033.240336-1-xukuohai@huaweicloud.com/
> - Get rid of unnecessary enum definition (Yonghong Song, Anton Protopopov)
>
> v2: https://lore.kernel.org/bpf/20251223085447.139301-1-xukuohai@huaweicloud.com/
> - Exclude instruction arrays not used for indirect jumps (Anton Protopopov)
>
> v1: https://lore.kernel.org/bpf/20251127140318.3944249-1-xukuohai@huaweicloud.com/
>
> Xu Kuohai (5):
> bpf: Move JIT for single-subprog programs to verifier
> bpf: Pass bpf_verifier_env to jit
> bpf: Add helper to detect indirect jump targets
> bpf, x86: Emit ENDBR for indirect jump targets
> bpf, arm64: Emit BTI for indirect jump target
>
> arch/arc/net/bpf_jit_core.c | 19 +++----
> arch/arm/net/bpf_jit_32.c | 4 +-
> arch/arm64/net/bpf_jit_comp.c | 21 ++++----
> arch/loongarch/net/bpf_jit.c | 4 +-
> arch/mips/net/bpf_jit_comp.c | 4 +-
> arch/parisc/net/bpf_jit_core.c | 4 +-
> arch/powerpc/net/bpf_jit_comp.c | 4 +-
> arch/riscv/net/bpf_jit_core.c | 4 +-
> arch/s390/net/bpf_jit_comp.c | 4 +-
> arch/sparc/net/bpf_jit_comp_64.c | 4 +-
> arch/x86/net/bpf_jit_comp.c | 25 +++++----
> arch/x86/net/bpf_jit_comp32.c | 4 +-
> include/linux/bpf.h | 2 +
> include/linux/bpf_verifier.h | 10 ++--
> include/linux/filter.h | 6 ++-
> kernel/bpf/core.c | 93 ++++++++++++++++++++++++--------
> kernel/bpf/syscall.c | 2 +-
> kernel/bpf/verifier.c | 22 ++++++--
> 18 files changed, 157 insertions(+), 79 deletions(-)
--
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets
2026-03-03 16:29 ` [PATCH bpf-next v5 0/5] emit ENDBR/BTI instructions for indirect jump targets Alexis Lothoré
@ 2026-03-04 12:11 ` Xu Kuohai
0 siblings, 0 replies; 20+ messages in thread
From: Xu Kuohai @ 2026-03-04 12:11 UTC (permalink / raw)
To: Alexis Lothoré, bpf, linux-kernel, linux-arm-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Yonghong Song, Puranjay Mohan,
Anton Protopopov
On 3/4/2026 12:29 AM, Alexis Lothoré wrote:
> Hi Xu,
>
> On Mon Mar 2, 2026 at 11:27 AM CET, Xu Kuohai wrote:
>> On x86 CPUs with CET/IBT and arm64 CPUs with BTI, missing landing pad instructions
>> at indirect jump targets triggers kernel panic. So emit ENDBR instructions for
>> indirect jump targets on x86 and BTI on arm64. Indirect jump targets are identified
>> based on the insn_aux_data created by the verifier.
>>
>> v5:
>> - Switch to pass env to JIT directly to get rid of coping private insn_aux_data for
>> each prog
>
> Nice, thanks for this. As discussed in [1], I'm planning to reuse this
> in my KASAN work to pass info to JIT comp about ldx/stx instructions
> (especially, whether those insn are accessing prog stack or elsewhere).
> So far I've been using a small POC aiming to do the same kind of thing,
> but your series made me aware of a few points I missed, like the offset
> induced by constant blinding. I'll keep my work rebased on top of your
> series, and track it until it is merged.
>
Glad to hear that. Thanks for letting me know.
> Thanks
>
> Alexis
>
> [1] https://lore.kernel.org/bpf/CAADnVQLX7RSnOqQuU32Cgq-e0MVqyeNrtCQSBbk0W2xGkE-ZNw@mail.gmail.com/
>
>> v4: https://lore.kernel.org/all/20260114093914.2403982-1-xukuohai@huaweicloud.com/
>> - Switch to the approach proposed by Eduard, using insn_aux_data to indentify indirect
>> jump targets, and emit ENDBR on x86
>>
>> v3: https://lore.kernel.org/bpf/20251227081033.240336-1-xukuohai@huaweicloud.com/
>> - Get rid of unnecessary enum definition (Yonghong Song, Anton Protopopov)
>>
>> v2: https://lore.kernel.org/bpf/20251223085447.139301-1-xukuohai@huaweicloud.com/
>> - Exclude instruction arrays not used for indirect jumps (Anton Protopopov)
>>
>> v1: https://lore.kernel.org/bpf/20251127140318.3944249-1-xukuohai@huaweicloud.com/
>>
>> Xu Kuohai (5):
>> bpf: Move JIT for single-subprog programs to verifier
>> bpf: Pass bpf_verifier_env to jit
>> bpf: Add helper to detect indirect jump targets
>> bpf, x86: Emit ENDBR for indirect jump targets
>> bpf, arm64: Emit BTI for indirect jump target
>>
>> arch/arc/net/bpf_jit_core.c | 19 +++----
>> arch/arm/net/bpf_jit_32.c | 4 +-
>> arch/arm64/net/bpf_jit_comp.c | 21 ++++----
>> arch/loongarch/net/bpf_jit.c | 4 +-
>> arch/mips/net/bpf_jit_comp.c | 4 +-
>> arch/parisc/net/bpf_jit_core.c | 4 +-
>> arch/powerpc/net/bpf_jit_comp.c | 4 +-
>> arch/riscv/net/bpf_jit_core.c | 4 +-
>> arch/s390/net/bpf_jit_comp.c | 4 +-
>> arch/sparc/net/bpf_jit_comp_64.c | 4 +-
>> arch/x86/net/bpf_jit_comp.c | 25 +++++----
>> arch/x86/net/bpf_jit_comp32.c | 4 +-
>> include/linux/bpf.h | 2 +
>> include/linux/bpf_verifier.h | 10 ++--
>> include/linux/filter.h | 6 ++-
>> kernel/bpf/core.c | 93 ++++++++++++++++++++++++--------
>> kernel/bpf/syscall.c | 2 +-
>> kernel/bpf/verifier.c | 22 ++++++--
>> 18 files changed, 157 insertions(+), 79 deletions(-)
>
>
>
>
^ permalink raw reply [flat|nested] 20+ messages in thread