Skip to content

Conversation

@kernel-patches-daemon-bpf
Copy link

Pull request for series with
subject: BPF indirect jumps
version: 10
url: https://patchwork.kernel.org/project/netdevbpf/list/?series=1018668

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: ab01bfa
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1018668
version: 10

@kernel-patches-review-bot
Copy link

commit b44690c09995d34f7665c7d687b359d41a6ab79f
Author: Anton Protopopov <[email protected]>

libbpf: support llvm-generated indirect jumps

This commit adds support for LLVM-generated indirect jumps in BPF v4
instruction set. It handles jump table metadata from the .jumptables
ELF section and creates BPF_MAP_TYPE_INSN_ARRAY maps for them.

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index fbe74686c..ed14090a9 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c

[ ... ]

> @@ -6144,6 +6192,157 @@ static void poison_kfunc_call(struct bpf_program *prog, int relo_idx,
>  	insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
>  }
>
> +static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off)
> +{

[ ... ]

> +static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struct reloc_desc *relo)
> +{
> +	const __u32 jt_entry_size = 8;
> +	int sym_off = relo->sym_off;
> +	int jt_size = relo->sym_size;
> +	__u32 max_entries = jt_size / jt_entry_size;
> +	__u32 value_size = sizeof(struct bpf_insn_array_value);
> +	struct bpf_insn_array_value val = {};
> +	int subprog_idx;
> +	int map_fd, err;
> +	__u64 insn_off;
> +	__u64 *jt;
> +	__u32 i;
> +
> +	map_fd = find_jt_map(obj, prog, sym_off);
> +	if (map_fd >= 0)
> +		return map_fd;
> +
> +	if (sym_off % jt_entry_size) {
> +		pr_warn("map '.jumptables': jumptable start %d should be multiple of %u\n",
> +			sym_off, jt_entry_size);
> +		return -EINVAL;
> +	}
> +
> +	if (jt_size % jt_entry_size) {
> +		pr_warn("map '.jumptables': jumptable size %d should be multiple of %u\n",
> +			jt_size, jt_entry_size);
> +		return -EINVAL;
> +	}
> +
> +	map_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, ".jumptables",
> +				4, value_size, max_entries, NULL);
> +	if (map_fd < 0)
> +		return map_fd;
> +
> +	if (!obj->jumptables_data) {
> +		pr_warn("map '.jumptables': ELF file is missing jump table data\n");
> +		err = -EINVAL;
> +		goto err_close;
> +	}
> +	if (sym_off + jt_size > obj->jumptables_data_sz) {
> +		pr_warn("map '.jumptables': jumptables_data size is %zd, trying to access %d\n",
> +			obj->jumptables_data_sz, sym_off + jt_size);
> +		err = -EINVAL;
> +		goto err_close;
> +	}
> +
> +	subprog_idx = -1; /* main program */
> +	if (relo->insn_idx < 0 || relo->insn_idx >= prog->insns_cnt) {
> +		pr_warn("map '.jumptables': invalid instruction index %d\n", relo->insn_idx);
> +		err = -EINVAL;
> +		goto err_close;
> +	}
> +	if (prog->subprogs)
> +		subprog_idx = find_subprog_idx(prog, relo->insn_idx);
> +
> +	jt = (__u64 *)(obj->jumptables_data + sym_off);
> +	for (i = 0; i < max_entries; i++) {
> +		/*
> +		 * The offset should be made to be relative to the beginning of
> +		 * the main function, not the subfunction.
> +		 */
> +		insn_off = jt[i]/sizeof(struct bpf_insn);
> +		if (subprog_idx >= 0) {
> +			insn_off -= prog->subprogs[subprog_idx].sec_insn_off;
> +			insn_off += prog->subprogs[subprog_idx].sub_insn_off;
> +		} else {
> +			insn_off -= prog->sec_insn_off;
> +		}
> +
> +		/*
> +		 * LLVM-generated jump tables contain u64 records, however
> +		 * should contain values that fit in u32.
> +		 */
> +		if (insn_off > UINT32_MAX) {
> +			pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %d\n",
> +				(long long)jt[i], sym_off + i);
                                                      ^^^^^^^^^^^^

Does this error message print the correct offset? Since jt is a pointer
to __u64, the array access jt[i] is at byte offset "sym_off + i * 8",
not "sym_off + i". All the other error messages in create_jt_map report
byte offsets and sizes (sym_off, jt_size, sym_off + jt_size), so this
one should probably be "sym_off + i * jt_entry_size" for consistency.

> +			err = -EINVAL;
> +			goto err_close;
> +		}
> +
> +		val.orig_off = insn_off;
> +		err = bpf_map_update_elem(map_fd, &i, &val, 0);
> +		if (err)
> +			goto err_close;
> +	}
> +
> +	err = bpf_map_freeze(map_fd);
> +	if (err)
> +		goto err_close;
> +
> +	err = add_jt_map(obj, prog, sym_off, map_fd);
> +	if (err)
> +		goto err_close;
> +
> +	return map_fd;
> +
> +err_close:
> +	close(map_fd);
> +	return err;
> +}

[ ... ]



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

In-Reply-To-Subject: libbpf: support llvm-generated indirect jumps
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19018051915

@kernel-patches-daemon-bpf
Copy link
Author

Forwarding comment 3478352639 via email
In-Reply-To: [email protected]
Patch: https://patchwork.kernel.org/project/netdevbpf/patch/[email protected]/

@kernel-patches-review-bot
Copy link

commit 6cb8001495faae09541d55309638b3c1920bae06
Author: Anton Protopopov <[email protected]>

bpf, x86: add support for indirect jumps

This patch adds support for indirect jumps in BPF through a new
BPF_JMP|BPF_X|BPF_JA instruction. It introduces the PTR_TO_INSN register
type for instruction pointers loaded from INSN_ARRAY maps, along with
verifier support for tracking jump tables and x86 JIT implementation.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c

[ ... ]

> @@ -21374,6 +21374,28 @@ static int bpf_adj_linfo_after_remove(struct bpf_verifier_env *env, u32 off,
>  	return 0;
>  }
>
> +/*
> + * Clean up dynamically allocated fields of aux data for instructions [start, ...]
> + */
> +static void clear_insn_aux_data(struct bpf_verifier_env *env, int start, int len)
> +{
> +	struct bpf_insn_aux_data *aux_data = env->insn_aux_data;
> +	struct bpf_insn *insns = env->prog->insnsi;
> +	int end = start + len;
> +	int i;
> +
> +	for (i = start; i < end; i++) {
> +		if (insn_is_gotox(&insns[i])) {
> +			kvfree(aux_data[i].jt);
> +			aux_data[i].jt = NULL;
> +		}
> +
> +		if (bpf_is_ldimm64(&insns[i]))
> +			i++;
> +	}
> +}
> +
>  static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)
>  {
>  	struct bpf_insn_aux_data *aux_data = env->insn_aux_data;
> @@ -21393,6 +21415,8 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt
>
>  	adjust_insn_arrays_after_remove(env, off, cnt);
>
> +	clear_insn_aux_data(env, off, cnt);
> +
>  	memmove(aux_data + off,	aux_data + off + cnt,
>  		sizeof(*aux_data) * (orig_prog_len - off - cnt));

Can clear_insn_aux_data() correctly identify which aux_data entries need
cleanup when it's called after bpf_remove_insns()? After instructions are
removed, the insns[] array has been modified - insns[off] now contains what
was previously insns[off+cnt]. But aux_data hasn't been shifted yet, so
clear_insn_aux_data() checks the wrong instructions when deciding which
aux_data entries to free.

For example, if instructions [1,2] are removed where insn 2 is a gotox:
- bpf_remove_insns() removes insns[1] and insns[2], shifts later insns down
- insns[1] now contains what was insns[3]
- clear_insn_aux_data() checks insn_is_gotox(&insns[1]) (now insn 3!)
- It might free aux_data[1].jt if insn 3 is a gotox, even though insn 3
  isn't being removed
- It never checks the original insns[2] which was the actual gotox that
  needed cleanup

Should clear_insn_aux_data() be called before bpf_remove_insns() instead?



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

In-Reply-To-Subject: bpf, x86: add support for indirect jumps
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19018051915

@kernel-patches-daemon-bpf
Copy link
Author

Forwarding comment 3478354329 via email
In-Reply-To: [email protected]
Patch: https://patchwork.kernel.org/project/netdevbpf/patch/[email protected]/

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: e6e10c5
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1018668
version: 10

aspsk added 11 commits November 3, 2025 15:46
On bpf(BPF_PROG_LOAD) syscall user-supplied BPF programs are
translated by the verifier into "xlated" BPF programs. During this
process the original instructions offsets might be adjusted and/or
individual instructions might be replaced by new sets of instructions,
or deleted.

Add a new BPF map type which is aimed to keep track of how, for a
given program, the original instructions were relocated during the
verification. Also, besides keeping track of the original -> xlated
mapping, make x86 JIT to build the xlated -> jitted mapping for every
instruction listed in an instruction array. This is required for every
future application of instruction arrays: static keys, indirect jumps
and indirect calls.

A map of the BPF_MAP_TYPE_INSN_ARRAY type must be created with a u32
keys and value of size 8. The values have different semantics for
userspace and for BPF space. For userspace a value consists of two
u32 values – xlated and jitted offsets. For BPF side the value is
a real pointer to a jitted instruction.

On map creation/initialization, before loading the program, each
element of the map should be initialized to point to an instruction
offset within the program. Before the program load such maps should
be made frozen. After the program verification xlated and jitted
offsets can be read via the bpf(2) syscall.

If a tracked instruction is removed by the verifier, then the xlated
offset is set to (u32)-1 which is considered to be too big for a valid
BPF program offset.

One such a map can, obviously, be used to track one and only one BPF
program.  If the verification process was unsuccessful, then the same
map can be re-used to verify the program with a different log level.
However, if the program was loaded fine, then such a map, being
frozen in any case, can't be reused by other programs even after the
program release.

Example. Consider the following original and xlated programs:

    Original prog:                      Xlated prog:

     0:  r1 = 0x0                        0: r1 = 0
     1:  *(u32 *)(r10 - 0x4) = r1        1: *(u32 *)(r10 -4) = r1
     2:  r2 = r10                        2: r2 = r10
     3:  r2 += -0x4                      3: r2 += -4
     4:  r1 = 0x0 ll                     4: r1 = map[id:88]
     6:  call 0x1                        6: r1 += 272
                                         7: r0 = *(u32 *)(r2 +0)
                                         8: if r0 >= 0x1 goto pc+3
                                         9: r0 <<= 3
                                        10: r0 += r1
                                        11: goto pc+1
                                        12: r0 = 0
     7:  r6 = r0                        13: r6 = r0
     8:  if r6 == 0x0 goto +0x2         14: if r6 == 0x0 goto pc+4
     9:  call 0x76                      15: r0 = 0xffffffff8d2079c0
                                        17: r0 = *(u64 *)(r0 +0)
    10:  *(u64 *)(r6 + 0x0) = r0        18: *(u64 *)(r6 +0) = r0
    11:  r0 = 0x0                       19: r0 = 0x0
    12:  exit                           20: exit

An instruction array map, containing, e.g., instructions [0,4,7,12]
will be translated by the verifier to [0,4,13,20]. A map with
index 5 (the middle of 16-byte instruction) or indexes greater than 12
(outside the program boundaries) would be rejected.

The functionality provided by this patch will be extended in consequent
patches to implement BPF Static Keys, indirect jumps, and indirect calls.

Signed-off-by: Anton Protopopov <[email protected]>
Reviewed-by: Eduard Zingerman <[email protected]>
Add the following selftests for new insn_array map:

  * Incorrect instruction indexes are rejected
  * Two programs can't use the same map
  * BPF progs can't operate the map
  * no changes to code => map is the same
  * expected changes when instructions are added
  * expected changes when instructions are deleted
  * expected changes when multiple functions are present

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
When bpf_jit_harden is enabled, all constants in the BPF code are
blinded to prevent JIT spraying attacks. This happens during JIT
phase. Adjust all the related instruction arrays accordingly.

Signed-off-by: Anton Protopopov <[email protected]>
Reviewed-by: Eduard Zingerman <[email protected]>
Add a specific test for instructions arrays with blinding enabled.

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
Currently the emit_indirect_jump() function only accepts one of the
RAX, RCX, ..., RBP registers as the destination. Make it to accept
R8, R9, ..., R15 as well, and make callers to pass BPF registers, not
native registers. This is required to enable indirect jumps support
in eBPF.

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
Add support for a new instruction

    BPF_JMP|BPF_X|BPF_JA, SRC=0, DST=Rx, off=0, imm=0

which does an indirect jump to a location stored in Rx.  The register
Rx should have type PTR_TO_INSN. This new type assures that the Rx
register contains a value (or a range of values) loaded from a
correct jump table – map of type instruction array.

For example, for a C switch LLVM will generate the following code:

    0:   r3 = r1                    # "switch (r3)"
    1:   if r3 > 0x13 goto +0x666   # check r3 boundaries
    2:   r3 <<= 0x3                 # adjust to an index in array of addresses
    3:   r1 = 0xbeef ll             # r1 is PTR_TO_MAP_VALUE, r1->map_ptr=M
    5:   r1 += r3                   # r1 inherits boundaries from r3
    6:   r1 = *(u64 *)(r1 + 0x0)    # r1 now has type INSN_TO_PTR
    7:   gotox r1                   # jit will generate proper code

Here the gotox instruction corresponds to one particular map. This is
possible however to have a gotox instruction which can be loaded from
different maps, e.g.

    0:   r1 &= 0x1
    1:   r2 <<= 0x3
    2:   r3 = 0x0 ll                # load from map M_1
    4:   r3 += r2
    5:   if r1 == 0x0 goto +0x4
    6:   r1 <<= 0x3
    7:   r3 = 0x0 ll                # load from map M_2
    9:   r3 += r1
    A:   r1 = *(u64 *)(r3 + 0x0)
    B:   gotox r1                   # jump to target loaded from M_1 or M_2

During check_cfg stage the verifier will collect all the maps which
point to inside the subprog being verified. When building the config,
the high 16 bytes of the insn_state are used, so this patch
(theoretically) supports jump tables of up to 2^16 slots.

During the later stage, in check_indirect_jump, it is checked that
the register Rx was loaded from a particular instruction array.

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
Add support for indirect jump instruction.

Example output from bpftool:

   0: (79) r3 = *(u64 *)(r1 +0)
   1: (25) if r3 > 0x4 goto pc+666
   2: (67) r3 <<= 3
   3: (18) r1 = 0xffffbeefspameggs
   5: (0f) r1 += r3
   6: (79) r1 = *(u64 *)(r1 +0)
   7: (0d) gotox r1

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
For v4 instruction set LLVM is allowed to generate indirect jumps for
switch statements and for 'goto *rX' assembly. Every such a jump will
be accompanied by necessary metadata, e.g. (`llvm-objdump -Sr ...`):

       0:       r2 = 0x0 ll
                0000000000000030:  R_BPF_64_64  BPF.JT.0.0

Here BPF.JT.1.0 is a symbol residing in the .jumptables section:

    Symbol table:
       4: 0000000000000000   240 OBJECT  GLOBAL DEFAULT     4 BPF.JT.0.0

The -bpf-min-jump-table-entries llvm option may be used to control the
minimal size of a switch which will be converted to an indirect jumps.

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
Teach bpftool to recognize instruction array map type.

Signed-off-by: Anton Protopopov <[email protected]>
Acked-by: Quentin Monnet <[email protected]>
Add a set of tests to validate core gotox functionality
without need to rely on compilers.

Signed-off-by: Anton Protopopov <[email protected]>
Add C-level selftests for indirect jumps to validate LLVM and libbpf
functionality. The tests are intentionally disabled, to be run
locally by developers, but will not make the CI red.

Signed-off-by: Anton Protopopov <[email protected]>
@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 5dae745
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1018668
version: 10

@kernel-patches-daemon-bpf
Copy link
Author

At least one diff in series https://patchwork.kernel.org/project/netdevbpf/list/?series=1018668 expired. Closing PR.

@kernel-patches-daemon-bpf kernel-patches-daemon-bpf bot deleted the series/1018668=>bpf-next branch November 6, 2025 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants