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
>
next prev 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