BPF List
 help / color / mirror / Atom feed
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";

  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