From: Yazhou Tang Add a selftest to verify the verifier and JIT behavior when handling bpf-to-bpf calls with relative jump offsets exceeding the s16 boundary. The test utilizes an inline assembly block with ".rept 200000" to generate a massive dummy subprogram. By placing this padding between the main program and the target subprogram, it forces the verifier to process a bpf-to-bpf call with the imm field exceeding the s16 range. The user-space test driver dynamically adapts to the host's configuration: - When JIT is enabled, it asserts that the program is successfully loaded, JIT-compiled, and executes correctly to return the expected value. - When JIT is disabled, it asserts that the verifier cleanly rejects the program with -EINVAL (or -ENOSPC if the log buffer is truncated), matching the expected "offset out of range" error log. Note: To ensure the test is both effective at load time and efficient at runtime, two specific tricks are employed: 1. Prevent Compiler Optimization: A volatile variable (magic_ret) is used inside target_subprog(). This prevents the compiler from optimizing the function away or aggressively inlining it, ensuring the subprogram and the call instruction are strictly retained in the final BPF bytecode. 2. Prevent Execution Degradation: The massive padding subprogram is conditionally called using a global volatile variable ("int zero", whose value is actually 0). This forces the verifier to statically analyze the branch and calculate the giant jump offset, but allows the runtime to completely bypass the 200,000 dummy instructions, preventing performance degradation during test execution. Co-developed-by: Tianci Cao Signed-off-by: Tianci Cao Co-developed-by: Shenghao Yuan Signed-off-by: Shenghao Yuan Signed-off-by: Yazhou Tang --- .../selftests/bpf/prog_tests/call_large_imm.c | 53 +++++++++++++++++++ .../selftests/bpf/progs/call_large_imm.c | 37 +++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/call_large_imm.c create mode 100644 tools/testing/selftests/bpf/progs/call_large_imm.c diff --git a/tools/testing/selftests/bpf/prog_tests/call_large_imm.c b/tools/testing/selftests/bpf/prog_tests/call_large_imm.c new file mode 100644 index 000000000000..95d51b3cc6b6 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/call_large_imm.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "call_large_imm.skel.h" + +/* + * This test dynamically adapts to the current environment, and will succeed + * whether JIT is enabled or not. + * 1. If JIT is enabled: The test case will successfully load and return + * the expected value. + * 2. If JIT is disabled: It asserts that the verifier cleanly rejects the + * program with -EINVAL (or -ENOSPC) and outputs the expected error log. + */ + +void test_call_large_imm(void) +{ + struct call_large_imm *skel; + int err, prog_fd; + char log_buf[4096]; + + LIBBPF_OPTS(bpf_test_run_opts, opts); + + skel = call_large_imm__open(); + if (!ASSERT_OK_PTR(skel, "skel_open")) + return; + + if (!env.jit_enabled) + bpf_program__set_log_buf(skel->progs.call_large_imm_test, + log_buf, sizeof(log_buf)); + + err = call_large_imm__load(skel); + + if (env.jit_enabled) { + if (!ASSERT_OK(err, "load_should_succeed_with_jit")) + goto cleanup; + + prog_fd = bpf_program__fd(skel->progs.call_large_imm_test); + err = bpf_prog_test_run_opts(prog_fd, &opts); + + if (ASSERT_OK(err, "prog_run_success")) + ASSERT_EQ(opts.retval, 3, "prog_retval"); + + } else { + ASSERT_ERR(err, "load_should_fail_in_interpreter"); + ASSERT_TRUE(err == -EINVAL || err == -ENOSPC, "err_should_be_einval_or_enospc"); + + if (!ASSERT_OK_PTR(strstr(log_buf, "bpf-to-bpf call offset out of range for interpreter"), + "check_verifier_log_msg")) { + printf("Actual verifier log:\n%s\n", log_buf); + } + } + +cleanup: + call_large_imm__destroy(skel); diff --git a/tools/testing/selftests/bpf/progs/call_large_imm.c b/tools/testing/selftests/bpf/progs/call_large_imm.c new file mode 100644 index 000000000000..ba6cd40738b7 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/call_large_imm.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +/* + * A volatile global variable is used here, so that padding_subprog() + * will not be optimized, and it will not be really executed even if + * it is successfully loaded (when JIT is enabled). + */ +volatile int zero = 0; + +static __attribute__((noinline)) void padding_subprog(void) +{ + asm volatile (" \ + r0 = 0; \ + .rept 200000; \ + r0 += 0; \ + .endr; \ + " ::: "r0"); +} + +static __attribute__((noinline)) int target_subprog(void) +{ + /* A volatile variable is used here to prevent optimization. */ + volatile int magic_ret = 3; + return magic_ret; +} + +SEC("syscall") +int call_large_imm_test(void *ctx) +{ + if (zero) + padding_subprog(); + return target_subprog(); +} + +char LICENSE[] SEC("license") = "GPL"; -- 2.53.0