From: Daniel Borkmann <daniel@iogearbox.net>
To: ast@kernel.org
Cc: memxor@gmail.com, eddyz87@gmail.com, a.s.protopopov@gmail.com,
info@starlabs.sg, bpf@vger.kernel.org
Subject: [PATCH bpf 5/6] selftests/bpf: Add tests for the indirect jump edge limit
Date: Wed, 9 Sep 2026 22:40:34 +0200 [thread overview]
Message-ID: <20260909204035.24289-5-daniel@iogearbox.net> (raw)
In-Reply-To: <20260909204035.24289-1-daniel@iogearbox.net>
Build programs whose gotox instructions are their own jump table targets,
which makes the edge count quadratic.
# LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t bpf_insn_array
[...]
#24/10 bpf_insn_array/too-many-gotox-edges:OK
#24/11 bpf_insn_array/gotox-edges-at-limit:OK
#24/12 bpf_insn_array/gotox-edges-across-subprogs:OK
#24 bpf_insn_array:OK
Summary: 1/12 PASSED, 0 SKIPPED, 0/0 FAILED
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
.../selftests/bpf/prog_tests/bpf_insn_array.c | 266 ++++++++++++++++++
1 file changed, 266 insertions(+)
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 0222a9a5d076..c69d44cd4607 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -453,6 +453,263 @@ static void check_bpf_no_lookup(void)
close(map_fd);
}
+#define GOTOX_CNT_AT_LIMIT 1000
+#define GOTOX_LOG_SZ (256 * 1024)
+
+static const char gotox_limit_msg[] =
+ "number of indirect jump edges in the program exceeds";
+
+static int gotox_jt_create(__u32 first_gotox, __u32 gotox_cnt)
+{
+ /* the run of gotox itself, plus the exit block right after it */
+ const __u32 jt_cnt = gotox_cnt + 1;
+ struct bpf_insn_array_value val = {};
+ int map_fd;
+ __u32 i;
+
+ map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, jt_cnt);
+ if (!ASSERT_GE(map_fd, 0, "map_create"))
+ return map_fd;
+
+ for (i = 0; i < jt_cnt; i++) {
+ val.orig_off = first_gotox + 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;
+}
+
+static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
+ int *fd_array, __u32 fd_array_cnt, char *log)
+{
+ LIBBPF_OPTS(bpf_prog_load_opts, opts);
+ int prog_fd;
+
+ log[0] = 0;
+ opts.fd_array = fd_array;
+ opts.fd_array_cnt = fd_array_cnt;
+ opts.log_buf = log;
+ opts.log_size = GOTOX_LOG_SZ;
+ opts.log_level = 1;
+
+ prog_fd = bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, &opts);
+ if (prog_fd >= 0) {
+ close(prog_fd);
+ return 0;
+ }
+ return prog_fd;
+}
+
+/* Fill in 'r1 = 0; gotox_cnt x gotox r1' at 'insns'. */
+static void gotox_run_fill(struct bpf_insn *insns, __u32 gotox_cnt)
+{
+ __u32 i;
+
+ insns[0] = BPF_MOV64_IMM(BPF_REG_1, 0);
+ for (i = 1; i <= gotox_cnt; i++)
+ insns[i] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
+}
+
+static void check_gotox_limit_hit(const char *log, int err)
+{
+ ASSERT_EQ(err, -E2BIG, "program should have been rejected");
+ ASSERT_HAS_SUBSTR(log, gotox_limit_msg, "verifier log");
+}
+
+static bool try_load_gotox_prog(__u32 gotox_cnt, char *log, int *err)
+{
+ const __u32 insn_cnt = gotox_cnt + 3;
+ struct bpf_insn *insns;
+ bool attempted = false;
+ int map_fd;
+
+ insns = calloc(insn_cnt, sizeof(*insns));
+ if (!ASSERT_OK_PTR(insns, "calloc insns"))
+ return false;
+
+ gotox_run_fill(insns, gotox_cnt);
+ insns[gotox_cnt + 1] = BPF_MOV64_IMM(BPF_REG_0, 0);
+ insns[gotox_cnt + 2] = BPF_EXIT_INSN();
+
+ map_fd = gotox_jt_create(1, gotox_cnt);
+ if (map_fd < 0)
+ goto free_insns;
+
+ *err = gotox_prog_load(insns, insn_cnt, &map_fd, 1, log);
+ close(map_fd);
+ attempted = true;
+free_insns:
+ free(insns);
+ return attempted;
+}
+
+/*
+ * The extra exit target in the jump table makes for gotox_cnt * (gotox_cnt
+ * + 1) edges, hence the program is over the limit by gotox_cnt edges.
+ */
+static void check_too_many_gotox_edges(void)
+{
+ const __u32 gotox_cnt = GOTOX_CNT_AT_LIMIT;
+ char *log;
+ int err;
+
+ log = calloc(1, GOTOX_LOG_SZ);
+ if (!ASSERT_OK_PTR(log, "calloc log"))
+ return;
+
+ if (try_load_gotox_prog(gotox_cnt, log, &err))
+ check_gotox_limit_hit(log, err);
+
+ free(log);
+}
+
+/*
+ * A chain of blocks, where block k loads jt[k] and jumps to it. The jump
+ * table holds the starts of the blocks that follow plus the exit block,
+ * which is gotox_cnt targets for gotox_cnt gotox, so the program sits
+ * exactly at the limit and must still load.
+ */
+#define GOTOX_BLOCK_SZ 4
+
+static void gotox_chain_fill(struct bpf_insn *insns, __u32 gotox_cnt)
+{
+ struct bpf_insn *at;
+ __u32 k;
+
+ for (k = 0; k < gotox_cnt; k++) {
+ at = insns + k * GOTOX_BLOCK_SZ;
+
+ /* r1 = &jt[0], by index 0 into fd_array */
+ at[0] = (struct bpf_insn) {
+ .code = BPF_LD | BPF_DW | BPF_IMM,
+ .dst_reg = BPF_REG_1,
+ .src_reg = BPF_PSEUDO_MAP_IDX_VALUE,
+ .imm = 0,
+ };
+ at[1] = (struct bpf_insn) { .imm = 0 };
+ at[2] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_1, k * 8);
+ at[3] = BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_1, 0, 0, 0);
+ }
+
+ insns[gotox_cnt * GOTOX_BLOCK_SZ] = BPF_MOV64_IMM(BPF_REG_0, 0);
+ insns[gotox_cnt * GOTOX_BLOCK_SZ + 1] = BPF_EXIT_INSN();
+}
+
+static int gotox_chain_jt_create(__u32 gotox_cnt)
+{
+ struct bpf_insn_array_value val = {};
+ int map_fd;
+ __u32 i;
+
+ map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, gotox_cnt);
+ if (!ASSERT_GE(map_fd, 0, "map_create"))
+ return map_fd;
+
+ for (i = 0; i < gotox_cnt; i++) {
+ val.orig_off = (i + 1) * GOTOX_BLOCK_SZ;
+ 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;
+}
+
+static void check_gotox_edges_at_limit(void)
+{
+ const __u32 gotox_cnt = GOTOX_CNT_AT_LIMIT;
+ const __u32 insn_cnt = gotox_cnt * GOTOX_BLOCK_SZ + 2;
+ struct bpf_insn *insns;
+ char *log;
+ int map_fd, err;
+
+ log = calloc(1, GOTOX_LOG_SZ);
+ if (!ASSERT_OK_PTR(log, "calloc log"))
+ return;
+
+ insns = calloc(insn_cnt, sizeof(*insns));
+ if (!ASSERT_OK_PTR(insns, "calloc insns"))
+ goto free_log;
+
+ gotox_chain_fill(insns, gotox_cnt);
+
+ map_fd = gotox_chain_jt_create(gotox_cnt);
+ if (map_fd < 0)
+ goto free_insns;
+
+ err = gotox_prog_load(insns, insn_cnt, &map_fd, 1, log);
+ close(map_fd);
+
+ if (!ASSERT_OK(err, "program at the edge limit should load"))
+ fprintf(stderr, "verifier log: %s\n", log);
+
+free_insns:
+ free(insns);
+free_log:
+ free(log);
+}
+
+static void check_gotox_edges_across_subprogs(void)
+{
+ const __u32 gotox_cnt = GOTOX_CNT_AT_LIMIT * 3 / 4;
+ const __u32 sub_start = gotox_cnt + 3;
+ const __u32 insn_cnt = 2 * (gotox_cnt + 3);
+ int map_fd[2] = { -1, -1 };
+ struct bpf_insn *insns;
+ char *log;
+ int err;
+
+ log = calloc(1, GOTOX_LOG_SZ);
+ if (!ASSERT_OK_PTR(log, "calloc log"))
+ return;
+
+ insns = calloc(insn_cnt, sizeof(*insns));
+ if (!ASSERT_OK_PTR(insns, "calloc insns"))
+ goto free_log;
+
+ gotox_run_fill(insns, gotox_cnt);
+ insns[gotox_cnt + 1] = BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0,
+ BPF_PSEUDO_CALL, 0,
+ sub_start - (gotox_cnt + 1) - 1);
+ insns[gotox_cnt + 2] = BPF_EXIT_INSN();
+
+ gotox_run_fill(insns + sub_start, gotox_cnt);
+ insns[sub_start + gotox_cnt + 1] = BPF_MOV64_IMM(BPF_REG_0, 0);
+ insns[sub_start + gotox_cnt + 2] = BPF_EXIT_INSN();
+
+ map_fd[0] = gotox_jt_create(1, gotox_cnt);
+ if (map_fd[0] < 0)
+ goto free_insns;
+ map_fd[1] = gotox_jt_create(sub_start + 1, gotox_cnt);
+ if (map_fd[1] < 0)
+ goto close_maps;
+
+ err = gotox_prog_load(insns, insn_cnt, map_fd, 2, log);
+ check_gotox_limit_hit(log, err);
+
+close_maps:
+ close(map_fd[0]);
+ close(map_fd[1]);
+free_insns:
+ free(insns);
+free_log:
+ free(log);
+}
+
static void check_bpf_side(void)
{
check_bpf_no_lookup();
@@ -490,6 +747,15 @@ static void __test_bpf_insn_array(void)
if (test__start_subtest("bpf-side-ops"))
check_bpf_side();
+
+ if (test__start_subtest("too-many-gotox-edges"))
+ check_too_many_gotox_edges();
+
+ if (test__start_subtest("gotox-edges-at-limit"))
+ check_gotox_edges_at_limit();
+
+ if (test__start_subtest("gotox-edges-across-subprogs"))
+ check_gotox_edges_across_subprogs();
}
#else
static void __test_bpf_insn_array(void)
--
2.43.0
next prev parent reply other threads:[~2026-09-09 20:40 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 ` Daniel Borkmann [this message]
2026-09-09 21:34 ` [PATCH bpf 5/6] selftests/bpf: Add tests for the indirect jump edge limit 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
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=20260909204035.24289-5-daniel@iogearbox.net \
--to=daniel@iogearbox.net \
--cc=a.s.protopopov@gmail.com \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--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;
as well as URLs for NNTP newsgroup(s).