Linux Kernel Selftest development
 help / color / mirror / Atom feed
* [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs
@ 2026-08-28  9:21 Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
                   ` (7 more replies)
  0 siblings, 8 replies; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Hello,
this is v8 of the series aiming to bring basic support for KASAN checks
to BPF JITed programs. Among the fixes following the comments on v7,
this version should have passing selftests, as Ihor merged the small PR
in vmtest ignoring the KASAN selftests splats.

Original cover letter:

"Traditional" KASAN allows to spot memory management mistakes by
reserving a fraction of memory as "shadow memory" that will map to the
rest of the memory and allow its monitoring. Each memory-accessing
instruction is then instrumented at build time to call some ASAN check
function, that will analyze the corresponding bits in shadow memory, and
if it detects the access as invalid, trigger a detailed report. The goal
of this series is to replicate this mechanism for BPF programs when they
are being JITed into native instructions: that's then the JIT compiler
that is in charge of inserting calls to the corresponding kasan checks,
when a program is being loaded into the kernel. This task involves:
- identifying at program load time the instructions performing memory
  accesses
- identifying those accesses properties (size ? read or write ?) to
  define the relevant kasan check function to call
- just before the identified instructions:
  - perform the basic context saving (ie: saving registers)
  - inserting a call to the relevant kasan check function
  - restore context
- whenever the instrumented program executes, if it performs an invalid
  access, it triggers a kasan report identical to those instrumented on
  kernel side at build time.

The series comes with new selftests programs that generate a wide
variety of kasan reports: those need the kernel to be running with
kasan_multi_shot enabled.

As discussed in [1], this series is based on some choices and
assumptions:
- it focuses on x86_64 for now, and so only on KASAN_GENERIC
- not all memory accessing BPF instructions are being instrumented:
  - it discards instructions accessing BPF program stack (already
    monitored by page guards)
  - it discards possibly faulting instructions, like BPF_PROBE_MEM or
    BPF_PROBE_ATOMIC insns

---
Changes in v8:
- Make sure that test programs involving STX are not turned into ST on
  cpuv4
- Execute ST tests only when compiler can emit ST (ie: cpuv4)
- make sure to test the reg type before the verifier can alter it (eg an
  on_stack access with dst_reg = src_reg)
- Add new test for the case mentioned above, ensuring that a pure stack
  access with dst_reg == src_reg is not instrumented
- add back save/restore logic for r10 and r11 in emit_kasan_check
- make the new kasan test serial to avoid side effects due to
  bpf_jit_harden being toggled
- dropped the set_bpf_jit_harden helper, as there is already a
  sysctl_set helper
- Link to v7: https://patch.msgid.link/20260822-kasan-v7-0-99afee6ef7fd@bootlin.com

Changes in v7:
- Rebase series on top of current bpf-next_base, fixed conflict with
  7ce090afbf72 ("bpf: Infer zext_dst based on static register liveness
  analysis")
- Link to v6: https://patch.msgid.link/20260804-kasan-v6-0-549ef845f491@bootlin.com

Changes in v6:
- dropped instruction original offset tracking
- when patching instructions, track former non_stack_access flag by
  passing original insn to adjust_insn_aux_data
- drop unecessary dep on CONFIG_KASAN in Kconfig
- fold patch adding the emit_kasan_helper into the patch actually
  calling it, to avoid an unused static function warning
- move stack access check out of emit_kasan_check
- replace hardcoded ip value by a computed value
- add OoB testing
- add fix commit to make cmdline_contains stricter

- Link to v5: https://patch.msgid.link/20260709-kasan-v5-0-1c64af8e4e1e@bootlin.com

Changes in v5:
- fixed a few instruction offset for generated fixups
- fix insn marking for single insn patches
- enforce more checks in tests
- skip tests if kasan_multi_shot isn't enabled
- Link to v4: https://patch.msgid.link/20260708-kasan-v4-0-d5c177ab8227@bootlin.com

Changes in v4:
- fix insn_offs_in_patch leakage in bpf_convert_ctx_access
- handle BPF_ATOMIC in is_mem_insn
- correctly mark fixup instructions if a single insn is generated
- clarify new kconfig (Andrey) and drop VMAP_STACK dep
- refactor BPF_FETCH atomic handling in JIT loop
- make kernel log reading resilient to unrelated, interleaved logs in
  the selftests
- make new test kfuncs depend on BPF_JIT_KASAN rather than KASAN_GENERIC
- Link to v3: https://patch.msgid.link/20260701-kasan-v3-0-bd09bb942d86@bootlin.com

Changes in v3:
- Do not insert KASAN instrumentation when dealing with cBPF
- Fix stack-accessing insn tracking for verifier patches, as original
  instruction location in the generated patch may vary
- drop cBPF support for stack-accessing insn marking
- make sure to flag correctly memory access if different verifier states
  involve different memory types (eg: stack in one path, non-stack in
  another path)
- refactor BPF_ST handling in x86 JIT compiler
- improve tests coverage (cover instrumentation for a few patches
  emitted by the verifier)
- Link to v2: https://patch.msgid.link/20260604-kasan-v2-0-c066e627fda8@bootlin.com

Changes in v2:
- declare asan functions as extern in JIT compiler rather than exposing
  them in kasan header
- invert stack-accessing instructions marking to make sure not to skip
  instructions that could end up accessing to-be-checked memory
- fix stack accesses marking when verifier patches instructions
- add best effort marking for cBPF
- add missing call depth accounting in jited instrumentation
- skip unused registers in kasan instrumentation save/restore
- remove faulty stack align in kasan instrumentation
- drop commit skipping some jit-related tests
- cover missing instructions: BPF_ST and atomics
- completely rework tests: directly tune shadow memory, increase
  coverage, do not consume kernel logs
- Link to v1: https://patch.msgid.link/20260413-kasan-v1-0-1a5831230821@bootlin.com

To: Alexei Starovoitov <ast@kernel.org>
To: Daniel Borkmann <daniel@iogearbox.net>
To: John Fastabend <john.fastabend@gmail.com>
To: Andrii Nakryiko <andrii@kernel.org>
To: Martin KaFai Lau <martin.lau@linux.dev>
To: Eduard Zingerman <eddyz87@gmail.com>
To: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: Song Liu <song@kernel.org>
To: Yonghong Song <yonghong.song@linux.dev>
To: Jiri Olsa <jolsa@kernel.org>
To: Thomas Gleixner <tglx@kernel.org>
To: Borislav Petkov <bp@alien8.de>
To: Dave Hansen <dave.hansen@linux.intel.com>
To: x86@kernel.org
To: "H. Peter Anvin" <hpa@zytor.com>
To: Shuah Khan <shuah@kernel.org>
To: Ingo Molnar <mingo@redhat.com>
To: Andrey Konovalov <andreyknvl@gmail.com>
To: Emil Tsalapatis <emil@etsalapatis.com>
To: Ihor Solodrai <ihor.solodrai@linux.dev>
To: Yafang Shao <laoar.shao@gmail.com>
Cc: ebpf@linuxfoundation.org
Cc: Bastien Curutchet <bastien.curutchet@bootlin.com>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Cc: bpf@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org

---
Alexis Lothoré (eBPF Foundation) (8):
      bpf: mark instructions accessing program stack
      bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
      bpf, x86: refactor BPF_ST management in do_jit
      bpf, x86: emit KASAN checks in x86 JITed programs
      bpf, x86: enable KASAN for JITed programs on x86
      selftests/bpf: make cmdline_contains stricter
      selftests/bpf: add helpers for KASAN in JIT testing
      selftests/bpf: add tests to validate KASAN on JIT programs

 arch/x86/Kconfig                                   |   1 +
 arch/x86/net/bpf_jit_comp.c                        | 282 +++++++++---
 include/linux/bpf_verifier.h                       |   2 +
 kernel/bpf/Kconfig                                 |  17 +
 kernel/bpf/fixups.c                                |  45 +-
 kernel/bpf/verifier.c                              |  10 +
 tools/testing/selftests/bpf/prog_tests/kasan.c     | 479 ++++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan.c          | 502 +++++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan_harden.c   |  52 +++
 .../testing/selftests/bpf/test_kmods/bpf_testmod.c |  55 +++
 tools/testing/selftests/bpf/unpriv_helpers.c       |  19 +-
 tools/testing/selftests/bpf/unpriv_helpers.h       |   2 +
 12 files changed, 1405 insertions(+), 61 deletions(-)
---
base-commit: 490912167ead6e5c64b7a7e960766b3ccdd8fdda
change-id: 20260126-kasan-fcd68f64cd7b

Best regards,
--  
Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28 10:30   ` bot+bpf-ci
  2026-08-28  9:21 ` [PATCH bpf-next v8 2/8] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

In order to prepare to emit KASAN checks in JITed programs, JIT
compilers need to be aware about whether some load/store instructions
are targeting the bpf program stack, as those should not be monitored
(we already have guard pages for that, and it is difficult anyway to
correctly monitor any kind of data passed on stack).

To support this need, make the BPF verifier mark the instructions
depending on whether they could access or not memory other than stack.
As different states in the verifier could lead to different memory types
for the same access, just marking an instruction as accessing stack only
is not enough (it could be some other memory type in another verifier
state), so the algorithm rather sets by default any load/store
instruction as stack only, and if _any_ state leads to any memory access
type other than PTR_TO_STACK, it overrides this setting. It also takes
care about shifting back the instruction marking in adjust_insn_aux_data
if the verifier patches instructions. However, if the verifier generates
new BPF_ST/BPF_STX/BPF_LDX while patching some instructions, those new
ones are systematically marked as non-stack-accessing: this may
over-instrument a few memory accessing instructions, but it allows
making sure that we will not miss accidentally any.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v8:
- make sure to test the original register type rather than a possibly
  verifier-mutated value in check_mem_access

Changes in v6:
- drop original instruction offset tracking, but add a best effort
  original insn marking preservation
- update comment style

Changes in v5:
- fix incorrect marking for single instruction patch

Changes in v4:
- include BPF_ATOMIC in is_mem_insn
- correctly mark instructions in adjust_insn_aux_data if patch generates
  a single instruction (ie replace an instruction)

Changes in v3:
- drop getter
- drop cBPF handling
- update marking shifting logic to track more precisely orignal
  instructions
- systematically mark newly generated instructions as non-stack
  accessing

Changes in v2:
- invert marking logic to cover possible different reg types when the
  verifier covers different states
- add a best-effort processing for classical bpf programs, inspecting
  directly src and dst registers since we don't have verifier env
- make sure to keep marking in sync with prog when it is patched by
  verifier
---
 include/linux/bpf_verifier.h |  2 ++
 kernel/bpf/fixups.c          | 45 +++++++++++++++++++++++++++++++++++++++++---
 kernel/bpf/verifier.c        | 10 ++++++++++
 3 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 004b06785521..412a13a664fa 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/fixups.c b/kernel/bpf/fixups.c
index 65b441e4a351..edcb0cbbb13d 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 e036ae20bf6b..364332eb4185 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3272,6 +3272,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)
@@ -6416,6 +6421,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 			    int value_regno, bool strict_alignment_once, bool is_ldsx)
 {
 	struct bpf_reg_state *regs = cur_regs(env);
+	enum bpf_reg_type ptr_type = reg->type;
 	int size, err = 0;
 
 	size = bpf_size_to_bytes(bpf_size);
@@ -6654,6 +6660,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 				clear_scalar_id(&regs[value_regno]);
 		}
 	}
+
+	if (!err && ptr_type != PTR_TO_STACK)
+		mark_non_stack_access(env, insn_idx);
+
 	return err;
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 2/8] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 3/8] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Add a new Kconfig option CONFIG_BPF_JIT_KASAN that automatically enables
generic KASAN (Kernel Address SANitizer) memory access checks for
JIT-compiled BPF programs as well, when both KASAN (and more
specifically, generic KASAN with KASAN_VMALLOC) and JIT compiler are
enabled. This new Kconfig is not a user selectable one: it is
automatically enabled if KASAN is enabled on a compatible platform. When
enabled, the JIT compiler will emit shadow memory checks before memory
loads and stores to detect use-after-free or out-of-bounds accesses at
runtime. The option is gated behind HAVE_EBPF_JIT_KASAN, as it needs
proper arch-specific implementation.

Acked-by: Andrey Konovalov <andreyknvl@gmail.com>
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v6:
- fix commit msg
- get rid of "if KASAN" when setting default value, already implied by
  KASAN_GENERIC

Changes in v4:
- reorganize dependencies (Andrey)
- drop VMAP_STACK dependency

Changes in v2:
- add dependency on kasan for vmalloc and vmalloc'ed stack
---
 kernel/bpf/Kconfig | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
index eb3de35734f0..d7d25477ef48 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"

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 3/8] bpf, x86: refactor BPF_ST management in do_jit
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 2/8] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 4/8] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

In order to prepare for KASAN checks insertion before every
memory-related load or store, group all BPF_ST instructions that indeed
access memory in a single helper to allow instrumenting those in one
call, rather than having to instrument all cases individually.

Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v7:
- align dst_reg type with same type as other emit helpers
- fix line wrapping
- add missing doc on top of new helper
- remove unneeded cast

Changes in v3:
- new patch
---
 arch/x86/net/bpf_jit_comp.c | 98 +++++++++++++++++++++++++++------------------
 1 file changed, 59 insertions(+), 39 deletions(-)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 48429fae0641..13ba3232993a 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1315,6 +1315,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);
@@ -2250,49 +2307,12 @@ 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 (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 */

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 4/8] bpf, x86: emit KASAN checks in x86 JITed programs
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (2 preceding siblings ...)
  2026-08-28  9:21 ` [PATCH bpf-next v8 3/8] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28 10:30   ` bot+bpf-ci
  2026-08-28  9:21 ` [PATCH bpf-next v8 5/8] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Insert KASAN shadow memory checks before memory load and store
operations in JIT-compiled BPF programs. This helps detect memory safety
bugs such as use-after-free and out-of-bounds accesses at runtime.

The main instructions being targeted are BPF_ST, BPF_STX and BPF_LDX,
but not all of them are being instrumented:
- if the load/store instruction is in fact accessing the program stack,
  emit_kasan_check silently skips the instrumentation, as we can already
  benefit from guard pages to monitor stack accesses.
- if the load/store instruction is a BPF_PROBE_MEM or a BPF_PROBE_ATOMIC
  instruction, we do not instrument it, as the passed address can fault
  (hence the custom fault management with BPF_PROBE_XXX instructions),
  and so the corresponding kasan check could fault as well.

To support those new instructions insertion, create the
emit_kasan_check() helper that emits KASAN shadow memory checks before
memory accesses in JIT-compiled BPF programs. The implementation relies
on the existing __asan_{load,store}X functions from KASAN subsystem. The
helper:
- saves registers. This includes caller-saved registers, but also
  temporary registers, as those were possibly used by the
  affected program.
- computes the accessed address and stores it in %rdi
- calls the relevant function, depending on the instruction being a load
  or a store, and the size of the access.
- restores registers

The special care needed when inserting this instrumentation comes at the
cost of a non negligeable increase in JITed code size. For example, a
bare

  mov 	0x0(%si),rbx # Load in rbx content at address stored in rsi

becomes

  push    %rax
  push    %rcx
  push    %rdx
  push    %rsi
  push    %rdi
  push    %r8
  push    %r9
  push    %r10
  push    %r11
  mov     %rsi,%rdi
  call    0xffffffff81da0a60 <__asan_load8>
  pop     %r11
  pop     %r10
  pop     %r9
  pop     %r8
  pop     %rdi
  pop     %rsi
  pop     %rdx
  pop     %rcx
  pop     %rax
  mov     0x0(%rsi),rbx

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v8:
- add back r10/r11 save/restore in emit_kasan_check

Changes in v6:
- add a comment about r10/r11 being skipped in emit_kasan_check
- merge the commit defining the helper into the commit actually using
  it
- move non_stack_access check out of the emit_kasan_check helper
- replace hardcoded ip increment with actual computation

Changes in v5:
- (from former split commit) change access type (read -> write) for
  atomic RMW check

Changes in v4:
- (from former split commit) refactor BPF_FETCH handling

Changes in v3:
- skip kasan instrumentation if there is no verifier env (cBPF)
- move helper up in the file
- (from former split commit) fix LLVM23 build failure

Changes in v2:
- move asan functions declaration directly into jit compiler, and guard
  them with IS_ENABLED
- remove faulty stack alignment, no arg is passed to kasan funcs on the
  stack anyway
- make sure to emit call depth accounting code
- do not save unneeded registers
- update helper signature to let caller configure some values (eg:
  is_write)
- (from former split commit) support BPF_ATOMICS
- (from former split commit) support BPF_ST
- (from former split commit) make sure to systematically pass correct
  instruction to kasan check
---
 arch/x86/net/bpf_jit_comp.c | 188 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 171 insertions(+), 17 deletions(-)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 13ba3232993a..adb62216dd20 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);
+	/* Save r10 */
+	EMIT2(0x41, 0x52);
+	/* Save r11 */
+	EMIT2(0x41, 0x53);
+	/* 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, 0x5B);
+	EMIT2(0x41, 0x5A);
+	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)
 {
@@ -1480,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",
@@ -1911,10 +2026,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;
@@ -1931,6 +2048,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 */
@@ -2311,6 +2432,13 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 		case BPF_ST | BPF_MEM | BPF_H:
 		case BPF_ST | BPF_MEM | BPF_W:
 		case BPF_ST | BPF_MEM | BPF_DW:
+			if (!accesses_stack_only) {
+				err = emit_kasan_check(env, &prog, dst_reg,
+						       insn, ip, true);
+				if (err)
+					return err;
+			}
+
 			emit_st(&prog, insn, dst_reg, outgoing_arg_base,
 				outgoing_rsp);
 			break;
@@ -2330,6 +2458,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 				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;
 
@@ -2511,6 +2645,11 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 				/* 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)
@@ -2572,28 +2711,42 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 			}
 			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);
@@ -2625,15 +2778,16 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 			}
 
 			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)) {

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 5/8] bpf, x86: enable KASAN for JITed programs on x86
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (3 preceding siblings ...)
  2026-08-28  9:21 ` [PATCH bpf-next v8 4/8] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 6/8] selftests/bpf: make cmdline_contains stricter Alexis Lothoré (eBPF Foundation)
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Mark x86 as supporting KASAN checks in JITed programs so that the
corresponding JIT compiler inserts checks on the translated
instructions.

Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
 arch/x86/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 48ccc3e6059d..745890d91e99 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

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 6/8] selftests/bpf: make cmdline_contains stricter
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (4 preceding siblings ...)
  2026-08-28  9:21 ` [PATCH bpf-next v8 5/8] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 7/8] selftests/bpf: add helpers for KASAN in JIT testing Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
  7 siblings, 0 replies; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

cmdline_contains is used by BPF selftests to check the presence of
specific kernel commandline parameters, but it currently suffers from
two issues:
- the read commandline isn't NULL terminated right after the read data
  but only at the end of the buffer, leaving uninitialized bytes that
  are then possibly tokenized
- the comparison of found tokens is done based on the size of found
  token. This could lead to too-short-but-matching tokens to wrongly
  match the search pattern.

Enforce stricter checks in cmdline_contains to avoid accidental matches.

Fixes: 399f6185a1c0 ("selftests/bpf: Fix selftests broken by mitigations=off")
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v7:
- add missing Fixes tag
- drop unneeded size check, already done by strcmp
---
 tools/testing/selftests/bpf/unpriv_helpers.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index f997d7ec8fd0..9dadcacaef0c 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;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 7/8] selftests/bpf: add helpers for KASAN in JIT testing
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (5 preceding siblings ...)
  2026-08-28  9:21 ` [PATCH bpf-next v8 6/8] selftests/bpf: make cmdline_contains stricter Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28  9:21 ` [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
  7 siblings, 0 replies; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Add two simple helpers to allow checking whether KASAN for eBPF tests
should be executed:
- one helper to check if BPF_JIT_KASAN is enabled in kernel
  configuration
- one helper to check if the kernel is running with kasan_multi_shot
  (otherwise only the first test will be able to trigger a report)

Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v5:
- add kasan_multi_shot helper

Changes in v2:
- fix condition
---
 tools/testing/selftests/bpf/unpriv_helpers.c | 10 ++++++++++
 tools/testing/selftests/bpf/unpriv_helpers.h |  2 ++
 2 files changed, 12 insertions(+)

diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index 9dadcacaef0c..2c8c5edb8751 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -143,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 151f67329665..a7ceb51577cd 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);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs
  2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (6 preceding siblings ...)
  2026-08-28  9:21 ` [PATCH bpf-next v8 7/8] selftests/bpf: add helpers for KASAN in JIT testing Alexis Lothoré (eBPF Foundation)
@ 2026-08-28  9:21 ` Alexis Lothoré (eBPF Foundation)
  2026-08-28 10:30   ` bot+bpf-ci
  7 siblings, 1 reply; 13+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-28  9:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
	Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
	Emil Tsalapatis, Ihor Solodrai, Yafang Shao
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Add a basic KASAN test runner that loads and test-run programs that can
trigger memory management bugs. The test captures kernel logs and ensure
that the expected KASAN splat is emitted by searching for the
corresponding first lines in the report, hence validated that the needed
instrumentation has been inserted by the JIT compiler before the
relevant memory accesses. To allow each test to trigger the expected
report, the kernel must run with the kasan_multi_shot configuration.

The runner covers different cases and settings: in the nominal case, it
validates kasan reports on basic instructions (on all supported accesses
sizes) but also when report _should not_ be emitted (eg: for accesses on
program stack). The runner also comes with a few specialized tests that
are then not executed for all sizes/locations:
- specific atomic ops
- test for instructions involving different verifier states, with some
  states flagging memory as stack, and other states as non-stack memory
- tests that validate the stack marking shifting when a patch is emitted
  by the verifier (zext/rnd_hi32, constant blindind).
Most of those tests are able to trigger kasan reports by altering the
shadow memory (triggering faulty accesses is otherwise complex, because
of the verifier). A few tests trigger actual faulty accesses (eg
out-of-bound accesses)

A few of those tests depends on cpuv4 (load_acquire/store_release, st,
st_blinded), and so are executed only with test_progs-cpuv4

  # ./test_progs -a kasan
  #175/1   kasan/st_1_not_on_stack:SKIP
  #175/2   kasan/st_1_on_stack:SKIP
  #175/3   kasan/st_2_not_on_stack:SKIP
  #175/4   kasan/st_2_on_stack:SKIP
  #175/5   kasan/st_4_not_on_stack:SKIP
  #175/6   kasan/st_4_on_stack:SKIP
  #175/7   kasan/st_8_not_on_stack:SKIP
  #175/8   kasan/st_8_on_stack:SKIP
  #175/9   kasan/stx_1_not_on_stack:OK
  #175/10  kasan/stx_1_on_stack:OK
  #175/11  kasan/stx_2_not_on_stack:OK
  #175/12  kasan/stx_2_on_stack:OK
  #175/13  kasan/stx_4_not_on_stack:OK
  #175/14  kasan/stx_4_on_stack:OK
  #175/15  kasan/stx_8_not_on_stack:OK
  #175/16  kasan/stx_8_on_stack:OK
  #175/17  kasan/ldx_1_not_on_stack:OK
  #175/18  kasan/ldx_1_on_stack:OK
  #175/19  kasan/ldx_2_not_on_stack:OK
  #175/20  kasan/ldx_2_on_stack:OK
  #175/21  kasan/ldx_4_not_on_stack:OK
  #175/22  kasan/ldx_4_on_stack:OK
  #175/23  kasan/ldx_8_not_on_stack:OK
  #175/24  kasan/ldx_8_on_stack:OK
  #175/25  kasan/simple_atomic_4_not_on_stack:OK
  #175/26  kasan/simple_atomic_4_on_stack:OK
  #175/27  kasan/simple_atomic_8_not_on_stack:OK
  #175/28  kasan/simple_atomic_8_on_stack:OK
  #175/29  kasan/simple_atomic_fetch_not_on_stack:OK
  #175/30  kasan/simple_atomic_fetch_on_stack:OK
  #175/31  kasan/load_acquire_1_not_on_stack:SKIP
  #175/32  kasan/load_acquire_1_on_stack:SKIP
  #175/33  kasan/load_acquire_2_not_on_stack:SKIP
  #175/34  kasan/load_acquire_2_on_stack:SKIP
  #175/35  kasan/load_acquire_4_not_on_stack:SKIP
  #175/36  kasan/load_acquire_4_on_stack:SKIP
  #175/37  kasan/load_acquire_8_not_on_stack:SKIP
  #175/38  kasan/load_acquire_8_on_stack:SKIP
  #175/39  kasan/store_release_1_not_on_stack:SKIP
  #175/40  kasan/store_release_1_on_stack:SKIP
  #175/41  kasan/store_release_2_not_on_stack:SKIP
  #175/42  kasan/store_release_2_on_stack:SKIP
  #175/43  kasan/store_release_4_not_on_stack:SKIP
  #175/44  kasan/store_release_4_on_stack:SKIP
  #175/45  kasan/store_release_8_not_on_stack:SKIP
  #175/46  kasan/store_release_8_on_stack:SKIP
  #175/47  kasan/ldx_patched_not_on_stack:OK
  #175/48  kasan/ldx_patched_on_stack:OK
  #175/49  kasan/verifier_paths_stack_and_non_stack:OK
  #175/50  kasan/ldx_oob_1_not_on_stack:OK
  #175/51  kasan/ldx_oob_2_not_on_stack:OK
  #175/52  kasan/ldx_oob_4_not_on_stack:OK
  #175/53  kasan/ldx_oob_8_not_on_stack:OK
  #175/54  kasan/ldx_self_alias_on_stack:OK
  #175/55  kasan/st_blinded:SKIP
  #175     kasan:OK (SKIP: 25/55)
  Summary: 1/30 PASSED, 25 SKIPPED, 0/0 FAILED

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v8:
- add a test ensuring that a stack access with dst_reg = src_reg is not
  instrumented
- update stx programs to make sure that STX is emitted with any cpu
  version (without risking having ST emitted for cpuv4 for example)
- conditionnaly compile and run ST tests, depending on whether BPF cpu
  supports ST instructions
- mark the test as serial to avoid side effects with bpf_jit_harden
  toggling
- use sysctl_set_or_fail directly instead of set_bpf_jit_harden

Changes in v6:
- rename test struct in kasan program
- add oob testing
- add atomic fetch tests
- refactor a bit the test runner so that ldx_patched tests are not
  treated as special cases
- break kernel log reading loop immediately if no bytes are read

Changes in v5:
- check snprintf return codes when checking kernel logs
- skip tests in kasan_multi_shot is not enabled
- update simple atomic test expected access type (read -> write)

Changes in v4:
- prevent interleaved kernel logs from breaking kasan report search
- make new kfuncs in bpf_testmod depend on BPF_JIT_KASAN rather than
  KASAN_GENERIC
- initialize buffer passed to bpf_prog_test_run_opts

Changes in v3:
- increase kernel log buffer size
- fix comment style
- check bpf_program__fd return code
- document kasan_multi_shot
- fix copy-paste mistakes on poisoning/unpoisoning sequences
- add test for patch due to constant blinding

Changes in v2:
- simplify tests by just manually poisoning test areas with a dedicated
  kfunc
- introduce one prog per covered instruction family
- make sure that tests do not consume kernel logs (use /dev/kmgs rather
  than klogctl)
- add tests for stack accesses:
  - marking correctly set when there are diverging verifier states
    leading to different memory types
  - marking kept in sync with prog when it is patched
---
 tools/testing/selftests/bpf/prog_tests/kasan.c     | 479 ++++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan.c          | 502 +++++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan_harden.c   |  52 +++
 .../testing/selftests/bpf/test_kmods/bpf_testmod.c |  55 +++
 4 files changed, 1088 insertions(+)

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 000000000000..7cd4d1208c3b
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
@@ -0,0 +1,479 @@
+// 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 "sysctl_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 needs_st;
+	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;
+	bool skip_st_tests;
+	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;
+	}
+
+	if (test->needs_st && ctx->skip_st_tests) {
+		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_orig[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 (sysctl_set_or_fail("/proc/sys/net/core/bpf_jit_harden",
+			       bpf_jit_harden_orig, "2"))
+		goto free_ctx;
+
+	skel = kasan_harden__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open and load blinded prog"))
+		goto restore;
+
+	if (skel->data->skip_st_tests) {
+		test__skip();
+		goto destroy;
+	}
+
+	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:
+	sysctl_set_or_fail("/proc/sys/net/core/bpf_jit_harden", NULL,
+			   bpf_jit_harden_orig);
+free_ctx:
+	free(ctx);
+}
+
+static struct test_spec tests[] = {
+	{
+		.prog_type = "st",
+		.is_write = true,
+		.needs_st = 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
+	},
+	{
+		.prog_type = "ldx_self_alias_on_stack",
+		.is_write = false,
+		.skip_multi_size_testing = true,
+		.skip_on_stack_testing = true,
+		.run_size = 8,
+		.expect_no_report = true
+	}
+};
+
+void serial_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->skip_st_tests = skel->data->skip_st_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 000000000000..fe8e0dd228a0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan.c
@@ -0,0 +1,502 @@
+// 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");
+
+/*
+ * ST instructions are only emitted if the BPF cpu supports it (eg cpuv4),
+ * they are otherwise turned into MOV + STX, so compile and exercise ST
+ * only if supported.
+ */
+#ifdef __BPF_FEATURE_ST
+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;
+}
+
+bool skip_st_tests SEC(".data") = 0;
+#else
+bool skip_st_tests SEC(".data") = 1;
+#endif
+
+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));
+	/*
+	 * Unlike the st() programs above, the stored value comes from a
+	 * runtime source (skb->len), so it cannot be constant-folded and
+	 * clang always emits a genuine BPF_STX (register store) regardless
+	 * of the target cpu version.
+	 */
+	switch (access_size) {
+	case 1:
+		val.data_1 = (__u8)skb->len;
+		break;
+	case 2:
+		val.data_2 = (__u16)skb->len;
+		break;
+	case 4:
+		val.data_4 = (__u32)skb->len;
+		break;
+	case 8:
+		val.data_8 = (__u64)skb->len;
+		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 = (__u8)skb->len;
+		break;
+	case 2:
+		val->data_2 = (__u16)skb->len;
+		break;
+	case 4:
+		val->data_4 = (__u32)skb->len;
+		break;
+	case 8:
+		val->data_8 = (__u64)skb->len;
+		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;
+}
+
+SEC("tcx/ingress")
+int ldx_self_alias_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_test_val val;
+	__u64 addr;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(val));
+	/*
+	 * Check that a stack access with dst_reg == src_reg is correctly
+	 * flagged as stack-only access
+	 */
+	addr = (__u64)&val;
+	asm volatile(
+		"r1 = %0\n"
+		"r1 = *(u64 *)(r1 + 0)\n"
+		:
+		: "r"(addr)
+		: "r1", "memory");
+	bpf_kfunc_kasan_unpoison(&val, sizeof(val));
+
+	return 0;
+}
+
+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 000000000000..8c9eb203419c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
@@ -0,0 +1,52 @@
+// 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>
+
+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");
+
+/*
+ * ST instructions are only emitted if the BPF cpu supports it (eg cpuv4),
+ * they are otherwise turned into MOV + STX, so compile and exercise ST
+ * only if supported.
+ */
+#ifdef __BPF_FEATURE_ST
+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;
+}
+bool skip_st_tests SEC(".data") = 0;
+#else
+bool skip_st_tests SEC(".data") = 1;
+#endif
+
+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 850cf4f830c4..f5e89ce0ff32 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)

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH bpf-next v8 4/8] bpf, x86: emit KASAN checks in x86 JITed programs
  2026-08-28  9:21 ` [PATCH bpf-next v8 4/8] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-28 10:30   ` bot+bpf-ci
  0 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-28 10:30 UTC (permalink / raw)
  To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
	eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
	dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
	ihor.solodrai, laoar.shao
  Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
	linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 8220 bytes --]

> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index 13ba3232993ac..adb62216dd204 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c

[ ... ]

> @@ -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;

Does emit_kasan_check need to re-read insn->off here rather than taking
the effective displacement as a parameter? At the BPF_STX | BPF_MEM site
(line 2458), do_jit() remaps the offset for stack arguments:

    insn_off = insn->off;
    if (dst_reg == BPF_REG_PARAMS) {
        insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
        dst_reg = BPF_REG_FP;
    }
    ...
    emit_kasan_check(env, &prog, dst_reg, insn, ip, true);
    emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);

The check receives the remapped dst_reg (BPF_REG_FP) but re-derives the
offset from insn->off, so the check validates 'rbp + insn->off' while
emit_stx() writes to 'rbp + insn_off'.

The BPF_ST | BPF_MEM case (line 2432) has a second issue: the check
runs before any register remapping, so it is given the raw dst_reg:

    if (!accesses_stack_only) {
        err = emit_kasan_check(env, &prog, dst_reg, insn, ip, true);
        ...
    }
    emit_st(&prog, insn, dst_reg, outgoing_arg_base, outgoing_rsp);

When dst_reg == BPF_REG_PARAMS (MAX_BPF_REG == 11), emit_st() remaps
both the register and offset internally, but emit_kasan_check() receives
the raw value. reg2hex[] has no entry for index 11, so reg2hex[11] == 0
and EMIT_mov(BPF_REG_1, BPF_REG_PARAMS) encodes 'mov rdi, rax'. The
check then validates 'rax + insn->off' - RAX being BPF R0, an arbitrary
program value - while the store goes to the outgoing-argument slot.

In the BPF_DW / insn->off == -8 sub-case of BPF_ST, emit_st() performs
no memory access at all (the value is materialized into R9), yet a full
__asan_store8 check is still emitted.

The BPF_LDX path (line 2645) handles this correctly: it remaps to
BPF_REG_FP before calling emit_kasan_check(), and the incoming-arg case
skips the check entirely.

Would passing the effective displacement to emit_kasan_check() as an
explicit parameter fix both the BPF_STX offset mismatch and let the
BPF_ST path perform the remap before the check?

> +	u8 *prog = *pprog;
> +	void *kasan_func;
> +
> +	if (!env)
> +		return 0;

[ ... ]

> @@ -1911,10 +2026,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;
> @@ -1931,6 +2048,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;

This isn't a bug, but when env == NULL, accesses_stack_only is set to
false ('not stack-only, do instrument'), yet every call site then
enters emit_kasan_check() which only no-ops because of its own 'if
(!env) return 0;' guard. Would 'env ? ... : true' make the fail-safe
default self-evident and avoid depending on emit_kasan_check()'s
internal guard for correctness?

Also, the '!env' test plus 'insn_idx += aux->subprog_start' is already
encapsulated by bpf_insn_is_indirect_target() (kernel/bpf/core.c:1646-
1652). Would a small helper like bpf_insn_accesses_non_stack(env, prog,
i - 1) keep the arch-independent invariant in one place and let other
JITs reuse it when they gain KASAN support?

> +
>  		switch (insn->code) {
>  			/* ALU */

[ ... ]

> @@ -2311,6 +2432,13 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
>  		case BPF_ST | BPF_MEM | BPF_H:
>  		case BPF_ST | BPF_MEM | BPF_W:
>  		case BPF_ST | BPF_MEM | BPF_DW:
> +			if (!accesses_stack_only) {
> +				err = emit_kasan_check(env, &prog, dst_reg,
> +						       insn, ip, true);
> +				if (err)
> +					return err;
> +			}
> +
>  			emit_st(&prog, insn, dst_reg, outgoing_arg_base,
>  				outgoing_rsp);
>  			break;
> @@ -2330,6 +2458,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
>  				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;
>

[ ... ]

> @@ -2572,28 +2711,42 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
>  			}
>  			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);

This isn't a bug, but does the restructured atomic case now make
branch_target a dead store on the non-fetch paths? branch_target is
written for every non-load/store atomic (plain BPF_ADD, BPF_XCHG,
BPF_CMPXCHG without BPF_FETCH) but only read under 'if
(is_atomic_fetch)'. Would moving 'branch_target = prog;' into the
following 'if (is_atomic_fetch)' block keep the KASAN check outside the
retry loop while restoring the definition-next-to-use property?

Also, the new local 'u8 *pprog;' shadows the file-wide convention where
pprog is the u8 ** out-parameter of every emit_* helper.

> @@ -2625,15 +2778,16 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
>  			}
>
>  			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;
> -
> +		}

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33160000533

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs
  2026-08-28  9:21 ` [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-28 10:30   ` bot+bpf-ci
  2026-08-28 13:49     ` Alexis Lothoré
  0 siblings, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-28 10:30 UTC (permalink / raw)
  To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
	eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
	dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
	ihor.solodrai, laoar.shao
  Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
	linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3085 bytes --]

> diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
> new file mode 100644
> index 0000000000000..fe8e0dd228a09
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/kasan.c

[ ... ]

> +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;
> +}

Can these two programs build with the no_alu32 flavor? Looking at
Documentation/bpf/clang-notes.rst, __sync_fetch_and_or() needs -mcpu=v3
for the BPF_OR atomic instruction, but the no_alu32 build uses plain
-mcpu=v2 (tools/testing/selftests/bpf/Makefile:497-501). Clang at v2 can
only generate BPF_ADD atomics without BPF_FETCH, so BPF_OR should be an
instruction-selection error.

The sibling programs simple_atomic_on_stack and simple_atomic_not_on_stack
use __sync_fetch_and_add(), which lowers to BPF_ADD without BPF_FETCH when
the return value is unused, so those build fine. Every other prog in
selftests/bpf/progs/ that uses a non-ADD or fetching atomic guards it with
#ifdef ENABLE_ATOMICS_TESTS (progs/atomics.c, progs/arena_atomics.c,
progs/atomic_bounds.c, progs/tcp_ca_write_sk_pacing.c), and
-DENABLE_ATOMICS_TESTS is only passed to the default and cpuv4 runners, not
to no_alu32.

Would it make sense to guard both simple_atomic_fetch functions with
#ifdef ENABLE_ATOMICS_TESTS, or to verify whether test_progs-no_alu32
actually builds?

Also, since the return value of __sync_fetch_and_or() is discarded here,
does clang emit BPF_ATOMIC | BPF_OR without the BPF_FETCH flag? That would
make these identical to simple_atomic_*, just with OR instead of ADD, and
the JIT path for is_atomic_fetch (the CMPXCHG loop in
arch/x86/net/bpf_jit_comp.c added by bc9fb0ddd5f3b, where the KASAN check
is emitted before branch_target assignment) would not be exercised by
kasan/simple_atomic_fetch_on_stack or kasan/simple_atomic_fetch_not_on_stack.

The commit message shows #175/29 and #175/30 as :OK, but
progs/atomics.c:or64 uses or64_result = __sync_fetch_and_or(...) to force
the fetch variant (checked in prog_tests/atomics.c:98), and keeps a
separate or_noreturn_value program for the discarded-result form. Would
consuming the result here test the intended path?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33160000533

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack
  2026-08-28  9:21 ` [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-08-28 10:30   ` bot+bpf-ci
  0 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-28 10:30 UTC (permalink / raw)
  To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
	eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
	dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
	ihor.solodrai, laoar.shao
  Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
	linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 11539 bytes --]

> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 004b06785521c..412a13a664fa3 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;
>  };

The new field is placed at the very end of struct bpf_insn_aux_data, pushing
the struct from 136 to 144 bytes due to alignment, which adds 8 bytes of
aux data per BPF instruction. For a program at BPF_COMPLEXITY_LIMIT_INSNS
this is roughly 8 MB of extra vmalloc space for one boolean.

There is already a 3-byte padding hole before orig_idx and 27 unused bits
in the adjacent u32 bitfield group (jmp_point:1 ... indirect_target:1).

Would declaring non_stack_access as a bit in the existing u32 bitfield
group avoid the size increase?

[ ... ]

> 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);
> +}

Does is_mem_insn() correctly handle the speculation barrier
pseudo-instruction BPF_ST_NOSPEC()?

BPF_NOSPEC is defined as 0xc0, the same mode value as BPF_ATOMIC (0xc0),
and BPF_ST_NOSPEC() encodes code = BPF_ST | BPF_NOSPEC (0x02 | 0xc0 = 0xc2).
The class test passes (BPF_ST) and the mode test passes via the
BPF_MODE(insn->code) == BPF_ATOMIC arm, so is_mem_insn() returns true for
an instruction that performs no memory access at all.

Every Spectre v1/v4 mitigation patch in do_misc_fixups() inserts such a
barrier through bpf_patch_insn_data(), so on any program that requires a
nospec barrier the barrier slot is given non_stack_access = true by the
new marking code in adjust_insn_aux_data().

On x86-64 the consumer added later in the series handles case BPF_ST |
BPF_NOSPEC in a separate switch arm that emits an LFENCE and never reaches
the load/store instrumentation path, so there is no functional
misbehaviour on x86 today.

However, the helper's stated contract ('is this a memory instruction') is
wrong, the flag is set on non-memory slots, and any future or other-arch
consumer of non_stack_access that dispatches on class/mode alone will
inherit the confusion.

Should the helper exclude BPF_ST | BPF_NOSPEC explicitly, for example by
testing insn_is_nospec() or handling the BPF_ST case separately?

>
>  /* 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 'inherit the original marking' logic is driven purely by memcmp() of
the raw instruction bytes against original_insn, which makes the result
depend on how many copies of an identical encoding a patch buffer happens
to contain. A subsystem pattern flags this as potentially concerning:

If a patch buffer legitimately contains the original instruction twice (or
contains a newly generated instruction that happens to encode identically
to the original, which is easy for a repeated BPF_LDX_MEM/BPF_STX_MEM with
the same regs/offset/size), the first match consumes the marking and
clears data[off + cnt - 1].non_stack_access. Every later identical copy
falls into the else if (is_mem_insn()) arm and is set to true, but the
memcmp arm is actually taken again for those slots and now copies an
already-cleared value, silently under-marking a real non-stack access.

Conversely, a genuinely new instruction that coincidentally matches
original_insn byte-for-byte is exempted from the 'systematically mark new
memory insns' rule by the trailing memcmp.

Neither case is reachable via a patch site in this tree (all current
multi-instruction patches place the original instruction exactly once,
either first or last), so this is latent fragility rather than an
observable bug today.

Should the identity of an instruction be tracked by position instead of by
content comparison, since the patch sites all know where they put the
original?

[ ... ]

Verifier-generated instructions that provably access the program stack are
marked as non-stack accesses, which is precisely the case the feature
exists to exclude, and the over-marking is systematic rather than
incidental. A subsystem pattern flags this as potentially concerning:

Concrete reachable instances:

(1) The ARG_PTR_TO_CTX save inserted by do_misc_fixups():

kernel/bpf/fixups.c:do_misc_fixups() {
	insn_buf[cnt++] = BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_1,
				      -subprogs[0].stack_depth);

This is a frame-pointer-relative store, and since it is not the original
instruction it is marked non_stack_access = true by the loop above.

(2) The matching frame-pointer load emitted by gen_epilogue callbacks,
for example:

net/sched/bpf_qdisc.c:bpf_qdisc_gen_epilogue() {
	BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_FP, ctx_stack_off)

This gets the same treatment when the epilogue is patched in, so every
struct_ops qdisc reset/destroy program is affected.

(3) bpf_jit_blind_constants() in kernel/bpf/core.c rewrites a BPF_ST |
BPF_MEM store into {MOV AX, XOR AX, BPF_STX_MEM(...)}, so with
bpf_jit_harden enabled every blinded stack store loses its stack-only
marking.

The commit message acknowledges over-instrumentation of 'a few memory
accessing instructions' as an accepted trade-off, but these cases are
trivially detectable (dst_reg/src_reg == BPF_REG_10) and, for the
KASAN-in-JIT consumer added later in the series, they mean pointless
instrumentation on the hottest prologue/epilogue accesses.

Would filtering on BPF_REG_FP in is_mem_insn() or adjust_insn_aux_data()
remove this class of false positives?

> @@ -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 e036ae20bf6b9..364332eb41859 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -3272,6 +3272,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;
> +}
> +

[ ... ]

> @@ -6416,6 +6421,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
>  			    int value_regno, bool strict_alignment_once, bool is_ldsx)
>  {
>  	struct bpf_reg_state *regs = cur_regs(env);
> +	enum bpf_reg_type ptr_type = reg->type;
>  	int size, err = 0;
>
>  	size = bpf_size_to_bytes(bpf_size);

[ ... ]

> @@ -6654,6 +6660,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
>  				clear_scalar_id(&regs[value_regno]);
>  		}
>  	}
> +
> +	if (!err && ptr_type != PTR_TO_STACK)
> +		mark_non_stack_access(env, insn_idx);
> +
>  	return err;
>  }

Does the unconditional marking at the tail of check_mem_access() handle
callers that are not load/store instructions?

check_mem_access() is called with the call instruction's index from
process_dynptr_func(), process_iter_arg(), check_helper_call(), and
process_irq_flag(), where it is used to validate a helper/kfunc argument
pointer rather than an actual BPF memory instruction.

Because those argument pointers are typically PTR_TO_MAP_VALUE, PTR_TO_MEM,
PTR_TO_BTF_ID etc. (i.e. != PTR_TO_STACK), the aux entry of the call
instruction is marked. The flag then no longer means 'this load/store
instruction may touch non-stack memory' for those slots; it means nothing,
since a call performs no direct memory access.

Consumers that only look at load/store opcodes are unaffected, but the
marking is semantically wrong and, combined with the BPF_ST_NOSPEC issue
above, means non_stack_access can be true for aux entries whose
instruction is not a memory access.

Should the marking be gated on the instruction class actually being
BPF_LDX/BPF_STX/BPF_ST, or should the mark be done by the load/store
callers rather than inside check_mem_access()?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33160000533

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs
  2026-08-28 10:30   ` bot+bpf-ci
@ 2026-08-28 13:49     ` Alexis Lothoré
  0 siblings, 0 replies; 13+ messages in thread
From: Alexis Lothoré @ 2026-08-28 13:49 UTC (permalink / raw)
  To: bot+bpf-ci, alexis.lothore, ast, daniel, john.fastabend, andrii,
	martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
	dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
	ihor.solodrai, laoar.shao
  Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
	linux-kselftest, martin.lau, clm

On Fri Aug 28, 2026 at 12:30 PM CEST, bot+bpf-ci wrote:
>> diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
>> new file mode 100644
>> index 0000000000000..fe8e0dd228a09
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/kasan.c
>
> [ ... ]
>
>> +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;
>> +}
>
> Can these two programs build with the no_alu32 flavor? Looking at
> Documentation/bpf/clang-notes.rst, __sync_fetch_and_or() needs -mcpu=v3
> for the BPF_OR atomic instruction, but the no_alu32 build uses plain
> -mcpu=v2 (tools/testing/selftests/bpf/Makefile:497-501). Clang at v2 can
> only generate BPF_ADD atomics without BPF_FETCH, so BPF_OR should be an
> instruction-selection error.
>
> The sibling programs simple_atomic_on_stack and simple_atomic_not_on_stack
> use __sync_fetch_and_add(), which lowers to BPF_ADD without BPF_FETCH when
> the return value is unused, so those build fine. Every other prog in
> selftests/bpf/progs/ that uses a non-ADD or fetching atomic guards it with
> #ifdef ENABLE_ATOMICS_TESTS (progs/atomics.c, progs/arena_atomics.c,
> progs/atomic_bounds.c, progs/tcp_ca_write_sk_pacing.c), and
> -DENABLE_ATOMICS_TESTS is only passed to the default and cpuv4 runners, not
> to no_alu32.
>
> Would it make sense to guard both simple_atomic_fetch functions with
> #ifdef ENABLE_ATOMICS_TESTS, or to verify whether test_progs-no_alu32
> actually builds?
>
> Also, since the return value of __sync_fetch_and_or() is discarded here,
> does clang emit BPF_ATOMIC | BPF_OR without the BPF_FETCH flag? That would
> make these identical to simple_atomic_*, just with OR instead of ADD, and
> the JIT path for is_atomic_fetch (the CMPXCHG loop in
> arch/x86/net/bpf_jit_comp.c added by bc9fb0ddd5f3b, where the KASAN check
> is emitted before branch_target assignment) would not be exercised by
> kasan/simple_atomic_fetch_on_stack or kasan/simple_atomic_fetch_not_on_stack.
>
> The commit message shows #175/29 and #175/30 as :OK, but
> progs/atomics.c:or64 uses or64_result = __sync_fetch_and_or(...) to force
> the fetch variant (checked in prog_tests/atomics.c:98), and keeps a
> separate or_noreturn_value program for the discarded-result form. Would
> consuming the result here test the intended path?

Even for no_alu32, the __sync_fetch_and_or is properly generated:

  $ llvm-objdump --disassemble-symbols=simple_atomic_fetch_on_stack tools/testing/selftests/bpf/no_alu32/kasan.bpf.o
  
  tools/testing/selftests/bpf/no_alu32/kasan.bpf.o:       file format elf64-bpf
  
  Disassembly of section tcx/ingress:
  
  00000000000006d0 <simple_atomic_fetch_on_stack>:
       218:       bf a6 00 00 00 00 00 00 r6 = r10
       219:       07 06 00 00 f0 ff ff ff r6 += -0x10
       220:       bf 61 00 00 00 00 00 00 r1 = r6
       221:       b7 02 00 00 10 00 00 00 r2 = 0x10
       222:       85 10 00 00 ff ff ff ff call -0x1
       223:       b7 01 00 00 08 00 00 00 r1 = 0x8
       224:       db 1a f8 ff 41 00 00 00 r1 = atomic_fetch_or((u64 *)(r10 - 0x8), r1)
       225:       bf 61 00 00 00 00 00 00 r1 = r6
       226:       b7 02 00 00 10 00 00 00 r2 = 0x10
       227:       85 10 00 00 ff ff ff ff call -0x1
       228:       b7 00 00 00 00 00 00 00 r0 = 0x0
       229:       95 00 00 00 00 00 00 00 exit


My understanding is that _32 bits_ atomics aren't available with
-mcpu=v2, but 64 bits atomic fetch operations are, so that's why I stuck
with a DW operation for the simple_atomic_fetch(_not)_on_stack tests.

>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33160000533




-- 
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-08-28 13:49 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28  9:21 [PATCH bpf-next v8 0/8] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-28  9:21 ` [PATCH bpf-next v8 1/8] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
2026-08-28 10:30   ` bot+bpf-ci
2026-08-28  9:21 ` [PATCH bpf-next v8 2/8] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-28  9:21 ` [PATCH bpf-next v8 3/8] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
2026-08-28  9:21 ` [PATCH bpf-next v8 4/8] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-28 10:30   ` bot+bpf-ci
2026-08-28  9:21 ` [PATCH bpf-next v8 5/8] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
2026-08-28  9:21 ` [PATCH bpf-next v8 6/8] selftests/bpf: make cmdline_contains stricter Alexis Lothoré (eBPF Foundation)
2026-08-28  9:21 ` [PATCH bpf-next v8 7/8] selftests/bpf: add helpers for KASAN in JIT testing Alexis Lothoré (eBPF Foundation)
2026-08-28  9:21 ` [PATCH bpf-next v8 8/8] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
2026-08-28 10:30   ` bot+bpf-ci
2026-08-28 13:49     ` Alexis Lothoré

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox