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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox