Re: [PATCH bpf-next v4 2/4] bpf: Add helper to detect indirect jump targets

From: Xu Kuohai

Date: Thu Jan 15 2026 - 02:37:54 EST


On 1/14/2026 7:00 PM, Anton Protopopov wrote:

[...]

+
+bool bpf_insn_is_indirect_target(const struct bpf_prog *prog, int idx)
+{
+ return prog->aux->insn_aux && prog->aux->insn_aux[idx].indirect_target;

Is there a case when insn_aux is NULL?


It is NULL when there is no indirect jump targets for the bpf prog, see the
has_indirect_target test in clone_insn_aux_data.

+}
#endif /* CONFIG_BPF_JIT */
/* Base function for offset calculation. Needs to go into .text section,
@@ -2540,24 +2579,24 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
if (!bpf_prog_is_offloaded(fp->aux)) {
*err = bpf_prog_alloc_jited_linfo(fp);
if (*err)
- return fp;
+ goto free_insn_aux;
fp = bpf_int_jit_compile(fp);
bpf_prog_jit_attempt_done(fp);
if (!fp->jited && jit_needed) {
*err = -ENOTSUPP;
- return fp;
+ goto free_insn_aux;
}
} else {
*err = bpf_prog_offload_compile(fp);
if (*err)
- return fp;
+ goto free_insn_aux;
}
finalize:
*err = bpf_prog_lock_ro(fp);
if (*err)
- return fp;
+ goto free_insn_aux;
/* The tail call compatibility check can only be done at
* this late stage as we need to determine, if we deal
@@ -2566,6 +2605,10 @@ struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
*/
*err = bpf_check_tail_call(fp);
+free_insn_aux:
+ vfree(fp->aux->insn_aux);
+ fp->aux->insn_aux = NULL;
+
return fp;
}
EXPORT_SYMBOL_GPL(bpf_prog_select_runtime);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 22605d9e0ffa..f2fe6baeceb9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3852,6 +3852,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)
@@ -20337,6 +20342,7 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
}
for (i = 0; i < n; i++) {

^ n -> n-1


ACK

+ 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))
@@ -21243,6 +21249,37 @@ static void convert_pseudo_ld_imm64(struct bpf_verifier_env *env)
}

mark_indirect_target(n-1)

}
+static int clone_insn_aux_data(struct bpf_prog *prog, struct bpf_verifier_env *env, u32 off)
+{
+ u32 i;
+ size_t size;
+ bool has_indirect_target = false;
+ struct bpf_insn_aux_data *insn_aux;
+
+ for (i = 0; i < prog->len; i++) {
+ if (env->insn_aux_data[off + i].indirect_target) {
+ has_indirect_target = true;
+ break;
+ }
+ }
+
+ /* insn_aux is copied into bpf_prog so the JIT can check whether an instruction is an
+ * indirect jump target. If no indirect jump targets exist, copying is unnecessary.
+ */
+ if (!has_indirect_target)
+ return 0;
+
+ size = array_size(sizeof(struct bpf_insn_aux_data), prog->len);
+ insn_aux = vzalloc(size);
+ if (!insn_aux)
+ return -ENOMEM;
+
+ memcpy(insn_aux, env->insn_aux_data + off, size);
+ prog->aux->insn_aux = insn_aux;
+
+ return 0;
+}
+
/* single env->prog->insni[off] instruction was replaced with the range
* insni[off, off + cnt). Adjust corresponding insn_aux_data by copying
* [0, off) and [off, end) to new locations, so the patched range stays zero
@@ -22239,6 +22276,10 @@ static int jit_subprogs(struct bpf_verifier_env *env)
if (!i)
func[i]->aux->exception_boundary = env->seen_exception;
+ err = clone_insn_aux_data(func[i], env, subprog_start);
+ if (err < 0)
+ goto out_free;
+
/*
* To properly pass the absolute subprog start to jit
* all instruction adjustments should be accumulated
@@ -22306,6 +22347,8 @@ static int jit_subprogs(struct bpf_verifier_env *env)
for (i = 0; i < env->subprog_cnt; i++) {
func[i]->aux->used_maps = NULL;
func[i]->aux->used_map_cnt = 0;
+ vfree(func[i]->aux->insn_aux);
+ func[i]->aux->insn_aux = NULL;
}
/* finally lock prog and jit images for all functions and
@@ -22367,6 +22410,7 @@ static int jit_subprogs(struct bpf_verifier_env *env)
for (i = 0; i < env->subprog_cnt; i++) {
if (!func[i])
continue;
+ vfree(func[i]->aux->insn_aux);
func[i]->aux->poke_tab = NULL;
bpf_jit_free(func[i]);
}
@@ -25350,6 +25394,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
env->verification_time = ktime_get_ns() - start_time;
print_verification_stats(env);
env->prog->aux->verified_insns = env->insn_processed;
+ env->prog->aux->insn_aux = env->insn_aux_data;
/* preserve original error even if log finalization is successful */
err = bpf_vlog_finalize(&env->log, &log_true_size);
@@ -25428,7 +25473,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
if (!is_priv)
mutex_unlock(&bpf_verifier_lock);
clear_insn_aux_data(env, 0, env->prog->len);
- vfree(env->insn_aux_data);
+ /* on success, insn_aux_data will be freed by bpf_prog_select_runtime */
+ if (ret) {
+ vfree(env->insn_aux_data);
+ env->prog->aux->insn_aux = NULL;
+ }
err_free_env:
bpf_stack_liveness_free(env);
kvfree(env->cfg.insn_postorder);
--
2.47.3


LGTM, just in case, could you please tell how you have tested
this patchset exactly?

I ran test_progs-cpuv4 on machines supporting x86 CET/IBT and arm64 BTI. I tested in three
environments: an arm64 physical machine with BTI support (CPU: Hisilicon KP920B), an arm64
QEMU VM using cpu=max for BTI support, and a Bochs VM with model=arrow_lake for x86 CET/IBT
support.