From: Eduard Zingerman <eddyz87@gmail.com>
To: Daniel Borkmann <daniel@iogearbox.net>, ast@kernel.org
Cc: memxor@gmail.com, a.s.protopopov@gmail.com, info@starlabs.sg,
bpf@vger.kernel.org
Subject: Re: [PATCH bpf v2 08/11] selftests/bpf: Add tests for the indirect jump edge limit
Date: Mon, 14 Sep 2026 18:59:25 -0700 [thread overview]
Message-ID: <efdf1e266dfd031acec558be11d6cc8461c2d26c.camel@gmail.com> (raw)
In-Reply-To: <20260914170326.249953-9-daniel@iogearbox.net>
[-- Attachment #1: Type: text/plain, Size: 878 bytes --]
On Mon, 2026-09-14 at 19:03 +0200, Daniel Borkmann wrote:
> Add corner case tests for programs whose gotox instructions are their own
> jump table targets.
>
> # 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>
> Acked-by: Anton Protopopov <a.s.protopopov@gmail.com>
> ---
I think it's a bit of an overkill of a test.
Codex generated transformation to inline assembly as attached.
Object file size is 100k, but I think it is significantly easier
to understand in this form. Wdyt?
[-- Attachment #2: inline-asm-gotox.diff --]
[-- Type: text/x-patch, Size: 10629 bytes --]
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 f102435675c2..70150b54bdaf 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -454,40 +454,8 @@ 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 insn 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_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)
@@ -523,208 +491,6 @@ static int gotox_prog_load(struct bpf_insn *insns, __u32 insn_cnt,
-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)
-{
- __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 int gotox_jt_create_offs(const __u32 *offs, __u32 cnt)
{
struct bpf_insn_array_value val = {};
@@ -1501,12 +1267,6 @@ static void __test_bpf_insn_array(void)
check_no_map_reuse();
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();
if (test__start_subtest("gotox-jt-spans-subprogs"))
check_gotox_jt_spans_subprogs();
if (test__start_subtest("gotox-jt-spans-with-own-table"))
diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c
index 4931a77ba0b2..b917c2e14283 100644
--- a/tools/testing/selftests/bpf/progs/verifier_gotox.c
+++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c
@@ -457,6 +457,100 @@ __naked void spill_fill_ptr_to_insn(void)
: __clobber_all);
}
+/*
+ * Emit each table entry next to the instruction it targets. Numeric labels
+ * can be reused by .rept, while %= keeps table symbols unique per asm block.
+ * BASE is the code section: libbpf relocates section-relative byte offsets
+ * to instruction offsets in the loaded program.
+ */
+#define GOTOX_TABLE_BEGIN(COUNT) \
+ ".pushsection .jumptables,\"\",@progbits;" \
+ "jt_%=:;" \
+ ".global jt_%=;" \
+ ".size jt_%=, 8 * (" #COUNT ");" \
+ ".popsection;" \
+ "r1 = jt_%= ll;"
+
+#define GOTOX_TABLE_ENTRY(LABEL, BASE) \
+ ".pushsection .jumptables,\"\",@progbits;" \
+ ".quad " LABEL " - " BASE ";" \
+ ".popsection;"
+
+/* N gotox instructions, each targeting all N gotox plus the final block. */
+#define GOTOX_SELF_TARGETS(N, BASE) \
+ GOTOX_TABLE_BEGIN(N + 1) \
+ "r0 = 0;" \
+ ".rept " #N ";" \
+ GOTOX_TABLE_ENTRY("1f", BASE) \
+ "1:; .8byte %[gotox];" \
+ ".endr;" \
+ GOTOX_TABLE_ENTRY("2f", BASE) \
+ "2:;"
+
+#define GOTOX_OPERAND \
+ __imm_insn(gotox, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0, 0))
+
+/*
+ * Count edges even when their targets are already discovered. These
+ * programs deliberately use a scalar: the CFG edge limit must reject them
+ * before symbolic verification reaches the invalid gotox operand.
+ */
+SEC("socket")
+__description("too-many-gotox-edges")
+__failure __msg("number of indirect jump edges in the program exceeds")
+__naked void too_many_gotox_edges(void)
+{
+ asm volatile (
+ /* 1000 * 1001 = 1,001,000 edges. */
+ GOTOX_SELF_TARGETS(1000, "socket")
+ "r0 = 0; exit;"
+ : : GOTOX_OPERAND : __clobber_all);
+}
+
+SEC("socket")
+__description("gotox-edges-at-limit")
+__success __retval(0)
+__naked void gotox_edges_at_limit(void)
+{
+ asm volatile (
+ /*
+ * 1000 gotox * 1000 targets = 1,000,000 CFG edges. At run
+ * time, each block loads the next table entry and jumps to
+ * the following block, so the program terminates.
+ */
+ GOTOX_TABLE_BEGIN(1000)
+ ".rept 1000;"
+ GOTOX_TABLE_ENTRY("1f", "socket")
+ "r0 = *(u64 *)(r1 + 0);"
+ "r1 += 8;"
+ ".8byte %[gotox];"
+ "1:;"
+ ".endr;"
+ "r0 = 0; exit;"
+ : : GOTOX_OPERAND : __clobber_all);
+}
+
+static __noinline __used __naked void gotox_edges_subprog(void)
+{
+ asm volatile (
+ GOTOX_SELF_TARGETS(750, ".text")
+ "r0 = 0; exit;"
+ : : GOTOX_OPERAND : __clobber_all);
+}
+
+SEC("socket")
+__description("gotox-edges-across-subprogs")
+__failure __msg("number of indirect jump edges in the program exceeds")
+__naked void gotox_edges_across_subprogs(void)
+{
+ asm volatile (
+ /* Each subprog has 563,250 edges; together, 1,126,500. */
+ GOTOX_SELF_TARGETS(750, "socket")
+ "call gotox_edges_subprog;"
+ "exit;"
+ : : GOTOX_OPERAND : __clobber_all);
+}
+
#endif /* __TARGET_ARCH_x86 || __TARGET_ARCH_arm64 || __TARGET_ARCH_powerpc*/
char _license[] SEC("license") = "GPL";
next prev parent reply other threads:[~2026-09-15 1:59 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 17:03 [PATCH bpf v2 00/11] Misc BPF gotox fixes Daniel Borkmann
2026-09-14 17:03 ` [PATCH bpf v2 01/11] bpf: Avoid quadratic successor rescan in bpf_compute_scc Daniel Borkmann
2026-09-14 17:03 ` [PATCH bpf v2 02/11] bpf: Bound the number of indirect jump edges in a program Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
2026-09-14 17:03 ` [PATCH bpf v2 03/11] bpf: Cache the jump table of a subprogram during CFG discovery Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
2026-09-14 20:05 ` Daniel Borkmann
2026-09-14 23:14 ` Eduard Zingerman
2026-09-14 17:03 ` [PATCH bpf v2 04/11] bpf: Reject indirect jumps that leave their subprogram Daniel Borkmann
2026-09-15 0:04 ` Eduard Zingerman
2026-09-14 17:03 ` [PATCH bpf v2 05/11] bpf: Don't remove indirect jump targets in the nop removal pass Daniel Borkmann
2026-09-15 0:14 ` Eduard Zingerman
2026-09-14 17:03 ` [PATCH bpf v2 06/11] bpf: Clear insn array jitted pointers on map reuse Daniel Borkmann
2026-09-14 17:29 ` sashiko-bot
2026-09-14 19:30 ` Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
2026-09-15 0:20 ` Eduard Zingerman
2026-09-14 17:03 ` [PATCH bpf v2 07/11] bpf: Retarget indirect jump targets across prologue prepends Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
2026-09-15 0:41 ` Eduard Zingerman
2026-09-14 17:03 ` [PATCH bpf v2 08/11] selftests/bpf: Add tests for the indirect jump edge limit Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
2026-09-15 1:59 ` Eduard Zingerman [this message]
2026-09-14 17:03 ` [PATCH bpf v2 09/11] selftests/bpf: Add tests for indirect jumps across subprograms Daniel Borkmann
2026-09-14 17:03 ` [PATCH bpf v2 10/11] selftests/bpf: Add tests for gotox nop targets and insn array reuse Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
2026-09-15 2:36 ` Eduard Zingerman
2026-09-14 17:03 ` [PATCH bpf v2 11/11] selftests/bpf: Add tests for gotox targets across prologue prepends Daniel Borkmann
2026-09-14 18:19 ` bot+bpf-ci
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=efdf1e266dfd031acec558be11d6cc8461c2d26c.camel@gmail.com \
--to=eddyz87@gmail.com \
--cc=a.s.protopopov@gmail.com \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--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