From: Yazhou Tang Add test_progs coverage for UHMUL and SHMUL with the following changes: 1. Add arithmetic execution tests in verifier_hmul.c and register the new test suite with the verifier test runner: - Gate the execution cases on x86, arm64, and RV64, matching the JIT support added in this series, and provide a dummy program for other targets. - Inject only UHMUL and SHMUL as raw .8byte values built with BPF_RAW_INSN(), while keeping the surrounding instructions in the existing assembly syntax. This avoids requiring assembler support for the new mnemonics. - Exercise register and immediate sources, signed and unsigned boundary values, negative immediate sign extension, and R0, R1, and R3 destinations. The R3 case covers the x86 JIT path where BPF R3 aliases the implicit RDX result register. Run these cases in both privileged and unprivileged modes. - BPF_PROG_TEST_RUN exposes only the low 32 bits of R0 as its return value, so a direct __retval() check cannot validate the upper 32 bits. Build a 64-bit mismatch mask as actual ^ expected, then fold it to 32 bits as (u32)mismatch | (u32)(mismatch >> 32) and return the folded value through R0. The return value is zero if and only if the complete 64-bit result matches the expected value. 2. Add arena pointer rejection tests in verifier_hmul_arena.c. Use an arena pointer as the destination of UHMUL and as the source of SHMUL, and verify that the verifier rejects both programs. Keep these cases in a separate BPF object with a dedicated userspace runner. Probe BPF_MAP_TYPE_ARENA support before loading the object, and skip this suite when arena maps are unavailable, while keeping the arithmetic tests in verifier_hmul.c runnable. Retain __BPF_FEATURE_ADDR_SPACE_CAST gating and a dummy skip program for toolchains without addr_space_cast support. 3. Extend the illegal instruction encoding tests in verifier_value_illegal_alu.c. Check reserved MUL offsets and UHMUL and SHMUL encodings in the BPF_ALU class, reusing the existing DEFINE_BAD_OFFSET_TEST() macro. Signed-off-by: Yazhou Tang Co-developed-by: Tianci Cao Signed-off-by: Tianci Cao Co-developed-by: Shenghao Yuan Signed-off-by: Shenghao Yuan --- tools/testing/selftests/bpf/prog_tests/verifier.c | 2 + .../selftests/bpf/prog_tests/verifier_hmul_arena.c | 19 ++ tools/testing/selftests/bpf/progs/verifier_hmul.c | 235 +++++++++++++++++++++ .../selftests/bpf/progs/verifier_hmul_arena.c | 70 ++++++ .../bpf/progs/verifier_value_illegal_alu.c | 7 +- 5 files changed, 332 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 04740ba37e91..795c3e56c23e 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -59,6 +59,7 @@ #include "verifier_helper_packet_access.skel.h" #include "verifier_helper_restricted.skel.h" #include "verifier_helper_value_access.skel.h" +#include "verifier_hmul.skel.h" #include "verifier_idoms.skel.h" #include "verifier_int_ptr.skel.h" #include "verifier_iterating_callbacks.skel.h" @@ -251,6 +252,7 @@ void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_ void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); } void test_verifier_helper_restricted(void) { RUN(verifier_helper_restricted); } void test_verifier_helper_value_access(void) { RUN(verifier_helper_value_access); } +void test_verifier_hmul(void) { RUN(verifier_hmul); } void test_verifier_idoms(void) { RUN(verifier_idoms); } void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); } void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks); } diff --git a/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c new file mode 100644 index 000000000000..f9cd7b7b9781 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include "verifier_hmul_arena.skel.h" + +void test_verifier_hmul_arena(void) +{ + int ret; + + /* Arena support can depend on CPU features, such as Zacas on RV64. */ + ret = libbpf_probe_bpf_map_type(BPF_MAP_TYPE_ARENA, NULL); + if (!ASSERT_GE(ret, 0, "probe_arena")) + return; + if (!ret) { + test__skip(); + return; + } + RUN_TESTS(verifier_hmul_arena); +} diff --git a/tools/testing/selftests/bpf/progs/verifier_hmul.c b/tools/testing/selftests/bpf/progs/verifier_hmul.c new file mode 100644 index 000000000000..acd92355c636 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_hmul.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include "bpf_misc.h" +#include "../../../include/linux/filter.h" + +#define BPF_UHMUL64_REG(DST, SRC) \ + BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_UHMUL, 0) + +#define BPF_UHMUL64_IMM(DST, IMM) \ + BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM) + +#define BPF_SHMUL64_REG(DST, SRC) \ + BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0) + +#define BPF_SHMUL64_IMM(DST, IMM) \ + BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_SHMUL, IMM) + +/* + * Inject UHMUL/SHMUL as raw instructions so these tests do not require + * assembler support for the new mnemonics. All surrounding instructions use + * existing BPF assembly syntax supported by the baseline selftests toolchain. + */ + +#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || \ + (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) + +/* + * BPF_PROG_TEST_RUN reports a 32-bit retval. Fold both halves of + * (actual ^ expected) into w0 so retval == 0 checks the full 64-bit result. + */ + +SEC("socket") +__description("UHMUL64, U64_MAX * U64_MAX, register source") +__success __success_unpriv __retval(0) +__naked void uhmul64_max_reg(void) +{ + asm volatile ( + "r0 = -1;" + "r1 = -1;" + ".8byte %[hmul];" + "r2 = -2;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_0, BPF_REG_1)) + : __clobber_all); +} + +SEC("socket") +__description("UHMUL64, (1ULL << 63) * 2, register source") +__success __success_unpriv __retval(0) +__naked void uhmul64_pow2_reg(void) +{ + asm volatile ( + "r0 = 2;" + "r1 = %[llong_min] ll;" + ".8byte %[hmul];" + "r0 = r1;" + "r2 = 1;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_const(llong_min, LLONG_MIN), + __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_1, BPF_REG_0)) + : __clobber_all); +} + +SEC("socket") +__description("UHMUL64, (1ULL << 63) * 2, R3 destination") +__success __success_unpriv __retval(0) +__naked void uhmul64_r3_dst(void) +{ + asm volatile ( + "r3 = %[llong_min] ll;" + "r0 = 2;" + ".8byte %[hmul];" + "r2 = 1;" + "r3 ^= r2;" + "r2 = r3;" + "r2 >>= 32;" + "w3 |= w2;" + "r0 = r3;" + "exit;" + : + : __imm_const(llong_min, LLONG_MIN), + __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_3, BPF_REG_0)) + : __clobber_all); +} + +SEC("socket") +__description("UHMUL64, (1ULL << 63) * 2, immediate source") +__success __success_unpriv __retval(0) +__naked void uhmul64_pow2_imm(void) +{ + asm volatile ( + "r0 = %[llong_min] ll;" + ".8byte %[hmul];" + "r2 = 1;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_const(llong_min, LLONG_MIN), + __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2)) + : __clobber_all); +} + +SEC("socket") +__description("UHMUL64, (1ULL << 63) * -2, immediate source") +__success __success_unpriv __retval(0) +__naked void uhmul64_neg_imm(void) +{ + asm volatile ( + "r0 = %[llong_min] ll;" + ".8byte %[hmul];" + "r2 = %[llong_max] ll;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_const(llong_min, LLONG_MIN), + __imm_const(llong_max, LLONG_MAX), + __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, -2)) + : __clobber_all); +} + +SEC("socket") +__description("SHMUL64, INT64_MAX * INT64_MAX, register source") +__success __success_unpriv __retval(0) +__naked void shmul64_max_reg(void) +{ + asm volatile ( + "r0 = %[llong_max] ll;" + "r1 = %[llong_max] ll;" + ".8byte %[hmul];" + "r2 = %[expected] ll;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_const(expected, 0x3fffffffffffffffULL), + __imm_const(llong_max, LLONG_MAX), + __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1)) + : __clobber_all); +} + +SEC("socket") +__description("SHMUL64, INT64_MIN * -2, register source") +__success __success_unpriv __retval(0) +__naked void shmul64_min_neg2_reg(void) +{ + asm volatile ( + "r0 = %[llong_min] ll;" + "r1 = -2;" + ".8byte %[hmul];" + "r2 = 1;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_const(llong_min, LLONG_MIN), + __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1)) + : __clobber_all); +} + +SEC("socket") +__description("SHMUL64, 1 * -2, immediate source") +__success __success_unpriv __retval(0) +__naked void shmul64_neg_imm(void) +{ + asm volatile ( + "r0 = 1;" + ".8byte %[hmul];" + "r2 = -1;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, -2)) + : __clobber_all); +} + +SEC("socket") +__description("SHMUL64, INT64_MIN * 2, immediate source") +__success __success_unpriv __retval(0) +__naked void shmul64_min_imm(void) +{ + asm volatile ( + "r0 = %[llong_min] ll;" + ".8byte %[hmul];" + "r2 = -1;" + "r0 ^= r2;" + "r2 = r0;" + "r2 >>= 32;" + "w0 |= w2;" + "exit;" + : + : __imm_const(llong_min, LLONG_MIN), + __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, 2)) + : __clobber_all); +} + +#else + +SEC("socket") +__description("UHMUL/SHMUL are not supported by this JIT, use a dummy test") +__skip("UHMUL/SHMUL are not supported by this jit") +__success +int dummy_test(void) +{ + return 0; +} + +#endif + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c new file mode 100644 index 000000000000..fc0ca88114d0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "bpf_misc.h" +#include "../../../include/linux/filter.h" + +#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) + +#define BPF_UHMUL64_IMM(DST, IMM) \ + BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM) + +#define BPF_SHMUL64_REG(DST, SRC) \ + BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0) + +struct { + __uint(type, BPF_MAP_TYPE_ARENA); + __uint(map_flags, BPF_F_MMAPABLE); + __uint(max_entries, 1); +} arena SEC(".maps"); + +SEC("syscall") +__description("UHMUL64 with arena pointer dst") +__failure __msg("UHMUL/SHMUL with arena pointers are not supported") +__naked void uhmul64_arena_ptr(void) +{ + asm volatile ( + "r1 = %[arena] ll;" + "r0 = 1;" + "r0 = addr_space_cast(r0, 0x0, 0x1);" + ".8byte %[hmul];" + "exit;" + : + : __imm_addr(arena), + __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2)) + : __clobber_all); +} + +SEC("syscall") +__description("SHMUL64 with arena pointer src") +__failure __msg("UHMUL/SHMUL with arena pointers are not supported") +__naked void shmul64_arena_ptr(void) +{ + asm volatile ( + "r1 = %[arena] ll;" + "r1 = 1;" + "r1 = addr_space_cast(r1, 0x0, 0x1);" + "r0 = 2;" + ".8byte %[hmul];" + "exit;" + : + : __imm_addr(arena), + __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1)) + : __clobber_all); +} + +#else + +SEC("socket") +__description("HMUL arena tests require compiler support for addr_space_cast") +__skip("compiler doesn't support addr_space_cast") +__success +int dummy_test(void) +{ + return 0; +} + +#endif + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c index 31663338866d..209d5be55155 100644 --- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c +++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c @@ -367,7 +367,8 @@ __naked void flow_keys_illegal_variable_offset_alu(void) /* * Offset fields of 0 and 1 are legal for BPF_{DIV,MOD} instructions. - * Offset fields of 0 are legal for the rest of ALU instructions. + * Offset fields of 0, 1 and 2 are legal for BPF_ALU64 | BPF_MUL. + * Only offset 0 is legal for BPF_ALU | BPF_MUL. * Test that error is reported for illegal offsets, assuming that tests * for legal offsets exist. */ @@ -377,5 +378,9 @@ DEFINE_BAD_OFFSET_TEST(bad_offset_addx, BPF_ALU64 | BPF_ADD | BPF_X, -1, 0) DEFINE_BAD_OFFSET_TEST(bad_offset_divx2, BPF_ALU64 | BPF_DIV | BPF_X, 2, 0) DEFINE_BAD_OFFSET_TEST(bad_offset_modk2, BPF_ALU64 | BPF_MOD | BPF_K, 2, 1) DEFINE_BAD_OFFSET_TEST(bad_offset_addx2, BPF_ALU64 | BPF_ADD | BPF_X, 1, 0) +DEFINE_BAD_OFFSET_TEST(bad_offset_mulx, BPF_ALU64 | BPF_MUL | BPF_X, -1, 0) +DEFINE_BAD_OFFSET_TEST(bad_offset_mulk, BPF_ALU64 | BPF_MUL | BPF_K, 3, 0) +DEFINE_BAD_OFFSET_TEST(bad_uhmul32_x, BPF_ALU | BPF_MUL | BPF_X, BPF_UHMUL, 0) +DEFINE_BAD_OFFSET_TEST(bad_shmul32_k, BPF_ALU | BPF_MUL | BPF_K, BPF_SHMUL, 0) char _license[] SEC("license") = "GPL"; -- 2.56.0