* [PATCH bpf-next 0/2] bpf: Fix gotox target validation against CFG
@ 2026-06-09 15:03 Nuoqi Gui
2026-06-09 15:03 ` [PATCH bpf-next 1/2] " Nuoqi Gui
2026-06-09 15:03 ` [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage Nuoqi Gui
0 siblings, 2 replies; 10+ messages in thread
From: Nuoqi Gui @ 2026-06-09 15:03 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Anton Protopopov, Shuah Khan, linux-kselftest,
linux-kernel, Nuoqi Gui
For gotox, CFG construction models the indirect-jump target set in
insn_aux_data->jt, but do_check() later follows targets from the runtime
PTR_TO_INSN register's own INSN_ARRAY map. If the same gotox can be
reached with PTR_TO_INSN values from different maps, do_check() can accept
a target that CFG did not model.
On x86, that can transfer control into another subprog without a matching
BPF call frame and crash when the program is run.
Fix this by rejecting gotox map targets that are absent from the CFG jump
table built for that instruction. Add a regression test covering the
two-map cross-subprog case.
Validation:
unpatched bpf-next b9452b594fd3:
F01_02_LOAD_FD=5 errno=0 (accepted)
SELFTEST F01-02 gotox-cross-subprog: FAIL
patched bpf-next b9452b594fd3 + this series:
F01_02_LOAD_FD=-1 errno=22 (Invalid argument)
gotox target 14 from map id=2 is not in the CFG jump table
SELFTEST F01-02 gotox-cross-subprog: PASS
Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
---
Nuoqi Gui (2):
bpf: Fix gotox target validation against CFG
selftests/bpf: Add cross-subprog gotox target coverage
kernel/bpf/verifier.c | 26 ++++++++
tools/testing/selftests/bpf/prog_tests/bpf_gotox.c | 73 ++++++++++++++++++++++
2 files changed, 99 insertions(+)
---
base-commit: b9452b594fd3aecbfd4aa0a6a1f741330a37dab7
change-id: 20260609-f01-02-gotox-bpf-next-272e6276085d
Best regards,
--
Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH bpf-next 1/2] bpf: Fix gotox target validation against CFG
2026-06-09 15:03 [PATCH bpf-next 0/2] bpf: Fix gotox target validation against CFG Nuoqi Gui
@ 2026-06-09 15:03 ` Nuoqi Gui
2026-06-09 15:13 ` sashiko-bot
` (2 more replies)
2026-06-09 15:03 ` [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage Nuoqi Gui
1 sibling, 3 replies; 10+ messages in thread
From: Nuoqi Gui @ 2026-06-09 15:03 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Anton Protopopov, Shuah Khan, linux-kselftest,
linux-kernel, Nuoqi Gui
CFG construction records the modeled gotox target set in
insn_aux_data->jt. It includes INSN_ARRAY maps based on whether the map
target is in the current subprog. check_indirect_jump() later validates and
follows the current PTR_TO_INSN register's actual INSN_ARRAY map. The
verifier does not check that targets copied from that map match the targets
that CFG construction modeled for this gotox instruction.
This lets one gotox instruction observe two different INSN_ARRAY maps. CFG
can select a map whose target is in the current subprog. Another path to
the same gotox can carry a PTR_TO_INSN value from a map whose target points
at a different subprog. The verifier then accepts an edge absent from the
CFG.
On x86, gotox becomes a raw indirect jump in the JIT image. Accepting a
target not modeled by CFG can enter another subprog without a matching BPF
call frame and crash when executed. Validation observed a GPF in
bpf_test_run().
Fix this by requiring every target copied from the actual PTR_TO_INSN map
to be present in the CFG jump table built for the current gotox
instruction.
Reject the program before pushing verifier states for any unmodeled target.
Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps")
Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
---
kernel/bpf/verifier.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ed7ba0e6a9ce..25fa90e731e3 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -17124,6 +17124,23 @@ static int indirect_jump_min_max_index(struct bpf_verifier_env *env,
return 0;
}
+static bool is_cfg_indirect_jump_target(struct bpf_verifier_env *env,
+ u32 target)
+{
+ struct bpf_iarray *jt = env->insn_aux_data[env->insn_idx].jt;
+ int i;
+
+ if (!jt)
+ return false;
+
+ for (i = 0; i < jt->cnt; i++) {
+ if (jt->items[i] == target)
+ return true;
+ }
+
+ return false;
+}
+
/* gotox *dst_reg */
static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
@@ -17171,6 +17188,15 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
return -EINVAL;
}
+ for (i = 0; i < n; i++) {
+ if (!is_cfg_indirect_jump_target(env, env->gotox_tmp_buf->items[i])) {
+ verbose(env,
+ "gotox target %u from map id=%d is not in the CFG jump table\n",
+ env->gotox_tmp_buf->items[i], map->id);
+ return -EINVAL;
+ }
+ }
+
for (i = 0; i < n - 1; i++) {
mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage
2026-06-09 15:03 [PATCH bpf-next 0/2] bpf: Fix gotox target validation against CFG Nuoqi Gui
2026-06-09 15:03 ` [PATCH bpf-next 1/2] " Nuoqi Gui
@ 2026-06-09 15:03 ` Nuoqi Gui
2026-06-09 15:40 ` sashiko-bot
` (2 more replies)
1 sibling, 3 replies; 10+ messages in thread
From: Nuoqi Gui @ 2026-06-09 15:03 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Anton Protopopov, Shuah Khan, linux-kselftest,
linux-kernel, Nuoqi Gui
Add a gotox regression test with two one-entry INSN_ARRAY maps. CFG can
model a map whose target stays in the main subprog, while the verified path
can load a different map whose target is the first instruction of another
subprog.
That second target is absent from the CFG jump table for this gotox
instruction, so program load must be rejected.
Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
---
tools/testing/selftests/bpf/prog_tests/bpf_gotox.c | 73 ++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
index 73dc63882b7d..866b4a14ccb7 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
@@ -255,6 +255,30 @@ static int create_jt_map(__u32 max_entries)
key_size, value_size, max_entries, NULL);
}
+static int create_jt_map_with_target(__u32 target)
+{
+ struct bpf_insn_array_value val = { .orig_off = target };
+ __u32 key = 0;
+ int map_fd;
+
+ map_fd = create_jt_map(1);
+ if (!ASSERT_GE(map_fd, 0, "create_jt_map"))
+ return -1;
+
+ if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &key, &val, 0),
+ 0, "bpf_map_update_elem")) {
+ close(map_fd);
+ return -1;
+ }
+
+ if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze")) {
+ close(map_fd);
+ return -1;
+ }
+
+ return map_fd;
+}
+
static int prog_load(struct bpf_insn *insns, __u32 insn_cnt)
{
return bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, NULL, "GPL", insns, insn_cnt, NULL);
@@ -393,6 +417,52 @@ reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, int off1, int off2, int o
close(prog_fd);
}
+static void
+check_cross_subprog_gotox_target(struct bpf_gotox *skel __always_unused)
+{
+ struct bpf_insn insns[] = {
+ /* main subprog [0,14) */
+ BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0, 12),
+ BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, 0),
+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, 0, 4),
+ BPF_LD_IMM64_RAW(BPF_REG_2, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_2, 0),
+ BPF_JMP_A(3),
+ BPF_LD_IMM64_RAW(BPF_REG_2, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_2, 0),
+ BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_2, 0, 0, 0),
+ BPF_MOV64_IMM(BPF_REG_0, 1),
+ BPF_EXIT_INSN(),
+
+ /* static subprog [14,16) */
+ BPF_MOV64_IMM(BPF_REG_0, 42),
+ BPF_EXIT_INSN(),
+ };
+ int good_fd, bad_fd, prog_fd;
+
+ good_fd = create_jt_map_with_target(12);
+ if (!ASSERT_GE(good_fd, 0, "create_good_jt_map"))
+ return;
+
+ bad_fd = create_jt_map_with_target(14);
+ if (!ASSERT_GE(bad_fd, 0, "create_bad_jt_map")) {
+ close(good_fd);
+ return;
+ }
+
+ insns[4].imm = bad_fd;
+ insns[8].imm = good_fd;
+
+ prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL",
+ insns, ARRAY_SIZE(insns), NULL);
+ if (!ASSERT_EQ(prog_fd, -EACCES, "cross_subprog_gotox_prog_load"))
+ close(prog_fd);
+
+ close(bad_fd);
+ close(good_fd);
+}
+
/*
* Verify a bit more complex programs which include indirect jumps
* and with jump tables loaded with a non-zero offset
@@ -538,6 +608,9 @@ void test_bpf_gotox(void)
if (test__start_subtest("check-ldimm64-off-gotox"))
__subtest(skel, check_ldimm64_off_gotox);
+ if (test__start_subtest("check-cross-subprog-gotox-target"))
+ __subtest(skel, check_cross_subprog_gotox_target);
+
if (test__start_subtest("check-ldimm64-off-gotox-llvm"))
__subtest(skel, check_ldimm64_off_gotox_llvm);
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: Fix gotox target validation against CFG
2026-06-09 15:03 ` [PATCH bpf-next 1/2] " Nuoqi Gui
@ 2026-06-09 15:13 ` sashiko-bot
2026-06-09 15:42 ` bot+bpf-ci
2026-06-09 15:56 ` Anton Protopopov
2 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-09 15:13 UTC (permalink / raw)
To: Nuoqi Gui; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The newly introduced jump target validation check in `check_indirect_jump` introduces an O(N * M) algorithmic complexity that bypasses verifier complexity limits, allowing a CPU soft lockup or Denial of Service (DoS).
--
commit 618413c7b9b3d9e098467c3629ea05da9fcb47af
Author: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
bpf: Fix gotox target validation against CFG
This commit adds validation to ensure that jump targets copied from the
INSN_ARRAY map are present in the CFG jump table built for the gotox
instruction. It rejects programs before pushing verifier states for unmodeled
targets to prevent entering another subprog without a matching call frame.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index ed7ba0e6a9ce0..25fa90e731e30 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -17124,6 +17124,23 @@ static int indirect_jump_min_max_index(struct bpf_verifier_env *env,
> return 0;
> }
>
> +static bool is_cfg_indirect_jump_target(struct bpf_verifier_env *env,
> + u32 target)
> +{
> + struct bpf_iarray *jt = env->insn_aux_data[env->insn_idx].jt;
> + int i;
> +
> + if (!jt)
> + return false;
> +
> + for (i = 0; i < jt->cnt; i++) {
> + if (jt->items[i] == target)
> + return true;
> + }
> +
> + return false;
> +}
> +
> /* gotox *dst_reg */
> static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)
> {
> @@ -17171,6 +17188,15 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
> return -EINVAL;
> }
>
> + for (i = 0; i < n; i++) {
> + if (!is_cfg_indirect_jump_target(env, env->gotox_tmp_buf->items[i])) {
[Severity: High]
Could this nested iteration result in a CPU soft lockup?
This loop inside check_indirect_jump() iterates n times, and
is_cfg_indirect_jump_target() performs a linear scan over jt->cnt. Both n
and jt->cnt are bounded by the map's max_entries, which can be very large.
Since there is no cond_resched() here, this creates an O(N * M) synchronous
iteration sequence that bypasses the verifier's global instruction limits
and could trigger the kernel watchdog panic.
Given that both gotox_tmp_buf->items and jt->items are strictly sorted
arrays, could this be optimized to O(N + M) using a two-pointer merge
approach or O(N log M) using binary search?
> + verbose(env,
> + "gotox target %u from map id=%d is not in the CFG jump table\n",
> + env->gotox_tmp_buf->items[i], map->id);
> + return -EINVAL;
> + }
> + }
> +
> for (i = 0; i < n - 1; i++) {
> mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
> other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609-f01-02-gotox-bpf-next-v1-0-b441d63a1559@mails.tsinghua.edu.cn?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage
2026-06-09 15:03 ` [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage Nuoqi Gui
@ 2026-06-09 15:40 ` sashiko-bot
2026-06-09 15:42 ` bot+bpf-ci
2026-06-09 16:14 ` Anton Protopopov
2 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-09 15:40 UTC (permalink / raw)
To: Nuoqi Gui; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The test `check_cross_subprog_gotox_target` incorrectly expects `bpf_prog_load` to fail with `-EACCES`, but the verifier actually rejects cross-subprog jump targets with `-EINVAL`, causing a false test failure.
--
commit f7ef2af9b62d2a415c2072928a61150823abda72
Author: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
selftests/bpf: Add cross-subprog gotox target coverage
This commit adds a gotox regression test with two one-entry INSN_ARRAY maps
to verify that the control flow graph handles indirect jumps correctly. It
ensures that when a verified path loads a map whose target points to another
subprog, the program load is rejected.
> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> index 73dc63882b7d5..866b4a14ccb7c 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
[ ... ]
> @@ -393,6 +417,52 @@ reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, int off1, int off2, int o
> close(prog_fd);
> }
>
> +static void
> +check_cross_subprog_gotox_target(struct bpf_gotox *skel __always_unused)
> +{
[ ... ]
> + prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL",
> + insns, ARRAY_SIZE(insns), NULL);
> + if (!ASSERT_EQ(prog_fd, -EACCES, "cross_subprog_gotox_prog_load"))
[Severity: Medium]
Will this assertion fail because the verifier returns -EINVAL instead of
-EACCES?
When the verifier inspects the gotox target via check_indirect_jump(), the
target fails the is_cfg_indirect_jump_target() check. This causes the
verifier to explicitly reject the load with -EINVAL.
The memory access itself passes the verifier's check_map_access() bounds
check since it reads from a valid offset, so we don't get the -EACCES return
value here.
Should this test expect -EINVAL instead?
> + close(prog_fd);
> +
> + close(bad_fd);
> + close(good_fd);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609-f01-02-gotox-bpf-next-v1-0-b441d63a1559@mails.tsinghua.edu.cn?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: Fix gotox target validation against CFG
2026-06-09 15:03 ` [PATCH bpf-next 1/2] " Nuoqi Gui
2026-06-09 15:13 ` sashiko-bot
@ 2026-06-09 15:42 ` bot+bpf-ci
2026-06-09 15:56 ` Anton Protopopov
2 siblings, 0 replies; 10+ messages in thread
From: bot+bpf-ci @ 2026-06-09 15:42 UTC (permalink / raw)
To: gnq25, bpf
Cc: ast, daniel, andrii, eddyz87, a.s.protopopov, shuah,
linux-kselftest, linux-kernel, gnq25, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2339 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index ed7ba0e6a9ce..25fa90e731e3 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -17124,6 +17124,23 @@ static int indirect_jump_min_max_index(struct bpf_verifier_env *env,
> return 0;
> }
>
> +static bool is_cfg_indirect_jump_target(struct bpf_verifier_env *env,
> + u32 target)
> +{
> + struct bpf_iarray *jt = env->insn_aux_data[env->insn_idx].jt;
> + int i;
> +
> + if (!jt)
> + return false;
> +
> + for (i = 0; i < jt->cnt; i++) {
> + if (jt->items[i] == target)
> + return true;
> + }
> +
> + return false;
> +}
> +
> /* gotox *dst_reg */
> static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)
> {
[ ... ]
> @@ -17171,6 +17188,15 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
> return -EINVAL;
> }
>
> + for (i = 0; i < n; i++) {
> + if (!is_cfg_indirect_jump_target(env, env->gotox_tmp_buf->items[i])) {
> + verbose(env,
> + "gotox target %u from map id=%d is not in the CFG jump table\n",
> + env->gotox_tmp_buf->items[i], map->id);
> + return -EINVAL;
> + }
> + }
> +
Could this nested loop create an O(N * M) algorithmic complexity that
bypasses verifier complexity limits?
The outer loop iterates n times, and is_cfg_indirect_jump_target()
performs a linear scan over jt->cnt items. Both n and jt->cnt are bounded
by the map's max_entries, which can be very large.
Since there is no cond_resched() here, this creates a synchronous
iteration sequence that could trigger the kernel watchdog panic or lead to
CPU soft lockup.
Given that both gotox_tmp_buf->items and jt->items are strictly sorted
arrays, could this be optimized to O(N + M) using a two-pointer merge
approach or O(N log M) using binary search?
Raised by sashiko-bot@kernel.org in v1:
https://lore.kernel.org/bpf/20260609151359.3C6F51F00893@smtp.kernel.org/
> for (i = 0; i < n - 1; i++) {
> mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
> other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27216356316
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage
2026-06-09 15:03 ` [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage Nuoqi Gui
2026-06-09 15:40 ` sashiko-bot
@ 2026-06-09 15:42 ` bot+bpf-ci
2026-06-09 16:14 ` Anton Protopopov
2 siblings, 0 replies; 10+ messages in thread
From: bot+bpf-ci @ 2026-06-09 15:42 UTC (permalink / raw)
To: gnq25, bpf
Cc: ast, daniel, andrii, eddyz87, a.s.protopopov, shuah,
linux-kselftest, linux-kernel, gnq25, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3183 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> index 73dc63882b7d..866b4a14ccb7 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
[ ... ]
> @@ -393,6 +417,52 @@ reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, int off1, int off2, int o
> close(prog_fd);
> }
>
> +static void
> +check_cross_subprog_gotox_target(struct bpf_gotox *skel __always_unused)
> +{
> + struct bpf_insn insns[] = {
> + /* main subprog [0,14) */
> + BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
> + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0, 12),
> + BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, 0),
> + BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, 0, 4),
> + BPF_LD_IMM64_RAW(BPF_REG_2, BPF_PSEUDO_MAP_VALUE, 0),
> + BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_2, 0),
> + BPF_JMP_A(3),
> + BPF_LD_IMM64_RAW(BPF_REG_2, BPF_PSEUDO_MAP_VALUE, 0),
> + BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_2, 0),
> + BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_2, 0, 0, 0),
> + BPF_MOV64_IMM(BPF_REG_0, 1),
> + BPF_EXIT_INSN(),
> +
> + /* static subprog [14,16) */
> + BPF_MOV64_IMM(BPF_REG_0, 42),
> + BPF_EXIT_INSN(),
> + };
> + int good_fd, bad_fd, prog_fd;
> +
> + good_fd = create_jt_map_with_target(12);
> + if (!ASSERT_GE(good_fd, 0, "create_good_jt_map"))
> + return;
> +
> + bad_fd = create_jt_map_with_target(14);
> + if (!ASSERT_GE(bad_fd, 0, "create_bad_jt_map")) {
> + close(good_fd);
> + return;
> + }
> +
> + insns[4].imm = bad_fd;
> + insns[8].imm = good_fd;
> +
> + prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL",
> + insns, ARRAY_SIZE(insns), NULL);
> + if (!ASSERT_EQ(prog_fd, -EACCES, "cross_subprog_gotox_prog_load"))
^^^^^^^
Does this test expect the correct error code? The kernel fix in commit
8f33cb405e13 ("bpf: Fix gotox target validation against CFG") rejects an
unmodeled gotox target with -EINVAL when the CFG jump table check fails:
kernel/bpf/verifier.c:check_indirect_jump() {
for (i = 0; i < n; i++) {
if (!is_cfg_indirect_jump_target(env, env->gotox_tmp_buf->items[i])) {
verbose(env, "gotox target %u from map id=%d is not in the CFG jump table\n", ...);
return -EINVAL;
}
}
}
This -EINVAL propagates unchanged through do_check() -> do_check_common() ->
bpf_check() -> the BPF_PROG_LOAD syscall handler.
The existing reject_offsets() tests in this file legitimately expect -EACCES
because they fail in check_map_access (the INSN_ARRAY read path for bad size
or map-value access checks), a different code path. This new program is
structurally valid through all the loads and only fails at the gotox
instruction's CFG-jump-table check.
Should the assertion compare against -EINVAL instead of -EACCES?
> + close(prog_fd);
> +
> + close(bad_fd);
> + close(good_fd);
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27216356316
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: Fix gotox target validation against CFG
2026-06-09 15:03 ` [PATCH bpf-next 1/2] " Nuoqi Gui
2026-06-09 15:13 ` sashiko-bot
2026-06-09 15:42 ` bot+bpf-ci
@ 2026-06-09 15:56 ` Anton Protopopov
2026-06-09 17:27 ` Eduard Zingerman
2 siblings, 1 reply; 10+ messages in thread
From: Anton Protopopov @ 2026-06-09 15:56 UTC (permalink / raw)
To: Nuoqi Gui
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Shuah Khan, linux-kselftest, linux-kernel
On 26/06/09 11:03PM, Nuoqi Gui wrote:
> CFG construction records the modeled gotox target set in
> insn_aux_data->jt. It includes INSN_ARRAY maps based on whether the map
> target is in the current subprog. check_indirect_jump() later validates and
> follows the current PTR_TO_INSN register's actual INSN_ARRAY map. The
> verifier does not check that targets copied from that map match the targets
> that CFG construction modeled for this gotox instruction.
>
> This lets one gotox instruction observe two different INSN_ARRAY maps. CFG
> can select a map whose target is in the current subprog. Another path to
> the same gotox can carry a PTR_TO_INSN value from a map whose target points
> at a different subprog. The verifier then accepts an edge absent from the
> CFG.
>
> On x86, gotox becomes a raw indirect jump in the JIT image. Accepting a
> target not modeled by CFG can enter another subprog without a matching BPF
> call frame and crash when executed. Validation observed a GPF in
> bpf_test_run().
>
> Fix this by requiring every target copied from the actual PTR_TO_INSN map
> to be present in the CFG jump table built for the current gotox
> instruction.
> Reject the program before pushing verifier states for any unmodeled target.
>
> Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps")
> Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
> ---
> kernel/bpf/verifier.c | 26 ++++++++++++++++++++++++++
> 1 file changed, 26 insertions(+)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index ed7ba0e6a9ce..25fa90e731e3 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -17124,6 +17124,23 @@ static int indirect_jump_min_max_index(struct bpf_verifier_env *env,
> return 0;
> }
>
> +static bool is_cfg_indirect_jump_target(struct bpf_verifier_env *env,
> + u32 target)
> +{
> + struct bpf_iarray *jt = env->insn_aux_data[env->insn_idx].jt;
> + int i;
> +
> + if (!jt)
> + return false;
> +
> + for (i = 0; i < jt->cnt; i++) {
> + if (jt->items[i] == target)
> + return true;
> + }
> +
> + return false;
> +}
> +
> /* gotox *dst_reg */
> static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)
> {
> @@ -17171,6 +17188,15 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
> return -EINVAL;
> }
>
> + for (i = 0; i < n; i++) {
> + if (!is_cfg_indirect_jump_target(env, env->gotox_tmp_buf->items[i])) {
> + verbose(env,
> + "gotox target %u from map id=%d is not in the CFG jump table\n",
> + env->gotox_tmp_buf->items[i], map->id);
> + return -EINVAL;
> + }
> + }
Thanks for reporting the bug.
As for the fix, would it make more sense to either record maps in
check_cfg or to re-check subfunc boundaries here?
> for (i = 0; i < n - 1; i++) {
> mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
> other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
>
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage
2026-06-09 15:03 ` [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage Nuoqi Gui
2026-06-09 15:40 ` sashiko-bot
2026-06-09 15:42 ` bot+bpf-ci
@ 2026-06-09 16:14 ` Anton Protopopov
2 siblings, 0 replies; 10+ messages in thread
From: Anton Protopopov @ 2026-06-09 16:14 UTC (permalink / raw)
To: Nuoqi Gui
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Shuah Khan, linux-kselftest, linux-kernel
On 26/06/09 11:03PM, Nuoqi Gui wrote:
> Add a gotox regression test with two one-entry INSN_ARRAY maps. CFG can
> model a map whose target stays in the main subprog, while the verified path
> can load a different map whose target is the first instruction of another
> subprog.
>
> That second target is absent from the CFG jump table for this gotox
> instruction, so program load must be rejected.
>
> Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
> ---
> tools/testing/selftests/bpf/prog_tests/bpf_gotox.c | 73 ++++++++++++++++++++++
> 1 file changed, 73 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> index 73dc63882b7d..866b4a14ccb7 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
> @@ -255,6 +255,30 @@ static int create_jt_map(__u32 max_entries)
> key_size, value_size, max_entries, NULL);
> }
>
> +static int create_jt_map_with_target(__u32 target)
> +{
> + struct bpf_insn_array_value val = { .orig_off = target };
> + __u32 key = 0;
> + int map_fd;
> +
> + map_fd = create_jt_map(1);
> + if (!ASSERT_GE(map_fd, 0, "create_jt_map"))
> + return -1;
> +
> + if (!ASSERT_EQ(bpf_map_update_elem(map_fd, &key, &val, 0),
> + 0, "bpf_map_update_elem")) {
> + close(map_fd);
> + return -1;
> + }
> +
> + if (!ASSERT_EQ(bpf_map_freeze(map_fd), 0, "bpf_map_freeze")) {
> + close(map_fd);
> + return -1;
> + }
> +
> + return map_fd;
> +}
> +
> static int prog_load(struct bpf_insn *insns, __u32 insn_cnt)
> {
> return bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, NULL, "GPL", insns, insn_cnt, NULL);
> @@ -393,6 +417,52 @@ reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, int off1, int off2, int o
> close(prog_fd);
> }
>
> +static void
> +check_cross_subprog_gotox_target(struct bpf_gotox *skel __always_unused)
> +{
> + struct bpf_insn insns[] = {
> + /* main subprog [0,14) */
> + BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
> + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0, 12),
> + BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, 0),
> + BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, 0, 4),
> + BPF_LD_IMM64_RAW(BPF_REG_2, BPF_PSEUDO_MAP_VALUE, 0),
> + BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_2, 0),
> + BPF_JMP_A(3),
> + BPF_LD_IMM64_RAW(BPF_REG_2, BPF_PSEUDO_MAP_VALUE, 0),
> + BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_2, 0),
> + BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_2, 0, 0, 0),
> + BPF_MOV64_IMM(BPF_REG_0, 1),
> + BPF_EXIT_INSN(),
> +
> + /* static subprog [14,16) */
> + BPF_MOV64_IMM(BPF_REG_0, 42),
> + BPF_EXIT_INSN(),
> + };
> + int good_fd, bad_fd, prog_fd;
> +
> + good_fd = create_jt_map_with_target(12);
> + if (!ASSERT_GE(good_fd, 0, "create_good_jt_map"))
> + return;
> +
> + bad_fd = create_jt_map_with_target(14);
> + if (!ASSERT_GE(bad_fd, 0, "create_bad_jt_map")) {
> + close(good_fd);
> + return;
> + }
> +
> + insns[4].imm = bad_fd;
> + insns[8].imm = good_fd;
> +
> + prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL",
> + insns, ARRAY_SIZE(insns), NULL);
> + if (!ASSERT_EQ(prog_fd, -EACCES, "cross_subprog_gotox_prog_load"))
> + close(prog_fd);
Robots are right, of course:
...
check_cross_subprog_gotox_target:PASS:create_bad_jt_map 0 nsec
check_cross_subprog_gotox_target:FAIL:cross_subprog_gotox_prog_load unexpected cross_subprog_gotox_prog_load: actual -22 != expected -13
#20/14 bpf_gotox/check-cross-subprog-gotox-target:FAIL
...
Please the next time actually run selftests before sending patches.
> +
> + close(bad_fd);
> + close(good_fd);
> +}
> +
> /*
> * Verify a bit more complex programs which include indirect jumps
> * and with jump tables loaded with a non-zero offset
> @@ -538,6 +608,9 @@ void test_bpf_gotox(void)
> if (test__start_subtest("check-ldimm64-off-gotox"))
> __subtest(skel, check_ldimm64_off_gotox);
>
> + if (test__start_subtest("check-cross-subprog-gotox-target"))
> + __subtest(skel, check_cross_subprog_gotox_target);
> +
> if (test__start_subtest("check-ldimm64-off-gotox-llvm"))
> __subtest(skel, check_ldimm64_off_gotox_llvm);
>
>
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: Fix gotox target validation against CFG
2026-06-09 15:56 ` Anton Protopopov
@ 2026-06-09 17:27 ` Eduard Zingerman
0 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2026-06-09 17:27 UTC (permalink / raw)
To: Anton Protopopov, Nuoqi Gui
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Shuah Khan, linux-kselftest, linux-kernel
On Tue, 2026-06-09 at 15:56 +0000, Anton Protopopov wrote:
> On 26/06/09 11:03PM, Nuoqi Gui wrote:
> > CFG construction records the modeled gotox target set in
> > insn_aux_data->jt. It includes INSN_ARRAY maps based on whether the map
> > target is in the current subprog. check_indirect_jump() later validates and
> > follows the current PTR_TO_INSN register's actual INSN_ARRAY map. The
> > verifier does not check that targets copied from that map match the targets
> > that CFG construction modeled for this gotox instruction.
> >
> > This lets one gotox instruction observe two different INSN_ARRAY maps. CFG
> > can select a map whose target is in the current subprog. Another path to
> > the same gotox can carry a PTR_TO_INSN value from a map whose target points
> > at a different subprog. The verifier then accepts an edge absent from the
> > CFG.
> >
> > On x86, gotox becomes a raw indirect jump in the JIT image. Accepting a
> > target not modeled by CFG can enter another subprog without a matching BPF
> > call frame and crash when executed. Validation observed a GPF in
> > bpf_test_run().
> >
> > Fix this by requiring every target copied from the actual PTR_TO_INSN map
> > to be present in the CFG jump table built for the current gotox
> > instruction.
> > Reject the program before pushing verifier states for any unmodeled target.
> >
> > Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps")
> > Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
> > ---
> > kernel/bpf/verifier.c | 26 ++++++++++++++++++++++++++
> > 1 file changed, 26 insertions(+)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index ed7ba0e6a9ce..25fa90e731e3 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -17124,6 +17124,23 @@ static int indirect_jump_min_max_index(struct bpf_verifier_env *env,
> > return 0;
> > }
> >
> > +static bool is_cfg_indirect_jump_target(struct bpf_verifier_env *env,
> > + u32 target)
> > +{
> > + struct bpf_iarray *jt = env->insn_aux_data[env->insn_idx].jt;
> > + int i;
> > +
> > + if (!jt)
> > + return false;
> > +
> > + for (i = 0; i < jt->cnt; i++) {
> > + if (jt->items[i] == target)
> > + return true;
> > + }
> > +
> > + return false;
> > +}
> > +
> > /* gotox *dst_reg */
> > static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)
> > {
> > @@ -17171,6 +17188,15 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
> > return -EINVAL;
> > }
> >
> > + for (i = 0; i < n; i++) {
> > + if (!is_cfg_indirect_jump_target(env, env->gotox_tmp_buf->items[i])) {
> > + verbose(env,
> > + "gotox target %u from map id=%d is not in the CFG jump table\n",
> > + env->gotox_tmp_buf->items[i], map->id);
> > + return -EINVAL;
> > + }
> > + }
>
> Thanks for reporting the bug.
>
> As for the fix, would it make more sense to either record maps in
> check_cfg or to re-check subfunc boundaries here?
+1 for checking if the jump is within subprog boundaries assumed for the gotox.
> > for (i = 0; i < n - 1; i++) {
> > mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
> > other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
> >
> > --
> > 2.34.1
> >
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-06-09 17:27 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-09 15:03 [PATCH bpf-next 0/2] bpf: Fix gotox target validation against CFG Nuoqi Gui
2026-06-09 15:03 ` [PATCH bpf-next 1/2] " Nuoqi Gui
2026-06-09 15:13 ` sashiko-bot
2026-06-09 15:42 ` bot+bpf-ci
2026-06-09 15:56 ` Anton Protopopov
2026-06-09 17:27 ` Eduard Zingerman
2026-06-09 15:03 ` [PATCH bpf-next 2/2] selftests/bpf: Add cross-subprog gotox target coverage Nuoqi Gui
2026-06-09 15:40 ` sashiko-bot
2026-06-09 15:42 ` bot+bpf-ci
2026-06-09 16:14 ` Anton Protopopov
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.