BPF List
 help / color / mirror / Atom feed
From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Daniel Borkmann <daniel@iogearbox.net>
Cc: ast@kernel.org, memxor@gmail.com, eddyz87@gmail.com,
	info@starlabs.sg, bpf@vger.kernel.org
Subject: Re: [PATCH bpf 6/6] selftests/bpf: Add tests for indirect jumps across subprograms
Date: Thu, 10 Sep 2026 12:22:24 +0000	[thread overview]
Message-ID: <aqKhACKfcJ1IDRK6@mail.gmail.com> (raw)
In-Reply-To: <20260909204035.24289-6-daniel@iogearbox.net>

On 26/09/09 10:40PM, Daniel Borkmann wrote:
> Add various gotox corner case tests to improve corner case coverage.
> 
>   # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- \
>       ./test_progs -t bpf_insn_array,verifier_gotox,signed_loader

signed_loader? :)

Dunno if there is real value in "leaves the subprog" for different
types of "leaves"... But nice to see more tests.

Acked-by: Anton Protopopov <a.s.protopopov@gmail.com>

>   [...]
>   #24/13   bpf_insn_array/gotox-tracker-map:OK
>   #24/14   bpf_insn_array/gotox-jt-spans-subprogs:OK
>   #24/15   bpf_insn_array/gotox-jt-spans-with-own-table:OK
>   #24/16   bpf_insn_array/gotox-target-without-cfg-edge:OK
>   #24/17   bpf_insn_array/gotox-target-other-subprog:OK
>   #24/18   bpf_insn_array/gotox-jt-per-subprog:OK
>   #24/19   bpf_insn_array/gotox-span-unreached-entry:OK
>   #24/20   bpf_insn_array/gotox-target-subprog-from-main:OK
>   #24/21   bpf_insn_array/gotox-index-slice-other-subprog:OK
>   #24/22   bpf_insn_array/gotox-target-other-global-subprog:OK
>   #24/23   bpf_insn_array/gotox-callback-leaves-subprog:OK
>   #24      bpf_insn_array:OK
>   [...]
>   #616     verifier_gotox:OK
>   Summary: 3/79 PASSED, 0 SKIPPED, 0/0 FAILED
> 
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
>  .../selftests/bpf/prog_tests/bpf_insn_array.c | 715 +++++++++++++++++-
>  1 file changed, 713 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> index c69d44cd4607..d5a831a75d82 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0
>  
>  #include <bpf/bpf.h>
> +#include <bpf/btf.h>
>  #include <test_progs.h>
>  
>  #if defined(__x86_64__) || defined(__powerpc__) || defined(__aarch64__)
> @@ -487,8 +488,9 @@ static int gotox_jt_create(__u32 first_gotox, __u32 gotox_cnt)
>  	return -1;
>  }
>  
> -static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
> -			   int *fd_array, __u32 fd_array_cnt, char *log)
> +static int gotox_prog_load_funcs(struct bpf_insn *insns, __u32 insn_cnt,
> +				 int *fd_array, __u32 fd_array_cnt, char *log,
> +				 int btf_fd, struct bpf_func_info *fi, __u32 fi_cnt)
>  {
>  	LIBBPF_OPTS(bpf_prog_load_opts, opts);
>  	int prog_fd;
> @@ -499,6 +501,12 @@ static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
>  	opts.log_buf = log;
>  	opts.log_size = GOTOX_LOG_SZ;
>  	opts.log_level = 1;
> +	if (fi_cnt) {
> +		opts.prog_btf_fd = btf_fd;
> +		opts.func_info = fi;
> +		opts.func_info_cnt = fi_cnt;
> +		opts.func_info_rec_size = sizeof(*fi);
> +	}
>  
>  	prog_fd = bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, &opts);
>  	if (prog_fd >= 0) {
> @@ -508,6 +516,13 @@ static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
>  	return prog_fd;
>  }
>  
> +static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
> +			   int *fd_array, __u32 fd_array_cnt, char *log)
> +{
> +	return gotox_prog_load_funcs(insns, insn_cnt, fd_array, fd_array_cnt, log,
> +				     -1, NULL, 0);
> +}
> +
>  /* Fill in 'r1 = 0; gotox_cnt x gotox r1' at 'insns'. */
>  static void gotox_run_fill(struct bpf_insn *insns, __u32 gotox_cnt)
>  {
> @@ -710,6 +725,669 @@ static void check_gotox_edges_across_subprogs(void)
>  	free(log);
>  }
>  
> +static int gotox_jt_create_offs(const __u32 *offs, __u32 cnt)
> +{
> +	struct bpf_insn_array_value val = {};
> +	int map_fd;
> +	__u32 i;
> +
> +	map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, cnt);
> +	if (!ASSERT_GE(map_fd, 0, "map_create"))
> +		return map_fd;
> +
> +	for (i = 0; i < cnt; i++) {
> +		val.orig_off = offs[i];
> +		if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &i, &val, 0), 0,
> +			       "bpf_map_update_elem"))
> +			goto err;
> +	}
> +
> +	if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze"))
> +		goto err;
> +
> +	return map_fd;
> +err:
> +	close(map_fd);
> +	return -1;
> +}
> +
> +#define GOTOX_SUB_START		4
> +#define GOTOX_MAIN_TGT		2
> +#define GOTOX_SUB_TGT		8
> +#define GOTOX_TWO_INSN_CNT	10
> +
> +static void gotox_two_subprogs_fill(struct bpf_insn *insns, __u32 jt_idx, __u32 jt_off)
> +{
> +	insns[0] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
> +				GOTOX_SUB_START - 1 - 1);
> +	insns[GOTOX_MAIN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[3] = BPF_EXIT_INSN();
> +
> +	/* r1 = &jt[0], by index 'jt_idx' into fd_array */
> +	insns[GOTOX_SUB_START] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_1,
> +		.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
> +		.imm = jt_idx,
> +	};
> +	insns[GOTOX_SUB_START + 1] = (struct bpf_insn) { .imm = 0 };
> +	insns[6] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, jt_off * 8);
> +	insns[7] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
> +	insns[GOTOX_SUB_TGT] = BPF_MOV64_IMM(BPF_REG_0, 1);
> +	insns[9] = BPF_EXIT_INSN();
> +}
> +
> +/*
> + * An insn_array map is not necessarily a jump table: one that tracks
> + * instruction offsets covers the whole program and is of no subprog. Such a
> + * map must not keep a program with a gotox elsewhere from loading.
> + */
> +static void check_gotox_tracker_map(void)
> +{
> +	const __u32 jt_track[] = { 0, GOTOX_MAIN_TGT, GOTOX_SUB_TGT };
> +	const __u32 jt_sub[] = { GOTOX_SUB_TGT };
> +	struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_two_subprogs_fill(insns, 1, 0);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_track, ARRAY_SIZE(jt_track));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	if (!ASSERT_OK(err, "program with a tracking map should load"))
> +		fprintf(stderr, "verifier log: %s\n", log);
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +static void check_gotox_target_other_subprog(void)
> +{
> +	const __u32 jt_main[] = { GOTOX_MAIN_TGT };
> +	const __u32 jt_sub[] = { GOTOX_SUB_TGT };
> +	struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_two_subprogs_fill(insns, 0, 0);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_main, ARRAY_SIZE(jt_main));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "indirect jump from insn 7 to 2 leaves the subprog [4,10)",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +static void check_gotox_jt_per_subprog(void)
> +{
> +	const __u32 jt_main[] = { GOTOX_MAIN_TGT };
> +	const __u32 jt_sub[] = { GOTOX_SUB_TGT };
> +	struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_two_subprogs_fill(insns, 1, 0);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_main, ARRAY_SIZE(jt_main));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, 0, "bpf(BPF_PROG_LOAD)");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +/*
> + * The spanning map is of no subprog and is dropped, and the entry the gotox
> + * register can reach is in the subprog of the gotox and in the jump table the
> + * CFG walked, so nothing unsafe is left and the program loads.
> + */
> +static void check_gotox_span_unreached_entry(void)
> +{
> +	const __u32 jt_span[] = { GOTOX_MAIN_TGT, GOTOX_SUB_TGT };
> +	const __u32 jt_sub[] = { GOTOX_SUB_TGT };
> +	struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_two_subprogs_fill(insns, 0, 1);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_span, ARRAY_SIZE(jt_span));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	if (!ASSERT_OK(err, "program with an unreachable spanning entry should load"))
> +		fprintf(stderr, "verifier log: %s\n", log);
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +#define GOTOX_FWD_GOTOX		11
> +#define GOTOX_FWD_OWN_TGT	12
> +#define GOTOX_FWD_SUB_START	14
> +#define GOTOX_FWD_INSN_CNT	16
> +
> +static void gotox_from_main_fill(struct bpf_insn *insns)
> +{
> +	insns[0] = BPF_MOV64_REG(BPF_REG_6, BPF_REG_1);
> +	insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
> +				GOTOX_FWD_SUB_START - 1 - 1);
> +	insns[2] = BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_6,
> +			       offsetof(struct xdp_md, ingress_ifindex));
> +	insns[3] = BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, 4);
> +
> +	/* r1 = &jt_leaves[0], by index 1 into fd_array */
> +	insns[4] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_1,
> +		.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
> +		.imm = 1,
> +	};
> +	insns[5] = (struct bpf_insn) { .imm = 0 };
> +	insns[6] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
> +	insns[7] = BPF_JMP_A(3);
> +
> +	/* r1 = &jt_own[0], by index 0 into fd_array */
> +	insns[8] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_1,
> +		.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
> +		.imm = 0,
> +	};
> +	insns[9] = (struct bpf_insn) { .imm = 0 };
> +	insns[10] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
> +
> +	insns[GOTOX_FWD_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
> +	insns[GOTOX_FWD_OWN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[13] = BPF_EXIT_INSN();
> +	insns[GOTOX_FWD_SUB_START] = BPF_MOV64_IMM(BPF_REG_0, 1);
> +	insns[15] = BPF_EXIT_INSN();
> +}
> +
> +static void check_gotox_target_subprog_from_main(void)
> +{
> +	const __u32 jt_own[] = { GOTOX_FWD_OWN_TGT };
> +	const __u32 jt_leaves[] = { GOTOX_FWD_SUB_START };
> +	struct bpf_insn insns[GOTOX_FWD_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_from_main_fill(insns);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_own, ARRAY_SIZE(jt_own));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_leaves, ARRAY_SIZE(jt_leaves));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "indirect jump from insn 11 to 14 leaves the subprog [0,14)",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +/*
> + * The only map of the subprog holding the gotox reaches past that subprog, so
> + * the subprog is left without a jump table at all.
> + */
> +static void check_gotox_jt_spans_subprogs(void)
> +{
> +	const __u32 jt_span[] = { GOTOX_FWD_OWN_TGT, GOTOX_FWD_SUB_START };
> +	const __u32 jt_leaves[] = { GOTOX_FWD_SUB_START };
> +	struct bpf_insn insns[GOTOX_FWD_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_from_main_fill(insns);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_span, ARRAY_SIZE(jt_span));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_leaves, ARRAY_SIZE(jt_leaves));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "jump table of subprog starting at 0 spans multiple subprogs",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +/*
> + * The subprog holding the gotox has a well formed jump table of its own and
> + * also collects a map that reaches past its end. The spanning map is still
> + * rejected, even though the subprog is not left without a table.
> + */
> +static void check_gotox_jt_spans_with_own_table(void)
> +{
> +	const __u32 jt_own[] = { GOTOX_FWD_OWN_TGT };
> +	const __u32 jt_span[] = { GOTOX_FWD_OWN_TGT, GOTOX_FWD_SUB_START };
> +	struct bpf_insn insns[GOTOX_FWD_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_from_main_fill(insns);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_own, ARRAY_SIZE(jt_own));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_span, ARRAY_SIZE(jt_span));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "jump table of subprog starting at 0 spans multiple subprogs",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +#define GOTOX_EDGE_MAIN_TGT	2
> +#define GOTOX_EDGE_SUB_START	4
> +#define GOTOX_EDGE_GOTOX	9
> +#define GOTOX_EDGE_BR_TGT	10
> +#define GOTOX_EDGE_JT_TGT	11
> +#define GOTOX_EDGE_INSN_CNT	12
> +
> +static void gotox_no_edge_fill(struct bpf_insn *insns)
> +{
> +	insns[0] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
> +				GOTOX_EDGE_SUB_START - 1 - 1);
> +	insns[GOTOX_EDGE_MAIN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[3] = BPF_EXIT_INSN();
> +
> +	insns[GOTOX_EDGE_SUB_START] =
> +		BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1,
> +			    offsetof(struct xdp_md, ingress_ifindex));
> +	insns[5] = BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 0, 4);
> +
> +	/* r1 = &jt_span[0], by index 0 into fd_array */
> +	insns[6] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_1,
> +		.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
> +		.imm = 0,
> +	};
> +	insns[7] = (struct bpf_insn) { .imm = 0 };
> +	insns[8] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 8);
> +
> +	insns[GOTOX_EDGE_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
> +	insns[GOTOX_EDGE_BR_TGT] = BPF_MOV64_IMM(BPF_REG_0, 1);
> +	insns[GOTOX_EDGE_JT_TGT] = BPF_EXIT_INSN();
> +}
> +
> +/*
> + * The gotox resolves a target inside its own subprog, but out of a map that
> + * spans subprogs and is therefore of no subprog. The CFG never walked that
> + * edge, so the jump has to be rejected even though it stays in the subprog.
> + */
> +static void check_gotox_target_without_cfg_edge(void)
> +{
> +	const __u32 jt_span[] = { GOTOX_EDGE_MAIN_TGT, GOTOX_EDGE_BR_TGT };
> +	const __u32 jt_sub[] = { GOTOX_EDGE_JT_TGT };
> +	struct bpf_insn insns[GOTOX_EDGE_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_no_edge_fill(insns);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_span, ARRAY_SIZE(jt_span));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log,
> +			  "indirect jump from insn 9 to 10 is not in the jump table of the subprog",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +#define GOTOX_SLICE_SUB_START	6
> +#define GOTOX_SLICE_GOTOX	14
> +#define GOTOX_SLICE_SUB_TGT	15
> +#define GOTOX_SLICE_INSN_CNT	17
> +
> +static void gotox_slice_fill(struct bpf_insn *insns)
> +{
> +	insns[0] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0,
> +				GOTOX_SLICE_SUB_START - 1 - 1);
> +	insns[2] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[3] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[4] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[5] = BPF_EXIT_INSN();
> +
> +	insns[GOTOX_SLICE_SUB_START] =
> +		BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1,
> +			    offsetof(struct xdp_md, ingress_ifindex));
> +	insns[7] = BPF_ALU64_IMM(BPF_AND, BPF_REG_2, 1);
> +	insns[8] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, 1);
> +	insns[9] = BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 3);
> +
> +	/* r1 = &jt_main[0], by index 0 into fd_array */
> +	insns[10] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_1,
> +		.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
> +		.imm = 0,
> +	};
> +	insns[11] = (struct bpf_insn) { .imm = 0 };
> +	insns[12] = BPF_ALU64_REG(BPF_ADD, BPF_REG_1, BPF_REG_2);
> +	insns[13] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
> +
> +	insns[GOTOX_SLICE_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
> +	insns[GOTOX_SLICE_SUB_TGT] = BPF_MOV64_IMM(BPF_REG_0, 1);
> +	insns[16] = BPF_EXIT_INSN();
> +}
> +
> +static void check_gotox_index_slice_other_subprog(void)
> +{
> +	const __u32 jt_main[] = { 2, 3, 4 };
> +	const __u32 jt_sub[] = { GOTOX_SLICE_SUB_TGT };
> +	struct bpf_insn insns[GOTOX_SLICE_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_slice_fill(insns);
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_main, ARRAY_SIZE(jt_main));
> +	if (map_fd[0] < 0)
> +		goto free_log;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load(insns, ARRAY_SIZE(insns), map_fd, 2, log);
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "indirect jump from insn 14 to 3 leaves the subprog [6,17)",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_log:
> +	free(log);
> +}
> +
> +static int gotox_btf_create(const __u32 *starts, const __u8 *linkage, __u32 cnt,
> +			    struct bpf_func_info *fi, struct btf **pbtf)
> +{
> +	int int_id, proto_id, id;
> +	struct btf *btf;
> +	char name[24];
> +	__u32 i;
> +
> +	btf = btf__new_empty();
> +	if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
> +		return -1;
> +
> +	int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
> +	if (!ASSERT_GT(int_id, 0, "btf__add_int"))
> +		goto err;
> +
> +	proto_id = btf__add_func_proto(btf, int_id);
> +	if (!ASSERT_GT(proto_id, 0, "btf__add_func_proto"))
> +		goto err;
> +
> +	for (i = 0; i < cnt; i++) {
> +		snprintf(name, sizeof(name), "gotox_f%u", i);
> +		id = btf__add_func(btf, name, linkage[i], proto_id);
> +		if (!ASSERT_GT(id, 0, "btf__add_func"))
> +			goto err;
> +		fi[i].insn_off = starts[i];
> +		fi[i].type_id = id;
> +	}
> +
> +	if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel"))
> +		goto err;
> +
> +	*pbtf = btf;
> +	return btf__fd(btf);
> +err:
> +	btf__free(btf);
> +	return -1;
> +}
> +
> +static void check_gotox_target_other_global_subprog(void)
> +{
> +	const __u32 starts[] = { 0, GOTOX_SUB_START };
> +	const __u8 linkage[] = { BTF_FUNC_GLOBAL, BTF_FUNC_GLOBAL };
> +	const __u32 jt_main[] = { GOTOX_MAIN_TGT };
> +	const __u32 jt_sub[] = { GOTOX_SUB_TGT };
> +	struct bpf_insn insns[GOTOX_TWO_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	struct bpf_func_info fi[2];
> +	struct btf *btf = NULL;
> +	int btf_fd;
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_two_subprogs_fill(insns, 0, 0);
> +
> +	btf_fd = gotox_btf_create(starts, linkage, ARRAY_SIZE(starts), fi, &btf);
> +	if (btf_fd < 0)
> +		goto free_log;
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_main, ARRAY_SIZE(jt_main));
> +	if (map_fd[0] < 0)
> +		goto free_btf;
> +	map_fd[1] = gotox_jt_create_offs(jt_sub, ARRAY_SIZE(jt_sub));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load_funcs(insns, ARRAY_SIZE(insns), map_fd, 2, log,
> +				    btf_fd, fi, ARRAY_SIZE(fi));
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "indirect jump from insn 7 to 2 leaves the subprog [4,10)",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_btf:
> +	btf__free(btf);
> +free_log:
> +	free(log);
> +}
> +
> +#define GOTOX_CB_MAIN_TGT	6
> +#define GOTOX_CB_START		8
> +#define GOTOX_CB_GOTOX		11
> +#define GOTOX_CB_TGT		12
> +#define GOTOX_CB_INSN_CNT	14
> +
> +static void gotox_callback_fill(struct bpf_insn *insns)
> +{
> +	insns[0] = BPF_MOV64_IMM(BPF_REG_1, 1);
> +	/* r2 = &callback */
> +	insns[1] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_2,
> +		.src_reg = BPF_PSEUDO_FUNC,
> +		.imm = GOTOX_CB_START - 1 - 1,
> +	};
> +	insns[2] = (struct bpf_insn) { .imm = 0 };
> +	insns[3] = BPF_MOV64_IMM(BPF_REG_3, 0);
> +	insns[4] = BPF_MOV64_IMM(BPF_REG_4, 0);
> +	insns[5] = BPF_EMIT_CALL(BPF_FUNC_loop);
> +	insns[GOTOX_CB_MAIN_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[7] = BPF_EXIT_INSN();
> +
> +	/* r1 = &jt_main[0], by index 0 into fd_array */
> +	insns[GOTOX_CB_START] = (struct bpf_insn) {
> +		.code = BPF_LD | BPF_DW | BPF_IMM,
> +		.dst_reg = BPF_REG_1,
> +		.src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
> +		.imm = 0,
> +	};
> +	insns[9] = (struct bpf_insn) { .imm = 0 };
> +	insns[10] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, 0);
> +	insns[GOTOX_CB_GOTOX] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
> +	insns[GOTOX_CB_TGT] = BPF_MOV64_IMM(BPF_REG_0, 0);
> +	insns[13] = BPF_EXIT_INSN();
> +}
> +
> +static void check_gotox_callback_leaves_subprog(void)
> +{
> +	const __u32 starts[] = { 0, GOTOX_CB_START };
> +	const __u8 linkage[] = { BTF_FUNC_GLOBAL, BTF_FUNC_STATIC };
> +	const __u32 jt_main[] = { GOTOX_CB_MAIN_TGT };
> +	const __u32 jt_cb[] = { GOTOX_CB_TGT };
> +	struct bpf_insn insns[GOTOX_CB_INSN_CNT];
> +	int map_fd[2] = { -1, -1 };
> +	struct bpf_func_info fi[2];
> +	struct btf *btf = NULL;
> +	int btf_fd;
> +	char *log;
> +	int err;
> +
> +	log = calloc(1, GOTOX_LOG_SZ);
> +	if (!ASSERT_OK_PTR(log, "calloc log"))
> +		return;
> +
> +	gotox_callback_fill(insns);
> +
> +	btf_fd = gotox_btf_create(starts, linkage, ARRAY_SIZE(starts), fi, &btf);
> +	if (btf_fd < 0)
> +		goto free_log;
> +
> +	map_fd[0] = gotox_jt_create_offs(jt_main, ARRAY_SIZE(jt_main));
> +	if (map_fd[0] < 0)
> +		goto free_btf;
> +	map_fd[1] = gotox_jt_create_offs(jt_cb, ARRAY_SIZE(jt_cb));
> +	if (map_fd[1] < 0)
> +		goto close_maps;
> +
> +	err = gotox_prog_load_funcs(insns, ARRAY_SIZE(insns), map_fd, 2, log,
> +				    btf_fd, fi, ARRAY_SIZE(fi));
> +	ASSERT_EQ(err, -EINVAL, "program should have been rejected");
> +	ASSERT_HAS_SUBSTR(log, "indirect jump from insn 11 to 6 leaves the subprog [8,14)",
> +			  "verifier log");
> +
> +close_maps:
> +	close(map_fd[0]);
> +	close(map_fd[1]);
> +free_btf:
> +	btf__free(btf);
> +free_log:
> +	free(log);
> +}
> +
>  static void check_bpf_side(void)
>  {
>  	check_bpf_no_lookup();
> @@ -756,6 +1434,39 @@ static void __test_bpf_insn_array(void)
>  
>  	if (test__start_subtest("gotox-edges-across-subprogs"))
>  		check_gotox_edges_across_subprogs();
> +
> +	if (test__start_subtest("gotox-tracker-map"))
> +		check_gotox_tracker_map();
> +
> +	if (test__start_subtest("gotox-jt-spans-subprogs"))
> +		check_gotox_jt_spans_subprogs();
> +
> +	if (test__start_subtest("gotox-jt-spans-with-own-table"))
> +		check_gotox_jt_spans_with_own_table();
> +
> +	if (test__start_subtest("gotox-target-without-cfg-edge"))
> +		check_gotox_target_without_cfg_edge();
> +
> +	if (test__start_subtest("gotox-target-other-subprog"))
> +		check_gotox_target_other_subprog();
> +
> +	if (test__start_subtest("gotox-jt-per-subprog"))
> +		check_gotox_jt_per_subprog();
> +
> +	if (test__start_subtest("gotox-span-unreached-entry"))
> +		check_gotox_span_unreached_entry();
> +
> +	if (test__start_subtest("gotox-target-subprog-from-main"))
> +		check_gotox_target_subprog_from_main();
> +
> +	if (test__start_subtest("gotox-index-slice-other-subprog"))
> +		check_gotox_index_slice_other_subprog();
> +
> +	if (test__start_subtest("gotox-target-other-global-subprog"))
> +		check_gotox_target_other_global_subprog();
> +
> +	if (test__start_subtest("gotox-callback-leaves-subprog"))
> +		check_gotox_callback_leaves_subprog();
>  }
>  #else
>  static void __test_bpf_insn_array(void)
> -- 
> 2.43.0
> 

  parent reply	other threads:[~2026-09-10 12:11 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-09 20:40 [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc Daniel Borkmann
2026-09-09 20:40 ` [PATCH bpf 2/6] bpf: Bound the number of indirect jump edges in a program Daniel Borkmann
2026-09-09 20:57   ` sashiko-bot
2026-09-10 11:15     ` Daniel Borkmann
2026-09-10 11:44   ` Anton Protopopov
2026-09-09 20:40 ` [PATCH bpf 3/6] bpf: Cache the jump table of a subprogram during CFG discovery Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 11:21     ` Daniel Borkmann
2026-09-10 11:46   ` Anton Protopopov
2026-09-10 21:02   ` Eduard Zingerman
2026-09-09 20:40 ` [PATCH bpf 4/6] bpf: Reject indirect jumps that leave their subprogram Daniel Borkmann
2026-09-09 21:50   ` bot+bpf-ci
2026-09-10 12:10   ` Anton Protopopov
2026-09-10 19:37   ` Eduard Zingerman
2026-09-09 20:40 ` [PATCH bpf 5/6] selftests/bpf: Add tests for the indirect jump edge limit Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 12:14   ` Anton Protopopov
2026-09-09 20:40 ` [PATCH bpf 6/6] selftests/bpf: Add tests for indirect jumps across subprograms Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 12:22   ` Anton Protopopov [this message]
2026-09-10 18:54 ` [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc Eduard Zingerman

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aqKhACKfcJ1IDRK6@mail.gmail.com \
    --to=a.s.protopopov@gmail.com \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=info@starlabs.sg \
    --cc=memxor@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox