]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/bpf: Add more tests for loading insn arrays with offsets
authorAnton Protopopov <a.s.protopopov@gmail.com>
Mon, 6 Apr 2026 16:01:41 +0000 (16:01 +0000)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 7 Apr 2026 01:38:32 +0000 (18:38 -0700)
A `gotox rX` instruction accepts only values of type PTR_TO_INSN.
The only way to create such a value is to load it from a map of
type insn_array:

   rX = *(rY + offset) # rY was read from an insn_array
   ...
   gotox rX

Add instruction-level and C-level selftests to validate loads
with nonzero offsets.

Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
Link: https://lore.kernel.org/r/20260406160141.36943-3-a.s.protopopov@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/bpf_gotox.c
tools/testing/selftests/bpf/progs/bpf_gotox.c

index 75b0cf2467abd2c1ff123ae0eb1fa055b46120ee..73dc63882b7d55cfdb2369536d0215fd1c85d523 100644 (file)
@@ -317,7 +317,7 @@ static void check_ldimm64_off_load(struct bpf_gotox *skel __always_unused)
 
 static int __check_ldimm64_gotox_prog_load(struct bpf_insn *insns,
                                           __u32 insn_cnt,
-                                          __u32 off1, __u32 off2)
+                                          int off1, int off2, int off3)
 {
        const __u32 values[] = {5, 7, 9, 11, 13, 15};
        const __u32 max_entries = ARRAY_SIZE(values);
@@ -349,16 +349,46 @@ static int __check_ldimm64_gotox_prog_load(struct bpf_insn *insns,
        /* r1 += off2 */
        insns[2].imm = off2;
 
+       /* r1 = *(r1 + off3) */
+       insns[3].off = off3;
+
        ret = prog_load(insns, insn_cnt);
        close(map_fd);
        return ret;
 }
 
-static void reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, __u32 off1, __u32 off2)
+static void
+allow_offsets(struct bpf_insn *insns, __u32 insn_cnt, int off1, int off2, int off3)
+{
+       LIBBPF_OPTS(bpf_test_run_opts, topts);
+       int prog_fd, err;
+       char s[128] = "";
+
+       prog_fd = __check_ldimm64_gotox_prog_load(insns, insn_cnt, off1, off2, off3);
+       snprintf(s, sizeof(s), "__check_ldimm64_gotox_prog_load(%d,%d,%d)", off1, off2, off3);
+       if (!ASSERT_GE(prog_fd, 0, s))
+               return;
+
+       err = bpf_prog_test_run_opts(prog_fd, &topts);
+       if (!ASSERT_OK(err, "test_run_opts err")) {
+               close(prog_fd);
+               return;
+       }
+
+       if (!ASSERT_EQ(topts.retval, (off1 + off2 + off3) / 8, "test_run_opts retval")) {
+               close(prog_fd);
+               return;
+       }
+
+       close(prog_fd);
+}
+
+static void
+reject_offsets(struct bpf_insn *insns, __u32 insn_cnt, int off1, int off2, int off3)
 {
        int prog_fd;
 
-       prog_fd = __check_ldimm64_gotox_prog_load(insns, insn_cnt, off1, off2);
+       prog_fd = __check_ldimm64_gotox_prog_load(insns, insn_cnt, off1, off2, off3);
        if (!ASSERT_EQ(prog_fd, -EACCES, "__check_ldimm64_gotox_prog_load"))
                close(prog_fd);
 }
@@ -376,7 +406,7 @@ static void check_ldimm64_off_gotox(struct bpf_gotox *skel __always_unused)
                 * The program rewrites the offsets in the instructions below:
                 *     r1 = &map + offset1
                 *     r1 += offset2
-                *     r1 = *r1
+                *     r1 = *(r1 + offset3)
                 *     gotox r1
                 */
                BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_VALUE, 0),
@@ -403,43 +433,55 @@ static void check_ldimm64_off_gotox(struct bpf_gotox *skel __always_unused)
                BPF_MOV64_IMM(BPF_REG_0, 5),
                BPF_EXIT_INSN(),
        };
-       int prog_fd, err;
-       __u32 off1, off2;
-
-       /* allow all combinations off1 + off2 < 6 */
-       for (off1 = 0; off1 < 6; off1++) {
-               for (off2 = 0; off1 + off2 < 6; off2++) {
-                       LIBBPF_OPTS(bpf_test_run_opts, topts);
-
-                       prog_fd = __check_ldimm64_gotox_prog_load(insns, ARRAY_SIZE(insns),
-                                                                 off1 * 8, off2 * 8);
-                       if (!ASSERT_GE(prog_fd, 0, "__check_ldimm64_gotox_prog_load"))
-                               return;
-
-                       err = bpf_prog_test_run_opts(prog_fd, &topts);
-                       if (!ASSERT_OK(err, "test_run_opts err")) {
-                               close(prog_fd);
-                               return;
-                       }
-
-                       if (!ASSERT_EQ(topts.retval, off1 + off2, "test_run_opts retval")) {
-                               close(prog_fd);
-                               return;
-                       }
-
-                       close(prog_fd);
-               }
-       }
+       int off1, off2, off3;
+
+       /* allow all combinations off1 + off2 + off3 < 6 */
+       for (off1 = 0; off1 < 6; off1++)
+               for (off2 = 0; off1 + off2 < 6; off2++)
+                       for (off3 = 0; off1 + off2 + off3 < 6; off3++)
+                               allow_offsets(insns, ARRAY_SIZE(insns),
+                                             off1 * 8, off2 * 8, off3 * 8);
+
+       /* allow for some offsets to be negative */
+       allow_offsets(insns, ARRAY_SIZE(insns), 8 * 3, 0, -(8 * 3));
+       allow_offsets(insns, ARRAY_SIZE(insns), 8 * 3, -(8 * 3), 0);
+       allow_offsets(insns, ARRAY_SIZE(insns), 0, 8 * 3, -(8 * 3));
+       allow_offsets(insns, ARRAY_SIZE(insns), 8 * 4, 0, -(8 * 2));
+       allow_offsets(insns, ARRAY_SIZE(insns), 8 * 4, -(8 * 2), 0);
+       allow_offsets(insns, ARRAY_SIZE(insns), 0, 8 * 4, -(8 * 2));
+
+       /* disallow negative sums of offsets */
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 3, 0, -(8 * 4));
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 3, -(8 * 4), 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 0, 8 * 3, -(8 * 4));
+
+       /* disallow the off1 to be negative in any case */
+       reject_offsets(insns, ARRAY_SIZE(insns), -8 * 1, 0, 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), -8 * 1, 8 * 1, 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), -8 * 1, 8 * 1, 8 * 1);
+
+       /* reject off1 + off2 + off3 >= 6 */
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 3, 8 * 3, 8 * 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 7, 8 * 0, 8 * 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 0, 8 * 7, 8 * 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 3, 8 * 0, 8 * 3);
+       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 0, 8 * 3, 8 * 3);
+
+       /* reject (off1 + off2) % 8 != 0, off3 % 8 != 0 */
+       reject_offsets(insns, ARRAY_SIZE(insns), 3, 3, 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 7, 0, 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 0, 7, 0);
+       reject_offsets(insns, ARRAY_SIZE(insns), 0, 0, 7);
+}
 
-       /* reject off1 + off2 >= 6 */
-       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 3, 8 * 3);
-       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 7, 8 * 0);
-       reject_offsets(insns, ARRAY_SIZE(insns), 8 * 0, 8 * 7);
+static void check_ldimm64_off_gotox_llvm(struct bpf_gotox *skel)
+{
+       __u64 in[]  = {0, 1, 2, 3, 4};
+       __u64 out[] = {1, 1, 5, 1, 1};
+       int i;
 
-       /* reject (off1 + off2) % 8 != 0 */
-       reject_offsets(insns, ARRAY_SIZE(insns), 3, 3);
-       reject_offsets(insns, ARRAY_SIZE(insns), 7, 0);
-       reject_offsets(insns, ARRAY_SIZE(insns), 0, 7);
+       for (i = 0; i < ARRAY_SIZE(in); i++)
+               check_simple(skel, skel->progs.load_with_nonzero_offset, in[i], out[i]);
 }
 
 void test_bpf_gotox(void)
@@ -496,5 +538,8 @@ void test_bpf_gotox(void)
        if (test__start_subtest("check-ldimm64-off-gotox"))
                __subtest(skel, check_ldimm64_off_gotox);
 
+       if (test__start_subtest("check-ldimm64-off-gotox-llvm"))
+               __subtest(skel, check_ldimm64_off_gotox_llvm);
+
        bpf_gotox__destroy(skel);
 }
index 216c71b94c644d556d931d4748ae040ba0f557d6..99b3c9c9a01c6e4d5838e1cc10e69253b69428e6 100644 (file)
@@ -421,6 +421,36 @@ int use_nonstatic_global_other_sec(void *ctx)
        return __nonstatic_global(in_user);
 }
 
+SEC("syscall")
+int load_with_nonzero_offset(struct simple_ctx *ctx)
+{
+       void *jj[] = { &&l1, &&l2, &&l3 };
+
+       /*
+        * This makes LLVM to generate a load from the jj map with an offset:
+        *      r1 = 0x0 ll
+        *      r1 = *(u64 *)(r1 + 0x10)
+        *      gotox r1
+        */
+       if (ctx->x == 2)
+               goto *jj[ctx->x];
+
+       ret_user = 1;
+       return 1;
+
+l1:
+       /* never reached, but leave it here to outsmart LLVM */
+       ret_user = 0;
+       return 0;
+l2:
+       /* never reached, but leave it here to outsmart LLVM */
+       ret_user = 3;
+       return 3;
+l3:
+       ret_user = 5;
+       return 5;
+}
+
 #else /* __BPF_FEATURE_GOTOX */
 
 #define SKIP_TEST(TEST_NAME)                           \
@@ -442,6 +472,7 @@ SKIP_TEST(use_static_global_other_sec);
 SKIP_TEST(use_nonstatic_global1);
 SKIP_TEST(use_nonstatic_global2);
 SKIP_TEST(use_nonstatic_global_other_sec);
+SKIP_TEST(load_with_nonzero_offset);
 
 #endif /* __BPF_FEATURE_GOTOX */