AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/21 23:00 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_JIT_KASAN",
    "KASAN",
    "KASAN_GENERIC",
    "KASAN_VMALLOC",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_patch_insn_data",
    "bpf_int_jit_compile",
    "bpf_prog_select_runtime"
  ],
  "KMSANReasoning": "The patch introduces KASAN (Kernel Address Sanitizer) instrumentation for the BPF JIT compiler on x86_64. It modifies the JIT compiler to emit calls to `__asan_load*` and `__asan_store*` for memory accesses in BPF programs, and updates the BPF verifier to track non-stack memory accesses. These changes are specifically designed to detect out-of-bounds accesses and use-after-free bugs in BPF programs, which are the domain of KASAN. The patch does not introduce new data structures sent to user space, nor does it modify allocations or initialization logic in a way that exposes uninitialized memory. Any bugs introduced by this patch (e.g., incorrect register saving/restoring in the JIT compiler, or incorrect tracking in the verifier) would result in state corruption, crashes, or KASAN false positives/negatives, which are effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces KASAN support for the BPF JIT compiler on x86_64. It modifies the BPF JIT compiler to insert KASAN checks before memory accesses (load/store/atomic). It also modifies the BPF verifier to track memory accesses that are not on the stack. This is a functional change to the core BPF JIT and verifier logic, which is reachable from userspace by loading BPF programs. Therefore, it is worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/21 23:00 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit cbc4c75c36320a09e8b963fdfe53b9ba1333946b\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Aug 21 23:00:35 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/x86/Kconfig b/arch/x86/Kconfig\nindex 48ccc3e6059d0..745890d91e99a 100644\n--- a/arch/x86/Kconfig\n+++ b/arch/x86/Kconfig\n@@ -234,6 +234,7 @@ config X86\n \tselect HAVE_SAMPLE_FTRACE_DIRECT\tif X86_64\n \tselect HAVE_SAMPLE_FTRACE_DIRECT_MULTI\tif X86_64\n \tselect HAVE_EBPF_JIT\n+\tselect HAVE_EBPF_JIT_KASAN\t\tif X86_64\n \tselect HAVE_EFFICIENT_UNALIGNED_ACCESS\n \tselect HAVE_EISA\t\t\tif X86_32\n \tselect HAVE_EXIT_THREAD\ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 1a9fb530adc3c..36e71aa2e877c 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -21,6 +21,17 @@\n #include \u003casm/unwind.h\u003e\n #include \u003casm/cfi.h\u003e\n \n+#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)\n+void __asan_load1(void *p);\n+void __asan_store1(void *p);\n+void __asan_load2(void *p);\n+void __asan_store2(void *p);\n+void __asan_load4(void *p);\n+void __asan_store4(void *p);\n+void __asan_load8(void *p);\n+void __asan_store8(void *p);\n+#endif\n+\n static bool all_callee_regs_used[4] = {true, true, true, true};\n \n static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len)\n@@ -1110,6 +1121,92 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)\n \t*pprog = prog;\n }\n \n+static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,\n+\t\t\t    u32 addr_reg, struct bpf_insn *insn, u8 *ip,\n+\t\t\t    bool is_write)\n+{\n+#ifdef CONFIG_BPF_JIT_KASAN\n+\tu32 bpf_size = BPF_SIZE(insn-\u003ecode);\n+\ts32 off = insn-\u003eoff;\n+\tu8 *prog = *pprog;\n+\tvoid *kasan_func;\n+\n+\tif (!env)\n+\t\treturn 0;\n+\n+\t/* Derive KASAN check function from access type and size */\n+\tswitch (bpf_size) {\n+\tcase BPF_B:\n+\t\tkasan_func = is_write ? __asan_store1 : __asan_load1;\n+\t\tbreak;\n+\tcase BPF_H:\n+\t\tkasan_func = is_write ? __asan_store2 : __asan_load2;\n+\t\tbreak;\n+\tcase BPF_W:\n+\t\tkasan_func = is_write ? __asan_store4 : __asan_load4;\n+\t\tbreak;\n+\tcase BPF_DW:\n+\t\tkasan_func = is_write ? __asan_store8 : __asan_load8;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Save rax */\n+\tEMIT1(0x50);\n+\t/* Save rcx */\n+\tEMIT1(0x51);\n+\t/* Save rdx */\n+\tEMIT1(0x52);\n+\t/* Save rsi */\n+\tEMIT1(0x56);\n+\t/* Save rdi */\n+\tEMIT1(0x57);\n+\t/* Save r8 */\n+\tEMIT2(0x41, 0x50);\n+\t/* Save r9 */\n+\tEMIT2(0x41, 0x51);\n+\t/*\n+\t * SystemV ABI states that we should also save r10/r11, but in\n+\t * practice those registers are _not_ used by the limited set of\n+\t * kasan helpers we are calling here, so that's fine not to save those.\n+\t */\n+\n+\t/* mov rdi, addr_reg */\n+\tEMIT_mov(BPF_REG_1, addr_reg);\n+\n+\t/* add rdi, off (if offset is non-zero) */\n+\tif (off) {\n+\t\tif (is_imm8(off)) {\n+\t\t\t/* add rdi, imm8 */\n+\t\t\tEMIT4(0x48, 0x83, 0xC7, (u8)off);\n+\t\t} else {\n+\t\t\t/* add rdi, imm32 */\n+\t\t\tEMIT3_off32(0x48, 0x81, 0xC7, off);\n+\t\t}\n+\t}\n+\n+\t/* Adjust ip to account for the instrumentation generated so far */\n+\tip += (prog - *pprog);\n+\t/* We emit a call, so update call depth counting */\n+\tip += x86_call_depth_emit_accounting(\u0026prog, kasan_func, ip);\n+\t/* call kasan_func */\n+\tif (emit_call(\u0026prog, kasan_func, ip))\n+\t\treturn -ERANGE;\n+\n+\tEMIT2(0x41, 0x59);\n+\tEMIT2(0x41, 0x58);\n+\tEMIT1(0x5F);\n+\tEMIT1(0x5E);\n+\tEMIT1(0x5A);\n+\tEMIT1(0x59);\n+\tEMIT1(0x58);\n+\n+\t*pprog = prog;\n+#endif /* CONFIG_BPF_JIT_KASAN */\n+\treturn 0;\n+}\n+\n /* LDX: dst_reg = *(u8*)(src_reg + off) */\n static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)\n {\n@@ -1315,6 +1412,63 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int\n \t*pprog = prog;\n }\n \n+/* ST: *(u8*)(dst_reg + off) = imm */\n+static void emit_st(u8 **pprog, struct bpf_insn *insn, u32 dst_reg,\n+\t\t    s32 outgoing_arg_base, u16 outgoing_rsp)\n+{\n+\ts32 imm32 = insn-\u003eimm;\n+\tu8 *prog = *pprog;\n+\ts32 insn_off;\n+\n+\tswitch (BPF_SIZE(insn-\u003ecode)) {\n+\tcase BPF_B:\n+\t\tif (is_ereg(dst_reg))\n+\t\t\tEMIT2(0x41, 0xC6);\n+\t\telse\n+\t\t\tEMIT1(0xC6);\n+\t\tbreak;\n+\tcase BPF_H:\n+\t\tif (is_ereg(dst_reg))\n+\t\t\tEMIT3(0x66, 0x41, 0xC7);\n+\t\telse\n+\t\t\tEMIT2(0x66, 0xC7);\n+\t\tbreak;\n+\tcase BPF_W:\n+\t\tif (is_ereg(dst_reg))\n+\t\t\tEMIT2(0x41, 0xC7);\n+\t\telse\n+\t\t\tEMIT1(0xC7);\n+\t\tbreak;\n+\tcase BPF_DW:\n+\t\tif (dst_reg == BPF_REG_PARAMS \u0026\u0026 insn-\u003eoff == -8) {\n+\t\t\t/* Arg 6: store immediate in r9 register */\n+\t\t\temit_mov_imm64(\u0026prog, X86_REG_R9, imm32 \u003e\u003e 31, imm32);\n+\t\t\t*pprog = prog;\n+\t\t\treturn;\n+\t\t}\n+\t\tEMIT2(add_1mod(0x48, dst_reg), 0xC7);\n+\t\tbreak;\n+\t}\n+\n+\tinsn_off = insn-\u003eoff;\n+\tif (dst_reg == BPF_REG_PARAMS) {\n+\t\t/*\n+\t\t * Args 7+: reverse BPF negative offsets to\n+\t\t * x86 positive rsp offsets.\n+\t\t * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...\n+\t\t */\n+\t\tinsn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;\n+\t\tdst_reg = BPF_REG_FP;\n+\t}\n+\tif (is_imm8(insn_off))\n+\t\tEMIT2(add_1reg(0x40, dst_reg), insn_off);\n+\telse\n+\t\tEMIT1_off32(add_1reg(0x80, dst_reg), insn_off);\n+\n+\tEMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn-\u003ecode)));\n+\t*pprog = prog;\n+}\n+\n static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm)\n {\n \temit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm);\n@@ -1423,17 +1577,35 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size,\n \treturn 0;\n }\n \n-static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg,\n-\t\t\t     u32 src_reg, s16 off, u8 bpf_size)\n+static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog,\n+\t\t\t     struct bpf_insn *insn, u8 *ip, u32 dst_reg,\n+\t\t\t     u32 src_reg, bool accesses_stack_only)\n {\n+\tu32 atomic_op = insn-\u003eimm;\n+\tint err;\n+\n \tswitch (atomic_op) {\n \tcase BPF_LOAD_ACQ:\n+\t\tif (!accesses_stack_only) {\n+\t\t\terr = emit_kasan_check(env, pprog, src_reg, insn, ip,\n+\t\t\t\t\t       false);\n+\t\t\tif (err)\n+\t\t\t\treturn err;\n+\t\t}\n \t\t/* dst_reg = smp_load_acquire(src_reg + off16) */\n-\t\temit_ldx(pprog, bpf_size, dst_reg, src_reg, off);\n+\t\temit_ldx(pprog, BPF_SIZE(insn-\u003ecode), dst_reg, src_reg,\n+\t\t\t insn-\u003eoff);\n \t\tbreak;\n \tcase BPF_STORE_REL:\n+\t\tif (!accesses_stack_only) {\n+\t\t\terr = emit_kasan_check(env, pprog, dst_reg, insn, ip,\n+\t\t\t\t\t       true);\n+\t\t\tif (err)\n+\t\t\t\treturn err;\n+\t\t}\n \t\t/* smp_store_release(dst_reg + off16, src_reg) */\n-\t\temit_stx(pprog, bpf_size, dst_reg, src_reg, off);\n+\t\temit_stx(pprog, BPF_SIZE(insn-\u003ecode), dst_reg, src_reg,\n+\t\t\t insn-\u003eoff);\n \t\tbreak;\n \tdefault:\n \t\tpr_err(\"bpf_jit: unknown atomic load/store opcode %02x\\n\",\n@@ -1859,10 +2031,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *\n \t\tconst s32 imm32 = insn-\u003eimm;\n \t\tu32 dst_reg = insn-\u003edst_reg;\n \t\tu32 src_reg = insn-\u003esrc_reg;\n+\t\tbool accesses_stack_only;\n \t\tu8 b2 = 0, b3 = 0;\n \t\tu8 *start_of_ldx;\n \t\ts64 jmp_offset;\n \t\ts32 insn_off;\n+\t\tint insn_idx;\n \t\tu8 jmp_cond;\n \t\tu8 *func;\n \t\tint nops;\n@@ -1879,6 +2053,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *\n \t\t\tEMIT_ENDBR();\n \n \t\tip = image + addrs[i - 1] + (prog - temp);\n+\t\tinsn_idx = i - 1 + bpf_prog-\u003eaux-\u003esubprog_start;\n+\t\taccesses_stack_only =\n+\t\t\tenv ? !env-\u003einsn_aux_data[insn_idx].non_stack_access :\n+\t\t\t      false;\n \n \t\tswitch (insn-\u003ecode) {\n \t\t\t/* ALU */\n@@ -2255,49 +2433,19 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *\n \t\t\tEMIT_LFENCE();\n \t\t\tbreak;\n \n-\t\t\t/* ST: *(u8*)(dst_reg + off) = imm */\n \t\tcase BPF_ST | BPF_MEM | BPF_B:\n-\t\t\tif (is_ereg(dst_reg))\n-\t\t\t\tEMIT2(0x41, 0xC6);\n-\t\t\telse\n-\t\t\t\tEMIT1(0xC6);\n-\t\t\tgoto st;\n \t\tcase BPF_ST | BPF_MEM | BPF_H:\n-\t\t\tif (is_ereg(dst_reg))\n-\t\t\t\tEMIT3(0x66, 0x41, 0xC7);\n-\t\t\telse\n-\t\t\t\tEMIT2(0x66, 0xC7);\n-\t\t\tgoto st;\n \t\tcase BPF_ST | BPF_MEM | BPF_W:\n-\t\t\tif (is_ereg(dst_reg))\n-\t\t\t\tEMIT2(0x41, 0xC7);\n-\t\t\telse\n-\t\t\t\tEMIT1(0xC7);\n-\t\t\tgoto st;\n \t\tcase BPF_ST | BPF_MEM | BPF_DW:\n-\t\t\tif (dst_reg == BPF_REG_PARAMS \u0026\u0026 insn-\u003eoff == -8) {\n-\t\t\t\t/* Arg 6: store immediate in r9 register */\n-\t\t\t\temit_mov_imm64(\u0026prog, X86_REG_R9, imm32 \u003e\u003e 31, (u32)imm32);\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tEMIT2(add_1mod(0x48, dst_reg), 0xC7);\n-\n-st:\t\t\tinsn_off = insn-\u003eoff;\n-\t\t\tif (dst_reg == BPF_REG_PARAMS) {\n-\t\t\t\t/*\n-\t\t\t\t * Args 7+: reverse BPF negative offsets to\n-\t\t\t\t * x86 positive rsp offsets.\n-\t\t\t\t * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...\n-\t\t\t\t */\n-\t\t\t\tinsn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;\n-\t\t\t\tdst_reg = BPF_REG_FP;\n+\t\t\tif (!accesses_stack_only) {\n+\t\t\t\terr = emit_kasan_check(env, \u0026prog, dst_reg,\n+\t\t\t\t\t\t       insn, ip, true);\n+\t\t\t\tif (err)\n+\t\t\t\t\treturn err;\n \t\t\t}\n-\t\t\tif (is_imm8(insn_off))\n-\t\t\t\tEMIT2(add_1reg(0x40, dst_reg), insn_off);\n-\t\t\telse\n-\t\t\t\tEMIT1_off32(add_1reg(0x80, dst_reg), insn_off);\n \n-\t\t\tEMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn-\u003ecode)));\n+\t\t\temit_st(\u0026prog, insn, dst_reg, outgoing_arg_base,\n+\t\t\t\toutgoing_rsp);\n \t\t\tbreak;\n \n \t\t\t/* STX: *(u8*)(dst_reg + off) = src_reg */\n@@ -2315,6 +2463,12 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t\tinsn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;\n \t\t\t\tdst_reg = BPF_REG_FP;\n \t\t\t}\n+\t\t\tif (!accesses_stack_only) {\n+\t\t\t\terr = emit_kasan_check(env, \u0026prog, dst_reg,\n+\t\t\t\t\t\t       insn, ip, true);\n+\t\t\t\tif (err)\n+\t\t\t\t\treturn err;\n+\t\t\t}\n \t\t\temit_stx(\u0026prog, BPF_SIZE(insn-\u003ecode), dst_reg, src_reg, insn_off);\n \t\t\tbreak;\n \n@@ -2496,6 +2650,11 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t\t/* populate jmp_offset for JAE above to jump to start_of_ldx */\n \t\t\t\tstart_of_ldx = prog;\n \t\t\t\tend_of_jmp[-1] = start_of_ldx - end_of_jmp;\n+\t\t\t} else if (!accesses_stack_only) {\n+\t\t\t\terr = emit_kasan_check(env, \u0026prog, src_reg,\n+\t\t\t\t\t\t       insn, ip, false);\n+\t\t\t\tif (err)\n+\t\t\t\t\treturn err;\n \t\t\t}\n \t\t\tif (BPF_MODE(insn-\u003ecode) == BPF_PROBE_MEMSX ||\n \t\t\t    BPF_MODE(insn-\u003ecode) == BPF_MEMSX)\n@@ -2557,28 +2716,42 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t}\n \t\t\tfallthrough;\n \t\tcase BPF_STX | BPF_ATOMIC | BPF_W:\n-\t\tcase BPF_STX | BPF_ATOMIC | BPF_DW:\n-\t\t\tif (insn-\u003eimm == (BPF_AND | BPF_FETCH) ||\n-\t\t\t    insn-\u003eimm == (BPF_OR | BPF_FETCH) ||\n-\t\t\t    insn-\u003eimm == (BPF_XOR | BPF_FETCH)) {\n-\t\t\t\tbool is64 = BPF_SIZE(insn-\u003ecode) == BPF_DW;\n-\t\t\t\tu32 real_src_reg = src_reg;\n-\t\t\t\tu32 real_dst_reg = dst_reg;\n-\t\t\t\tu8 *branch_target;\n-\n+\t\tcase BPF_STX | BPF_ATOMIC | BPF_DW: {\n+\t\t\tbool is64 = BPF_SIZE(insn-\u003ecode) == BPF_DW;\n+\t\t\tu32 real_src_reg = src_reg;\n+\t\t\tu32 real_dst_reg = dst_reg;\n+\t\t\tu8 *branch_target;\n+\t\t\tu8 *pprog;\n+\t\t\tbool is_atomic_fetch =\n+\t\t\t\t(insn-\u003eimm == (BPF_AND | BPF_FETCH) ||\n+\t\t\t\t insn-\u003eimm == (BPF_OR | BPF_FETCH) ||\n+\t\t\t\t insn-\u003eimm == (BPF_XOR | BPF_FETCH));\n+\t\t\tif (is_atomic_fetch) {\n \t\t\t\t/*\n \t\t\t\t * Can't be implemented with a single x86 insn.\n \t\t\t\t * Need to do a CMPXCHG loop.\n \t\t\t\t */\n \n \t\t\t\t/* Will need RAX as a CMPXCHG operand so save R0 */\n+\t\t\t\tpprog = prog;\n \t\t\t\temit_mov_reg(\u0026prog, true, BPF_REG_AX, BPF_REG_0);\n \t\t\t\tif (src_reg == BPF_REG_0)\n \t\t\t\t\treal_src_reg = BPF_REG_AX;\n \t\t\t\tif (dst_reg == BPF_REG_0)\n \t\t\t\t\treal_dst_reg = BPF_REG_AX;\n-\n+\t\t\t\tip += (prog - pprog);\n+\t\t\t}\n+\t\t\tif (!bpf_atomic_is_load_store(insn)) {\n+\t\t\t\tif (!accesses_stack_only) {\n+\t\t\t\t\terr = emit_kasan_check(env, \u0026prog,\n+\t\t\t\t\t\t\t       real_dst_reg,\n+\t\t\t\t\t\t\t       insn, ip, true);\n+\t\t\t\t\tif (err)\n+\t\t\t\t\t\treturn err;\n+\t\t\t\t}\n \t\t\t\tbranch_target = prog;\n+\t\t\t}\n+\t\t\tif (is_atomic_fetch) {\n \t\t\t\t/* Load old value */\n \t\t\t\temit_ldx(\u0026prog, BPF_SIZE(insn-\u003ecode),\n \t\t\t\t\t BPF_REG_0, real_dst_reg, insn-\u003eoff);\n@@ -2610,15 +2783,16 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t}\n \n \t\t\tif (bpf_atomic_is_load_store(insn))\n-\t\t\t\terr = emit_atomic_ld_st(\u0026prog, insn-\u003eimm, dst_reg, src_reg,\n-\t\t\t\t\t\t\tinsn-\u003eoff, BPF_SIZE(insn-\u003ecode));\n+\t\t\t\terr = emit_atomic_ld_st(env, \u0026prog, insn, ip,\n+\t\t\t\t\t\t\tdst_reg, src_reg,\n+\t\t\t\t\t\t\taccesses_stack_only);\n \t\t\telse\n \t\t\t\terr = emit_atomic_rmw(\u0026prog, insn-\u003eimm, dst_reg, src_reg,\n \t\t\t\t\t\t      insn-\u003eoff, BPF_SIZE(insn-\u003ecode));\n \t\t\tif (err)\n \t\t\t\treturn err;\n \t\t\tbreak;\n-\n+\t\t}\n \t\tcase BPF_STX | BPF_PROBE_ATOMIC | BPF_B:\n \t\tcase BPF_STX | BPF_PROBE_ATOMIC | BPF_H:\n \t\t\tif (!bpf_atomic_is_load_store(insn)) {\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 5fad59fdab0d0..09cd0355b921d 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -725,6 +725,8 @@ struct bpf_insn_aux_data {\n \tu16 const_reg_map_mask;\n \tu16 const_reg_subprog_mask;\n \tu32 const_reg_vals[10];\n+\t/* instruction can access non-stack memory */\n+\tbool non_stack_access;\n };\n \n #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */\ndiff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig\nindex eb3de35734f09..d7d25477ef481 100644\n--- a/kernel/bpf/Kconfig\n+++ b/kernel/bpf/Kconfig\n@@ -17,6 +17,10 @@ config HAVE_CBPF_JIT\n config HAVE_EBPF_JIT\n \tbool\n \n+# KASAN support for JIT compiler\n+config HAVE_EBPF_JIT_KASAN\n+\tbool\n+\n # Used by archs to tell that they want the BPF JIT compiler enabled by\n # default for kernels that were compiled with BPF JIT support.\n config ARCH_WANT_DEFAULT_BPF_JIT\n@@ -101,4 +105,17 @@ config BPF_LSM\n \n \t  If you are unsure how to answer this question, answer N.\n \n+config BPF_JIT_KASAN\n+\tbool\n+\tdepends on HAVE_EBPF_JIT_KASAN\n+\tdepends on KASAN_GENERIC\n+\tdepends on KASAN_VMALLOC\n+\tdepends on BPF_JIT\n+\tdefault y\n+\thelp\n+\t  Makes JIT compiler insert generic outline KASAN checks in BPF\n+\t  programs when they are inserted in the kernel. This feature is\n+\t  automatically enabled if the needed set of KASAN and BPF\n+\t  configuration options is enabled.\n+\n endmenu # \"BPF subsystem\"\ndiff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c\nindex 65b441e4a3517..edcb0cbbb13de 100644\n--- a/kernel/bpf/fixups.c\n+++ b/kernel/bpf/fixups.c\n@@ -208,12 +208,25 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,\n }\n #endif\n \n+static bool is_mem_insn(struct bpf_insn *insn)\n+{\n+\tif (BPF_CLASS(insn-\u003ecode) != BPF_ST \u0026\u0026\n+\t    BPF_CLASS(insn-\u003ecode) != BPF_STX \u0026\u0026\n+\t    BPF_CLASS(insn-\u003ecode) != BPF_LDX)\n+\t\treturn false;\n+\n+\treturn (BPF_MODE(insn-\u003ecode) == BPF_MEM ||\n+\t\tBPF_MODE(insn-\u003ecode) == BPF_MEMSX ||\n+\t\tBPF_MODE(insn-\u003ecode) == BPF_ATOMIC);\n+}\n+\n /* single env-\u003eprog-\u003einsni[off] instruction was replaced with the range\n  * insni[off, off + cnt).  Adjust corresponding insn_aux_data by copying\n  * [0, off) and [off, end) to new locations, so the patched range stays zero\n  */\n static void adjust_insn_aux_data(struct bpf_verifier_env *env,\n-\t\t\t\t struct bpf_prog *new_prog, u32 off, u32 cnt)\n+\t\t\t\t struct bpf_prog *new_prog, u32 off, u32 cnt,\n+\t\t\t\t struct bpf_insn *original_insn)\n {\n \tstruct bpf_insn_aux_data *data = env-\u003einsn_aux_data;\n \tstruct bpf_insn *insn = new_prog-\u003einsnsi;\n@@ -227,8 +240,15 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,\n \t */\n \tdata[off].zext_dst = bpf_insn_def32(new_prog, insn + off + cnt - 1) \u003e= 0;\n \n-\tif (cnt == 1)\n+\tif (cnt == 1) {\n+\t\t/*\n+\t\t * A non-memory accessing insn could have been replaced by a\n+\t\t * memory accessing insn, systematically mark it for non-stack\n+\t\t * access\n+\t\t */\n+\t\tdata[off].non_stack_access = is_mem_insn(insn + off);\n \t\treturn;\n+\t}\n \tprog_len = new_prog-\u003elen;\n \tenv-\u003einsn_aux_data_len = prog_len;\n \n@@ -239,8 +259,25 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,\n \t\t/* Expand insni[off]'s seen count to the patched range. */\n \t\tdata[i].seen = old_seen;\n \t\tdata[i].zext_dst = bpf_insn_def32(new_prog, insn + i) \u003e= 0;\n+\t\tif (!memcmp(insn + i, original_insn, sizeof(struct bpf_insn))) {\n+\t\t\tdata[i].non_stack_access =\n+\t\t\t\tdata[off + cnt - 1].non_stack_access;\n+\t\t\tdata[off + cnt - 1].non_stack_access = false;\n+\t\t} else if (is_mem_insn(insn + i)) {\n+\t\t\tdata[i].non_stack_access = true;\n+\t\t}\n \t}\n \n+\t/*\n+\t * Last slot instruction could be a newly generated\n+\t * BPF_ST/BPF_LDX/BPF_STX, systematically mark it for non-stack access\n+\t * if it is not the original instruction, otherwise keep the\n+\t * original marking\n+\t */\n+\tif (is_mem_insn(insn + off + cnt - 1) \u0026\u0026\n+\t    memcmp(insn + off + cnt - 1, original_insn, sizeof(struct bpf_insn)))\n+\t\tdata[off + cnt - 1].non_stack_access = true;\n+\n \t/*\n \t * The indirect_target flag of the original instruction was moved to the last of the\n \t * new instructions by the above memmove and memset, but the indirect jump target is\n@@ -306,6 +343,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,\n {\n \tstruct bpf_prog *new_prog;\n \tstruct bpf_insn_aux_data *new_data = NULL;\n+\tstruct bpf_insn original_insn;\n \n \tif (len \u003e 1) {\n \t\tnew_data = vrealloc(env-\u003einsn_aux_data,\n@@ -318,6 +356,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,\n \t\tenv-\u003einsn_aux_data = new_data;\n \t}\n \n+\tmemcpy(\u0026original_insn, env-\u003eprog-\u003einsnsi + off, sizeof(struct bpf_insn));\n \tnew_prog = bpf_patch_insn_single(env-\u003eprog, off, patch, len);\n \tif (IS_ERR(new_prog)) {\n \t\tif (PTR_ERR(new_prog) == -ERANGE)\n@@ -326,7 +365,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,\n \t\t\t\tenv-\u003einsn_aux_data[off].orig_idx);\n \t\treturn NULL;\n \t}\n-\tadjust_insn_aux_data(env, new_prog, off, len);\n+\tadjust_insn_aux_data(env, new_prog, off, len, \u0026original_insn);\n \tadjust_subprog_starts(env, off, len);\n \tadjust_insn_arrays(env, off, len);\n \tadjust_poke_descs(new_prog, off, len);\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 821b47ac75c5f..3d1a8e14408ac 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -3218,6 +3218,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx)\n \tenv-\u003einsn_aux_data[idx].indirect_target = true;\n }\n \n+static void mark_non_stack_access(struct bpf_verifier_env *env, int idx)\n+{\n+\tenv-\u003einsn_aux_data[idx].non_stack_access = true;\n+}\n+\n #define LR_FRAMENO_BITS\t4\n #define LR_SPI_BITS\t6\n #define LR_ENTRY_BITS\t(LR_SPI_BITS + LR_FRAMENO_BITS + 1)\n@@ -6600,6 +6605,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b\n \t\t\t\tclear_scalar_id(\u0026regs[value_regno]);\n \t\t}\n \t}\n+\n+\tif (!err \u0026\u0026 reg-\u003etype != PTR_TO_STACK)\n+\t\tmark_non_stack_access(env, insn_idx);\n+\n \treturn err;\n }\n \ndiff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c\nindex 0222a9a5d0761..815f3e04540ff 100644\n--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c\n+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c\n@@ -227,42 +227,6 @@ static void check_incorrect_index(void)\n \tcheck_mid_insn_index();\n }\n \n-static int set_bpf_jit_harden(char *level)\n-{\n-\tchar old_level;\n-\tint err = -1;\n-\tint fd = -1;\n-\n-\tfd = open(\"/proc/sys/net/core/bpf_jit_harden\", O_RDWR | O_NONBLOCK);\n-\tif (fd \u003c 0) {\n-\t\tASSERT_FAIL(\"open .../bpf_jit_harden returned %d (errno=%d)\", fd, errno);\n-\t\treturn -1;\n-\t}\n-\n-\terr = read(fd, \u0026old_level, 1);\n-\tif (err != 1) {\n-\t\tASSERT_FAIL(\"read from .../bpf_jit_harden returned %d (errno=%d)\", err, errno);\n-\t\terr = -1;\n-\t\tgoto end;\n-\t}\n-\n-\tlseek(fd, 0, SEEK_SET);\n-\n-\terr = write(fd, level, 1);\n-\tif (err != 1) {\n-\t\tASSERT_FAIL(\"write to .../bpf_jit_harden returned %d (errno=%d)\", err, errno);\n-\t\terr = -1;\n-\t\tgoto end;\n-\t}\n-\n-\terr = 0;\n-\t*level = old_level;\n-end:\n-\tif (fd \u003e= 0)\n-\t\tclose(fd);\n-\treturn err;\n-}\n-\n static void check_blindness(void)\n {\n \tstruct bpf_insn insns[] = {\n@@ -272,7 +236,7 @@ static void check_blindness(void)\n \t\tBPF_MOV64_IMM(BPF_REG_0, 1),\n \t\tBPF_EXIT_INSN(),\n \t};\n-\tint prog_fd = -1, map_fd;\n+\tint prog_fd = -1, map_fd, ret;\n \tstruct bpf_insn_array_value val = {};\n \tchar bpf_jit_harden = '@'; /* non-exizsting value */\n \tint i;\n@@ -291,7 +255,8 @@ static void check_blindness(void)\n \t\tgoto cleanup;\n \n \tbpf_jit_harden = '2';\n-\tif (set_bpf_jit_harden(\u0026bpf_jit_harden)) {\n+\tret = set_bpf_jit_harden(\u0026bpf_jit_harden);\n+\tif (!ASSERT_OK(ret, \"set bpf_jit_harden\")) {\n \t\tbpf_jit_harden = '@'; /* open, read or write failed =\u003e no write was done */\n \t\tgoto cleanup;\n \t}\n@@ -313,7 +278,8 @@ static void check_blindness(void)\n cleanup:\n \t/* restore the old one */\n \tif (bpf_jit_harden != '@')\n-\t\tset_bpf_jit_harden(\u0026bpf_jit_harden);\n+\t\tASSERT_OK(set_bpf_jit_harden(\u0026bpf_jit_harden),\n+\t\t\t  \"restore hardening configuration\");\n \n \tclose(prog_fd);\n \tclose(map_fd);\ndiff --git a/tools/testing/selftests/bpf/prog_tests/kasan.c b/tools/testing/selftests/bpf/prog_tests/kasan.c\nnew file mode 100644\nindex 0000000000000..2b424767a0f3f\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c\n@@ -0,0 +1,454 @@\n+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause\n+\n+/*\n+ * Tests validating that KASAN reports are properly instrumented and\n+ * generated on a wide variety of instructions. The running kernel needs\n+ * kasan_multi_shot to run multiple kasan-generating subtests at once\n+ */\n+#include \u003cbpf/bpf.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003clinux/if_ether.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003ctest_progs.h\u003e\n+#include \u003cunpriv_helpers.h\u003e\n+#include \"kasan.skel.h\"\n+#include \"kasan_harden.skel.h\"\n+\n+#define SUBTEST_NAME_MAX_LEN\t128\n+#define PROG_NAME_MAX_LEN\t128\n+\n+#define MAX_LOG_SIZE\t\t(8 * 1024)\n+#define READ_CHUNK_SIZE\t\t256\n+\n+#define KASAN_PATTERN_SLAB_UAF \"BUG: KASAN: slab-use-after-free \" \\\n+\t\"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s\"\n+#define KASAN_PATTERN_SLAB_OOB \"BUG: KASAN: slab-out-of-bounds \" \\\n+\t\"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s\"\n+#define KASAN_PATTERN_REPORT \"%s of size %d at addr\"\n+\n+static char klog_buffer[MAX_LOG_SIZE];\n+static char record[MAX_LOG_SIZE];\n+\n+struct test_spec {\n+\tchar *prog_type;\n+\tbool is_write;\n+\tbool only_32_or_64;\n+\tbool needs_load_acq_store_rel;\n+\tbool skip_multi_size_testing;\n+\tbool skip_on_stack_testing;\n+\tint run_size;\n+\tbool expect_no_report;\n+\tbool rnd_hi32;\n+\tbool is_oob;\n+};\n+\n+struct kasan_write_val {\n+\t__u8 data_1;\n+\t__u16 data_2;\n+\t__u32 data_4;\n+\t__u64 data_8;\n+};\n+\n+struct test_ctx {\n+\t__u8  prog_tag[BPF_TAG_SIZE];\n+\tstruct bpf_object *obj;\n+\tint *access_size;\n+\tbool skip_load_acq_store_rel;\n+\tstruct bpf_program *prog;\n+\tchar prog_name[SUBTEST_NAME_MAX_LEN];\n+\tint klog_fd;\n+};\n+\n+static int open_kernel_logs(void)\n+{\n+\tint fd;\n+\n+\tfd = open(\"/dev/kmsg\", O_RDONLY | O_NONBLOCK);\n+\n+\treturn fd;\n+}\n+\n+static void skip_kernel_logs(int fd)\n+{\n+\tlseek(fd, 0, SEEK_END);\n+}\n+\n+static int read_kernel_logs(int fd, char *buf, size_t max_len)\n+{\n+\tsize_t total = 0;\n+\tssize_t n;\n+\n+\tbuf[0] = '\\0';\n+\twhile (1) {\n+\t\tchar *msg, *eol;\n+\t\tsize_t len;\n+\n+\t\tn = read(fd, record, sizeof(record) - 1);\n+\t\tif (n == 0)\n+\t\t\tbreak;\n+\n+\t\tif (n \u003c 0) {\n+\t\t\tif (errno == EAGAIN)\n+\t\t\t\tbreak;\n+\t\t\treturn n;\n+\t\t}\n+\t\trecord[n] = '\\0';\n+\n+\t\t/*\n+\t\t * Each kmsg record starts with some metadata, separated\n+\t\t * from the actual content by a semi-colon\n+\t\t */\n+\t\tmsg = strchr(record, ';');\n+\t\tif (!msg)\n+\t\t\tcontinue;\n+\t\tmsg++;\n+\t\teol = strchr(msg, '\\n');\n+\t\tif (eol)\n+\t\t\t*eol = '\\0';\n+\n+\t\tlen = strlen(msg);\n+\t\tif (total + len + 2 \u003e max_len)\n+\t\t\tbreak;\n+\t\tmemcpy(buf + total, msg, len);\n+\t\ttotal += len;\n+\t\tbuf[total++] = '\\n';\n+\t\tbuf[total] = '\\0';\n+\t}\n+\n+\treturn total;\n+}\n+\n+static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,\n+\t\t\t\t\t     bool is_write, int size,\n+\t\t\t\t\t     bool is_oob)\n+{\n+\tchar access_log[READ_CHUNK_SIZE];\n+\tconst char *pattern;\n+\tchar *kasan_report_start;\n+\tint nsize;\n+\n+\tpattern = is_oob ? KASAN_PATTERN_SLAB_OOB : KASAN_PATTERN_SLAB_UAF;\n+\tnsize = snprintf(access_log, READ_CHUNK_SIZE, pattern,\n+\t\t\t ctx-\u003eprog_tag[0], ctx-\u003eprog_tag[1], ctx-\u003eprog_tag[2],\n+\t\t\t ctx-\u003eprog_tag[3], ctx-\u003eprog_tag[4], ctx-\u003eprog_tag[5],\n+\t\t\t ctx-\u003eprog_tag[6], ctx-\u003eprog_tag[7], ctx-\u003eprog_name);\n+\tif (!ASSERT_GE(nsize, 0, \"format kasan access header line\"))\n+\t\treturn nsize;\n+\t/*\n+\t * Searched kasan report is valid if\n+\t * - it contains the expected kasan pattern\n+\t * - the description of the faulty access is found somewhere\n+\t *   after the header (not necessarily on the very next line,\n+\t *   because other kernel messages may interleave)\n+\t * - faulty access properties match the tested type and size\n+\t */\n+\tkasan_report_start = strstr(buf, access_log);\n+\n+\tif (!kasan_report_start)\n+\t\treturn 1;\n+\n+\tnsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT,\n+\t\t\t is_write ? \"Write\" : \"Read\", size);\n+\tif (!ASSERT_GE(nsize, 0, \"format kasan access report line\"))\n+\t\treturn nsize;\n+\n+\tif (!strstr(kasan_report_start, access_log))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+static void exec_subtest(struct test_ctx *ctx, struct test_spec *test,\n+\t\t\t int access_size, bool on_stack)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tstruct bpf_prog_info info;\n+\tuint8_t buf[ETH_HLEN] = {0};\n+\tint ret, prog_fd;\n+\t__u32 info_len;\n+\n+\tctx-\u003eprog = bpf_object__find_program_by_name(ctx-\u003eobj,\n+\t\t\t\t\t\t     ctx-\u003eprog_name);\n+\tif (!ASSERT_OK_PTR(ctx-\u003eprog, \"find test prog\"))\n+\t\treturn;\n+\n+\tinfo_len = sizeof(info);\n+\tmemset(\u0026info, 0, info_len);\n+\tprog_fd = bpf_program__fd(ctx-\u003eprog);\n+\tif (!ASSERT_OK_FD(prog_fd, \"get prog fd\"))\n+\t\treturn;\n+\tret = bpf_prog_get_info_by_fd(prog_fd, \u0026info, \u0026info_len);\n+\tif (!ASSERT_OK(ret, \"fetch loaded program info\"))\n+\t\treturn;\n+\tmemcpy(ctx-\u003eprog_tag, info.tag, BPF_TAG_SIZE);\n+\n+\tskip_kernel_logs(ctx-\u003eklog_fd);\n+\n+\ttopts.sz = sizeof(struct bpf_test_run_opts);\n+\ttopts.data_size_in = ETH_HLEN;\n+\ttopts.data_in = buf;\n+\tif (ctx-\u003eaccess_size)\n+\t\t*ctx-\u003eaccess_size = access_size;\n+\tret = bpf_prog_test_run_opts(bpf_program__fd(ctx-\u003eprog),\n+\t\t\t\t     \u0026topts);\n+\tif (!ASSERT_OK(ret, \"run prog\"))\n+\t\treturn;\n+\n+\tret = read_kernel_logs(ctx-\u003eklog_fd, klog_buffer, MAX_LOG_SIZE);\n+\tif (!ASSERT_GE(ret, 0, \"read kernel logs\"))\n+\t\treturn;\n+\n+\tret = check_kasan_report_in_kernel_logs(klog_buffer, ctx,\n+\t\t\t\t\t\ttest-\u003eis_write, access_size,\n+\t\t\t\t\t\ttest-\u003eis_oob);\n+\tif (on_stack || test-\u003eexpect_no_report)\n+\t\tASSERT_NEQ(ret, 0, \"no report should be generated\");\n+\telse\n+\t\tASSERT_OK(ret, \"report should be generated\");\n+}\n+\n+static void run_subtest_with_size_and_location(struct test_ctx *ctx,\n+\t\t\t\t\t       struct test_spec *test,\n+\t\t\t\t\t       int access_size,\n+\t\t\t\t\t       bool on_stack)\n+{\n+\tchar subtest_name[SUBTEST_NAME_MAX_LEN];\n+\n+\tif (test-\u003eskip_multi_size_testing) {\n+\t\tsnprintf(subtest_name, SUBTEST_NAME_MAX_LEN, \"%s%s\",\n+\t\t\t test-\u003eprog_type,\n+\t\t\t test-\u003eskip_on_stack_testing ? \"\" :\n+\t\t\t on_stack\t\t     ? \"_on_stack\" :\n+\t\t\t\t\t\t       \"_not_on_stack\");\n+\t} else {\n+\t\tsnprintf(subtest_name, SUBTEST_NAME_MAX_LEN, \"%s_%d_%s\",\n+\t\t\t test-\u003eprog_type, access_size,\n+\t\t\t on_stack ? \"on_stack\" : \"not_on_stack\");\n+\t}\n+\n+\tsnprintf(ctx-\u003eprog_name, PROG_NAME_MAX_LEN, \"%s%s\", test-\u003eprog_type,\n+\t\t test-\u003eskip_on_stack_testing ? \"\" :\n+\t\t on_stack\t\t     ? \"_on_stack\" :\n+\t\t\t\t\t       \"_not_on_stack\");\n+\n+\tif (!test__start_subtest(subtest_name))\n+\t\treturn;\n+\n+\tif (test-\u003eneeds_load_acq_store_rel \u0026\u0026 ctx-\u003eskip_load_acq_store_rel) {\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\texec_subtest(ctx, test, access_size, on_stack);\n+}\n+\n+static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test,\n+\t\t\t\t  int size)\n+{\n+\trun_subtest_with_size_and_location(ctx, test, size, false);\n+\tif (!test-\u003eskip_on_stack_testing)\n+\t\trun_subtest_with_size_and_location(ctx, test, size, true);\n+}\n+\n+static void run_subtest(struct test_ctx *ctx, struct test_spec *test)\n+{\n+\tif (test-\u003eskip_multi_size_testing) {\n+\t\trun_subtest_with_size(ctx, test, test-\u003erun_size);\n+\t\treturn;\n+\t}\n+\n+\tif (!test-\u003eonly_32_or_64) {\n+\t\trun_subtest_with_size(ctx, test, 1);\n+\t\trun_subtest_with_size(ctx, test, 2);\n+\t}\n+\trun_subtest_with_size(ctx, test, 4);\n+\trun_subtest_with_size(ctx, test, 8);\n+}\n+\n+static void run_blinding_subtest(void)\n+{\n+\tstruct test_spec blinding_spec = {\n+\t\t.prog_type = \"st_blinded\",\n+\t\t.is_write = true,\n+\t};\n+\tchar bpf_jit_harden = '2';\n+\tstruct kasan_harden *skel;\n+\tstruct test_ctx *ctx;\n+\n+\tif (!test__start_subtest(\"st_blinded\"))\n+\t\treturn;\n+\n+\tctx = calloc(1, sizeof(*ctx));\n+\tif (!ASSERT_OK_PTR(ctx, \"alloc blinding ctx\"))\n+\t\treturn;\n+\tctx-\u003eklog_fd = -1;\n+\n+\tif (set_bpf_jit_harden(\u0026bpf_jit_harden))\n+\t\tgoto free_ctx;\n+\n+\tskel = kasan_harden__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"open and load blinded prog\"))\n+\t\tgoto restore;\n+\n+\tctx-\u003eklog_fd = open_kernel_logs();\n+\tif (!ASSERT_OK_FD(ctx-\u003eklog_fd, \"open kernel logs\"))\n+\t\tgoto destroy;\n+\n+\tctx-\u003eobj = skel-\u003eobj;\n+\tstrncpy(ctx-\u003eprog_name, \"st_blinded\", PROG_NAME_MAX_LEN);\n+\n+\texec_subtest(ctx, \u0026blinding_spec, 1, false);\n+\n+destroy:\n+\tclose(ctx-\u003eklog_fd);\n+\tkasan_harden__destroy(skel);\n+restore:\n+\tset_bpf_jit_harden(\u0026bpf_jit_harden);\n+free_ctx:\n+\tfree(ctx);\n+}\n+\n+static struct test_spec tests[] = {\n+\t{\n+\t\t.prog_type = \"st\",\n+\t\t.is_write = true\n+\t},\n+\t{\n+\t\t.prog_type = \"stx\",\n+\t\t.is_write = true\n+\t},\n+\t{\n+\t\t.prog_type = \"ldx\",\n+\t\t.is_write = false\n+\t},\n+\t{\n+\t\t.prog_type = \"simple_atomic\",\n+\t\t.is_write = true,\n+\t\t.only_32_or_64 = true\n+\t},\n+\t{\n+\t\t.prog_type = \"simple_atomic_fetch\",\n+\t\t.is_write = true,\n+\t\t.skip_multi_size_testing = true,\n+\t\t.run_size = 8,\n+\t},\n+\t{\n+\t\t.prog_type = \"load_acquire\",\n+\t\t.is_write = false,\n+\t\t.needs_load_acq_store_rel = true\n+\t},\n+\t{\n+\t\t.prog_type = \"store_release\",\n+\t\t.is_write = true,\n+\t\t.needs_load_acq_store_rel = true\n+\t},\n+\t{\n+\t\t.prog_type = \"ldx_patched\",\n+\t\t.is_write = false,\n+\t\t.skip_multi_size_testing = true,\n+\t\t.run_size = 4,\n+\t\t.rnd_hi32 = true\n+\t},\n+\t{\n+\t\t.prog_type = \"verifier_paths_stack_and_non_stack\",\n+\t\t.is_write = true,\n+\t\t.skip_multi_size_testing = true,\n+\t\t.skip_on_stack_testing = true,\n+\t\t.run_size = 1\n+\t},\n+\t{\n+\t\t.prog_type = \"ldx_oob\",\n+\t\t.is_write = false,\n+\t\t.skip_on_stack_testing = true,\n+\t\t.is_oob = true\n+\t},\n+};\n+\n+void test_kasan(void)\n+{\n+\tstruct kasan_write_val val;\n+\tstruct test_spec *test;\n+\tstruct test_ctx *ctx;\n+\tstruct kasan *skel;\n+\t__u32 key = 0;\n+\tint i, ret;\n+\n+\tctx = calloc(1, sizeof(struct test_ctx));\n+\tif (!ASSERT_OK_PTR(ctx, \"alloc test ctx\"))\n+\t\treturn;\n+\n+\tif (!is_jit_enabled() || !get_kasan_jit_enabled() ||\n+\t    !get_kasan_multi_shot_enabled()) {\n+\t\ttest__skip();\n+\t\tgoto end;\n+\t}\n+\n+\tskel = kasan__open();\n+\tif (!ASSERT_OK_PTR(skel, \"open prog\"))\n+\t\tgoto end;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(tests); i++) {\n+\t\tchar prog_name[SUBTEST_NAME_MAX_LEN];\n+\t\tstruct bpf_program *prog;\n+\n+\t\tif (!tests[i].rnd_hi32)\n+\t\t\tcontinue;\n+\n+\t\tsnprintf(prog_name, SUBTEST_NAME_MAX_LEN, \"%s_%s\",\n+\t\t\t tests[i].prog_type, \"on_stack\");\n+\t\tprog = bpf_object__find_program_by_name(skel-\u003eobj, prog_name);\n+\t\tif (!ASSERT_OK_PTR(prog, \"find rnd_hi32 on_stack prog\"))\n+\t\t\tgoto destroy;\n+\t\tbpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);\n+\t\tsnprintf(prog_name, SUBTEST_NAME_MAX_LEN, \"%s_%s\",\n+\t\t\t tests[i].prog_type, \"not_on_stack\");\n+\t\tprog = bpf_object__find_program_by_name(skel-\u003eobj, prog_name);\n+\t\tif (!ASSERT_OK_PTR(prog, \"find rnd_hi32 not_on_stack prog\"))\n+\t\t\tgoto destroy;\n+\t\tbpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);\n+\t}\n+\n+\tif (!ASSERT_OK(kasan__load(skel), \"load prog\"))\n+\t\tgoto destroy;\n+\n+\tctx-\u003eobj = skel-\u003eobj;\n+\tctx-\u003eaccess_size = \u0026skel-\u003ebss-\u003eaccess_size;\n+\tctx-\u003eskip_load_acq_store_rel = skel-\u003edata-\u003eskip_load_acq_store_rel_tests;\n+\n+\tctx-\u003eklog_fd = open_kernel_logs();\n+\tif (!ASSERT_OK_FD(ctx-\u003eklog_fd, \"open kernel logs\"))\n+\t\tgoto destroy;\n+\n+\t/* Fill map with recognizable values */\n+\tret = bpf_map__lookup_elem(skel-\u003emaps.test_map, \u0026key, sizeof(key),\n+\t\t\t\t   \u0026val, sizeof(val), 0);\n+\tif (!ASSERT_OK(ret, \"get map\"))\n+\t\tgoto close;\n+\tval.data_1 = 0xAA;\n+\tval.data_2 = 0xBBBB;\n+\tval.data_4 = 0xCCCCCCCC;\n+\tval.data_8 = 0xDDDDDDDDDDDDDDDD;\n+\tret = bpf_map__update_elem(skel-\u003emaps.test_map, \u0026key, sizeof(key),\n+\t\t\t\t   \u0026val, sizeof(val), 0);\n+\tif (!ASSERT_OK(ret, \"set map\"))\n+\t\tgoto close;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(tests); i++) {\n+\t\ttest = \u0026tests[i];\n+\t\trun_subtest(ctx, test);\n+\t}\n+\n+\t/*\n+\t * Blinding subtest is handled differently as it needs the\n+\t * corresponding program to be loaded with bpf_jit_harden raised\n+\t */\n+\trun_blinding_subtest();\n+\n+close:\n+\tclose(ctx-\u003eklog_fd);\n+destroy:\n+\tkasan__destroy(skel);\n+end:\n+\tfree(ctx);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c\nnew file mode 100644\nindex 0000000000000..ea29197646b0b\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/kasan.c\n@@ -0,0 +1,462 @@\n+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause\n+\n+#include \u003cstdbool.h\u003e\n+#include \u003clinux/bpf.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"bpf_misc.h\"\n+\n+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;\n+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;\n+\n+struct bpf_testmod_oob {\n+\t__u8 data;\n+\tunion {\n+\t\t__u8 redzone_1;\n+\t\t__u16 redzone_2;\n+\t\t__u32 redzone_4;\n+\t\t__u64 redzone_8;\n+\t};\n+};\n+\n+extern struct bpf_testmod_oob *bpf_testmod_oob_alloc(void) __ksym;\n+extern void bpf_testmod_oob_free(struct bpf_testmod_oob *oob) __ksym;\n+\n+int access_size;\n+\n+struct kasan_test_val {\n+\t__u8 data_1;\n+\t__u16 data_2;\n+\t__u32 data_4;\n+\t__u64 data_8;\n+};\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, 1);\n+\t__type(key, __u32);\n+\t__type(value, struct kasan_test_val);\n+} test_map SEC(\".maps\");\n+\n+SEC(\"tcx/ingress\")\n+int st_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\tval.data_1 = 0xAA;\n+\t\tbreak;\n+\tcase 2:\n+\t\tval.data_2 = 0xAA;\n+\t\tbreak;\n+\tcase 4:\n+\t\tval.data_4 = 0xAA;\n+\t\tbreak;\n+\tcase 8:\n+\t\tval.data_8 = 0xAA;\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int st_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\tval-\u003edata_1 = 0xAA;\n+\t\tbreak;\n+\tcase 2:\n+\t\tval-\u003edata_2 = 0xAA;\n+\t\tbreak;\n+\tcase 4:\n+\t\tval-\u003edata_4 = 0xAA;\n+\t\tbreak;\n+\tcase 8:\n+\t\tval-\u003edata_8 = 0xAA;\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int stx_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\tval.data_1 = access_size;\n+\t\tbreak;\n+\tcase 2:\n+\t\tval.data_2 = access_size;\n+\t\tbreak;\n+\tcase 4:\n+\t\tval.data_4 = access_size;\n+\t\tbreak;\n+\tcase 8:\n+\t\tval.data_8 = access_size;\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int stx_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\tval-\u003edata_1 = access_size;\n+\t\tbreak;\n+\tcase 2:\n+\t\tval-\u003edata_2 = access_size;\n+\t\tbreak;\n+\tcase 4:\n+\t\tval-\u003edata_4 = access_size;\n+\t\tbreak;\n+\tcase 8:\n+\t\tval-\u003edata_8 = access_size;\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int ldx_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\t__sink(val.data_1);\n+\t\tbreak;\n+\tcase 2:\n+\t\t__sink(val.data_2);\n+\t\tbreak;\n+\tcase 4:\n+\t\t__sink(val.data_4);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__sink(val.data_8);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int ldx_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\t__sink(val-\u003edata_1);\n+\t\tbreak;\n+\tcase 2:\n+\t\t__sink(val-\u003edata_2);\n+\t\tbreak;\n+\tcase 4:\n+\t\t__sink(val-\u003edata_4);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__sink(val-\u003edata_8);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int ldx_patched_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\t__sink(val-\u003edata_4);\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int ldx_patched_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\t__sink(val.data_4);\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int simple_atomic_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 4:\n+\t\t__sync_fetch_and_add(\u0026val.data_4, 4);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__sync_fetch_and_add(\u0026val.data_8, 8);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int simple_atomic_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 4:\n+\t\t__sync_fetch_and_add(\u0026val-\u003edata_4, 4);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__sync_fetch_and_add(\u0026val-\u003edata_8, 8);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int simple_atomic_fetch_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\t__sync_fetch_and_or(\u0026val.data_8, 8);\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int simple_atomic_fetch_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\t__sync_fetch_and_or(\u0026val-\u003edata_8, 8);\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL\n+bool skip_load_acq_store_rel_tests SEC(\".data\") = 0;\n+\n+SEC(\"tcx/ingress\")\n+int load_acquire_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\t__atomic_load_n(\u0026val.data_1, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\tcase 2:\n+\t\t__atomic_load_n(\u0026val.data_2, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\tcase 4:\n+\t\t__atomic_load_n(\u0026val.data_4, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__atomic_load_n(\u0026val.data_8, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int load_acquire_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\t__atomic_load_n(\u0026val-\u003edata_1, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\tcase 2:\n+\t\t__atomic_load_n(\u0026val-\u003edata_2, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\tcase 4:\n+\t\t__atomic_load_n(\u0026val-\u003edata_4, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__atomic_load_n(\u0026val-\u003edata_8, __ATOMIC_ACQUIRE);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int store_release_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val val;\n+\n+\tbpf_kfunc_kasan_poison(\u0026val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\t__atomic_store_n(\u0026val.data_1, 0xAA, __ATOMIC_RELEASE);\n+\t\tbreak;\n+\tcase 2:\n+\t\t__atomic_store_n(\u0026val.data_2, 0xBBBB, __ATOMIC_RELEASE);\n+\t\tbreak;\n+\tcase 4:\n+\t\t__atomic_store_n(\u0026val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__atomic_store_n(\u0026val.data_8, 0xDDDDDDDDDDDDDDDD,\n+\t\t\t\t __ATOMIC_RELEASE);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(\u0026val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int store_release_not_on_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\t__atomic_store_n(\u0026val-\u003edata_1, 0xAA, __ATOMIC_RELEASE);\n+\t\tbreak;\n+\tcase 2:\n+\t\t__atomic_store_n(\u0026val-\u003edata_2, 0xBBBB, __ATOMIC_RELEASE);\n+\t\tbreak;\n+\tcase 4:\n+\t\t__atomic_store_n(\u0026val-\u003edata_4, 0xCCCCCCCC, __ATOMIC_RELEASE);\n+\t\tbreak;\n+\tcase 8:\n+\t\t__atomic_store_n(\u0026val-\u003edata_8, 0xDDDDDDDDDDDDDDDD,\n+\t\t\t\t __ATOMIC_RELEASE);\n+\t\tbreak;\n+\t}\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\treturn 0;\n+}\n+#else\n+bool skip_load_acq_store_rel_tests SEC(\".data\") = 1;\n+#endif\n+\n+SEC(\"tcx/ingress\")\n+int verifier_paths_stack_and_non_stack(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val stack_val = {};\n+\tstruct kasan_test_val *val;\n+\tvoid *ptr;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tif (access_size)\n+\t\tptr = val;\n+\telse\n+\t\tptr = \u0026stack_val;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(*val));\n+\t*(__u8 *)ptr = 0xAA;\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(*val));\n+\treturn 0;\n+}\n+\n+SEC(\"tcx/ingress\")\n+int ldx_oob(struct __sk_buff *skb)\n+{\n+\tstruct bpf_testmod_oob *val;\n+\tstruct kasan_test_val volatile tmp;\n+\n+\tval = bpf_testmod_oob_alloc();\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tswitch (access_size) {\n+\tcase 1:\n+\t\ttmp.data_1 = (__u8)val-\u003eredzone_1;\n+\t\tbreak;\n+\tcase 2:\n+\t\ttmp.data_2 = (__u16)val-\u003eredzone_2;\n+\t\tbreak;\n+\tcase 4:\n+\t\ttmp.data_4 = (__u32)val-\u003eredzone_4;\n+\t\tbreak;\n+\tcase 8:\n+\t\ttmp.data_8 = (__u64)val-\u003eredzone_8;\n+\t\tbreak;\n+\t}\n+\tbpf_testmod_oob_free(val);\n+\treturn tmp.data_1;\n+}\n+\n+char LICENSE[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/kasan_harden.c b/tools/testing/selftests/bpf/progs/kasan_harden.c\nnew file mode 100644\nindex 0000000000000..a2756bbfd5292\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c\n@@ -0,0 +1,41 @@\n+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause\n+\n+#include \u003clinux/bpf.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;\n+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;\n+\n+struct kasan_test_val {\n+\t__u8 data_1;\n+\t__u16 data_2;\n+\t__u32 data_4;\n+\t__u64 data_8;\n+};\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, 1);\n+\t__type(key, __u32);\n+\t__type(value, struct kasan_test_val);\n+} test_map SEC(\".maps\");\n+\n+SEC(\"tcx/ingress\")\n+int st_blinded(struct __sk_buff *skb)\n+{\n+\tstruct kasan_test_val *val;\n+\t__u32 key = 0;\n+\n+\tval = bpf_map_lookup_elem(\u0026test_map, \u0026key);\n+\tif (!val)\n+\t\treturn 0;\n+\n+\tbpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));\n+\tval-\u003edata_1 = 0xAA;\n+\tbpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));\n+\n+\treturn 0;\n+}\n+\n+char LICENSE[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\nindex 9366a3c578f13..8e72a3d1b9a23 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n@@ -75,6 +75,16 @@ union bpf_testmod_union_arg_2 {\n \tstruct bpf_testmod_struct_arg_2 arg;\n };\n \n+struct bpf_testmod_oob {\n+\t__u8 data;\n+\tunion {\n+\t\t__u8 redzone_1;\n+\t\t__u16 redzone_2;\n+\t\t__u32 redzone_4;\n+\t\t__u64 redzone_8;\n+\t};\n+};\n+\n __bpf_hook_start();\n \n noinline int\n@@ -336,6 +346,47 @@ __bpf_kfunc void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member\n \t */\n }\n \n+#ifdef CONFIG_BPF_JIT_KASAN\n+\n+extern void kasan_poison(const void *addr, size_t size, u8 value, bool init);\n+\n+#define KASAN_SLAB_FREE 0xFB\n+\n+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz)\n+{\n+\tkasan_poison(mem, mem__sz, KASAN_SLAB_FREE, false);\n+}\n+\n+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz)\n+{\n+\tkasan_poison(mem, mem__sz, 0x00, false);\n+}\n+#else\n+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) { }\n+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) { }\n+#endif\n+\n+__bpf_kfunc struct bpf_testmod_oob *bpf_testmod_oob_alloc(void)\n+{\n+\tstruct bpf_testmod_oob *p;\n+\n+\t/*\n+\t * Only allocate size of data (and so, voluntarily use kmalloc\n+\t * instead of kmalloc_obj), not the rest of the structure, so\n+\t * that programs under test trying to access the rest of the\n+\t * structure trigger OoB accesses\n+\t */\n+\tp = kmalloc(sizeof(p-\u003edata), GFP_ATOMIC);\n+\tif (!p)\n+\t\treturn NULL;\n+\treturn p;\n+}\n+\n+__bpf_kfunc void bpf_testmod_oob_free(struct bpf_testmod_oob *oob)\n+{\n+\tkfree(oob);\n+}\n+\n __bpf_kfunc struct bpf_testmod_ctx *\n bpf_testmod_ctx_create(int *err)\n {\n@@ -869,6 +920,10 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)\n BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_multislot)\n BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);\n BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);\n+BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison)\n+BTF_ID_FLAGS(func, bpf_kfunc_kasan_unpoison)\n+BTF_ID_FLAGS(func, bpf_testmod_oob_alloc, KF_ACQUIRE | KF_RET_NULL)\n+BTF_ID_FLAGS(func, bpf_testmod_oob_free, KF_RELEASE)\n BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)\n \n BTF_ID_LIST(bpf_testmod_dtor_ids)\ndiff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c\nindex c970e7793dfcb..737f668b35e23 100644\n--- a/tools/testing/selftests/bpf/testing_helpers.c\n+++ b/tools/testing/selftests/bpf/testing_helpers.c\n@@ -519,6 +519,38 @@ bool is_jit_enabled(void)\n \treturn enabled;\n }\n \n+int set_bpf_jit_harden(char *level)\n+{\n+\tchar old_level;\n+\tint err = -1;\n+\tint fd = -1;\n+\n+\tfd = open(\"/proc/sys/net/core/bpf_jit_harden\", O_RDWR | O_NONBLOCK);\n+\tif (fd \u003c 0)\n+\t\treturn -1;\n+\n+\terr = read(fd, \u0026old_level, 1);\n+\tif (err != 1) {\n+\t\terr = -1;\n+\t\tgoto end;\n+\t}\n+\n+\tlseek(fd, 0, SEEK_SET);\n+\n+\terr = write(fd, level, 1);\n+\tif (err != 1) {\n+\t\terr = -1;\n+\t\tgoto end;\n+\t}\n+\n+\terr = 0;\n+\t*level = old_level;\n+end:\n+\tif (fd \u003e= 0)\n+\t\tclose(fd);\n+\treturn err;\n+}\n+\n int stack_mprotect(void)\n {\n \tvoid *buf;\ndiff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h\nindex 2edc6fb7fc521..e00642afe86f4 100644\n--- a/tools/testing/selftests/bpf/testing_helpers.h\n+++ b/tools/testing/selftests/bpf/testing_helpers.h\n@@ -59,6 +59,7 @@ struct bpf_insn;\n int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt);\n int testing_prog_flags(void);\n bool is_jit_enabled(void);\n+int set_bpf_jit_harden(char *level);\n int stack_mprotect(void);\n \n #endif /* __TESTING_HELPERS_H */\ndiff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c\nindex f997d7ec8fd08..2c8c5edb87517 100644\n--- a/tools/testing/selftests/bpf/unpriv_helpers.c\n+++ b/tools/testing/selftests/bpf/unpriv_helpers.c\n@@ -72,8 +72,8 @@ static int config_contains(const char *pat)\n \n static bool cmdline_contains(const char *pat)\n {\n+\tint fd, cnt, ret = false;\n \tchar cmdline[4096], *c;\n-\tint fd, ret = false;\n \n \tfd = open(\"/proc/cmdline\", O_RDONLY);\n \tif (fd \u003c 0) {\n@@ -81,14 +81,15 @@ static bool cmdline_contains(const char *pat)\n \t\treturn false;\n \t}\n \n-\tif (read(fd, cmdline, sizeof(cmdline) - 1) \u003c 0) {\n+\tcnt = read(fd, cmdline, sizeof(cmdline) - 1);\n+\tif (cnt \u003c 0) {\n \t\tperror(\"read /proc/cmdline\");\n \t\tgoto out;\n \t}\n \n-\tcmdline[sizeof(cmdline) - 1] = '\\0';\n+\tcmdline[cnt] = '\\0';\n \tfor (c = strtok(cmdline, \" \\n\"); c; c = strtok(NULL, \" \\n\")) {\n-\t\tif (strncmp(c, pat, strlen(c)))\n+\t\tif (strcmp(c, pat))\n \t\t\tcontinue;\n \t\tret = true;\n \t\tbreak;\n@@ -142,3 +143,13 @@ bool get_unpriv_disabled(void)\n \t}\n \treturn mitigations_off;\n }\n+\n+bool get_kasan_jit_enabled(void)\n+{\n+\treturn config_contains(\"CONFIG_BPF_JIT_KASAN=y\") == 1;\n+}\n+\n+bool get_kasan_multi_shot_enabled(void)\n+{\n+\treturn cmdline_contains(\"kasan_multi_shot\");\n+}\ndiff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h\nindex 151f673296652..a7ceb51577cdc 100644\n--- a/tools/testing/selftests/bpf/unpriv_helpers.h\n+++ b/tools/testing/selftests/bpf/unpriv_helpers.h\n@@ -5,3 +5,5 @@\n #define UNPRIV_SYSCTL \"kernel/unprivileged_bpf_disabled\"\n \n bool get_unpriv_disabled(void);\n+bool get_kasan_jit_enabled(void);\n+bool get_kasan_multi_shot_enabled(void);\n"
}

2/1 2026/08/21 23:00 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_JIT_KASAN",
    "KASAN",
    "KASAN_GENERIC",
    "KASAN_VMALLOC",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_patch_insn_data",
    "bpf_int_jit_compile",
    "bpf_prog_select_runtime"
  ],
  "Reasoning": "The patch introduces KASAN support for the BPF JIT compiler on x86_64. It modifies the BPF JIT compiler to insert KASAN checks before memory accesses (load/store/atomic). It also modifies the BPF verifier to track memory accesses that are not on the stack. This is a functional change to the core BPF JIT and verifier logic, which is reachable from userspace by loading BPF programs. Therefore, it is worth fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit cbc4c75c36320a09e8b963fdfe53b9ba1333946b
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 21 23:00:35 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 48ccc3e6059d0..745890d91e99a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -234,6 +234,7 @@ config X86
 	select HAVE_SAMPLE_FTRACE_DIRECT	if X86_64
 	select HAVE_SAMPLE_FTRACE_DIRECT_MULTI	if X86_64
 	select HAVE_EBPF_JIT
+	select HAVE_EBPF_JIT_KASAN		if X86_64
 	select HAVE_EFFICIENT_UNALIGNED_ACCESS
 	select HAVE_EISA			if X86_32
 	select HAVE_EXIT_THREAD
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 1a9fb530adc3c..36e71aa2e877c 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -21,6 +21,17 @@
 #include <asm/unwind.h>
 #include <asm/cfi.h>
 
+#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)
+void __asan_load1(void *p);
+void __asan_store1(void *p);
+void __asan_load2(void *p);
+void __asan_store2(void *p);
+void __asan_load4(void *p);
+void __asan_store4(void *p);
+void __asan_load8(void *p);
+void __asan_store8(void *p);
+#endif
+
 static bool all_callee_regs_used[4] = {true, true, true, true};
 
 static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len)
@@ -1110,6 +1121,92 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
 	*pprog = prog;
 }
 
+static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
+			    u32 addr_reg, struct bpf_insn *insn, u8 *ip,
+			    bool is_write)
+{
+#ifdef CONFIG_BPF_JIT_KASAN
+	u32 bpf_size = BPF_SIZE(insn->code);
+	s32 off = insn->off;
+	u8 *prog = *pprog;
+	void *kasan_func;
+
+	if (!env)
+		return 0;
+
+	/* Derive KASAN check function from access type and size */
+	switch (bpf_size) {
+	case BPF_B:
+		kasan_func = is_write ? __asan_store1 : __asan_load1;
+		break;
+	case BPF_H:
+		kasan_func = is_write ? __asan_store2 : __asan_load2;
+		break;
+	case BPF_W:
+		kasan_func = is_write ? __asan_store4 : __asan_load4;
+		break;
+	case BPF_DW:
+		kasan_func = is_write ? __asan_store8 : __asan_load8;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Save rax */
+	EMIT1(0x50);
+	/* Save rcx */
+	EMIT1(0x51);
+	/* Save rdx */
+	EMIT1(0x52);
+	/* Save rsi */
+	EMIT1(0x56);
+	/* Save rdi */
+	EMIT1(0x57);
+	/* Save r8 */
+	EMIT2(0x41, 0x50);
+	/* Save r9 */
+	EMIT2(0x41, 0x51);
+	/*
+	 * SystemV ABI states that we should also save r10/r11, but in
+	 * practice those registers are _not_ used by the limited set of
+	 * kasan helpers we are calling here, so that's fine not to save those.
+	 */
+
+	/* mov rdi, addr_reg */
+	EMIT_mov(BPF_REG_1, addr_reg);
+
+	/* add rdi, off (if offset is non-zero) */
+	if (off) {
+		if (is_imm8(off)) {
+			/* add rdi, imm8 */
+			EMIT4(0x48, 0x83, 0xC7, (u8)off);
+		} else {
+			/* add rdi, imm32 */
+			EMIT3_off32(0x48, 0x81, 0xC7, off);
+		}
+	}
+
+	/* Adjust ip to account for the instrumentation generated so far */
+	ip += (prog - *pprog);
+	/* We emit a call, so update call depth counting */
+	ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip);
+	/* call kasan_func */
+	if (emit_call(&prog, kasan_func, ip))
+		return -ERANGE;
+
+	EMIT2(0x41, 0x59);
+	EMIT2(0x41, 0x58);
+	EMIT1(0x5F);
+	EMIT1(0x5E);
+	EMIT1(0x5A);
+	EMIT1(0x59);
+	EMIT1(0x58);
+
+	*pprog = prog;
+#endif /* CONFIG_BPF_JIT_KASAN */
+	return 0;
+}
+
 /* LDX: dst_reg = *(u8*)(src_reg + off) */
 static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
 {
@@ -1315,6 +1412,63 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int
 	*pprog = prog;
 }
 
+/* ST: *(u8*)(dst_reg + off) = imm */
+static void emit_st(u8 **pprog, struct bpf_insn *insn, u32 dst_reg,
+		    s32 outgoing_arg_base, u16 outgoing_rsp)
+{
+	s32 imm32 = insn->imm;
+	u8 *prog = *pprog;
+	s32 insn_off;
+
+	switch (BPF_SIZE(insn->code)) {
+	case BPF_B:
+		if (is_ereg(dst_reg))
+			EMIT2(0x41, 0xC6);
+		else
+			EMIT1(0xC6);
+		break;
+	case BPF_H:
+		if (is_ereg(dst_reg))
+			EMIT3(0x66, 0x41, 0xC7);
+		else
+			EMIT2(0x66, 0xC7);
+		break;
+	case BPF_W:
+		if (is_ereg(dst_reg))
+			EMIT2(0x41, 0xC7);
+		else
+			EMIT1(0xC7);
+		break;
+	case BPF_DW:
+		if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
+			/* Arg 6: store immediate in r9 register */
+			emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, imm32);
+			*pprog = prog;
+			return;
+		}
+		EMIT2(add_1mod(0x48, dst_reg), 0xC7);
+		break;
+	}
+
+	insn_off = insn->off;
+	if (dst_reg == BPF_REG_PARAMS) {
+		/*
+		 * Args 7+: reverse BPF negative offsets to
+		 * x86 positive rsp offsets.
+		 * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
+		 */
+		insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
+		dst_reg = BPF_REG_FP;
+	}
+	if (is_imm8(insn_off))
+		EMIT2(add_1reg(0x40, dst_reg), insn_off);
+	else
+		EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
+
+	EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+	*pprog = prog;
+}
+
 static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm)
 {
 	emit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm);
@@ -1423,17 +1577,35 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size,
 	return 0;
 }
 
-static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg,
-			     u32 src_reg, s16 off, u8 bpf_size)
+static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog,
+			     struct bpf_insn *insn, u8 *ip, u32 dst_reg,
+			     u32 src_reg, bool accesses_stack_only)
 {
+	u32 atomic_op = insn->imm;
+	int err;
+
 	switch (atomic_op) {
 	case BPF_LOAD_ACQ:
+		if (!accesses_stack_only) {
+			err = emit_kasan_check(env, pprog, src_reg, insn, ip,
+					       false);
+			if (err)
+				return err;
+		}
 		/* dst_reg = smp_load_acquire(src_reg + off16) */
-		emit_ldx(pprog, bpf_size, dst_reg, src_reg, off);
+		emit_ldx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+			 insn->off);
 		break;
 	case BPF_STORE_REL:
+		if (!accesses_stack_only) {
+			err = emit_kasan_check(env, pprog, dst_reg, insn, ip,
+					       true);
+			if (err)
+				return err;
+		}
 		/* smp_store_release(dst_reg + off16, src_reg) */
-		emit_stx(pprog, bpf_size, dst_reg, src_reg, off);
+		emit_stx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+			 insn->off);
 		break;
 	default:
 		pr_err("bpf_jit: unknown atomic load/store opcode %02x\n",
@@ -1859,10 +2031,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 		const s32 imm32 = insn->imm;
 		u32 dst_reg = insn->dst_reg;
 		u32 src_reg = insn->src_reg;
+		bool accesses_stack_only;
 		u8 b2 = 0, b3 = 0;
 		u8 *start_of_ldx;
 		s64 jmp_offset;
 		s32 insn_off;
+		int insn_idx;
 		u8 jmp_cond;
 		u8 *func;
 		int nops;
@@ -1879,6 +2053,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 			EMIT_ENDBR();
 
 		ip = image + addrs[i - 1] + (prog - temp);
+		insn_idx = i - 1 + bpf_prog->aux->subprog_start;
+		accesses_stack_only =
+			env ? !env->insn_aux_data[insn_idx].non_stack_access :
+			      false;
 
 		switch (insn->code) {
 			/* ALU */
@@ -2255,49 +2433,19 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 			EMIT_LFENCE();
 			break;
 
-			/* ST: *(u8*)(dst_reg + off) = imm */
 		case BPF_ST | BPF_MEM | BPF_B:
-			if (is_ereg(dst_reg))
-				EMIT2(0x41, 0xC6);
-			else
-				EMIT1(0xC6);
-			goto st;
 		case BPF_ST | BPF_MEM | BPF_H:
-			if (is_ereg(dst_reg))
-				EMIT3(0x66, 0x41, 0xC7);
-			else
-				EMIT2(0x66, 0xC7);
-			goto st;
 		case BPF_ST | BPF_MEM | BPF_W:
-			if (is_ereg(dst_reg))
-				EMIT2(0x41, 0xC7);
-			else
-				EMIT1(0xC7);
-			goto st;
 		case BPF_ST | BPF_MEM | BPF_DW:
-			if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
-				/* Arg 6: store immediate in r9 register */
-				emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32);
-				break;
-			}
-			EMIT2(add_1mod(0x48, dst_reg), 0xC7);
-
-st:			insn_off = insn->off;
-			if (dst_reg == BPF_REG_PARAMS) {
-				/*
-				 * Args 7+: reverse BPF negative offsets to
-				 * x86 positive rsp offsets.
-				 * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
-				 */
-				insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
-				dst_reg = BPF_REG_FP;
+			if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, dst_reg,
+						       insn, ip, true);
+				if (err)
+					return err;
 			}
-			if (is_imm8(insn_off))
-				EMIT2(add_1reg(0x40, dst_reg), insn_off);
-			else
-				EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
 
-			EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+			emit_st(&prog, insn, dst_reg, outgoing_arg_base,
+				outgoing_rsp);
 			break;
 
 			/* STX: *(u8*)(dst_reg + off) = src_reg */
@@ -2315,6 +2463,12 @@ st:			insn_off = insn->off;
 				insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
 				dst_reg = BPF_REG_FP;
 			}
+			if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, dst_reg,
+						       insn, ip, true);
+				if (err)
+					return err;
+			}
 			emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
 			break;
 
@@ -2496,6 +2650,11 @@ st:			insn_off = insn->off;
 				/* populate jmp_offset for JAE above to jump to start_of_ldx */
 				start_of_ldx = prog;
 				end_of_jmp[-1] = start_of_ldx - end_of_jmp;
+			} else if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, src_reg,
+						       insn, ip, false);
+				if (err)
+					return err;
 			}
 			if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX ||
 			    BPF_MODE(insn->code) == BPF_MEMSX)
@@ -2557,28 +2716,42 @@ st:			insn_off = insn->off;
 			}
 			fallthrough;
 		case BPF_STX | BPF_ATOMIC | BPF_W:
-		case BPF_STX | BPF_ATOMIC | BPF_DW:
-			if (insn->imm == (BPF_AND | BPF_FETCH) ||
-			    insn->imm == (BPF_OR | BPF_FETCH) ||
-			    insn->imm == (BPF_XOR | BPF_FETCH)) {
-				bool is64 = BPF_SIZE(insn->code) == BPF_DW;
-				u32 real_src_reg = src_reg;
-				u32 real_dst_reg = dst_reg;
-				u8 *branch_target;
-
+		case BPF_STX | BPF_ATOMIC | BPF_DW: {
+			bool is64 = BPF_SIZE(insn->code) == BPF_DW;
+			u32 real_src_reg = src_reg;
+			u32 real_dst_reg = dst_reg;
+			u8 *branch_target;
+			u8 *pprog;
+			bool is_atomic_fetch =
+				(insn->imm == (BPF_AND | BPF_FETCH) ||
+				 insn->imm == (BPF_OR | BPF_FETCH) ||
+				 insn->imm == (BPF_XOR | BPF_FETCH));
+			if (is_atomic_fetch) {
 				/*
 				 * Can't be implemented with a single x86 insn.
 				 * Need to do a CMPXCHG loop.
 				 */
 
 				/* Will need RAX as a CMPXCHG operand so save R0 */
+				pprog = prog;
 				emit_mov_reg(&prog, true, BPF_REG_AX, BPF_REG_0);
 				if (src_reg == BPF_REG_0)
 					real_src_reg = BPF_REG_AX;
 				if (dst_reg == BPF_REG_0)
 					real_dst_reg = BPF_REG_AX;
-
+				ip += (prog - pprog);
+			}
+			if (!bpf_atomic_is_load_store(insn)) {
+				if (!accesses_stack_only) {
+					err = emit_kasan_check(env, &prog,
+							       real_dst_reg,
+							       insn, ip, true);
+					if (err)
+						return err;
+				}
 				branch_target = prog;
+			}
+			if (is_atomic_fetch) {
 				/* Load old value */
 				emit_ldx(&prog, BPF_SIZE(insn->code),
 					 BPF_REG_0, real_dst_reg, insn->off);
@@ -2610,15 +2783,16 @@ st:			insn_off = insn->off;
 			}
 
 			if (bpf_atomic_is_load_store(insn))
-				err = emit_atomic_ld_st(&prog, insn->imm, dst_reg, src_reg,
-							insn->off, BPF_SIZE(insn->code));
+				err = emit_atomic_ld_st(env, &prog, insn, ip,
+							dst_reg, src_reg,
+							accesses_stack_only);
 			else
 				err = emit_atomic_rmw(&prog, insn->imm, dst_reg, src_reg,
 						      insn->off, BPF_SIZE(insn->code));
 			if (err)
 				return err;
 			break;
-
+		}
 		case BPF_STX | BPF_PROBE_ATOMIC | BPF_B:
 		case BPF_STX | BPF_PROBE_ATOMIC | BPF_H:
 			if (!bpf_atomic_is_load_store(insn)) {
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 5fad59fdab0d0..09cd0355b921d 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -725,6 +725,8 @@ struct bpf_insn_aux_data {
 	u16 const_reg_map_mask;
 	u16 const_reg_subprog_mask;
 	u32 const_reg_vals[10];
+	/* instruction can access non-stack memory */
+	bool non_stack_access;
 };
 
 #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */
diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
index eb3de35734f09..d7d25477ef481 100644
--- a/kernel/bpf/Kconfig
+++ b/kernel/bpf/Kconfig
@@ -17,6 +17,10 @@ config HAVE_CBPF_JIT
 config HAVE_EBPF_JIT
 	bool
 
+# KASAN support for JIT compiler
+config HAVE_EBPF_JIT_KASAN
+	bool
+
 # Used by archs to tell that they want the BPF JIT compiler enabled by
 # default for kernels that were compiled with BPF JIT support.
 config ARCH_WANT_DEFAULT_BPF_JIT
@@ -101,4 +105,17 @@ config BPF_LSM
 
 	  If you are unsure how to answer this question, answer N.
 
+config BPF_JIT_KASAN
+	bool
+	depends on HAVE_EBPF_JIT_KASAN
+	depends on KASAN_GENERIC
+	depends on KASAN_VMALLOC
+	depends on BPF_JIT
+	default y
+	help
+	  Makes JIT compiler insert generic outline KASAN checks in BPF
+	  programs when they are inserted in the kernel. This feature is
+	  automatically enabled if the needed set of KASAN and BPF
+	  configuration options is enabled.
+
 endmenu # "BPF subsystem"
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 65b441e4a3517..edcb0cbbb13de 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -208,12 +208,25 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
 }
 #endif
 
+static bool is_mem_insn(struct bpf_insn *insn)
+{
+	if (BPF_CLASS(insn->code) != BPF_ST &&
+	    BPF_CLASS(insn->code) != BPF_STX &&
+	    BPF_CLASS(insn->code) != BPF_LDX)
+		return false;
+
+	return (BPF_MODE(insn->code) == BPF_MEM ||
+		BPF_MODE(insn->code) == BPF_MEMSX ||
+		BPF_MODE(insn->code) == BPF_ATOMIC);
+}
+
 /* single env->prog->insni[off] instruction was replaced with the range
  * insni[off, off + cnt).  Adjust corresponding insn_aux_data by copying
  * [0, off) and [off, end) to new locations, so the patched range stays zero
  */
 static void adjust_insn_aux_data(struct bpf_verifier_env *env,
-				 struct bpf_prog *new_prog, u32 off, u32 cnt)
+				 struct bpf_prog *new_prog, u32 off, u32 cnt,
+				 struct bpf_insn *original_insn)
 {
 	struct bpf_insn_aux_data *data = env->insn_aux_data;
 	struct bpf_insn *insn = new_prog->insnsi;
@@ -227,8 +240,15 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
 	 */
 	data[off].zext_dst = bpf_insn_def32(new_prog, insn + off + cnt - 1) >= 0;
 
-	if (cnt == 1)
+	if (cnt == 1) {
+		/*
+		 * A non-memory accessing insn could have been replaced by a
+		 * memory accessing insn, systematically mark it for non-stack
+		 * access
+		 */
+		data[off].non_stack_access = is_mem_insn(insn + off);
 		return;
+	}
 	prog_len = new_prog->len;
 	env->insn_aux_data_len = prog_len;
 
@@ -239,8 +259,25 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
 		/* Expand insni[off]'s seen count to the patched range. */
 		data[i].seen = old_seen;
 		data[i].zext_dst = bpf_insn_def32(new_prog, insn + i) >= 0;
+		if (!memcmp(insn + i, original_insn, sizeof(struct bpf_insn))) {
+			data[i].non_stack_access =
+				data[off + cnt - 1].non_stack_access;
+			data[off + cnt - 1].non_stack_access = false;
+		} else if (is_mem_insn(insn + i)) {
+			data[i].non_stack_access = true;
+		}
 	}
 
+	/*
+	 * Last slot instruction could be a newly generated
+	 * BPF_ST/BPF_LDX/BPF_STX, systematically mark it for non-stack access
+	 * if it is not the original instruction, otherwise keep the
+	 * original marking
+	 */
+	if (is_mem_insn(insn + off + cnt - 1) &&
+	    memcmp(insn + off + cnt - 1, original_insn, sizeof(struct bpf_insn)))
+		data[off + cnt - 1].non_stack_access = true;
+
 	/*
 	 * The indirect_target flag of the original instruction was moved to the last of the
 	 * new instructions by the above memmove and memset, but the indirect jump target is
@@ -306,6 +343,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 {
 	struct bpf_prog *new_prog;
 	struct bpf_insn_aux_data *new_data = NULL;
+	struct bpf_insn original_insn;
 
 	if (len > 1) {
 		new_data = vrealloc(env->insn_aux_data,
@@ -318,6 +356,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 		env->insn_aux_data = new_data;
 	}
 
+	memcpy(&original_insn, env->prog->insnsi + off, sizeof(struct bpf_insn));
 	new_prog = bpf_patch_insn_single(env->prog, off, patch, len);
 	if (IS_ERR(new_prog)) {
 		if (PTR_ERR(new_prog) == -ERANGE)
@@ -326,7 +365,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 				env->insn_aux_data[off].orig_idx);
 		return NULL;
 	}
-	adjust_insn_aux_data(env, new_prog, off, len);
+	adjust_insn_aux_data(env, new_prog, off, len, &original_insn);
 	adjust_subprog_starts(env, off, len);
 	adjust_insn_arrays(env, off, len);
 	adjust_poke_descs(new_prog, off, len);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 821b47ac75c5f..3d1a8e14408ac 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3218,6 +3218,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
 	env->insn_aux_data[idx].indirect_target = true;
 }
 
+static void mark_non_stack_access(struct bpf_verifier_env *env, int idx)
+{
+	env->insn_aux_data[idx].non_stack_access = true;
+}
+
 #define LR_FRAMENO_BITS	4
 #define LR_SPI_BITS	6
 #define LR_ENTRY_BITS	(LR_SPI_BITS + LR_FRAMENO_BITS + 1)
@@ -6600,6 +6605,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 				clear_scalar_id(&regs[value_regno]);
 		}
 	}
+
+	if (!err && reg->type != PTR_TO_STACK)
+		mark_non_stack_access(env, insn_idx);
+
 	return err;
 }
 
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
index 0222a9a5d0761..815f3e04540ff 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -227,42 +227,6 @@ static void check_incorrect_index(void)
 	check_mid_insn_index();
 }
 
-static int set_bpf_jit_harden(char *level)
-{
-	char old_level;
-	int err = -1;
-	int fd = -1;
-
-	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
-	if (fd < 0) {
-		ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
-		return -1;
-	}
-
-	err = read(fd, &old_level, 1);
-	if (err != 1) {
-		ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
-		err = -1;
-		goto end;
-	}
-
-	lseek(fd, 0, SEEK_SET);
-
-	err = write(fd, level, 1);
-	if (err != 1) {
-		ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
-		err = -1;
-		goto end;
-	}
-
-	err = 0;
-	*level = old_level;
-end:
-	if (fd >= 0)
-		close(fd);
-	return err;
-}
-
 static void check_blindness(void)
 {
 	struct bpf_insn insns[] = {
@@ -272,7 +236,7 @@ static void check_blindness(void)
 		BPF_MOV64_IMM(BPF_REG_0, 1),
 		BPF_EXIT_INSN(),
 	};
-	int prog_fd = -1, map_fd;
+	int prog_fd = -1, map_fd, ret;
 	struct bpf_insn_array_value val = {};
 	char bpf_jit_harden = '@'; /* non-exizsting value */
 	int i;
@@ -291,7 +255,8 @@ static void check_blindness(void)
 		goto cleanup;
 
 	bpf_jit_harden = '2';
-	if (set_bpf_jit_harden(&bpf_jit_harden)) {
+	ret = set_bpf_jit_harden(&bpf_jit_harden);
+	if (!ASSERT_OK(ret, "set bpf_jit_harden")) {
 		bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
 		goto cleanup;
 	}
@@ -313,7 +278,8 @@ static void check_blindness(void)
 cleanup:
 	/* restore the old one */
 	if (bpf_jit_harden != '@')
-		set_bpf_jit_harden(&bpf_jit_harden);
+		ASSERT_OK(set_bpf_jit_harden(&bpf_jit_harden),
+			  "restore hardening configuration");
 
 	close(prog_fd);
 	close(map_fd);
diff --git a/tools/testing/selftests/bpf/prog_tests/kasan.c b/tools/testing/selftests/bpf/prog_tests/kasan.c
new file mode 100644
index 0000000000000..2b424767a0f3f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * Tests validating that KASAN reports are properly instrumented and
+ * generated on a wide variety of instructions. The running kernel needs
+ * kasan_multi_shot to run multiple kasan-generating subtests at once
+ */
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include <unpriv_helpers.h>
+#include "kasan.skel.h"
+#include "kasan_harden.skel.h"
+
+#define SUBTEST_NAME_MAX_LEN	128
+#define PROG_NAME_MAX_LEN	128
+
+#define MAX_LOG_SIZE		(8 * 1024)
+#define READ_CHUNK_SIZE		256
+
+#define KASAN_PATTERN_SLAB_UAF "BUG: KASAN: slab-use-after-free " \
+	"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_SLAB_OOB "BUG: KASAN: slab-out-of-bounds " \
+	"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_REPORT "%s of size %d at addr"
+
+static char klog_buffer[MAX_LOG_SIZE];
+static char record[MAX_LOG_SIZE];
+
+struct test_spec {
+	char *prog_type;
+	bool is_write;
+	bool only_32_or_64;
+	bool needs_load_acq_store_rel;
+	bool skip_multi_size_testing;
+	bool skip_on_stack_testing;
+	int run_size;
+	bool expect_no_report;
+	bool rnd_hi32;
+	bool is_oob;
+};
+
+struct kasan_write_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct test_ctx {
+	__u8  prog_tag[BPF_TAG_SIZE];
+	struct bpf_object *obj;
+	int *access_size;
+	bool skip_load_acq_store_rel;
+	struct bpf_program *prog;
+	char prog_name[SUBTEST_NAME_MAX_LEN];
+	int klog_fd;
+};
+
+static int open_kernel_logs(void)
+{
+	int fd;
+
+	fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+	return fd;
+}
+
+static void skip_kernel_logs(int fd)
+{
+	lseek(fd, 0, SEEK_END);
+}
+
+static int read_kernel_logs(int fd, char *buf, size_t max_len)
+{
+	size_t total = 0;
+	ssize_t n;
+
+	buf[0] = '\0';
+	while (1) {
+		char *msg, *eol;
+		size_t len;
+
+		n = read(fd, record, sizeof(record) - 1);
+		if (n == 0)
+			break;
+
+		if (n < 0) {
+			if (errno == EAGAIN)
+				break;
+			return n;
+		}
+		record[n] = '\0';
+
+		/*
+		 * Each kmsg record starts with some metadata, separated
+		 * from the actual content by a semi-colon
+		 */
+		msg = strchr(record, ';');
+		if (!msg)
+			continue;
+		msg++;
+		eol = strchr(msg, '\n');
+		if (eol)
+			*eol = '\0';
+
+		len = strlen(msg);
+		if (total + len + 2 > max_len)
+			break;
+		memcpy(buf + total, msg, len);
+		total += len;
+		buf[total++] = '\n';
+		buf[total] = '\0';
+	}
+
+	return total;
+}
+
+static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
+					     bool is_write, int size,
+					     bool is_oob)
+{
+	char access_log[READ_CHUNK_SIZE];
+	const char *pattern;
+	char *kasan_report_start;
+	int nsize;
+
+	pattern = is_oob ? KASAN_PATTERN_SLAB_OOB : KASAN_PATTERN_SLAB_UAF;
+	nsize = snprintf(access_log, READ_CHUNK_SIZE, pattern,
+			 ctx->prog_tag[0], ctx->prog_tag[1], ctx->prog_tag[2],
+			 ctx->prog_tag[3], ctx->prog_tag[4], ctx->prog_tag[5],
+			 ctx->prog_tag[6], ctx->prog_tag[7], ctx->prog_name);
+	if (!ASSERT_GE(nsize, 0, "format kasan access header line"))
+		return nsize;
+	/*
+	 * Searched kasan report is valid if
+	 * - it contains the expected kasan pattern
+	 * - the description of the faulty access is found somewhere
+	 *   after the header (not necessarily on the very next line,
+	 *   because other kernel messages may interleave)
+	 * - faulty access properties match the tested type and size
+	 */
+	kasan_report_start = strstr(buf, access_log);
+
+	if (!kasan_report_start)
+		return 1;
+
+	nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT,
+			 is_write ? "Write" : "Read", size);
+	if (!ASSERT_GE(nsize, 0, "format kasan access report line"))
+		return nsize;
+
+	if (!strstr(kasan_report_start, access_log))
+		return 1;
+
+	return 0;
+}
+
+static void exec_subtest(struct test_ctx *ctx, struct test_spec *test,
+			 int access_size, bool on_stack)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	struct bpf_prog_info info;
+	uint8_t buf[ETH_HLEN] = {0};
+	int ret, prog_fd;
+	__u32 info_len;
+
+	ctx->prog = bpf_object__find_program_by_name(ctx->obj,
+						     ctx->prog_name);
+	if (!ASSERT_OK_PTR(ctx->prog, "find test prog"))
+		return;
+
+	info_len = sizeof(info);
+	memset(&info, 0, info_len);
+	prog_fd = bpf_program__fd(ctx->prog);
+	if (!ASSERT_OK_FD(prog_fd, "get prog fd"))
+		return;
+	ret = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len);
+	if (!ASSERT_OK(ret, "fetch loaded program info"))
+		return;
+	memcpy(ctx->prog_tag, info.tag, BPF_TAG_SIZE);
+
+	skip_kernel_logs(ctx->klog_fd);
+
+	topts.sz = sizeof(struct bpf_test_run_opts);
+	topts.data_size_in = ETH_HLEN;
+	topts.data_in = buf;
+	if (ctx->access_size)
+		*ctx->access_size = access_size;
+	ret = bpf_prog_test_run_opts(bpf_program__fd(ctx->prog),
+				     &topts);
+	if (!ASSERT_OK(ret, "run prog"))
+		return;
+
+	ret = read_kernel_logs(ctx->klog_fd, klog_buffer, MAX_LOG_SIZE);
+	if (!ASSERT_GE(ret, 0, "read kernel logs"))
+		return;
+
+	ret = check_kasan_report_in_kernel_logs(klog_buffer, ctx,
+						test->is_write, access_size,
+						test->is_oob);
+	if (on_stack || test->expect_no_report)
+		ASSERT_NEQ(ret, 0, "no report should be generated");
+	else
+		ASSERT_OK(ret, "report should be generated");
+}
+
+static void run_subtest_with_size_and_location(struct test_ctx *ctx,
+					       struct test_spec *test,
+					       int access_size,
+					       bool on_stack)
+{
+	char subtest_name[SUBTEST_NAME_MAX_LEN];
+
+	if (test->skip_multi_size_testing) {
+		snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s%s",
+			 test->prog_type,
+			 test->skip_on_stack_testing ? "" :
+			 on_stack		     ? "_on_stack" :
+						       "_not_on_stack");
+	} else {
+		snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s_%d_%s",
+			 test->prog_type, access_size,
+			 on_stack ? "on_stack" : "not_on_stack");
+	}
+
+	snprintf(ctx->prog_name, PROG_NAME_MAX_LEN, "%s%s", test->prog_type,
+		 test->skip_on_stack_testing ? "" :
+		 on_stack		     ? "_on_stack" :
+					       "_not_on_stack");
+
+	if (!test__start_subtest(subtest_name))
+		return;
+
+	if (test->needs_load_acq_store_rel && ctx->skip_load_acq_store_rel) {
+		test__skip();
+		return;
+	}
+
+	exec_subtest(ctx, test, access_size, on_stack);
+}
+
+static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test,
+				  int size)
+{
+	run_subtest_with_size_and_location(ctx, test, size, false);
+	if (!test->skip_on_stack_testing)
+		run_subtest_with_size_and_location(ctx, test, size, true);
+}
+
+static void run_subtest(struct test_ctx *ctx, struct test_spec *test)
+{
+	if (test->skip_multi_size_testing) {
+		run_subtest_with_size(ctx, test, test->run_size);
+		return;
+	}
+
+	if (!test->only_32_or_64) {
+		run_subtest_with_size(ctx, test, 1);
+		run_subtest_with_size(ctx, test, 2);
+	}
+	run_subtest_with_size(ctx, test, 4);
+	run_subtest_with_size(ctx, test, 8);
+}
+
+static void run_blinding_subtest(void)
+{
+	struct test_spec blinding_spec = {
+		.prog_type = "st_blinded",
+		.is_write = true,
+	};
+	char bpf_jit_harden = '2';
+	struct kasan_harden *skel;
+	struct test_ctx *ctx;
+
+	if (!test__start_subtest("st_blinded"))
+		return;
+
+	ctx = calloc(1, sizeof(*ctx));
+	if (!ASSERT_OK_PTR(ctx, "alloc blinding ctx"))
+		return;
+	ctx->klog_fd = -1;
+
+	if (set_bpf_jit_harden(&bpf_jit_harden))
+		goto free_ctx;
+
+	skel = kasan_harden__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open and load blinded prog"))
+		goto restore;
+
+	ctx->klog_fd = open_kernel_logs();
+	if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+		goto destroy;
+
+	ctx->obj = skel->obj;
+	strncpy(ctx->prog_name, "st_blinded", PROG_NAME_MAX_LEN);
+
+	exec_subtest(ctx, &blinding_spec, 1, false);
+
+destroy:
+	close(ctx->klog_fd);
+	kasan_harden__destroy(skel);
+restore:
+	set_bpf_jit_harden(&bpf_jit_harden);
+free_ctx:
+	free(ctx);
+}
+
+static struct test_spec tests[] = {
+	{
+		.prog_type = "st",
+		.is_write = true
+	},
+	{
+		.prog_type = "stx",
+		.is_write = true
+	},
+	{
+		.prog_type = "ldx",
+		.is_write = false
+	},
+	{
+		.prog_type = "simple_atomic",
+		.is_write = true,
+		.only_32_or_64 = true
+	},
+	{
+		.prog_type = "simple_atomic_fetch",
+		.is_write = true,
+		.skip_multi_size_testing = true,
+		.run_size = 8,
+	},
+	{
+		.prog_type = "load_acquire",
+		.is_write = false,
+		.needs_load_acq_store_rel = true
+	},
+	{
+		.prog_type = "store_release",
+		.is_write = true,
+		.needs_load_acq_store_rel = true
+	},
+	{
+		.prog_type = "ldx_patched",
+		.is_write = false,
+		.skip_multi_size_testing = true,
+		.run_size = 4,
+		.rnd_hi32 = true
+	},
+	{
+		.prog_type = "verifier_paths_stack_and_non_stack",
+		.is_write = true,
+		.skip_multi_size_testing = true,
+		.skip_on_stack_testing = true,
+		.run_size = 1
+	},
+	{
+		.prog_type = "ldx_oob",
+		.is_write = false,
+		.skip_on_stack_testing = true,
+		.is_oob = true
+	},
+};
+
+void test_kasan(void)
+{
+	struct kasan_write_val val;
+	struct test_spec *test;
+	struct test_ctx *ctx;
+	struct kasan *skel;
+	__u32 key = 0;
+	int i, ret;
+
+	ctx = calloc(1, sizeof(struct test_ctx));
+	if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
+		return;
+
+	if (!is_jit_enabled() || !get_kasan_jit_enabled() ||
+	    !get_kasan_multi_shot_enabled()) {
+		test__skip();
+		goto end;
+	}
+
+	skel = kasan__open();
+	if (!ASSERT_OK_PTR(skel, "open prog"))
+		goto end;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		char prog_name[SUBTEST_NAME_MAX_LEN];
+		struct bpf_program *prog;
+
+		if (!tests[i].rnd_hi32)
+			continue;
+
+		snprintf(prog_name, SUBTEST_NAME_MAX_LEN, "%s_%s",
+			 tests[i].prog_type, "on_stack");
+		prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+		if (!ASSERT_OK_PTR(prog, "find rnd_hi32 on_stack prog"))
+			goto destroy;
+		bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+		snprintf(prog_name, SUBTEST_NAME_MAX_LEN, "%s_%s",
+			 tests[i].prog_type, "not_on_stack");
+		prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+		if (!ASSERT_OK_PTR(prog, "find rnd_hi32 not_on_stack prog"))
+			goto destroy;
+		bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+	}
+
+	if (!ASSERT_OK(kasan__load(skel), "load prog"))
+		goto destroy;
+
+	ctx->obj = skel->obj;
+	ctx->access_size = &skel->bss->access_size;
+	ctx->skip_load_acq_store_rel = skel->data->skip_load_acq_store_rel_tests;
+
+	ctx->klog_fd = open_kernel_logs();
+	if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+		goto destroy;
+
+	/* Fill map with recognizable values */
+	ret = bpf_map__lookup_elem(skel->maps.test_map, &key, sizeof(key),
+				   &val, sizeof(val), 0);
+	if (!ASSERT_OK(ret, "get map"))
+		goto close;
+	val.data_1 = 0xAA;
+	val.data_2 = 0xBBBB;
+	val.data_4 = 0xCCCCCCCC;
+	val.data_8 = 0xDDDDDDDDDDDDDDDD;
+	ret = bpf_map__update_elem(skel->maps.test_map, &key, sizeof(key),
+				   &val, sizeof(val), 0);
+	if (!ASSERT_OK(ret, "set map"))
+		goto close;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		test = &tests[i];
+		run_subtest(ctx, test);
+	}
+
+	/*
+	 * Blinding subtest is handled differently as it needs the
+	 * corresponding program to be loaded with bpf_jit_harden raised
+	 */
+	run_blinding_subtest();
+
+close:
+	close(ctx->klog_fd);
+destroy:
+	kasan__destroy(skel);
+end:
+	free(ctx);
+}
diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
new file mode 100644
index 0000000000000..ea29197646b0b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <stdbool.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct bpf_testmod_oob {
+	__u8 data;
+	union {
+		__u8 redzone_1;
+		__u16 redzone_2;
+		__u32 redzone_4;
+		__u64 redzone_8;
+	};
+};
+
+extern struct bpf_testmod_oob *bpf_testmod_oob_alloc(void) __ksym;
+extern void bpf_testmod_oob_free(struct bpf_testmod_oob *oob) __ksym;
+
+int access_size;
+
+struct kasan_test_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, struct kasan_test_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val.data_1 = 0xAA;
+		break;
+	case 2:
+		val.data_2 = 0xAA;
+		break;
+	case 4:
+		val.data_4 = 0xAA;
+		break;
+	case 8:
+		val.data_8 = 0xAA;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int st_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val->data_1 = 0xAA;
+		break;
+	case 2:
+		val->data_2 = 0xAA;
+		break;
+	case 4:
+		val->data_4 = 0xAA;
+		break;
+	case 8:
+		val->data_8 = 0xAA;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int stx_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val.data_1 = access_size;
+		break;
+	case 2:
+		val.data_2 = access_size;
+		break;
+	case 4:
+		val.data_4 = access_size;
+		break;
+	case 8:
+		val.data_8 = access_size;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int stx_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val->data_1 = access_size;
+		break;
+	case 2:
+		val->data_2 = access_size;
+		break;
+	case 4:
+		val->data_4 = access_size;
+		break;
+	case 8:
+		val->data_8 = access_size;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__sink(val.data_1);
+		break;
+	case 2:
+		__sink(val.data_2);
+		break;
+	case 4:
+		__sink(val.data_4);
+		break;
+	case 8:
+		__sink(val.data_8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__sink(val->data_1);
+		break;
+	case 2:
+		__sink(val->data_2);
+		break;
+	case 4:
+		__sink(val->data_4);
+		break;
+	case 8:
+		__sink(val->data_8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	__sink(val->data_4);
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	__sink(val.data_4);
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 4:
+		__sync_fetch_and_add(&val.data_4, 4);
+		break;
+	case 8:
+		__sync_fetch_and_add(&val.data_8, 8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 4:
+		__sync_fetch_and_add(&val->data_4, 4);
+		break;
+	case 8:
+		__sync_fetch_and_add(&val->data_8, 8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_fetch_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	__sync_fetch_and_or(&val.data_8, 8);
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_fetch_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	__sync_fetch_and_or(&val->data_8, 8);
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL
+bool skip_load_acq_store_rel_tests SEC(".data") = 0;
+
+SEC("tcx/ingress")
+int load_acquire_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_load_n(&val.data_1, __ATOMIC_ACQUIRE);
+		break;
+	case 2:
+		__atomic_load_n(&val.data_2, __ATOMIC_ACQUIRE);
+		break;
+	case 4:
+		__atomic_load_n(&val.data_4, __ATOMIC_ACQUIRE);
+		break;
+	case 8:
+		__atomic_load_n(&val.data_8, __ATOMIC_ACQUIRE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int load_acquire_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_load_n(&val->data_1, __ATOMIC_ACQUIRE);
+		break;
+	case 2:
+		__atomic_load_n(&val->data_2, __ATOMIC_ACQUIRE);
+		break;
+	case 4:
+		__atomic_load_n(&val->data_4, __ATOMIC_ACQUIRE);
+		break;
+	case 8:
+		__atomic_load_n(&val->data_8, __ATOMIC_ACQUIRE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_store_n(&val.data_1, 0xAA, __ATOMIC_RELEASE);
+		break;
+	case 2:
+		__atomic_store_n(&val.data_2, 0xBBBB, __ATOMIC_RELEASE);
+		break;
+	case 4:
+		__atomic_store_n(&val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+		break;
+	case 8:
+		__atomic_store_n(&val.data_8, 0xDDDDDDDDDDDDDDDD,
+				 __ATOMIC_RELEASE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_store_n(&val->data_1, 0xAA, __ATOMIC_RELEASE);
+		break;
+	case 2:
+		__atomic_store_n(&val->data_2, 0xBBBB, __ATOMIC_RELEASE);
+		break;
+	case 4:
+		__atomic_store_n(&val->data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+		break;
+	case 8:
+		__atomic_store_n(&val->data_8, 0xDDDDDDDDDDDDDDDD,
+				 __ATOMIC_RELEASE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+#else
+bool skip_load_acq_store_rel_tests SEC(".data") = 1;
+#endif
+
+SEC("tcx/ingress")
+int verifier_paths_stack_and_non_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val stack_val = {};
+	struct kasan_test_val *val;
+	void *ptr;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	if (access_size)
+		ptr = val;
+	else
+		ptr = &stack_val;
+
+	bpf_kfunc_kasan_poison(val, sizeof(*val));
+	*(__u8 *)ptr = 0xAA;
+	bpf_kfunc_kasan_unpoison(val, sizeof(*val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_oob(struct __sk_buff *skb)
+{
+	struct bpf_testmod_oob *val;
+	struct kasan_test_val volatile tmp;
+
+	val = bpf_testmod_oob_alloc();
+	if (!val)
+		return 0;
+
+	switch (access_size) {
+	case 1:
+		tmp.data_1 = (__u8)val->redzone_1;
+		break;
+	case 2:
+		tmp.data_2 = (__u16)val->redzone_2;
+		break;
+	case 4:
+		tmp.data_4 = (__u32)val->redzone_4;
+		break;
+	case 8:
+		tmp.data_8 = (__u64)val->redzone_8;
+		break;
+	}
+	bpf_testmod_oob_free(val);
+	return tmp.data_1;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kasan_harden.c b/tools/testing/selftests/bpf/progs/kasan_harden.c
new file mode 100644
index 0000000000000..a2756bbfd5292
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct kasan_test_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, struct kasan_test_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_blinded(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	val->data_1 = 0xAA;
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 9366a3c578f13..8e72a3d1b9a23 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -75,6 +75,16 @@ union bpf_testmod_union_arg_2 {
 	struct bpf_testmod_struct_arg_2 arg;
 };
 
+struct bpf_testmod_oob {
+	__u8 data;
+	union {
+		__u8 redzone_1;
+		__u16 redzone_2;
+		__u32 redzone_4;
+		__u64 redzone_8;
+	};
+};
+
 __bpf_hook_start();
 
 noinline int
@@ -336,6 +346,47 @@ __bpf_kfunc void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member
 	 */
 }
 
+#ifdef CONFIG_BPF_JIT_KASAN
+
+extern void kasan_poison(const void *addr, size_t size, u8 value, bool init);
+
+#define KASAN_SLAB_FREE 0xFB
+
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz)
+{
+	kasan_poison(mem, mem__sz, KASAN_SLAB_FREE, false);
+}
+
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz)
+{
+	kasan_poison(mem, mem__sz, 0x00, false);
+}
+#else
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) { }
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) { }
+#endif
+
+__bpf_kfunc struct bpf_testmod_oob *bpf_testmod_oob_alloc(void)
+{
+	struct bpf_testmod_oob *p;
+
+	/*
+	 * Only allocate size of data (and so, voluntarily use kmalloc
+	 * instead of kmalloc_obj), not the rest of the structure, so
+	 * that programs under test trying to access the rest of the
+	 * structure trigger OoB accesses
+	 */
+	p = kmalloc(sizeof(p->data), GFP_ATOMIC);
+	if (!p)
+		return NULL;
+	return p;
+}
+
+__bpf_kfunc void bpf_testmod_oob_free(struct bpf_testmod_oob *oob)
+{
+	kfree(oob);
+}
+
 __bpf_kfunc struct bpf_testmod_ctx *
 bpf_testmod_ctx_create(int *err)
 {
@@ -869,6 +920,10 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_multislot)
 BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
 BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison)
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_unpoison)
+BTF_ID_FLAGS(func, bpf_testmod_oob_alloc, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_testmod_oob_free, KF_RELEASE)
 BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
 
 BTF_ID_LIST(bpf_testmod_dtor_ids)
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index c970e7793dfcb..737f668b35e23 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -519,6 +519,38 @@ bool is_jit_enabled(void)
 	return enabled;
 }
 
+int set_bpf_jit_harden(char *level)
+{
+	char old_level;
+	int err = -1;
+	int fd = -1;
+
+	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
+	if (fd < 0)
+		return -1;
+
+	err = read(fd, &old_level, 1);
+	if (err != 1) {
+		err = -1;
+		goto end;
+	}
+
+	lseek(fd, 0, SEEK_SET);
+
+	err = write(fd, level, 1);
+	if (err != 1) {
+		err = -1;
+		goto end;
+	}
+
+	err = 0;
+	*level = old_level;
+end:
+	if (fd >= 0)
+		close(fd);
+	return err;
+}
+
 int stack_mprotect(void)
 {
 	void *buf;
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index 2edc6fb7fc521..e00642afe86f4 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -59,6 +59,7 @@ struct bpf_insn;
 int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt);
 int testing_prog_flags(void);
 bool is_jit_enabled(void);
+int set_bpf_jit_harden(char *level);
 int stack_mprotect(void);
 
 #endif /* __TESTING_HELPERS_H */
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index f997d7ec8fd08..2c8c5edb87517 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -72,8 +72,8 @@ static int config_contains(const char *pat)
 
 static bool cmdline_contains(const char *pat)
 {
+	int fd, cnt, ret = false;
 	char cmdline[4096], *c;
-	int fd, ret = false;
 
 	fd = open("/proc/cmdline", O_RDONLY);
 	if (fd < 0) {
@@ -81,14 +81,15 @@ static bool cmdline_contains(const char *pat)
 		return false;
 	}
 
-	if (read(fd, cmdline, sizeof(cmdline) - 1) < 0) {
+	cnt = read(fd, cmdline, sizeof(cmdline) - 1);
+	if (cnt < 0) {
 		perror("read /proc/cmdline");
 		goto out;
 	}
 
-	cmdline[sizeof(cmdline) - 1] = '\0';
+	cmdline[cnt] = '\0';
 	for (c = strtok(cmdline, " \n"); c; c = strtok(NULL, " \n")) {
-		if (strncmp(c, pat, strlen(c)))
+		if (strcmp(c, pat))
 			continue;
 		ret = true;
 		break;
@@ -142,3 +143,13 @@ bool get_unpriv_disabled(void)
 	}
 	return mitigations_off;
 }
+
+bool get_kasan_jit_enabled(void)
+{
+	return config_contains("CONFIG_BPF_JIT_KASAN=y") == 1;
+}
+
+bool get_kasan_multi_shot_enabled(void)
+{
+	return cmdline_contains("kasan_multi_shot");
+}
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h
index 151f673296652..a7ceb51577cdc 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.h
+++ b/tools/testing/selftests/bpf/unpriv_helpers.h
@@ -5,3 +5,5 @@
 #define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled"
 
 bool get_unpriv_disabled(void);
+bool get_kasan_jit_enabled(void);
+bool get_kasan_multi_shot_enabled(void);

3/2 2026/08/21 23:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24005
							output: 38
							thoughts: 709
						

Thoughts:
**My Assessment of the KASAN BPF JIT Patch Series**

Okay, so I'm looking at this patch series that brings KASAN support to the BPF JIT compiler on x86_64. It looks pretty involved; touching a good chunk of code, specifically `arch/x86/Kconfig`, `arch/x86/net/bpf_jit_comp.c`, `include/linux/bpf_verifier.h`, `kernel/bpf/Kconfig`, `kernel/bpf/fixups.c`, `kernel/bpf/verifier.c`, plus some selftests. This definitely warrants serious attention.

The core idea is to introduce a `CONFIG_BPF_JIT_KASAN` config and a `CONFIG_HAVE_EBPF_JIT_KASAN` flag. The bulk of the work seems to be around modifying `do_jit` in `arch/x86/net/bpf_jit_comp.c` to sprinkle in those KASAN checks before memory accesses. They're using the standard KASAN instrumentation: `__asan_load1`, `__asan_store1`, etc. I'll need to dig into how those interact with the JIT's code generation. Also, modifications to `bpf_patch_insn_data` and `check_mem_access` in the verifier to track a `non_stack_access` flag sound like important changes to consider.

Given that this touches core kernel components like the BPF JIT and verifier, this is definitely a good target for fuzzing. I need to make sure I have all the necessary configs enabled. Let's see... I'll need `BPF_JIT_KASAN`, `KASAN_GENERIC`, `KASAN_VMALLOC`, and `BPF_JIT`. Good to know they've laid out the dependencies in `kernel/bpf/Kconfig`.

Now, let's zoom in on the functions most likely to be exposed. From `arch/x86/net/bpf_jit_comp.c`, `do_jit` is modified, which is a hot path for compilation, so that's crucial. `emit_kasan_check` and `emit_atomic_ld_st` look static, so probably not good fuzzing entry points. The main entry point, `bpf_int_jit_compile` looks unmodified, which seems unexpected, but that's fine. In `kernel/bpf/fixups.c`, `bpf_patch_insn_data` is touched and that seems like a good candidate for deeper exploration. Lastly, `check_mem_access` is static, so I can probably ignore that.

Okay, so I'm thinking of focusing on `bpf_patch_insn_data`, and `bpf_int_jit_compile`. Time to see where those functions live and how they're called. I'll search for `bpf_patch_insn_data` in `kernel/bpf/fixups.c`.



4/2 2026/08/21 23:00 tool grepper
0m Args:
{
  "Expression": "bpf_patch_insn_data",
  "PathPrefix": "kernel/bpf/fixups.c"
}

Results:
{
  "Output": "kernel/bpf/fixups.c=327=static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)\n--\nkernel/bpf/fixups.c-340-\nkernel/bpf/fixups.c:341:struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,\nkernel/bpf/fixups.c-342-\t\t\t\t     const struct bpf_insn *patch, u32 len)\n--\nkernel/bpf/fixups.c=678=int bpf_opt_subreg_zext_lo32_rnd_hi32(struct bpf_verifier_env *env,\n--\nkernel/bpf/fixups.c-762-apply_patch_buffer:\nkernel/bpf/fixups.c:763:\t\tnew_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len);\nkernel/bpf/fixups.c-764-\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c=780=int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)\n--\nkernel/bpf/fixups.c-807-\t\t\tinsn_buf[cnt++] = env-\u003eprog-\u003einsnsi[0];\nkernel/bpf/fixups.c:808:\t\t\tnew_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt);\nkernel/bpf/fixups.c-809-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-830-\t\t} else if (cnt) {\nkernel/bpf/fixups.c:831:\t\t\tnew_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt);\nkernel/bpf/fixups.c-832-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-863-\t\t\tcnt = patch - insn_buf;\nkernel/bpf/fixups.c:864:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-865-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-936-\t\t\tcnt = patch - insn_buf;\nkernel/bpf/fixups.c:937:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-938-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1070-patch_insn_buf:\nkernel/bpf/fixups.c:1071:\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1072-\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c=1518=static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *patch, int len)\n--\nkernel/bpf/fixups.c-1530-\t * ones for the hidden subprog. Hence all of the adjustment operations\nkernel/bpf/fixups.c:1531:\t * in bpf_patch_insn_data are no-ops.\nkernel/bpf/fixups.c-1532-\t */\nkernel/bpf/fixups.c:1533:\tprog = bpf_patch_insn_data(env, env-\u003eprog-\u003elen - 1, patch, len);\nkernel/bpf/fixups.c-1534-\tif (!prog)\n--\nkernel/bpf/fixups.c=1547=int bpf_do_misc_fixups(struct bpf_verifier_env *env)\n--\nkernel/bpf/fixups.c-1614-\nkernel/bpf/fixups.c:1615:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1616-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1705-\nkernel/bpf/fixups.c:1706:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1707-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1735-\t\t\tcnt = patch - insn_buf;\nkernel/bpf/fixups.c:1736:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1737-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1755-\nkernel/bpf/fixups.c:1756:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1757-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1808-\nkernel/bpf/fixups.c:1809:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1810-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1853-\nkernel/bpf/fixups.c:1854:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1855-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1874-\nkernel/bpf/fixups.c:1875:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1876-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1901-\t\t\t * with that same half + mov64_percpu_reg insn.\nkernel/bpf/fixups.c:1902:\t\t\t * All because bpf_patch_insn_data() can only\nkernel/bpf/fixups.c-1903-\t\t\t * replace one 8-byte insn, which does not work\n--\nkernel/bpf/fixups.c-1911-\t\t\ti++;\nkernel/bpf/fixups.c:1912:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1913-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-1932-\nkernel/bpf/fixups.c:1933:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-1934-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2019-\t\t\tcnt = 3;\nkernel/bpf/fixups.c:2020:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2021-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2052-\nkernel/bpf/fixups.c:2053:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2054-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2071-\nkernel/bpf/fixups.c:2072:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2073-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2114-\nkernel/bpf/fixups.c:2115:\t\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta,\nkernel/bpf/fixups.c-2116-\t\t\t\t\t\t\t       insn_buf, cnt);\n--\nkernel/bpf/fixups.c-2197-\nkernel/bpf/fixups.c:2198:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf,\nkernel/bpf/fixups.c-2199-\t\t\t\t\t\t       cnt);\n--\nkernel/bpf/fixups.c-2227-#endif\nkernel/bpf/fixups.c:2228:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2229-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2246-\nkernel/bpf/fixups.c:2247:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2248-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2280-\nkernel/bpf/fixups.c:2281:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2282-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2312-\nkernel/bpf/fixups.c:2313:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2314-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2338-\nkernel/bpf/fixups.c:2339:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2340-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2354-\nkernel/bpf/fixups.c:2355:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1);\nkernel/bpf/fixups.c-2356-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2409-\nkernel/bpf/fixups.c:2410:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2411-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2427-\nkernel/bpf/fixups.c:2428:\t\t\tnew_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);\nkernel/bpf/fixups.c-2429-\t\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2495-\nkernel/bpf/fixups.c:2496:\t\tnew_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt);\nkernel/bpf/fixups.c-2497-\t\tif (!new_prog)\n--\nkernel/bpf/fixups.c-2503-\t\t * to insn after BPF_ST that inits may_goto count.\nkernel/bpf/fixups.c:2504:\t\t * Adjustment will succeed because bpf_patch_insn_data() didn't fail.\nkernel/bpf/fixups.c-2505-\t\t */\n--\nkernel/bpf/fixups.c=2533=static struct bpf_prog *inline_bpf_loop(struct bpf_verifier_env *env,\n--\nkernel/bpf/fixups.c-2594-\t*total_cnt = cnt;\nkernel/bpf/fixups.c:2595:\tnew_prog = bpf_patch_insn_data(env, position, insn_buf, cnt);\nkernel/bpf/fixups.c-2596-\tif (!new_prog)\n"
}

5/2 2026/08/21 23:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28742
							output: 44
							thoughts: 51
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own mental process, given my expertise:

**Checking Out `bpf_patch_insn_data` & Jumping to `bpf_int_jit_compile`**

Right, `bpf_patch_insn_data` feels like a solid starting point for this investigation. It likely plays a crucial role in, well, patching instructions, which suggests it's involved in the code modification stage within the BPF JIT compilation process. That immediate suggests something about how specific data gets injected into the compiled BPF code.

To get a clearer picture of how this function fits into the bigger picture, and especially how those instructions are *generated* in the first place, I think the next logical step is to delve into `bpf_int_jit_compile` function. Since we are on an x86 system, I know that function should be in `arch/x86/net/bpf_jit_comp.c`. That should give me the core logic for the JIT compiler itself, hopefully illustrating where and how `bpf_patch_insn_data` is being used and why it's there. Understanding how the JIT compiler takes the BPF bytecode and translates it to x86 assembly is key to understand how the instruction data gets added or changed. This is where the real meat of the transformation happens. I'm expecting this will provide the context I need to really understand what's going on. Let's see...



6/2 2026/08/21 23:00 tool grepper
0m Args:
{
  "Expression": "bpf_int_jit_compile",
  "PathPrefix": "arch/x86/net/bpf_jit_comp.c"
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=4124=struct x64_jit_data {\n--\narch/x86/net/bpf_jit_comp.c-4135-\narch/x86/net/bpf_jit_comp.c:4136:struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\narch/x86/net/bpf_jit_comp.c-4137-{\n"
}

7/2 2026/08/21 23:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29306
							output: 39
							thoughts: 48
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm already quite familiar with BPF and the x86 architecture.

**Let's Dig into the JIT Compilation**

Alright, so `bpf_int_jit_compile` is where the magic happens – the main entry point for kicking off BPF JIT compilation. My next logical step is to delve into the `do_jit` function. I remember that's in `arch/x86/net/bpf_jit_comp.c`, so I'll go there and see how things are set up. Time to check how the BPF bytecode is translated to x86 instructions. I need to understand the specifics of that compilation. Hopefully, `do_jit` will illuminate the register allocation, instruction selection, and other x86-specific optimizations. I'm keen to see if they're handling things as efficiently as possible. I'm especially interested in how they're dealing with edge cases and potential performance bottlenecks.



8/2 2026/08/21 23:00 tool grepper
0m Args:
{
  "Expression": "do_jit",
  "PathPrefix": "arch/x86/net/bpf_jit_comp.c"
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=1863=static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,\n--\narch/x86/net/bpf_jit_comp.c-1900-\narch/x86/net/bpf_jit_comp.c:1901:static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,\narch/x86/net/bpf_jit_comp.c-1902-\t\t  u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)\n--\narch/x86/net/bpf_jit_comp.c=4136=struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\n--\narch/x86/net/bpf_jit_comp.c-4213-\t\t\tpadding = true;\narch/x86/net/bpf_jit_comp.c:4214:\t\tproglen = do_jit(env, prog, addrs, image, rw_image, oldproglen,\narch/x86/net/bpf_jit_comp.c-4215-\t\t\t\t \u0026ctx, padding);\n"
}

9/2 2026/08/21 23:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29966
							output: 33
							thoughts: 31
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Checking `check_mem_access` in the BPF Verifier**

Alright, so `do_jit` is declared as static. That's a key detail to keep in mind, and it's probably relevant to how it's being called and scoped. It suggests that it's tightly coupled to a specific compilation unit. Now, the next logical step, given the context, is to delve into the heart of the BPF verifier. I need to understand how memory accesses are scrutinized in relation to the JIT process. So, let's drill down into `kernel/bpf/verifier.c` and specifically pinpoint the implementation of `check_mem_access`. I need to see how memory access permissions and bounds are validated within the JIT environment, because the static nature of `do_jit` could very well be a security measure, an optimization constraint, or both, in the verification of potentially malicious or malformed BPF programs. This seems like it could lead to interesting insights in terms of the jit's interaction with memory and its potential for exploitation.



10/2 2026/08/21 23:01 tool grepper
0m Args:
{
  "Expression": "check_mem_access",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=58=static bool bpf_global_percpu_ma_set;\n--\nkernel/bpf/verifier.c-105- * can be: PTR_TO_MAP_VALUE, PTR_TO_CTX, PTR_TO_STACK, PTR_TO_SOCKET. These are\nkernel/bpf/verifier.c:106: * four pointer types recognized by check_mem_access() function.\nkernel/bpf/verifier.c-107- *\n--\nkernel/bpf/verifier.c=836=static bool is_dynptr_reg_valid_uninit(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\n--\nkernel/bpf/verifier.c-846-\t * error because this just means the stack state hasn't been updated yet.\nkernel/bpf/verifier.c:847:\t * We will do check_mem_access to check and update stack bounds later.\nkernel/bpf/verifier.c-848-\t */\n--\nkernel/bpf/verifier.c=995=static bool is_iter_reg_valid_uninit(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1001-\t/* For -ERANGE (i.e. spi not falling into allocated stack slots), we\nkernel/bpf/verifier.c:1002:\t * will do check_mem_access to check and update stack bounds later, so\nkernel/bpf/verifier.c-1003-\t * return true for that case.\n--\nkernel/bpf/verifier.c=1155=static bool is_irq_flag_reg_valid_uninit(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\n--\nkernel/bpf/verifier.c-1161-\t/* For -ERANGE (i.e. spi not falling into allocated stack slots), we\nkernel/bpf/verifier.c:1162:\t * will do check_mem_access to check and update stack bounds later, so\nkernel/bpf/verifier.c-1163-\t * return true for that case.\n--\nkernel/bpf/verifier.c=3497=static void scrub_special_slot(struct bpf_func_state *state, int spi)\n--\nkernel/bpf/verifier.c-3509-/* check_stack_{read,write}_fixed_off functions track spill/fill of registers,\nkernel/bpf/verifier.c:3510: * stack boundary and alignment are checked in check_mem_access()\nkernel/bpf/verifier.c-3511- */\n--\nkernel/bpf/verifier.c=4279=static int check_map_access_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-4300-/* check read/write into memory region (e.g., map value, ringbuf sample, etc) */\nkernel/bpf/verifier.c:4301:static int __check_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\nkernel/bpf/verifier.c-4302-\t\t\t      int off, int size, u32 mem_size,\n--\nkernel/bpf/verifier.c=4339=static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-4373-\nkernel/bpf/verifier.c:4374:\terr = __check_mem_access(env, reg, argno, reg_smin(reg) + off, size,\nkernel/bpf/verifier.c-4375-\t\t\t\t mem_size, zero_size_allowed);\n--\nkernel/bpf/verifier.c-4402-\nkernel/bpf/verifier.c:4403:\terr = __check_mem_access(env, reg, argno, reg_umax(reg) + off, size,\nkernel/bpf/verifier.c-4404-\t\t\t\t mem_size, zero_size_allowed);\n--\nkernel/bpf/verifier.c=4834=static int check_packet_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int off,\n--\nkernel/bpf/verifier.c-4847-\nkernel/bpf/verifier.c:4848:\t/* __check_mem_access has made sure \"off + size - 1\" is within u16.\nkernel/bpf/verifier.c-4849-\t * reg_umax(reg) can't be bigger than MAX_PACKET_OFF which is 0xffff,\nkernel/bpf/verifier.c-4850-\t * otherwise find_good_pkt_pointers would have refused to set range info\nkernel/bpf/verifier.c:4851:\t * that __check_mem_access would have rejected this pkt access.\nkernel/bpf/verifier.c-4852-\t * Therefore, \"off + reg_umax(reg) + size - 1\" won't overflow u32.\n--\nkernel/bpf/verifier.c=6314=static int check_map_mem_read(struct bpf_verifier_env *env, struct bpf_reg_state *reg, int off,\n--\nkernel/bpf/verifier.c-6364- */\nkernel/bpf/verifier.c:6365:static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct bpf_reg_state *reg, argno_t argno,\nkernel/bpf/verifier.c-6366-\t\t\t    int off, int bpf_size, enum bpf_access_type t,\n--\nkernel/bpf/verifier.c=6618=static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-6652-\tbpf_diag_mod_begin(env, \u0026regs[insn-\u003edst_reg], NULL, BPF_DIAG_MOD_WRITE);\nkernel/bpf/verifier.c:6653:\terr = check_mem_access(env, env-\u003einsn_idx, regs + insn-\u003esrc_reg, argno_from_reg(insn-\u003esrc_reg), insn-\u003eoff,\nkernel/bpf/verifier.c-6654-\t\t\t       BPF_SIZE(insn-\u003ecode), BPF_READ, insn-\u003edst_reg,\n--\nkernel/bpf/verifier.c=6665=static int check_store_reg(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-6694-\t/* Check if (dst_reg + off) is writeable. */\nkernel/bpf/verifier.c:6695:\terr = check_mem_access(env, env-\u003einsn_idx, regs + insn-\u003edst_reg, argno_from_reg(insn-\u003edst_reg), insn-\u003eoff,\nkernel/bpf/verifier.c-6696-\t\t\t       BPF_SIZE(insn-\u003ecode), BPF_WRITE, insn-\u003esrc_reg,\n--\nkernel/bpf/verifier.c=6703=static int check_atomic_rmw(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-6763-\t */\nkernel/bpf/verifier.c:6764:\terr = check_mem_access(env, env-\u003einsn_idx, dst_reg, argno_from_reg(insn-\u003edst_reg), insn-\u003eoff,\nkernel/bpf/verifier.c-6765-\t\t\t       BPF_SIZE(insn-\u003ecode), BPF_READ, -1, true, false);\n--\nkernel/bpf/verifier.c-6767-\t\tbpf_diag_mod_begin(env, cur_regs(env) + load_reg, NULL, BPF_DIAG_MOD_WRITE);\nkernel/bpf/verifier.c:6768:\t\terr = check_mem_access(env, env-\u003einsn_idx, dst_reg, argno_from_reg(insn-\u003edst_reg),\nkernel/bpf/verifier.c-6769-\t\t\t\t       insn-\u003eoff, BPF_SIZE(insn-\u003ecode),\n--\nkernel/bpf/verifier.c-6780-\t/* Check whether we can write into the same memory. */\nkernel/bpf/verifier.c:6781:\terr = check_mem_access(env, env-\u003einsn_idx, dst_reg, argno_from_reg(insn-\u003edst_reg), insn-\u003eoff,\nkernel/bpf/verifier.c-6782-\t\t\t       BPF_SIZE(insn-\u003ecode), BPF_WRITE, -1, true, false);\n--\nkernel/bpf/verifier.c=7585=static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-7631-\t\tfor (i = 0; i \u003c BPF_DYNPTR_SIZE; i += 8) {\nkernel/bpf/verifier.c:7632:\t\t\terr = check_mem_access(env, insn_idx, reg, argno,\nkernel/bpf/verifier.c-7633-\t\t\t\t\t       i, BPF_DW, BPF_WRITE, -1, false, false);\n--\nkernel/bpf/verifier.c=7724=static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int insn_idx,\n--\nkernel/bpf/verifier.c-7774-\t\tfor (i = 0; i \u003c nr_slots * 8; i += BPF_REG_SIZE) {\nkernel/bpf/verifier.c:7775:\t\t\terr = check_mem_access(env, insn_idx, reg, argno,\nkernel/bpf/verifier.c-7776-\t\t\t\t\t       i, BPF_DW, BPF_WRITE, -1, false, false);\n--\nkernel/bpf/verifier.c=10769=static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-10874-\tfor (i = 0; i \u003c meta.arg_raw_mem.size; i++) {\nkernel/bpf/verifier.c:10875:\t\terr = check_mem_access(env, insn_idx, regs + meta.arg_raw_mem.regno,\nkernel/bpf/verifier.c-10876-\t\t\t\t       argno_from_reg(meta.arg_raw_mem.regno), i, BPF_B,\n--\nkernel/bpf/verifier.c-11124-\t\t\t\t * it will confuse the check of PTR_TO_BTF_ID in\nkernel/bpf/verifier.c:11125:\t\t\t\t * check_mem_access().\nkernel/bpf/verifier.c-11126-\t\t\t\t */\n--\nkernel/bpf/verifier.c=12110=static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-12143-\nkernel/bpf/verifier.c:12144:\t\terr = check_mem_access(env, env-\u003einsn_idx, reg, argno, 0, BPF_DW,\nkernel/bpf/verifier.c-12145-\t\t\t\t       BPF_WRITE, -1, false, false);\n--\nkernel/bpf/verifier.c=18105=static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)\n--\nkernel/bpf/verifier.c-18142-\nkernel/bpf/verifier.c:18143:\t\terr = check_mem_access(env, env-\u003einsn_idx, cur_regs(env) + insn-\u003edst_reg, argno_from_reg(insn-\u003edst_reg),\nkernel/bpf/verifier.c-18144-\t\t\t\t       insn-\u003eoff, BPF_SIZE(insn-\u003ecode),\n"
}

11/2 2026/08/21 23:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33427
							output: 27
							thoughts: 401
						

Thoughts:
**My Initial BPF Function Analysis**

Okay, so I'm honing in on the BPF JIT compilation and patching process. I think `bpf_patch_insn_data` is a prime target for deeper inspection, and also `bpf_int_jit_compile`, and `bpf_prog_select_runtime`. Let's confirm that `bpf_patch_insn_data` isn't static. Yep, it isn't. Good. And `bpf_int_jit_compile` also isn't static. Now, let me see... Are there any related static functions that might be interesting? `do_jit` is static. Alright, what else? `adjust_insn_aux_data` is static. `is_mem_insn` is static, as is `emit_kasan_check`, `emit_atomic_ld_st`, and `check_mem_access`.

So, the non-static functions, `bpf_patch_insn_data` and `bpf_int_jit_compile`, are likely where I need to focus initially.

Let me now re-examine the relevant configuration options. Okay, let's see, `BPF_JIT_KASAN`, `KASAN_GENERIC`, `KASAN_VMALLOC`, and `BPF_JIT` all seem relevant. But, ah, `BPF_JIT_KASAN` has a dependency – it relies on `HAVE_EBPF_JIT_KASAN`. And `HAVE_EBPF_JIT_KASAN` is selected by `X86_64`. So I just need to make sure `BPF_JIT_KASAN`, `KASAN_GENERIC`, `KASAN_VMALLOC`, and `BPF_JIT` are set. Let's make sure I didn't miss anything else related to the configurations. Ah, yes! Generally, `CONFIG_KASAN` is required for `KASAN_GENERIC`. Let me double-check `KASAN_GENERIC` in `lib/Kconfig.kasan`.



12/2 2026/08/21 23:01 tool grepper
0m Args:
{
  "Expression": "config KASAN_GENERIC",
  "PathPrefix": ""
}

Results:
{
  "Output": "lib/Kconfig.kasan=73=choice\n--\nlib/Kconfig.kasan-88-\nlib/Kconfig.kasan:89:config KASAN_GENERIC\nlib/Kconfig.kasan-90-\tbool \"Generic KASAN\"\n"
}

13/2 2026/08/21 23:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34405
							output: 27
							thoughts: 43
						

Thoughts:
**My KASAN Configuration Thoughts**

Okay, so I'm looking at configuring KASAN. It seems pretty straightforward; `KASAN_GENERIC` is presented as an option, so I'll definitely enable that. It's a fundamental piece, and I'll need it. Now, the next step is to make sure that KASAN works for vmalloc regions. I need to take a look at `KASAN_VMALLOC` and confirm it's enabled to ensure full coverage. It would be a problem if it was not. Let's see...



14/2 2026/08/21 23:01 tool grepper
0m Args:
{
  "Expression": "config KASAN_VMALLOC",
  "PathPrefix": ""
}

Results:
{
  "Output": "lib/Kconfig.kasan=163=config KASAN_STACK\n--\nlib/Kconfig.kasan-185-\nlib/Kconfig.kasan:186:config KASAN_VMALLOC\nlib/Kconfig.kasan-187-\tbool \"Check accesses to vmalloc allocations\"\n"
}

15/2 2026/08/21 23:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34688
							output: 36
							thoughts: 345
						

Thoughts:
**My Initial Thoughts on BPF JIT and KASAN Integration**

Okay, so I'm looking at these kernel configuration options: `KASAN`, `KASAN_GENERIC`, `KASAN_VMALLOC`, `BPF_JIT`, and `BPF_JIT_KASAN`. My immediate thought is, "Let's confirm if `BPF_JIT_KASAN` is enabled by default." The config shows it is, which is good to know, but I'll make a note of it anyway.

Now, let's shift gears and look at the relevant code. First up, I'm checking `bpf_patch_insn_data` in `kernel/bpf/fixups.c`. Yup, it's a global function, which is what I'd expect. Now I'll check `bpf_int_jit_compile` in `arch/x86/net/bpf_jit_comp.c`. That's also a global function, as anticipated.

Finally, I need to check `bpf_prog_select_runtime` in `kernel/bpf/core.c`.



16/2 2026/08/21 23:01 tool grepper
0m Args:
{
  "Expression": "bpf_prog_select_runtime",
  "PathPrefix": "kernel/bpf/core.c"
}

Results:
{
  "Output": "kernel/bpf/core.c=2633=static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\n--\nkernel/bpf/core.c-2663-\nkernel/bpf/core.c:2664:struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp,\nkernel/bpf/core.c-2665-\t\t\t\t\t   int *err)\n--\nkernel/bpf/core.c-2716-/**\nkernel/bpf/core.c:2717: *\tbpf_prog_select_runtime - select exec runtime for BPF program\nkernel/bpf/core.c-2718- *\t@fp: bpf_prog populated with BPF program\n--\nkernel/bpf/core.c-2726- */\nkernel/bpf/core.c:2727:struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)\nkernel/bpf/core.c-2728-{\nkernel/bpf/core.c:2729:\treturn __bpf_prog_select_runtime(NULL, fp, err);\nkernel/bpf/core.c-2730-}\nkernel/bpf/core.c:2731:EXPORT_SYMBOL_GPL(bpf_prog_select_runtime);\nkernel/bpf/core.c-2732-\n"
}

17/2 2026/08/21 23:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35721
							output: 209
							thoughts: 378
						

Thoughts:
**My Fuzzing Strategy: BPF JIT with KASAN Support**

Okay, let's break this down. My analysis tells me this is definitely worth fuzzing. We have some important changes related to KASAN being integrated into the BPF JIT compiler for x86_64. Specifically, it seems the `do_jit` function in `arch/x86/net/bpf_jit_comp.c` is being modified to include KASAN checks before all memory accesses – loads, stores, atomics, the works. That's a huge potential attack surface.

Furthermore, the verifier code, specifically `bpf_patch_insn_data` and the memory access checking logic, is being modified too. It's tracking these memory accesses that aren't on the stack. The verifier's role in the security of BPF programs is critical; any weakness there is ripe for exploitation. Both `bpf_patch_insn_data` and `bpf_int_jit_compile` are excellent focus symbols for me. Actually, come to think of it, `bpf_prog_select_runtime` seems like a good choice to start with too.

The really interesting thing is the configuration. It seems `BPF_JIT_KASAN` is the new config, but naturally depends on `KASAN`, `KASAN_GENERIC`, and `KASAN_VMALLOC`. I'll want to enable all those for fuzzing.

My primary focus will be on those functions I identified, especially in the context of memory access, but also the interaction between the verifier and JIT compilation. This is a functional change, reachable from userspace by loading BPF programs, so it's a prime target. I'll get to work and `set-results`.



18/2 2026/08/21 23:01 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_JIT_KASAN",
    "KASAN",
    "KASAN_GENERIC",
    "KASAN_VMALLOC",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_patch_insn_data",
    "bpf_int_jit_compile",
    "bpf_prog_select_runtime"
  ],
  "Reasoning": "The patch introduces KASAN support for the BPF JIT compiler on x86_64. It modifies the BPF JIT compiler to insert KASAN checks before memory accesses (load/store/atomic). It also modifies the BPF verifier to track memory accesses that are not on the stack. This is a functional change to the core BPF JIT and verifier logic, which is reachable from userspace by loading BPF programs. Therefore, it is worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_JIT_KASAN",
    "KASAN",
    "KASAN_GENERIC",
    "KASAN_VMALLOC",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "bpf_patch_insn_data",
    "bpf_int_jit_compile",
    "bpf_prog_select_runtime"
  ],
  "Reasoning": "The patch introduces KASAN support for the BPF JIT compiler on x86_64. It modifies the BPF JIT compiler to insert KASAN checks before memory accesses (load/store/atomic). It also modifies the BPF verifier to track memory accesses that are not on the stack. This is a functional change to the core BPF JIT and verifier logic, which is reachable from userspace by loading BPF programs. Therefore, it is worth fuzzing.",
  "WorthFuzzing": true
}

19/1 2026/08/21 23:01 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/08/21 23:01 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch introduces KASAN (Kernel Address Sanitizer) instrumentation for the BPF JIT compiler on x86_64. It modifies the JIT compiler to emit calls to `__asan_load*` and `__asan_store*` for memory accesses in BPF programs, and updates the BPF verifier to track non-stack memory accesses. These changes are specifically designed to detect out-of-bounds accesses and use-after-free bugs in BPF programs, which are the domain of KASAN. The patch does not introduce new data structures sent to user space, nor does it modify allocations or initialization logic in a way that exposes uninitialized memory. Any bugs introduced by this patch (e.g., incorrect register saving/restoring in the JIT compiler, or incorrect tracking in the verifier) would result in state corruption, crashes, or KASAN false positives/negatives, which are effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit cbc4c75c36320a09e8b963fdfe53b9ba1333946b
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 21 23:00:35 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 48ccc3e6059d0..745890d91e99a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -234,6 +234,7 @@ config X86
 	select HAVE_SAMPLE_FTRACE_DIRECT	if X86_64
 	select HAVE_SAMPLE_FTRACE_DIRECT_MULTI	if X86_64
 	select HAVE_EBPF_JIT
+	select HAVE_EBPF_JIT_KASAN		if X86_64
 	select HAVE_EFFICIENT_UNALIGNED_ACCESS
 	select HAVE_EISA			if X86_32
 	select HAVE_EXIT_THREAD
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 1a9fb530adc3c..36e71aa2e877c 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -21,6 +21,17 @@
 #include <asm/unwind.h>
 #include <asm/cfi.h>
 
+#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)
+void __asan_load1(void *p);
+void __asan_store1(void *p);
+void __asan_load2(void *p);
+void __asan_store2(void *p);
+void __asan_load4(void *p);
+void __asan_store4(void *p);
+void __asan_load8(void *p);
+void __asan_store8(void *p);
+#endif
+
 static bool all_callee_regs_used[4] = {true, true, true, true};
 
 static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len)
@@ -1110,6 +1121,92 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
 	*pprog = prog;
 }
 
+static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
+			    u32 addr_reg, struct bpf_insn *insn, u8 *ip,
+			    bool is_write)
+{
+#ifdef CONFIG_BPF_JIT_KASAN
+	u32 bpf_size = BPF_SIZE(insn->code);
+	s32 off = insn->off;
+	u8 *prog = *pprog;
+	void *kasan_func;
+
+	if (!env)
+		return 0;
+
+	/* Derive KASAN check function from access type and size */
+	switch (bpf_size) {
+	case BPF_B:
+		kasan_func = is_write ? __asan_store1 : __asan_load1;
+		break;
+	case BPF_H:
+		kasan_func = is_write ? __asan_store2 : __asan_load2;
+		break;
+	case BPF_W:
+		kasan_func = is_write ? __asan_store4 : __asan_load4;
+		break;
+	case BPF_DW:
+		kasan_func = is_write ? __asan_store8 : __asan_load8;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Save rax */
+	EMIT1(0x50);
+	/* Save rcx */
+	EMIT1(0x51);
+	/* Save rdx */
+	EMIT1(0x52);
+	/* Save rsi */
+	EMIT1(0x56);
+	/* Save rdi */
+	EMIT1(0x57);
+	/* Save r8 */
+	EMIT2(0x41, 0x50);
+	/* Save r9 */
+	EMIT2(0x41, 0x51);
+	/*
+	 * SystemV ABI states that we should also save r10/r11, but in
+	 * practice those registers are _not_ used by the limited set of
+	 * kasan helpers we are calling here, so that's fine not to save those.
+	 */
+
+	/* mov rdi, addr_reg */
+	EMIT_mov(BPF_REG_1, addr_reg);
+
+	/* add rdi, off (if offset is non-zero) */
+	if (off) {
+		if (is_imm8(off)) {
+			/* add rdi, imm8 */
+			EMIT4(0x48, 0x83, 0xC7, (u8)off);
+		} else {
+			/* add rdi, imm32 */
+			EMIT3_off32(0x48, 0x81, 0xC7, off);
+		}
+	}
+
+	/* Adjust ip to account for the instrumentation generated so far */
+	ip += (prog - *pprog);
+	/* We emit a call, so update call depth counting */
+	ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip);
+	/* call kasan_func */
+	if (emit_call(&prog, kasan_func, ip))
+		return -ERANGE;
+
+	EMIT2(0x41, 0x59);
+	EMIT2(0x41, 0x58);
+	EMIT1(0x5F);
+	EMIT1(0x5E);
+	EMIT1(0x5A);
+	EMIT1(0x59);
+	EMIT1(0x58);
+
+	*pprog = prog;
+#endif /* CONFIG_BPF_JIT_KASAN */
+	return 0;
+}
+
 /* LDX: dst_reg = *(u8*)(src_reg + off) */
 static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
 {
@@ -1315,6 +1412,63 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int
 	*pprog = prog;
 }
 
+/* ST: *(u8*)(dst_reg + off) = imm */
+static void emit_st(u8 **pprog, struct bpf_insn *insn, u32 dst_reg,
+		    s32 outgoing_arg_base, u16 outgoing_rsp)
+{
+	s32 imm32 = insn->imm;
+	u8 *prog = *pprog;
+	s32 insn_off;
+
+	switch (BPF_SIZE(insn->code)) {
+	case BPF_B:
+		if (is_ereg(dst_reg))
+			EMIT2(0x41, 0xC6);
+		else
+			EMIT1(0xC6);
+		break;
+	case BPF_H:
+		if (is_ereg(dst_reg))
+			EMIT3(0x66, 0x41, 0xC7);
+		else
+			EMIT2(0x66, 0xC7);
+		break;
+	case BPF_W:
+		if (is_ereg(dst_reg))
+			EMIT2(0x41, 0xC7);
+		else
+			EMIT1(0xC7);
+		break;
+	case BPF_DW:
+		if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
+			/* Arg 6: store immediate in r9 register */
+			emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, imm32);
+			*pprog = prog;
+			return;
+		}
+		EMIT2(add_1mod(0x48, dst_reg), 0xC7);
+		break;
+	}
+
+	insn_off = insn->off;
+	if (dst_reg == BPF_REG_PARAMS) {
+		/*
+		 * Args 7+: reverse BPF negative offsets to
+		 * x86 positive rsp offsets.
+		 * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
+		 */
+		insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
+		dst_reg = BPF_REG_FP;
+	}
+	if (is_imm8(insn_off))
+		EMIT2(add_1reg(0x40, dst_reg), insn_off);
+	else
+		EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
+
+	EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+	*pprog = prog;
+}
+
 static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm)
 {
 	emit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm);
@@ -1423,17 +1577,35 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size,
 	return 0;
 }
 
-static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg,
-			     u32 src_reg, s16 off, u8 bpf_size)
+static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog,
+			     struct bpf_insn *insn, u8 *ip, u32 dst_reg,
+			     u32 src_reg, bool accesses_stack_only)
 {
+	u32 atomic_op = insn->imm;
+	int err;
+
 	switch (atomic_op) {
 	case BPF_LOAD_ACQ:
+		if (!accesses_stack_only) {
+			err = emit_kasan_check(env, pprog, src_reg, insn, ip,
+					       false);
+			if (err)
+				return err;
+		}
 		/* dst_reg = smp_load_acquire(src_reg + off16) */
-		emit_ldx(pprog, bpf_size, dst_reg, src_reg, off);
+		emit_ldx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+			 insn->off);
 		break;
 	case BPF_STORE_REL:
+		if (!accesses_stack_only) {
+			err = emit_kasan_check(env, pprog, dst_reg, insn, ip,
+					       true);
+			if (err)
+				return err;
+		}
 		/* smp_store_release(dst_reg + off16, src_reg) */
-		emit_stx(pprog, bpf_size, dst_reg, src_reg, off);
+		emit_stx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+			 insn->off);
 		break;
 	default:
 		pr_err("bpf_jit: unknown atomic load/store opcode %02x\n",
@@ -1859,10 +2031,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 		const s32 imm32 = insn->imm;
 		u32 dst_reg = insn->dst_reg;
 		u32 src_reg = insn->src_reg;
+		bool accesses_stack_only;
 		u8 b2 = 0, b3 = 0;
 		u8 *start_of_ldx;
 		s64 jmp_offset;
 		s32 insn_off;
+		int insn_idx;
 		u8 jmp_cond;
 		u8 *func;
 		int nops;
@@ -1879,6 +2053,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 			EMIT_ENDBR();
 
 		ip = image + addrs[i - 1] + (prog - temp);
+		insn_idx = i - 1 + bpf_prog->aux->subprog_start;
+		accesses_stack_only =
+			env ? !env->insn_aux_data[insn_idx].non_stack_access :
+			      false;
 
 		switch (insn->code) {
 			/* ALU */
@@ -2255,49 +2433,19 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 			EMIT_LFENCE();
 			break;
 
-			/* ST: *(u8*)(dst_reg + off) = imm */
 		case BPF_ST | BPF_MEM | BPF_B:
-			if (is_ereg(dst_reg))
-				EMIT2(0x41, 0xC6);
-			else
-				EMIT1(0xC6);
-			goto st;
 		case BPF_ST | BPF_MEM | BPF_H:
-			if (is_ereg(dst_reg))
-				EMIT3(0x66, 0x41, 0xC7);
-			else
-				EMIT2(0x66, 0xC7);
-			goto st;
 		case BPF_ST | BPF_MEM | BPF_W:
-			if (is_ereg(dst_reg))
-				EMIT2(0x41, 0xC7);
-			else
-				EMIT1(0xC7);
-			goto st;
 		case BPF_ST | BPF_MEM | BPF_DW:
-			if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
-				/* Arg 6: store immediate in r9 register */
-				emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32);
-				break;
-			}
-			EMIT2(add_1mod(0x48, dst_reg), 0xC7);
-
-st:			insn_off = insn->off;
-			if (dst_reg == BPF_REG_PARAMS) {
-				/*
-				 * Args 7+: reverse BPF negative offsets to
-				 * x86 positive rsp offsets.
-				 * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
-				 */
-				insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
-				dst_reg = BPF_REG_FP;
+			if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, dst_reg,
+						       insn, ip, true);
+				if (err)
+					return err;
 			}
-			if (is_imm8(insn_off))
-				EMIT2(add_1reg(0x40, dst_reg), insn_off);
-			else
-				EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
 
-			EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+			emit_st(&prog, insn, dst_reg, outgoing_arg_base,
+				outgoing_rsp);
 			break;
 
 			/* STX: *(u8*)(dst_reg + off) = src_reg */
@@ -2315,6 +2463,12 @@ st:			insn_off = insn->off;
 				insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
 				dst_reg = BPF_REG_FP;
 			}
+			if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, dst_reg,
+						       insn, ip, true);
+				if (err)
+					return err;
+			}
 			emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
 			break;
 
@@ -2496,6 +2650,11 @@ st:			insn_off = insn->off;
 				/* populate jmp_offset for JAE above to jump to start_of_ldx */
 				start_of_ldx = prog;
 				end_of_jmp[-1] = start_of_ldx - end_of_jmp;
+			} else if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, src_reg,
+						       insn, ip, false);
+				if (err)
+					return err;
 			}
 			if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX ||
 			    BPF_MODE(insn->code) == BPF_MEMSX)
@@ -2557,28 +2716,42 @@ st:			insn_off = insn->off;
 			}
 			fallthrough;
 		case BPF_STX | BPF_ATOMIC | BPF_W:
-		case BPF_STX | BPF_ATOMIC | BPF_DW:
-			if (insn->imm == (BPF_AND | BPF_FETCH) ||
-			    insn->imm == (BPF_OR | BPF_FETCH) ||
-			    insn->imm == (BPF_XOR | BPF_FETCH)) {
-				bool is64 = BPF_SIZE(insn->code) == BPF_DW;
-				u32 real_src_reg = src_reg;
-				u32 real_dst_reg = dst_reg;
-				u8 *branch_target;
-
+		case BPF_STX | BPF_ATOMIC | BPF_DW: {
+			bool is64 = BPF_SIZE(insn->code) == BPF_DW;
+			u32 real_src_reg = src_reg;
+			u32 real_dst_reg = dst_reg;
+			u8 *branch_target;
+			u8 *pprog;
+			bool is_atomic_fetch =
+				(insn->imm == (BPF_AND | BPF_FETCH) ||
+				 insn->imm == (BPF_OR | BPF_FETCH) ||
+				 insn->imm == (BPF_XOR | BPF_FETCH));
+			if (is_atomic_fetch) {
 				/*
 				 * Can't be implemented with a single x86 insn.
 				 * Need to do a CMPXCHG loop.
 				 */
 
 				/* Will need RAX as a CMPXCHG operand so save R0 */
+				pprog = prog;
 				emit_mov_reg(&prog, true, BPF_REG_AX, BPF_REG_0);
 				if (src_reg == BPF_REG_0)
 					real_src_reg = BPF_REG_AX;
 				if (dst_reg == BPF_REG_0)
 					real_dst_reg = BPF_REG_AX;
-
+				ip += (prog - pprog);
+			}
+			if (!bpf_atomic_is_load_store(insn)) {
+				if (!accesses_stack_only) {
+					err = emit_kasan_check(env, &prog,
+							       real_dst_reg,
+							       insn, ip, true);
+					if (err)
+						return err;
+				}
 				branch_target = prog;
+			}
+			if (is_atomic_fetch) {
 				/* Load old value */
 				emit_ldx(&prog, BPF_SIZE(insn->code),
 					 BPF_REG_0, real_dst_reg, insn->off);
@@ -2610,15 +2783,16 @@ st:			insn_off = insn->off;
 			}
 
 			if (bpf_atomic_is_load_store(insn))
-				err = emit_atomic_ld_st(&prog, insn->imm, dst_reg, src_reg,
-							insn->off, BPF_SIZE(insn->code));
+				err = emit_atomic_ld_st(env, &prog, insn, ip,
+							dst_reg, src_reg,
+							accesses_stack_only);
 			else
 				err = emit_atomic_rmw(&prog, insn->imm, dst_reg, src_reg,
 						      insn->off, BPF_SIZE(insn->code));
 			if (err)
 				return err;
 			break;
-
+		}
 		case BPF_STX | BPF_PROBE_ATOMIC | BPF_B:
 		case BPF_STX | BPF_PROBE_ATOMIC | BPF_H:
 			if (!bpf_atomic_is_load_store(insn)) {
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 5fad59fdab0d0..09cd0355b921d 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -725,6 +725,8 @@ struct bpf_insn_aux_data {
 	u16 const_reg_map_mask;
 	u16 const_reg_subprog_mask;
 	u32 const_reg_vals[10];
+	/* instruction can access non-stack memory */
+	bool non_stack_access;
 };
 
 #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */
diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
index eb3de35734f09..d7d25477ef481 100644
--- a/kernel/bpf/Kconfig
+++ b/kernel/bpf/Kconfig
@@ -17,6 +17,10 @@ config HAVE_CBPF_JIT
 config HAVE_EBPF_JIT
 	bool
 
+# KASAN support for JIT compiler
+config HAVE_EBPF_JIT_KASAN
+	bool
+
 # Used by archs to tell that they want the BPF JIT compiler enabled by
 # default for kernels that were compiled with BPF JIT support.
 config ARCH_WANT_DEFAULT_BPF_JIT
@@ -101,4 +105,17 @@ config BPF_LSM
 
 	  If you are unsure how to answer this question, answer N.
 
+config BPF_JIT_KASAN
+	bool
+	depends on HAVE_EBPF_JIT_KASAN
+	depends on KASAN_GENERIC
+	depends on KASAN_VMALLOC
+	depends on BPF_JIT
+	default y
+	help
+	  Makes JIT compiler insert generic outline KASAN checks in BPF
+	  programs when they are inserted in the kernel. This feature is
+	  automatically enabled if the needed set of KASAN and BPF
+	  configuration options is enabled.
+
 endmenu # "BPF subsystem"
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 65b441e4a3517..edcb0cbbb13de 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -208,12 +208,25 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
 }
 #endif
 
+static bool is_mem_insn(struct bpf_insn *insn)
+{
+	if (BPF_CLASS(insn->code) != BPF_ST &&
+	    BPF_CLASS(insn->code) != BPF_STX &&
+	    BPF_CLASS(insn->code) != BPF_LDX)
+		return false;
+
+	return (BPF_MODE(insn->code) == BPF_MEM ||
+		BPF_MODE(insn->code) == BPF_MEMSX ||
+		BPF_MODE(insn->code) == BPF_ATOMIC);
+}
+
 /* single env->prog->insni[off] instruction was replaced with the range
  * insni[off, off + cnt).  Adjust corresponding insn_aux_data by copying
  * [0, off) and [off, end) to new locations, so the patched range stays zero
  */
 static void adjust_insn_aux_data(struct bpf_verifier_env *env,
-				 struct bpf_prog *new_prog, u32 off, u32 cnt)
+				 struct bpf_prog *new_prog, u32 off, u32 cnt,
+				 struct bpf_insn *original_insn)
 {
 	struct bpf_insn_aux_data *data = env->insn_aux_data;
 	struct bpf_insn *insn = new_prog->insnsi;
@@ -227,8 +240,15 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
 	 */
 	data[off].zext_dst = bpf_insn_def32(new_prog, insn + off + cnt - 1) >= 0;
 
-	if (cnt == 1)
+	if (cnt == 1) {
+		/*
+		 * A non-memory accessing insn could have been replaced by a
+		 * memory accessing insn, systematically mark it for non-stack
+		 * access
+		 */
+		data[off].non_stack_access = is_mem_insn(insn + off);
 		return;
+	}
 	prog_len = new_prog->len;
 	env->insn_aux_data_len = prog_len;
 
@@ -239,8 +259,25 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
 		/* Expand insni[off]'s seen count to the patched range. */
 		data[i].seen = old_seen;
 		data[i].zext_dst = bpf_insn_def32(new_prog, insn + i) >= 0;
+		if (!memcmp(insn + i, original_insn, sizeof(struct bpf_insn))) {
+			data[i].non_stack_access =
+				data[off + cnt - 1].non_stack_access;
+			data[off + cnt - 1].non_stack_access = false;
+		} else if (is_mem_insn(insn + i)) {
+			data[i].non_stack_access = true;
+		}
 	}
 
+	/*
+	 * Last slot instruction could be a newly generated
+	 * BPF_ST/BPF_LDX/BPF_STX, systematically mark it for non-stack access
+	 * if it is not the original instruction, otherwise keep the
+	 * original marking
+	 */
+	if (is_mem_insn(insn + off + cnt - 1) &&
+	    memcmp(insn + off + cnt - 1, original_insn, sizeof(struct bpf_insn)))
+		data[off + cnt - 1].non_stack_access = true;
+
 	/*
 	 * The indirect_target flag of the original instruction was moved to the last of the
 	 * new instructions by the above memmove and memset, but the indirect jump target is
@@ -306,6 +343,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 {
 	struct bpf_prog *new_prog;
 	struct bpf_insn_aux_data *new_data = NULL;
+	struct bpf_insn original_insn;
 
 	if (len > 1) {
 		new_data = vrealloc(env->insn_aux_data,
@@ -318,6 +356,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 		env->insn_aux_data = new_data;
 	}
 
+	memcpy(&original_insn, env->prog->insnsi + off, sizeof(struct bpf_insn));
 	new_prog = bpf_patch_insn_single(env->prog, off, patch, len);
 	if (IS_ERR(new_prog)) {
 		if (PTR_ERR(new_prog) == -ERANGE)
@@ -326,7 +365,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
 				env->insn_aux_data[off].orig_idx);
 		return NULL;
 	}
-	adjust_insn_aux_data(env, new_prog, off, len);
+	adjust_insn_aux_data(env, new_prog, off, len, &original_insn);
 	adjust_subprog_starts(env, off, len);
 	adjust_insn_arrays(env, off, len);
 	adjust_poke_descs(new_prog, off, len);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 821b47ac75c5f..3d1a8e14408ac 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3218,6 +3218,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
 	env->insn_aux_data[idx].indirect_target = true;
 }
 
+static void mark_non_stack_access(struct bpf_verifier_env *env, int idx)
+{
+	env->insn_aux_data[idx].non_stack_access = true;
+}
+
 #define LR_FRAMENO_BITS	4
 #define LR_SPI_BITS	6
 #define LR_ENTRY_BITS	(LR_SPI_BITS + LR_FRAMENO_BITS + 1)
@@ -6600,6 +6605,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 				clear_scalar_id(&regs[value_regno]);
 		}
 	}
+
+	if (!err && reg->type != PTR_TO_STACK)
+		mark_non_stack_access(env, insn_idx);
+
 	return err;
 }
 
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
index 0222a9a5d0761..815f3e04540ff 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -227,42 +227,6 @@ static void check_incorrect_index(void)
 	check_mid_insn_index();
 }
 
-static int set_bpf_jit_harden(char *level)
-{
-	char old_level;
-	int err = -1;
-	int fd = -1;
-
-	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
-	if (fd < 0) {
-		ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
-		return -1;
-	}
-
-	err = read(fd, &old_level, 1);
-	if (err != 1) {
-		ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
-		err = -1;
-		goto end;
-	}
-
-	lseek(fd, 0, SEEK_SET);
-
-	err = write(fd, level, 1);
-	if (err != 1) {
-		ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
-		err = -1;
-		goto end;
-	}
-
-	err = 0;
-	*level = old_level;
-end:
-	if (fd >= 0)
-		close(fd);
-	return err;
-}
-
 static void check_blindness(void)
 {
 	struct bpf_insn insns[] = {
@@ -272,7 +236,7 @@ static void check_blindness(void)
 		BPF_MOV64_IMM(BPF_REG_0, 1),
 		BPF_EXIT_INSN(),
 	};
-	int prog_fd = -1, map_fd;
+	int prog_fd = -1, map_fd, ret;
 	struct bpf_insn_array_value val = {};
 	char bpf_jit_harden = '@'; /* non-exizsting value */
 	int i;
@@ -291,7 +255,8 @@ static void check_blindness(void)
 		goto cleanup;
 
 	bpf_jit_harden = '2';
-	if (set_bpf_jit_harden(&bpf_jit_harden)) {
+	ret = set_bpf_jit_harden(&bpf_jit_harden);
+	if (!ASSERT_OK(ret, "set bpf_jit_harden")) {
 		bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
 		goto cleanup;
 	}
@@ -313,7 +278,8 @@ static void check_blindness(void)
 cleanup:
 	/* restore the old one */
 	if (bpf_jit_harden != '@')
-		set_bpf_jit_harden(&bpf_jit_harden);
+		ASSERT_OK(set_bpf_jit_harden(&bpf_jit_harden),
+			  "restore hardening configuration");
 
 	close(prog_fd);
 	close(map_fd);
diff --git a/tools/testing/selftests/bpf/prog_tests/kasan.c b/tools/testing/selftests/bpf/prog_tests/kasan.c
new file mode 100644
index 0000000000000..2b424767a0f3f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * Tests validating that KASAN reports are properly instrumented and
+ * generated on a wide variety of instructions. The running kernel needs
+ * kasan_multi_shot to run multiple kasan-generating subtests at once
+ */
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include <unpriv_helpers.h>
+#include "kasan.skel.h"
+#include "kasan_harden.skel.h"
+
+#define SUBTEST_NAME_MAX_LEN	128
+#define PROG_NAME_MAX_LEN	128
+
+#define MAX_LOG_SIZE		(8 * 1024)
+#define READ_CHUNK_SIZE		256
+
+#define KASAN_PATTERN_SLAB_UAF "BUG: KASAN: slab-use-after-free " \
+	"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_SLAB_OOB "BUG: KASAN: slab-out-of-bounds " \
+	"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_REPORT "%s of size %d at addr"
+
+static char klog_buffer[MAX_LOG_SIZE];
+static char record[MAX_LOG_SIZE];
+
+struct test_spec {
+	char *prog_type;
+	bool is_write;
+	bool only_32_or_64;
+	bool needs_load_acq_store_rel;
+	bool skip_multi_size_testing;
+	bool skip_on_stack_testing;
+	int run_size;
+	bool expect_no_report;
+	bool rnd_hi32;
+	bool is_oob;
+};
+
+struct kasan_write_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct test_ctx {
+	__u8  prog_tag[BPF_TAG_SIZE];
+	struct bpf_object *obj;
+	int *access_size;
+	bool skip_load_acq_store_rel;
+	struct bpf_program *prog;
+	char prog_name[SUBTEST_NAME_MAX_LEN];
+	int klog_fd;
+};
+
+static int open_kernel_logs(void)
+{
+	int fd;
+
+	fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+	return fd;
+}
+
+static void skip_kernel_logs(int fd)
+{
+	lseek(fd, 0, SEEK_END);
+}
+
+static int read_kernel_logs(int fd, char *buf, size_t max_len)
+{
+	size_t total = 0;
+	ssize_t n;
+
+	buf[0] = '\0';
+	while (1) {
+		char *msg, *eol;
+		size_t len;
+
+		n = read(fd, record, sizeof(record) - 1);
+		if (n == 0)
+			break;
+
+		if (n < 0) {
+			if (errno == EAGAIN)
+				break;
+			return n;
+		}
+		record[n] = '\0';
+
+		/*
+		 * Each kmsg record starts with some metadata, separated
+		 * from the actual content by a semi-colon
+		 */
+		msg = strchr(record, ';');
+		if (!msg)
+			continue;
+		msg++;
+		eol = strchr(msg, '\n');
+		if (eol)
+			*eol = '\0';
+
+		len = strlen(msg);
+		if (total + len + 2 > max_len)
+			break;
+		memcpy(buf + total, msg, len);
+		total += len;
+		buf[total++] = '\n';
+		buf[total] = '\0';
+	}
+
+	return total;
+}
+
+static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
+					     bool is_write, int size,
+					     bool is_oob)
+{
+	char access_log[READ_CHUNK_SIZE];
+	const char *pattern;
+	char *kasan_report_start;
+	int nsize;
+
+	pattern = is_oob ? KASAN_PATTERN_SLAB_OOB : KASAN_PATTERN_SLAB_UAF;
+	nsize = snprintf(access_log, READ_CHUNK_SIZE, pattern,
+			 ctx->prog_tag[0], ctx->prog_tag[1], ctx->prog_tag[2],
+			 ctx->prog_tag[3], ctx->prog_tag[4], ctx->prog_tag[5],
+			 ctx->prog_tag[6], ctx->prog_tag[7], ctx->prog_name);
+	if (!ASSERT_GE(nsize, 0, "format kasan access header line"))
+		return nsize;
+	/*
+	 * Searched kasan report is valid if
+	 * - it contains the expected kasan pattern
+	 * - the description of the faulty access is found somewhere
+	 *   after the header (not necessarily on the very next line,
+	 *   because other kernel messages may interleave)
+	 * - faulty access properties match the tested type and size
+	 */
+	kasan_report_start = strstr(buf, access_log);
+
+	if (!kasan_report_start)
+		return 1;
+
+	nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT,
+			 is_write ? "Write" : "Read", size);
+	if (!ASSERT_GE(nsize, 0, "format kasan access report line"))
+		return nsize;
+
+	if (!strstr(kasan_report_start, access_log))
+		return 1;
+
+	return 0;
+}
+
+static void exec_subtest(struct test_ctx *ctx, struct test_spec *test,
+			 int access_size, bool on_stack)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	struct bpf_prog_info info;
+	uint8_t buf[ETH_HLEN] = {0};
+	int ret, prog_fd;
+	__u32 info_len;
+
+	ctx->prog = bpf_object__find_program_by_name(ctx->obj,
+						     ctx->prog_name);
+	if (!ASSERT_OK_PTR(ctx->prog, "find test prog"))
+		return;
+
+	info_len = sizeof(info);
+	memset(&info, 0, info_len);
+	prog_fd = bpf_program__fd(ctx->prog);
+	if (!ASSERT_OK_FD(prog_fd, "get prog fd"))
+		return;
+	ret = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len);
+	if (!ASSERT_OK(ret, "fetch loaded program info"))
+		return;
+	memcpy(ctx->prog_tag, info.tag, BPF_TAG_SIZE);
+
+	skip_kernel_logs(ctx->klog_fd);
+
+	topts.sz = sizeof(struct bpf_test_run_opts);
+	topts.data_size_in = ETH_HLEN;
+	topts.data_in = buf;
+	if (ctx->access_size)
+		*ctx->access_size = access_size;
+	ret = bpf_prog_test_run_opts(bpf_program__fd(ctx->prog),
+				     &topts);
+	if (!ASSERT_OK(ret, "run prog"))
+		return;
+
+	ret = read_kernel_logs(ctx->klog_fd, klog_buffer, MAX_LOG_SIZE);
+	if (!ASSERT_GE(ret, 0, "read kernel logs"))
+		return;
+
+	ret = check_kasan_report_in_kernel_logs(klog_buffer, ctx,
+						test->is_write, access_size,
+						test->is_oob);
+	if (on_stack || test->expect_no_report)
+		ASSERT_NEQ(ret, 0, "no report should be generated");
+	else
+		ASSERT_OK(ret, "report should be generated");
+}
+
+static void run_subtest_with_size_and_location(struct test_ctx *ctx,
+					       struct test_spec *test,
+					       int access_size,
+					       bool on_stack)
+{
+	char subtest_name[SUBTEST_NAME_MAX_LEN];
+
+	if (test->skip_multi_size_testing) {
+		snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s%s",
+			 test->prog_type,
+			 test->skip_on_stack_testing ? "" :
+			 on_stack		     ? "_on_stack" :
+						       "_not_on_stack");
+	} else {
+		snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s_%d_%s",
+			 test->prog_type, access_size,
+			 on_stack ? "on_stack" : "not_on_stack");
+	}
+
+	snprintf(ctx->prog_name, PROG_NAME_MAX_LEN, "%s%s", test->prog_type,
+		 test->skip_on_stack_testing ? "" :
+		 on_stack		     ? "_on_stack" :
+					       "_not_on_stack");
+
+	if (!test__start_subtest(subtest_name))
+		return;
+
+	if (test->needs_load_acq_store_rel && ctx->skip_load_acq_store_rel) {
+		test__skip();
+		return;
+	}
+
+	exec_subtest(ctx, test, access_size, on_stack);
+}
+
+static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test,
+				  int size)
+{
+	run_subtest_with_size_and_location(ctx, test, size, false);
+	if (!test->skip_on_stack_testing)
+		run_subtest_with_size_and_location(ctx, test, size, true);
+}
+
+static void run_subtest(struct test_ctx *ctx, struct test_spec *test)
+{
+	if (test->skip_multi_size_testing) {
+		run_subtest_with_size(ctx, test, test->run_size);
+		return;
+	}
+
+	if (!test->only_32_or_64) {
+		run_subtest_with_size(ctx, test, 1);
+		run_subtest_with_size(ctx, test, 2);
+	}
+	run_subtest_with_size(ctx, test, 4);
+	run_subtest_with_size(ctx, test, 8);
+}
+
+static void run_blinding_subtest(void)
+{
+	struct test_spec blinding_spec = {
+		.prog_type = "st_blinded",
+		.is_write = true,
+	};
+	char bpf_jit_harden = '2';
+	struct kasan_harden *skel;
+	struct test_ctx *ctx;
+
+	if (!test__start_subtest("st_blinded"))
+		return;
+
+	ctx = calloc(1, sizeof(*ctx));
+	if (!ASSERT_OK_PTR(ctx, "alloc blinding ctx"))
+		return;
+	ctx->klog_fd = -1;
+
+	if (set_bpf_jit_harden(&bpf_jit_harden))
+		goto free_ctx;
+
+	skel = kasan_harden__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open and load blinded prog"))
+		goto restore;
+
+	ctx->klog_fd = open_kernel_logs();
+	if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+		goto destroy;
+
+	ctx->obj = skel->obj;
+	strncpy(ctx->prog_name, "st_blinded", PROG_NAME_MAX_LEN);
+
+	exec_subtest(ctx, &blinding_spec, 1, false);
+
+destroy:
+	close(ctx->klog_fd);
+	kasan_harden__destroy(skel);
+restore:
+	set_bpf_jit_harden(&bpf_jit_harden);
+free_ctx:
+	free(ctx);
+}
+
+static struct test_spec tests[] = {
+	{
+		.prog_type = "st",
+		.is_write = true
+	},
+	{
+		.prog_type = "stx",
+		.is_write = true
+	},
+	{
+		.prog_type = "ldx",
+		.is_write = false
+	},
+	{
+		.prog_type = "simple_atomic",
+		.is_write = true,
+		.only_32_or_64 = true
+	},
+	{
+		.prog_type = "simple_atomic_fetch",
+		.is_write = true,
+		.skip_multi_size_testing = true,
+		.run_size = 8,
+	},
+	{
+		.prog_type = "load_acquire",
+		.is_write = false,
+		.needs_load_acq_store_rel = true
+	},
+	{
+		.prog_type = "store_release",
+		.is_write = true,
+		.needs_load_acq_store_rel = true
+	},
+	{
+		.prog_type = "ldx_patched",
+		.is_write = false,
+		.skip_multi_size_testing = true,
+		.run_size = 4,
+		.rnd_hi32 = true
+	},
+	{
+		.prog_type = "verifier_paths_stack_and_non_stack",
+		.is_write = true,
+		.skip_multi_size_testing = true,
+		.skip_on_stack_testing = true,
+		.run_size = 1
+	},
+	{
+		.prog_type = "ldx_oob",
+		.is_write = false,
+		.skip_on_stack_testing = true,
+		.is_oob = true
+	},
+};
+
+void test_kasan(void)
+{
+	struct kasan_write_val val;
+	struct test_spec *test;
+	struct test_ctx *ctx;
+	struct kasan *skel;
+	__u32 key = 0;
+	int i, ret;
+
+	ctx = calloc(1, sizeof(struct test_ctx));
+	if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
+		return;
+
+	if (!is_jit_enabled() || !get_kasan_jit_enabled() ||
+	    !get_kasan_multi_shot_enabled()) {
+		test__skip();
+		goto end;
+	}
+
+	skel = kasan__open();
+	if (!ASSERT_OK_PTR(skel, "open prog"))
+		goto end;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		char prog_name[SUBTEST_NAME_MAX_LEN];
+		struct bpf_program *prog;
+
+		if (!tests[i].rnd_hi32)
+			continue;
+
+		snprintf(prog_name, SUBTEST_NAME_MAX_LEN, "%s_%s",
+			 tests[i].prog_type, "on_stack");
+		prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+		if (!ASSERT_OK_PTR(prog, "find rnd_hi32 on_stack prog"))
+			goto destroy;
+		bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+		snprintf(prog_name, SUBTEST_NAME_MAX_LEN, "%s_%s",
+			 tests[i].prog_type, "not_on_stack");
+		prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+		if (!ASSERT_OK_PTR(prog, "find rnd_hi32 not_on_stack prog"))
+			goto destroy;
+		bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+	}
+
+	if (!ASSERT_OK(kasan__load(skel), "load prog"))
+		goto destroy;
+
+	ctx->obj = skel->obj;
+	ctx->access_size = &skel->bss->access_size;
+	ctx->skip_load_acq_store_rel = skel->data->skip_load_acq_store_rel_tests;
+
+	ctx->klog_fd = open_kernel_logs();
+	if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+		goto destroy;
+
+	/* Fill map with recognizable values */
+	ret = bpf_map__lookup_elem(skel->maps.test_map, &key, sizeof(key),
+				   &val, sizeof(val), 0);
+	if (!ASSERT_OK(ret, "get map"))
+		goto close;
+	val.data_1 = 0xAA;
+	val.data_2 = 0xBBBB;
+	val.data_4 = 0xCCCCCCCC;
+	val.data_8 = 0xDDDDDDDDDDDDDDDD;
+	ret = bpf_map__update_elem(skel->maps.test_map, &key, sizeof(key),
+				   &val, sizeof(val), 0);
+	if (!ASSERT_OK(ret, "set map"))
+		goto close;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		test = &tests[i];
+		run_subtest(ctx, test);
+	}
+
+	/*
+	 * Blinding subtest is handled differently as it needs the
+	 * corresponding program to be loaded with bpf_jit_harden raised
+	 */
+	run_blinding_subtest();
+
+close:
+	close(ctx->klog_fd);
+destroy:
+	kasan__destroy(skel);
+end:
+	free(ctx);
+}
diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
new file mode 100644
index 0000000000000..ea29197646b0b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <stdbool.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct bpf_testmod_oob {
+	__u8 data;
+	union {
+		__u8 redzone_1;
+		__u16 redzone_2;
+		__u32 redzone_4;
+		__u64 redzone_8;
+	};
+};
+
+extern struct bpf_testmod_oob *bpf_testmod_oob_alloc(void) __ksym;
+extern void bpf_testmod_oob_free(struct bpf_testmod_oob *oob) __ksym;
+
+int access_size;
+
+struct kasan_test_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, struct kasan_test_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val.data_1 = 0xAA;
+		break;
+	case 2:
+		val.data_2 = 0xAA;
+		break;
+	case 4:
+		val.data_4 = 0xAA;
+		break;
+	case 8:
+		val.data_8 = 0xAA;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int st_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val->data_1 = 0xAA;
+		break;
+	case 2:
+		val->data_2 = 0xAA;
+		break;
+	case 4:
+		val->data_4 = 0xAA;
+		break;
+	case 8:
+		val->data_8 = 0xAA;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int stx_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val.data_1 = access_size;
+		break;
+	case 2:
+		val.data_2 = access_size;
+		break;
+	case 4:
+		val.data_4 = access_size;
+		break;
+	case 8:
+		val.data_8 = access_size;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int stx_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		val->data_1 = access_size;
+		break;
+	case 2:
+		val->data_2 = access_size;
+		break;
+	case 4:
+		val->data_4 = access_size;
+		break;
+	case 8:
+		val->data_8 = access_size;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__sink(val.data_1);
+		break;
+	case 2:
+		__sink(val.data_2);
+		break;
+	case 4:
+		__sink(val.data_4);
+		break;
+	case 8:
+		__sink(val.data_8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__sink(val->data_1);
+		break;
+	case 2:
+		__sink(val->data_2);
+		break;
+	case 4:
+		__sink(val->data_4);
+		break;
+	case 8:
+		__sink(val->data_8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	__sink(val->data_4);
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	__sink(val.data_4);
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 4:
+		__sync_fetch_and_add(&val.data_4, 4);
+		break;
+	case 8:
+		__sync_fetch_and_add(&val.data_8, 8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 4:
+		__sync_fetch_and_add(&val->data_4, 4);
+		break;
+	case 8:
+		__sync_fetch_and_add(&val->data_8, 8);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_fetch_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	__sync_fetch_and_or(&val.data_8, 8);
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_fetch_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	__sync_fetch_and_or(&val->data_8, 8);
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL
+bool skip_load_acq_store_rel_tests SEC(".data") = 0;
+
+SEC("tcx/ingress")
+int load_acquire_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_load_n(&val.data_1, __ATOMIC_ACQUIRE);
+		break;
+	case 2:
+		__atomic_load_n(&val.data_2, __ATOMIC_ACQUIRE);
+		break;
+	case 4:
+		__atomic_load_n(&val.data_4, __ATOMIC_ACQUIRE);
+		break;
+	case 8:
+		__atomic_load_n(&val.data_8, __ATOMIC_ACQUIRE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int load_acquire_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_load_n(&val->data_1, __ATOMIC_ACQUIRE);
+		break;
+	case 2:
+		__atomic_load_n(&val->data_2, __ATOMIC_ACQUIRE);
+		break;
+	case 4:
+		__atomic_load_n(&val->data_4, __ATOMIC_ACQUIRE);
+		break;
+	case 8:
+		__atomic_load_n(&val->data_8, __ATOMIC_ACQUIRE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_store_n(&val.data_1, 0xAA, __ATOMIC_RELEASE);
+		break;
+	case 2:
+		__atomic_store_n(&val.data_2, 0xBBBB, __ATOMIC_RELEASE);
+		break;
+	case 4:
+		__atomic_store_n(&val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+		break;
+	case 8:
+		__atomic_store_n(&val.data_8, 0xDDDDDDDDDDDDDDDD,
+				 __ATOMIC_RELEASE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	switch (access_size) {
+	case 1:
+		__atomic_store_n(&val->data_1, 0xAA, __ATOMIC_RELEASE);
+		break;
+	case 2:
+		__atomic_store_n(&val->data_2, 0xBBBB, __ATOMIC_RELEASE);
+		break;
+	case 4:
+		__atomic_store_n(&val->data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+		break;
+	case 8:
+		__atomic_store_n(&val->data_8, 0xDDDDDDDDDDDDDDDD,
+				 __ATOMIC_RELEASE);
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+	return 0;
+}
+#else
+bool skip_load_acq_store_rel_tests SEC(".data") = 1;
+#endif
+
+SEC("tcx/ingress")
+int verifier_paths_stack_and_non_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val stack_val = {};
+	struct kasan_test_val *val;
+	void *ptr;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	if (access_size)
+		ptr = val;
+	else
+		ptr = &stack_val;
+
+	bpf_kfunc_kasan_poison(val, sizeof(*val));
+	*(__u8 *)ptr = 0xAA;
+	bpf_kfunc_kasan_unpoison(val, sizeof(*val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_oob(struct __sk_buff *skb)
+{
+	struct bpf_testmod_oob *val;
+	struct kasan_test_val volatile tmp;
+
+	val = bpf_testmod_oob_alloc();
+	if (!val)
+		return 0;
+
+	switch (access_size) {
+	case 1:
+		tmp.data_1 = (__u8)val->redzone_1;
+		break;
+	case 2:
+		tmp.data_2 = (__u16)val->redzone_2;
+		break;
+	case 4:
+		tmp.data_4 = (__u32)val->redzone_4;
+		break;
+	case 8:
+		tmp.data_8 = (__u64)val->redzone_8;
+		break;
+	}
+	bpf_testmod_oob_free(val);
+	return tmp.data_1;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kasan_harden.c b/tools/testing/selftests/bpf/progs/kasan_harden.c
new file mode 100644
index 0000000000000..a2756bbfd5292
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct kasan_test_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, struct kasan_test_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_blinded(struct __sk_buff *skb)
+{
+	struct kasan_test_val *val;
+	__u32 key = 0;
+
+	val = bpf_map_lookup_elem(&test_map, &key);
+	if (!val)
+		return 0;
+
+	bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+	val->data_1 = 0xAA;
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 9366a3c578f13..8e72a3d1b9a23 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -75,6 +75,16 @@ union bpf_testmod_union_arg_2 {
 	struct bpf_testmod_struct_arg_2 arg;
 };
 
+struct bpf_testmod_oob {
+	__u8 data;
+	union {
+		__u8 redzone_1;
+		__u16 redzone_2;
+		__u32 redzone_4;
+		__u64 redzone_8;
+	};
+};
+
 __bpf_hook_start();
 
 noinline int
@@ -336,6 +346,47 @@ __bpf_kfunc void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member
 	 */
 }
 
+#ifdef CONFIG_BPF_JIT_KASAN
+
+extern void kasan_poison(const void *addr, size_t size, u8 value, bool init);
+
+#define KASAN_SLAB_FREE 0xFB
+
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz)
+{
+	kasan_poison(mem, mem__sz, KASAN_SLAB_FREE, false);
+}
+
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz)
+{
+	kasan_poison(mem, mem__sz, 0x00, false);
+}
+#else
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) { }
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) { }
+#endif
+
+__bpf_kfunc struct bpf_testmod_oob *bpf_testmod_oob_alloc(void)
+{
+	struct bpf_testmod_oob *p;
+
+	/*
+	 * Only allocate size of data (and so, voluntarily use kmalloc
+	 * instead of kmalloc_obj), not the rest of the structure, so
+	 * that programs under test trying to access the rest of the
+	 * structure trigger OoB accesses
+	 */
+	p = kmalloc(sizeof(p->data), GFP_ATOMIC);
+	if (!p)
+		return NULL;
+	return p;
+}
+
+__bpf_kfunc void bpf_testmod_oob_free(struct bpf_testmod_oob *oob)
+{
+	kfree(oob);
+}
+
 __bpf_kfunc struct bpf_testmod_ctx *
 bpf_testmod_ctx_create(int *err)
 {
@@ -869,6 +920,10 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_multislot)
 BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
 BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison)
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_unpoison)
+BTF_ID_FLAGS(func, bpf_testmod_oob_alloc, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_testmod_oob_free, KF_RELEASE)
 BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
 
 BTF_ID_LIST(bpf_testmod_dtor_ids)
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index c970e7793dfcb..737f668b35e23 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -519,6 +519,38 @@ bool is_jit_enabled(void)
 	return enabled;
 }
 
+int set_bpf_jit_harden(char *level)
+{
+	char old_level;
+	int err = -1;
+	int fd = -1;
+
+	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
+	if (fd < 0)
+		return -1;
+
+	err = read(fd, &old_level, 1);
+	if (err != 1) {
+		err = -1;
+		goto end;
+	}
+
+	lseek(fd, 0, SEEK_SET);
+
+	err = write(fd, level, 1);
+	if (err != 1) {
+		err = -1;
+		goto end;
+	}
+
+	err = 0;
+	*level = old_level;
+end:
+	if (fd >= 0)
+		close(fd);
+	return err;
+}
+
 int stack_mprotect(void)
 {
 	void *buf;
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index 2edc6fb7fc521..e00642afe86f4 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -59,6 +59,7 @@ struct bpf_insn;
 int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt);
 int testing_prog_flags(void);
 bool is_jit_enabled(void);
+int set_bpf_jit_harden(char *level);
 int stack_mprotect(void);
 
 #endif /* __TESTING_HELPERS_H */
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index f997d7ec8fd08..2c8c5edb87517 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -72,8 +72,8 @@ static int config_contains(const char *pat)
 
 static bool cmdline_contains(const char *pat)
 {
+	int fd, cnt, ret = false;
 	char cmdline[4096], *c;
-	int fd, ret = false;
 
 	fd = open("/proc/cmdline", O_RDONLY);
 	if (fd < 0) {
@@ -81,14 +81,15 @@ static bool cmdline_contains(const char *pat)
 		return false;
 	}
 
-	if (read(fd, cmdline, sizeof(cmdline) - 1) < 0) {
+	cnt = read(fd, cmdline, sizeof(cmdline) - 1);
+	if (cnt < 0) {
 		perror("read /proc/cmdline");
 		goto out;
 	}
 
-	cmdline[sizeof(cmdline) - 1] = '\0';
+	cmdline[cnt] = '\0';
 	for (c = strtok(cmdline, " \n"); c; c = strtok(NULL, " \n")) {
-		if (strncmp(c, pat, strlen(c)))
+		if (strcmp(c, pat))
 			continue;
 		ret = true;
 		break;
@@ -142,3 +143,13 @@ bool get_unpriv_disabled(void)
 	}
 	return mitigations_off;
 }
+
+bool get_kasan_jit_enabled(void)
+{
+	return config_contains("CONFIG_BPF_JIT_KASAN=y") == 1;
+}
+
+bool get_kasan_multi_shot_enabled(void)
+{
+	return cmdline_contains("kasan_multi_shot");
+}
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h
index 151f673296652..a7ceb51577cdc 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.h
+++ b/tools/testing/selftests/bpf/unpriv_helpers.h
@@ -5,3 +5,5 @@
 #define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled"
 
 bool get_unpriv_disabled(void);
+bool get_kasan_jit_enabled(void);
+bool get_kasan_multi_shot_enabled(void);

21/3 2026/08/21 23:01 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24002
							output: 243
							thoughts: 812
						

Thoughts:
**KMSAN Fuzzing Session Assessment**

Okay, so I've got this patch series adding KASAN support for the BPF JIT compiler on x86_64. It seems pretty straightforward: it introduces `CONFIG_BPF_JIT_KASAN`, modifies `arch/x86/net/bpf_jit_comp.c` to emit KASAN checks for memory accesses, and tweaks the BPF verifier to track non-stack memory accesses with the `non_stack_access` flag. They've also added selftests, which is good.

The question is, does this warrant a *dedicated* KMSAN fuzzing session? I'm thinking, probably not, but let's be thorough. KMSAN is all about detecting reads of uninitialized memory. This patch explicitly deals with KASAN instrumentation—out-of-bounds accesses, use-after-free, etc. I'm not seeing any KMSAN-specific instrumentation being added here.

The patch doesn't introduce any new structures that go to userspace. It doesn't seem to alter any control flow based on uninitialized variables. It's essentially modifying the JIT compiler to emit KASAN function calls. If there are bugs in *this* patch, it would likely be in the JIT compiler itself (incorrect register handling, wrong offsets, wrong KASAN function called) or the verifier's tracking of `non_stack_access`.

Now, would KMSAN catch *anything* here that KASAN wouldn't? Hmm... If the JIT compiler messes up register saving/restoring, it's going to lead to state corruption and crashes—KASAN or general protection faults would likely catch that. If it's emitting KASAN checks for stack accesses incorrectly, it's just a KASAN false positive/negative. This isn't KMSAN's domain.

KMSAN focuses on uninitialized memory, and this patch doesn't seem to be touching uninitialized memory. It's about detecting memory safety issues, which KASAN specializes in. There's no KMSAN instrumentation being introduced *at all*.

Alright, let's double-check the rules to be sure:

*   Does it introduce or modify kernel structures sent to userspace? No.
*   Does it introduce conditional logic or branching that depends on potentially uninitialized variables? No.
*   Does it involve allocation or initialization of complex data structures? No.
*   Does it introduce new bounds checks, lengths, or logic that could allow access to uninitialized bytes of existing buffers? No.

Nope. This patch doesn't violate any of those rules. It solely focuses on adding KASAN checks to the BPF JIT.

So, the conclusion is *NeedsKMSAN=false*. This patch deals with KASAN instrumentation. A dedicated KMSAN fuzzing session isn't justified.



22/3 2026/08/21 23:01 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch introduces KASAN (Kernel Address Sanitizer) instrumentation for the BPF JIT compiler on x86_64. It modifies the JIT compiler to emit calls to `__asan_load*` and `__asan_store*` for memory accesses in BPF programs, and updates the BPF verifier to track non-stack memory accesses. These changes are specifically designed to detect out-of-bounds accesses and use-after-free bugs in BPF programs, which are the domain of KASAN. The patch does not introduce new data structures sent to user space, nor does it modify allocations or initialization logic in a way that exposes uninitialized memory. Any bugs introduced by this patch (e.g., incorrect register saving/restoring in the JIT compiler, or incorrect tracking in the verifier) would result in state corruption, crashes, or KASAN false positives/negatives, which are effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch introduces KASAN (Kernel Address Sanitizer) instrumentation for the BPF JIT compiler on x86_64. It modifies the JIT compiler to emit calls to `__asan_load*` and `__asan_store*` for memory accesses in BPF programs, and updates the BPF verifier to track non-stack memory accesses. These changes are specifically designed to detect out-of-bounds accesses and use-after-free bugs in BPF programs, which are the domain of KASAN. The patch does not introduce new data structures sent to user space, nor does it modify allocations or initialization logic in a way that exposes uninitialized memory. Any bugs introduced by this patch (e.g., incorrect register saving/restoring in the JIT compiler, or incorrect tracking in the verifier) would result in state corruption, crashes, or KASAN false positives/negatives, which are effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)