From: Leon Hwang <leon.hwang@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
John Fastabend <john.fastabend@gmail.com>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Martin KaFai Lau <martin.lau@linux.dev>,
Song Liu <song@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Andrew Morton <akpm@linux-foundation.org>,
Shuah Khan <shuah@kernel.org>,
Puranjay Mohan <puranjay@kernel.org>,
Anton Protopopov <a.s.protopopov@gmail.com>,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
Leon Hwang <leon.hwang@linux.dev>
Subject: [RFC PATCH bpf 6/6] lib/test_bpf: Add interpreter-fallback tests
Date: Fri, 26 Jun 2026 23:43:30 +0800 [thread overview]
Message-ID: <20260626154330.33619-7-leon.hwang@linux.dev> (raw)
In-Reply-To: <20260626154330.33619-1-leon.hwang@linux.dev>
Add following tests to verify interpreter-fallback issues:
* arena-related insns, such as user ADDR_SPACE_CAST insn, ST/STX/LDX
insns, are not recognized by the interpreter.
* the internal ADDR_PERCPU insn is not recognized by the interpreter.
* the internal PROBE_ATOMIC insn is not recognized by the interpreter,
too.
* the gotox insn used for indirect jumps is not recognized by the
interpreter.
With the fixes, all of them are rejected by returning -EOPNOTSUPP.
Assisted-by: Codex:gpt-5.5
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
include/linux/bpf.h | 1 +
include/linux/filter.h | 4 +
kernel/bpf/core.c | 15 +
lib/test_bpf.c | 800 ++++++++++++++++++++++-
tools/lib/bpf/skel_internal.h | 2 +
tools/testing/selftests/bpf/test_kmod.sh | 39 +-
6 files changed, 855 insertions(+), 6 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f6528445..7baab7dd3444 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2699,6 +2699,7 @@ extern const struct bpf_prog_ops bpf_offload_prog_ops;
extern const struct bpf_verifier_ops tc_cls_act_analyzer_ops;
extern const struct bpf_verifier_ops xdp_analyzer_ops;
+int kern_sys_bpf(int cmd, union bpf_attr *attr, unsigned int size);
struct bpf_prog *bpf_prog_get(u32 ufd);
struct bpf_prog *bpf_prog_get_type_dev(u32 ufd, enum bpf_prog_type type,
bool attach_drv);
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 67d337ede91b..82d349ee03ed 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1312,6 +1312,10 @@ extern int bpf_jit_kallsyms;
extern long bpf_jit_limit;
extern long bpf_jit_limit_max;
+#if IS_ENABLED(CONFIG_TEST_BPF)
+void bpf_jit_set_test_force_fail(bool force);
+#endif
+
typedef void (*bpf_jit_fill_hole_t)(void *area, unsigned int size);
void bpf_jit_fill_hole_with_zero(void *area, unsigned int size);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 427d4e54ede4..6b28c8641b26 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -550,6 +550,16 @@ int bpf_jit_harden __read_mostly;
long bpf_jit_limit __read_mostly;
long bpf_jit_limit_max __read_mostly;
+#if IS_ENABLED(CONFIG_TEST_BPF)
+static struct task_struct *bpf_jit_test_fail_task;
+
+void bpf_jit_set_test_force_fail(bool force)
+{
+ WRITE_ONCE(bpf_jit_test_fail_task, force ? current : NULL);
+}
+EXPORT_SYMBOL_GPL(bpf_jit_set_test_force_fail);
+#endif
+
static void
bpf_prog_ksym_set_addr(struct bpf_prog *prog)
{
@@ -2567,6 +2577,11 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc
struct bpf_prog *orig_prog;
struct bpf_insn_aux_data *orig_insn_aux;
+#if IS_ENABLED(CONFIG_TEST_BPF)
+ if (unlikely(READ_ONCE(bpf_jit_test_fail_task) == current))
+ return prog;
+#endif
+
if (!bpf_prog_need_blind(prog))
return bpf_int_jit_compile(env, prog);
diff --git a/lib/test_bpf.c b/lib/test_bpf.c
index af6f3340c034..8bf227e8ab75 100644
--- a/lib/test_bpf.c
+++ b/lib/test_bpf.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/filter.h>
#include <linux/bpf.h>
+#include <linux/fdtable.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/if_vlan.h>
@@ -15786,6 +15787,778 @@ static __init int test_tail_calls(struct bpf_array *progs)
static char test_suite[32];
module_param_string(test_suite, test_suite, sizeof(test_suite), 0);
+#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_BPF_JIT) && !defined(CONFIG_UML)
+#define CAN_TEST_INTERPRETER_FALLBACK
+
+struct test_bpf_insn_array {
+ struct bpf_map map;
+ atomic_t used;
+ long *ips;
+};
+
+enum interpreter_fallback_expect {
+ EXPECT_LOAD_REJECT,
+ EXPECT_LOAD_RUN,
+};
+
+struct interpreter_fallback_test {
+ const char *name;
+ bool (*supported)(void);
+ int (*load)(void);
+ int (*verify)(const struct bpf_prog *prog);
+ int (*check_result)(u32 retval);
+ void (*cleanup)(void);
+ enum interpreter_fallback_expect expect;
+ u32 retval;
+};
+
+static __init int prog_load(const struct bpf_insn *insns, u32 insn_cnt)
+{
+ const size_t attr_sz = offsetofend(union bpf_attr, license);
+ union bpf_attr attr = {
+ .prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
+ .insn_cnt = insn_cnt,
+ .insns = (long)insns,
+ .license = (long)"GPL",
+ };
+
+ return kern_sys_bpf(BPF_PROG_LOAD, &attr, attr_sz);
+}
+
+static __init int map_create(enum bpf_map_type map_type, u32 key_size,
+ u32 value_size, u32 max_entries, u32 map_flags,
+ u64 map_extra)
+{
+ union bpf_attr attr = {
+ .map_type = map_type,
+ .key_size = key_size,
+ .value_size = value_size,
+ .max_entries = max_entries,
+ .map_flags = map_flags,
+ .map_extra = map_extra,
+ };
+
+ return kern_sys_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
+}
+
+static __init int map_update(int map_fd, const void *key, const void *value)
+{
+ union bpf_attr attr = {
+ .map_fd = map_fd,
+ .key = (long)key,
+ .value = (long)value,
+ .flags = BPF_ANY,
+ };
+
+ return kern_sys_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
+}
+
+static __init int map_freeze(int map_fd)
+{
+ union bpf_attr attr = {
+ .map_fd = map_fd,
+ };
+
+ return kern_sys_bpf(BPF_MAP_FREEZE, &attr, sizeof(attr));
+}
+
+static __init int check_interpreter_fallback_env(void)
+{
+ const struct bpf_insn insns[] = {
+ BPF_MOV64_IMM(R0, 0),
+ BPF_EXIT_INSN(),
+ };
+ struct bpf_prog *prog;
+ int prog_fd, err = 0;
+
+ prog_fd = prog_load(insns, ARRAY_SIZE(insns));
+ if (prog_fd < 0)
+ return prog_fd;
+
+ prog = bpf_prog_get_type_dev(prog_fd, BPF_PROG_TYPE_SOCKET_FILTER,
+ false);
+ close_fd(prog_fd);
+ if (IS_ERR(prog))
+ return PTR_ERR(prog);
+
+ if (!prog->jit_requested || prog->jited)
+ err = -EAGAIN;
+
+ bpf_prog_put(prog);
+ return err;
+}
+
+static __init bool x86_interpreter_fallback_supported(void)
+{
+ return IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_SMP);
+}
+
+static __init bool jit_inline_helper_supported(void)
+{
+ return IS_ENABLED(CONFIG_X86_64) ||
+ IS_ENABLED(CONFIG_ARM64) ||
+ IS_ENABLED(CONFIG_ARCH_RV64I) ||
+ IS_ENABLED(CONFIG_S390) ||
+ IS_ENABLED(CONFIG_PPC);
+}
+
+static __init int load_addr_percpu_prog(void)
+{
+ const struct bpf_insn insns[] = {
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
+ BPF_FUNC_get_smp_processor_id),
+ BPF_EXIT_INSN(),
+ };
+
+ return prog_load(insns, ARRAY_SIZE(insns));
+}
+
+static __init int verify_addr_percpu_prog(const struct bpf_prog *prog)
+{
+ const struct bpf_insn *insn = prog->insnsi;
+
+ if (prog->len != 4)
+ goto unexpected;
+
+ if (insn[0].code != (BPF_ALU64 | BPF_MOV | BPF_K) ||
+ insn[0].dst_reg != BPF_REG_0 ||
+ insn[0].src_reg != BPF_REG_0 ||
+ insn[0].off != 0)
+ goto unexpected;
+
+ if (!insn_is_mov_percpu_addr(&insn[1]) ||
+ insn[1].dst_reg != BPF_REG_0 ||
+ insn[1].src_reg != BPF_REG_0 ||
+ insn[1].imm != 0)
+ goto unexpected;
+
+ if (insn[2].code != (BPF_LDX | BPF_MEM | BPF_W) ||
+ insn[2].dst_reg != BPF_REG_0 ||
+ insn[2].src_reg != BPF_REG_0 ||
+ insn[2].off != 0 ||
+ insn[2].imm != 0)
+ goto unexpected;
+
+ if (insn[3].code != (BPF_JMP | BPF_EXIT) ||
+ insn[3].dst_reg != BPF_REG_0 ||
+ insn[3].src_reg != BPF_REG_0 ||
+ insn[3].off != 0 ||
+ insn[3].imm != 0)
+ goto unexpected;
+
+ pr_info("ADDR_PERCPU: verified BPF_MOV64_PERCPU_REG in prog->insnsi\n");
+ return 0;
+
+unexpected:
+ pr_info("ADDR_PERCPU: unexpected prog->insnsi, len=%u\n", prog->len);
+ return -EINVAL;
+}
+
+static __init int load_jit_inline_helper_prog(void)
+{
+ const struct bpf_insn insns[] = {
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
+ BPF_FUNC_get_smp_processor_id),
+ BPF_MOV64_IMM(R0, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return prog_load(insns, ARRAY_SIZE(insns));
+}
+
+#define BPF_CAST_ADDR_SPACE(REG, IMM) \
+ BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_X, REG, REG, \
+ BPF_ADDR_SPACE_CAST, IMM)
+
+#define TEST_ARENA_USER_ADDR ((1ULL << 44) | PAGE_SIZE)
+
+static int addr_space_cast_result_map_fd __initdata = -1;
+
+static __init int load_arena_prog(struct bpf_insn *insns, u32 insn_cnt)
+{
+ int map_fd, prog_fd;
+
+ map_fd = map_create(BPF_MAP_TYPE_ARENA, 0, 0, 1, BPF_F_MMAPABLE,
+ TEST_ARENA_USER_ADDR);
+ if (map_fd < 0)
+ return map_fd;
+
+ insns[0].imm = map_fd;
+ prog_fd = prog_load(insns, insn_cnt);
+ close_fd(map_fd);
+ return prog_fd;
+}
+
+static __init void cleanup_addr_space_cast_prog(void)
+{
+ if (addr_space_cast_result_map_fd < 0)
+ return;
+
+ close_fd(addr_space_cast_result_map_fd);
+ addr_space_cast_result_map_fd = -1;
+}
+
+static __init int load_addr_space_cast_prog(void)
+{
+ enum {
+ ARENA_MAP_INSN = 0,
+ RESULT_MAP_INSN = 6,
+ };
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_CAST_ADDR_SPACE(R1, 1U << 16),
+ BPF_MOV64_REG(R0, R1),
+ BPF_ALU64_IMM(BPF_RSH, R0, 32),
+ BPF_LD_IMM64_RAW(R2, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_JMP_IMM(BPF_JNE, R0, TEST_ARENA_USER_ADDR >> 32, 3),
+ BPF_MOV64_IMM(R0, 0),
+ BPF_STX_MEM(BPF_W, R2, R0, 0),
+ BPF_EXIT_INSN(),
+ BPF_MOV64_IMM(R0, 1),
+ BPF_STX_MEM(BPF_W, R2, R0, 0),
+ BPF_EXIT_INSN(),
+ };
+ u32 key = 0, value = U32_MAX;
+ int arena_fd, prog_fd, result_fd;
+ int err;
+
+ cleanup_addr_space_cast_prog();
+
+ arena_fd = map_create(BPF_MAP_TYPE_ARENA, 0, 0, 1, BPF_F_MMAPABLE,
+ TEST_ARENA_USER_ADDR);
+ if (arena_fd < 0)
+ return arena_fd;
+
+ result_fd = map_create(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value),
+ 1, 0, 0);
+ if (result_fd < 0) {
+ prog_fd = result_fd;
+ goto out_close_arena;
+ }
+
+ addr_space_cast_result_map_fd = result_fd;
+
+ err = map_update(result_fd, &key, &value);
+ if (err) {
+ prog_fd = err;
+ goto out_cleanup_result;
+ }
+
+ insns[ARENA_MAP_INSN].imm = arena_fd;
+ insns[RESULT_MAP_INSN].imm = result_fd;
+ prog_fd = prog_load(insns, ARRAY_SIZE(insns));
+ if (prog_fd < 0)
+ goto out_cleanup_result;
+
+ close_fd(arena_fd);
+ return prog_fd;
+
+out_cleanup_result:
+ cleanup_addr_space_cast_prog();
+out_close_arena:
+ close_fd(arena_fd);
+ return prog_fd;
+}
+
+static __init int verify_addr_space_cast_prog(const struct bpf_prog *prog)
+{
+ const struct bpf_insn *insn = prog->insnsi;
+
+ if (prog->len != 15)
+ goto unexpected;
+
+ if (insn[0].code != (BPF_LD | BPF_IMM | BPF_DW) ||
+ insn[0].dst_reg != BPF_REG_1 ||
+ insn[0].src_reg != BPF_REG_0 ||
+ insn[0].off != 0 ||
+ insn[1].code != 0 ||
+ insn[1].dst_reg != BPF_REG_0 ||
+ insn[1].src_reg != BPF_REG_0 ||
+ insn[1].off != 0)
+ goto unexpected;
+
+ if (insn[2].code != (BPF_ALU | BPF_MOV | BPF_X) ||
+ insn[2].dst_reg != BPF_REG_1 ||
+ insn[2].src_reg != BPF_REG_1 ||
+ insn[2].off != 0 ||
+ insn[2].imm != 0)
+ goto unexpected;
+
+ if (insn[3].code != (BPF_ALU64 | BPF_MOV | BPF_X) ||
+ insn[3].dst_reg != BPF_REG_1 ||
+ insn[3].src_reg != BPF_REG_1 ||
+ insn[3].off != BPF_ADDR_SPACE_CAST ||
+ insn[3].imm != 1U << 16)
+ goto unexpected;
+
+ if (insn[4].code != (BPF_ALU64 | BPF_MOV | BPF_X) ||
+ insn[4].dst_reg != BPF_REG_0 ||
+ insn[4].src_reg != BPF_REG_1 ||
+ insn[4].off != 0 ||
+ insn[4].imm != 0)
+ goto unexpected;
+
+ if (insn[5].code != (BPF_ALU64 | BPF_RSH | BPF_K) ||
+ insn[5].dst_reg != BPF_REG_0 ||
+ insn[5].src_reg != BPF_REG_0 ||
+ insn[5].off != 0 ||
+ insn[5].imm != 32)
+ goto unexpected;
+
+ if (insn[6].code != (BPF_LD | BPF_IMM | BPF_DW) ||
+ insn[6].dst_reg != BPF_REG_2 ||
+ insn[6].src_reg != BPF_REG_0 ||
+ insn[6].off != 0 ||
+ insn[7].code != 0 ||
+ insn[7].dst_reg != BPF_REG_0 ||
+ insn[7].src_reg != BPF_REG_0 ||
+ insn[7].off != 0)
+ goto unexpected;
+
+ if (insn[8].code != (BPF_JMP | BPF_JNE | BPF_K) ||
+ insn[8].dst_reg != BPF_REG_0 ||
+ insn[8].src_reg != BPF_REG_0 ||
+ insn[8].off != 3 ||
+ insn[8].imm != TEST_ARENA_USER_ADDR >> 32)
+ goto unexpected;
+
+ if (insn[9].code != (BPF_ALU64 | BPF_MOV | BPF_K) ||
+ insn[9].dst_reg != BPF_REG_0 ||
+ insn[9].src_reg != BPF_REG_0 ||
+ insn[9].off != 0 ||
+ insn[9].imm != 0 ||
+ insn[10].code != (BPF_STX | BPF_MEM | BPF_W) ||
+ insn[10].dst_reg != BPF_REG_2 ||
+ insn[10].src_reg != BPF_REG_0 ||
+ insn[10].off != 0 ||
+ insn[10].imm != 0)
+ goto unexpected;
+
+ if (insn[11].code != (BPF_JMP | BPF_EXIT) ||
+ insn[11].dst_reg != BPF_REG_0 ||
+ insn[11].src_reg != BPF_REG_0 ||
+ insn[11].off != 0 ||
+ insn[11].imm != 0)
+ goto unexpected;
+
+ if (insn[12].code != (BPF_ALU64 | BPF_MOV | BPF_K) ||
+ insn[12].dst_reg != BPF_REG_0 ||
+ insn[12].src_reg != BPF_REG_0 ||
+ insn[12].off != 0 ||
+ insn[12].imm != 1 ||
+ insn[13].code != (BPF_STX | BPF_MEM | BPF_W) ||
+ insn[13].dst_reg != BPF_REG_2 ||
+ insn[13].src_reg != BPF_REG_0 ||
+ insn[13].off != 0 ||
+ insn[13].imm != 0)
+ goto unexpected;
+
+ if (insn[14].code != (BPF_JMP | BPF_EXIT) ||
+ insn[14].dst_reg != BPF_REG_0 ||
+ insn[14].src_reg != BPF_REG_0 ||
+ insn[14].off != 0 ||
+ insn[14].imm != 0)
+ goto unexpected;
+
+ pr_info("ADDR_SPACE_CAST: verified BPF_ADDR_SPACE_CAST in prog->insnsi\n");
+ return 0;
+
+unexpected:
+ pr_info("ADDR_SPACE_CAST: unexpected prog->insnsi, len=%u\n",
+ prog->len);
+ return -EINVAL;
+}
+
+static __init int check_addr_space_cast_result(u32 retval)
+{
+ struct bpf_map *map;
+ void *ptr;
+ u32 key = 0, value = U32_MAX;
+ int err = 0;
+
+ if (addr_space_cast_result_map_fd < 0) {
+ pr_info("ADDR_SPACE_CAST: result map is not available\n");
+ return -EINVAL;
+ }
+
+ map = bpf_map_get(addr_space_cast_result_map_fd);
+ if (IS_ERR(map)) {
+ err = PTR_ERR(map);
+ pr_info("ADDR_SPACE_CAST: result map get failed err=%d\n", err);
+ return err;
+ }
+
+ rcu_read_lock();
+ ptr = map->ops->map_lookup_elem(map, &key);
+ if (IS_ERR(ptr))
+ err = PTR_ERR(ptr);
+ else if (!ptr)
+ err = -ENOENT;
+ else
+ value = READ_ONCE(*(u32 *)ptr);
+ rcu_read_unlock();
+ bpf_map_put(map);
+
+ if (err) {
+ pr_info("ADDR_SPACE_CAST: result map lookup failed err=%d\n",
+ err);
+ return err;
+ }
+
+ pr_info("ADDR_SPACE_CAST: retval=%u map_value=%u\n", retval, value);
+ if (value != retval) {
+ pr_info("ADDR_SPACE_CAST: result map value mismatch\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static __init int load_arena_st_prog(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_ST_MEM(BPF_W, R1, 0, 0),
+ BPF_MOV64_IMM(R0, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return load_arena_prog(insns, ARRAY_SIZE(insns));
+}
+
+static __init int load_arena_ldx_prog(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_LDX_MEM(BPF_W, R0, R1, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return load_arena_prog(insns, ARRAY_SIZE(insns));
+}
+
+static __init int load_arena_ldxsx_prog(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_LDX_MEMSX(BPF_W, R0, R1, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return load_arena_prog(insns, ARRAY_SIZE(insns));
+}
+
+static __init int load_arena_stx_prog(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_MOV64_IMM(R2, 0),
+ BPF_STX_MEM(BPF_W, R1, R2, 0),
+ BPF_MOV64_IMM(R0, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return load_arena_prog(insns, ARRAY_SIZE(insns));
+}
+
+static __init int load_acquire_prog(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_ATOMIC_OP(BPF_W, BPF_LOAD_ACQ, R0, R1, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return load_arena_prog(insns, ARRAY_SIZE(insns));
+}
+
+static __init int store_release_prog(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_CAST_ADDR_SPACE(R1, 1),
+ BPF_MOV64_IMM(R2, 0),
+ BPF_ATOMIC_OP(BPF_W, BPF_STORE_REL, R1, R2, 0),
+ BPF_MOV64_IMM(R0, 0),
+ BPF_EXIT_INSN(),
+ };
+
+ return load_arena_prog(insns, ARRAY_SIZE(insns));
+}
+
+static __init int load_gotox_prog(void)
+{
+ struct bpf_insn_array_value value = {
+ .orig_off = 4,
+ };
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(R0, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_LDX_MEM(BPF_DW, R0, R0, 0),
+ BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, R0, 0, 0, 0),
+ BPF_MOV64_IMM(R0, 0),
+ BPF_EXIT_INSN(),
+ };
+ struct test_bpf_insn_array *array;
+ struct bpf_map *map;
+ u32 key = 0;
+ int map_fd, prog_fd;
+ int err;
+
+ map_fd = map_create(BPF_MAP_TYPE_INSN_ARRAY, sizeof(key),
+ sizeof(value), 1, 0, 0);
+ if (map_fd < 0)
+ return map_fd;
+
+ err = map_update(map_fd, &key, &value);
+ if (err)
+ goto out_close_map;
+
+ err = map_freeze(map_fd);
+ if (err)
+ goto out_close_map;
+
+ map = bpf_map_get(map_fd);
+ if (IS_ERR(map)) {
+ err = PTR_ERR(map);
+ goto out_close_map;
+ }
+
+ array = container_of(map, struct test_bpf_insn_array, map);
+ WRITE_ONCE(array->ips[0], 1);
+ bpf_map_put(map);
+
+ insns[0].imm = map_fd;
+ prog_fd = prog_load(insns, ARRAY_SIZE(insns));
+ close_fd(map_fd);
+ return prog_fd;
+
+out_close_map:
+ close_fd(map_fd);
+ return err;
+}
+
+static struct interpreter_fallback_test interpreter_fallback_tests[] __initdata = {
+ {
+ .name = "ADDR_PERCPU",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_addr_percpu_prog,
+ .verify = verify_addr_percpu_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "ADDR_SPACE_CAST",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_addr_space_cast_prog,
+ .verify = verify_addr_space_cast_prog,
+ .check_result = check_addr_space_cast_result,
+ .cleanup = cleanup_addr_space_cast_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "JIT_INLINE_HELPER",
+ .supported = jit_inline_helper_supported,
+ .load = load_jit_inline_helper_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ .retval = 0,
+ },
+ {
+ .name = "ARENA_ST",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_arena_st_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "ARENA_LDX",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_arena_ldx_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "ARENA_LDXSX",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_arena_ldxsx_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "ARENA_STX",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_arena_stx_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "LOAD_ACQ",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_acquire_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "STORE_REL",
+ .supported = x86_interpreter_fallback_supported,
+ .load = store_release_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+ {
+ .name = "GOTOX",
+ .supported = x86_interpreter_fallback_supported,
+ .load = load_gotox_prog,
+ .expect = EXPECT_LOAD_REJECT,
+ },
+};
+
+#define INTERPRETER_FALLBACK_TEST_CNT ARRAY_SIZE(interpreter_fallback_tests)
+
+static __init int run_prog(int prog_fd,
+ const struct interpreter_fallback_test *test,
+ u32 *retval)
+{
+ struct bpf_prog *prog;
+ int err = 0;
+
+ prog = bpf_prog_get_type_dev(prog_fd, BPF_PROG_TYPE_SOCKET_FILTER,
+ false);
+ close_fd(prog_fd);
+ if (IS_ERR(prog))
+ return PTR_ERR(prog);
+
+ if (test->verify) {
+ err = test->verify(prog);
+ if (err)
+ goto out_put_prog;
+ }
+
+ migrate_disable();
+ *retval = bpf_prog_run(prog, NULL);
+ migrate_enable();
+
+ if (test->check_result) {
+ err = test->check_result(*retval);
+ if (err)
+ goto out_put_prog;
+ }
+
+out_put_prog:
+ bpf_prog_put(prog);
+ return err;
+}
+#else
+#define INTERPRETER_FALLBACK_TEST_CNT 1
+#endif
+
+static __init int test_interpreter_fallback(void)
+{
+#ifdef CAN_TEST_INTERPRETER_FALLBACK
+ int err_cnt = 0, pass_cnt = 0, skip_cnt = 0;
+ int err, i;
+
+ if (IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON)) {
+ pr_info("interpreter fallback: SKIP (JIT always on)\n");
+ return 0;
+ }
+
+ bpf_jit_set_test_force_fail(true);
+ err = check_interpreter_fallback_env();
+ if (err == -EAGAIN) {
+ pr_info("interpreter fallback: SKIP (JIT fallback environment not set)\n");
+ err = 0;
+ goto out_disable_force_fail;
+ }
+ if (err) {
+ pr_info("interpreter fallback: FAIL to verify environment err=%d\n",
+ err);
+ goto out_disable_force_fail;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(interpreter_fallback_tests); i++) {
+ const struct interpreter_fallback_test *test;
+ u32 retval;
+ int prog_fd;
+
+ if (exclude_test(i))
+ continue;
+
+ test = &interpreter_fallback_tests[i];
+ if (test->supported && !test->supported()) {
+ pr_info("#%d %s: SKIP\n", i, test->name);
+ skip_cnt++;
+ goto next_test;
+ }
+
+ pr_info("#%d %s: loading\n", i, test->name);
+
+ prog_fd = test->load();
+
+ if (test->expect == EXPECT_LOAD_REJECT) {
+ if (prog_fd == -EOPNOTSUPP) {
+ pr_info("#%d %s: PASS\n", i, test->name);
+ pass_cnt++;
+ goto next_test;
+ }
+
+ if (prog_fd < 0)
+ pr_info("#%d %s: FAIL prog_load returned %d, expected %d\n",
+ i, test->name, prog_fd, -EOPNOTSUPP);
+ else {
+ err = run_prog(prog_fd, test, &retval);
+ if (err)
+ pr_info("#%d %s: FAIL to run loaded prog err=%d\n",
+ i, test->name, err);
+ else
+ pr_info("#%d %s: FAIL prog loaded and returned %u\n",
+ i, test->name, retval);
+ }
+ err_cnt++;
+ goto next_test;
+ }
+
+ if (prog_fd < 0) {
+ pr_info("#%d %s: FAIL prog_load returned %d\n",
+ i, test->name, prog_fd);
+ err_cnt++;
+ goto next_test;
+ }
+
+ err = run_prog(prog_fd, test, &retval);
+ if (err) {
+ pr_info("#%d %s: FAIL to run loaded prog err=%d\n",
+ i, test->name, err);
+ err_cnt++;
+ goto next_test;
+ }
+
+ if (retval != test->retval) {
+ pr_info("#%d %s: FAIL prog returned %u, expected %u\n",
+ i, test->name, retval, test->retval);
+ err_cnt++;
+ goto next_test;
+ }
+
+ pr_info("#%d %s: PASS\n", i, test->name);
+ pass_cnt++;
+
+next_test:
+ if (test->cleanup)
+ test->cleanup();
+ }
+
+ pr_info("interpreter fallback: Summary: %d PASSED, %d SKIPPED, %d FAILED\n",
+ pass_cnt, skip_cnt, err_cnt);
+ err = err_cnt ? -EINVAL : 0;
+
+out_disable_force_fail:
+ bpf_jit_set_test_force_fail(false);
+ return err;
+#else
+ pr_info("interpreter fallback: SKIP (unsupported configuration)\n");
+ return 0;
+#endif
+}
+
static __init int find_test_index(const char *test_name)
{
int i;
@@ -15811,6 +16584,15 @@ static __init int find_test_index(const char *test_name)
}
}
+ if (!strcmp(test_suite, "test_interpreter_fallback")) {
+#ifdef CAN_TEST_INTERPRETER_FALLBACK
+ for (i = 0; i < ARRAY_SIZE(interpreter_fallback_tests); i++) {
+ if (!strcmp(interpreter_fallback_tests[i].name, test_name))
+ return i;
+ }
+#endif
+ }
+
return -1;
}
@@ -15824,6 +16606,8 @@ static __init int prepare_test_range(void)
valid_range = ARRAY_SIZE(tail_call_tests);
else if (!strcmp(test_suite, "test_skb_segment"))
valid_range = ARRAY_SIZE(skb_segment_tests);
+ else if (!strcmp(test_suite, "test_interpreter_fallback"))
+ valid_range = INTERPRETER_FALLBACK_TEST_CNT;
else
return 0;
@@ -15881,7 +16665,8 @@ static int __init test_bpf_init(void)
if (strlen(test_suite) &&
strcmp(test_suite, "test_bpf") &&
strcmp(test_suite, "test_tail_calls") &&
- strcmp(test_suite, "test_skb_segment")) {
+ strcmp(test_suite, "test_skb_segment") &&
+ strcmp(test_suite, "test_interpreter_fallback")) {
pr_err("test_bpf: invalid test_suite '%s' specified.\n", test_suite);
return -EINVAL;
}
@@ -15917,8 +16702,14 @@ static int __init test_bpf_init(void)
return ret;
}
- if (!strlen(test_suite) || !strcmp(test_suite, "test_skb_segment"))
- return test_skb_segment();
+ if (!strlen(test_suite) || !strcmp(test_suite, "test_skb_segment")) {
+ ret = test_skb_segment();
+ if (ret)
+ return ret;
+ }
+
+ if (!strcmp(test_suite, "test_interpreter_fallback"))
+ return test_interpreter_fallback();
return 0;
}
@@ -15930,5 +16721,8 @@ static void __exit test_bpf_exit(void)
module_init(test_bpf_init);
module_exit(test_bpf_exit);
+#ifdef CAN_TEST_INTERPRETER_FALLBACK
+MODULE_IMPORT_NS("BPF_INTERNAL");
+#endif
MODULE_DESCRIPTION("Testsuite for BPF interpreter and BPF JIT compiler");
MODULE_LICENSE("GPL");
diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 74503d358bc8..ede991cb0cdc 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -76,7 +76,9 @@ struct bpf_load_and_run_opts {
__u32 excl_prog_hash_sz;
};
+#ifndef __KERNEL__
long kern_sys_bpf(__u32 cmd, void *attr, __u32 attr_size);
+#endif
static inline int skel_sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
unsigned int size)
diff --git a/tools/testing/selftests/bpf/test_kmod.sh b/tools/testing/selftests/bpf/test_kmod.sh
index 50dca53ac536..6dbad2a6ef0a 100755
--- a/tools/testing/selftests/bpf/test_kmod.sh
+++ b/tools/testing/selftests/bpf/test_kmod.sh
@@ -47,14 +47,14 @@ test_run()
rc=1
fi
fi
- rmmod test_bpf 2> /dev/null
+ rmmod test_bpf 2> /dev/null
dmesg | grep FAIL
}
test_save()
{
- JE=`sysctl -n net.core.bpf_jit_enable`
- JH=`sysctl -n net.core.bpf_jit_harden`
+ JE=$(sysctl -n net.core.bpf_jit_enable)
+ JH=$(sysctl -n net.core.bpf_jit_harden)
}
test_restore()
@@ -63,11 +63,44 @@ test_restore()
sysctl -w net.core.bpf_jit_harden=$JH 2>&1 > /dev/null
}
+test_interpreter_fallback()
+{
+ if ! sysctl -w net.core.bpf_jit_enable=1 >/dev/null 2>&1 ||
+ ! sysctl -w net.core.bpf_jit_harden=0 >/dev/null 2>&1; then
+ echo "[ interpreter fallback: SKIP ]"
+ return
+ fi
+
+ echo "[ interpreter fallback ]"
+ dmesg -C
+ if [ -f ${OUTPUT}/lib/test_bpf.ko ]; then
+ insmod ${OUTPUT}/lib/test_bpf.ko \
+ test_suite=test_interpreter_fallback 2>/dev/null
+ if [ $? -ne 0 ]; then
+ rc=1
+ fi
+ else
+ if ! /sbin/modprobe -q -n test_bpf \
+ test_suite=test_interpreter_fallback; then
+ echo "test_bpf (test_suite=test_interpreter_fallback): [SKIP]"
+ elif /sbin/modprobe -q test_bpf \
+ test_suite=test_interpreter_fallback; then
+ echo "test_bpf (test_suite=test_interpreter_fallback): ok"
+ else
+ echo "test_bpf (test_suite=test_interpreter_fallback): [FAIL]"
+ rc=1
+ fi
+ fi
+ rmmod test_bpf 2>/dev/null
+ dmesg | grep FAIL
+}
+
rc=0
test_save
test_run 0 0 "$@"
test_run 1 0 "$@"
test_run 1 1 "$@"
test_run 1 2 "$@"
+test_interpreter_fallback
test_restore
exit $rc
--
2.54.0
next prev parent reply other threads:[~2026-06-26 15:44 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-26 15:43 [RFC PATCH bpf 0/6] bpf: Disallow interpreter fallback for interpreter-unsupported insns Leon Hwang
2026-06-26 15:43 ` [RFC PATCH bpf 1/6] bpf: Disallow interpreter fallback for user BPF_ADDR_SPACE_CAST insn Leon Hwang
2026-06-26 15:43 ` [RFC PATCH bpf 2/6] bpf: Disallow interpreter fallback for arena insn Leon Hwang
2026-06-26 15:43 ` [RFC PATCH bpf 3/6] bpf: Disallow interpreter fallback for BPF_MOV64_PERCPU_REG insn Leon Hwang
2026-06-26 15:43 ` [RFC PATCH bpf 4/6] bpf: Disallow interpreter fallback for internal BPF_PROBE_ATOMIC insn Leon Hwang
2026-06-26 15:43 ` [RFC PATCH bpf 5/6] bpf: Disallow interpreter fallback for gotox insn Leon Hwang
2026-06-26 15:43 ` Leon Hwang [this message]
2026-06-26 16:11 ` [RFC PATCH bpf 0/6] bpf: Disallow interpreter fallback for interpreter-unsupported insns Leon Hwang
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=20260626154330.33619-7-leon.hwang@linux.dev \
--to=leon.hwang@linux.dev \
--cc=a.s.protopopov@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=puranjay@kernel.org \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=yonghong.song@linux.dev \
/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 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.