BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs
@ 2026-07-08  8:00 Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
                   ` (9 more replies)
  0 siblings, 10 replies; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Hello,
this is v4 of the series aiming to bring basic support for KASAN checks
to BPF JITed programs.

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 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>
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) (10):
      bpf: propagate original instruction offset when patching program
      bpf: mark instructions accessing program stack
      bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
      bpf, x86: add helper to emit kasan checks in x86 JITed programs
      bpf, x86: refactor BPF_ST management in do_jit
      bpf, x86: emit KASAN checks into x86 JITed programs
      bpf, x86: enable KASAN for JITed programs on x86
      selftests/bpf: add helper to check whether eBPF KASAN is active
      selftests/bpf: move bpf_jit_harden helper into testing_helpers
      selftests/bpf: add tests to validate KASAN on JIT programs

 arch/x86/Kconfig                                   |   1 +
 arch/x86/net/bpf_jit_comp.c                        | 271 ++++++++++---
 include/linux/bpf_verifier.h                       |   2 +
 include/linux/filter.h                             |  10 +-
 kernel/bpf/Kconfig                                 |  17 +
 kernel/bpf/core.c                                  |   2 +-
 kernel/bpf/fixups.c                                | 137 +++++--
 kernel/bpf/verifier.c                              |   9 +
 .../selftests/bpf/prog_tests/bpf_insn_array.c      |  41 +-
 tools/testing/selftests/bpf/prog_tests/kasan.c     | 425 +++++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan.c          | 394 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan_harden.c   |  41 ++
 .../testing/selftests/bpf/test_kmods/bpf_testmod.c |  22 ++
 tools/testing/selftests/bpf/testing_helpers.c      |  32 ++
 tools/testing/selftests/bpf/testing_helpers.h      |   1 +
 tools/testing/selftests/bpf/unpriv_helpers.c       |   5 +
 tools/testing/selftests/bpf/unpriv_helpers.h       |   1 +
 17 files changed, 1280 insertions(+), 131 deletions(-)
---
base-commit: 066e13a096aa8bd787413a78aff34f6308e187d4
change-id: 20260126-kasan-fcd68f64cd7b

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


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

* [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:19   ` sashiko-bot
  2026-07-08  8:00 ` [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

When the verifier patches an ebpf program with bpf_patch_insn_data, it
then calls adjust_insn_aux_data to make sure that insn_aux_data takes
into account the newly inserted patch. Some of the data offset is pretty
straightforward to deduce, it is for example the case for
indirect_target, as any patch affecting indirect calls will
systematically move the original instruction to the end of the new
patch.

In order to introduce KASAN support for eBPF JIT, we need to mark any
load/store instruction that accesses non-stack memory, but updating this
new marking after a patch is not as straightforward as for indirect
calls: the original BPF_ST/BPF_STX/BPF_LDX can be at the beginning, at
the end or somewhere in the middle of the new patch: we then need some
additional info to properly update this marking.

Add a new parameter to bpf_patch_insn_data and adjust_insn_aux_data to
convey the info about the new location in the patch of the original
instruction. This info does not always make sense depending on the
generated patch (eg, some bpf helpers being inlined by the verifier, and
so turned into multiple new instructions without any remaining BPF_CALL),
the parameter can then be set to -1. It will be used in next patches to
properly handle insn_aux_data adjustment for the new KASAN
instrumentation

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v4:
- define insn_off_in_patch inside convert_ctx_accesses main loop to
  avoid old value leakage

Changes in v3:
- new patch
---
 include/linux/filter.h |  10 +++--
 kernel/bpf/core.c      |   2 +-
 kernel/bpf/fixups.c    | 105 +++++++++++++++++++++++++++++++++----------------
 3 files changed, 79 insertions(+), 38 deletions(-)

diff --git a/include/linux/filter.h b/include/linux/filter.h
index 14acb2455746..1ebcd247ef4e 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1210,13 +1210,17 @@ struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off,
 
 #ifdef CONFIG_BPF_SYSCALL
 struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
-				     const struct bpf_insn *patch, u32 len);
+				     const struct bpf_insn *patch, u32 len,
+				     s32 insn_off_in_patch);
 struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env);
 void bpf_restore_insn_aux_data(struct bpf_verifier_env *env,
 			       struct bpf_insn_aux_data *orig_insn_aux);
 #else
-static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
-						   const struct bpf_insn *patch, u32 len)
+static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env,
+						   u32 off,
+						   const struct bpf_insn *patch,
+						   u32 len,
+						   s32 insn_off_in_patch)
 {
 	return ERR_PTR(-ENOTSUPP);
 }
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 6e19a030da6f..1e18b999f1b5 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1598,7 +1598,7 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
 			continue;
 
 		if (env)
-			tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten);
+			tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten, rewritten - 1);
 		else
 			tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten);
 
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 12a8a4eb757f..f515fc9669ce 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -157,7 +157,8 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
  * [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,
+				 s32 insn_off_in_patch)
 {
 	struct bpf_insn_aux_data *data = env->insn_aux_data;
 	struct bpf_insn *insn = new_prog->insnsi;
@@ -245,7 +246,8 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)
 }
 
 struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
-				     const struct bpf_insn *patch, u32 len)
+				     const struct bpf_insn *patch, u32 len,
+				     s32 insn_off_in_patch)
 {
 	struct bpf_prog *new_prog;
 	struct bpf_insn_aux_data *new_data = NULL;
@@ -269,7 +271,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, insn_off_in_patch);
 	adjust_subprog_starts(env, off, len);
 	adjust_insn_arrays(env, off, len);
 	adjust_poke_descs(new_prog, off, len);
@@ -668,7 +670,7 @@ int bpf_opt_subreg_zext_lo32_rnd_hi32(struct bpf_verifier_env *env,
 		patch = zext_patch;
 		patch_len = 2;
 apply_patch_buffer:
-		new_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len);
+		new_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len, 0);
 		if (!new_prog)
 			return -ENOMEM;
 		env->prog = new_prog;
@@ -713,7 +715,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 			insn_buf[cnt++] = BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_1,
 						      -subprogs[0].stack_depth);
 			insn_buf[cnt++] = env->prog->insnsi[0];
-			new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt, 1);
 			if (!new_prog)
 				return -ENOMEM;
 			env->prog = new_prog;
@@ -736,7 +738,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 			verifier_bug(env, "prologue is too long");
 			return -EFAULT;
 		} else if (cnt) {
-			new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt,
+						       cnt - 1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -759,6 +762,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 
 	for (i = 0; i < insn_cnt; i++, insn++) {
 		bpf_convert_ctx_access_t convert_ctx_access;
+		s32 insn_off_in_patch = -1;
 		u8 mode;
 
 		if (env->insn_aux_data[i + delta].nospec) {
@@ -768,7 +772,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 			*patch++ = BPF_ST_NOSPEC();
 			*patch++ = *insn;
 			cnt = patch - insn_buf;
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, cnt - 1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -841,7 +846,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 			*patch++ = *insn;
 			*patch++ = BPF_ST_NOSPEC();
 			cnt = patch - insn_buf;
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, 0);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -856,16 +862,20 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 			if (!ops->convert_ctx_access)
 				continue;
 			convert_ctx_access = ops->convert_ctx_access;
+			insn_off_in_patch = 0;
 			break;
 		case PTR_TO_SOCKET:
 		case PTR_TO_SOCK_COMMON:
 			convert_ctx_access = bpf_sock_convert_ctx_access;
+			insn_off_in_patch = 0;
 			break;
 		case PTR_TO_TCP_SOCK:
 			convert_ctx_access = bpf_tcp_sock_convert_ctx_access;
+			insn_off_in_patch = 0;
 			break;
 		case PTR_TO_XDP_SOCK:
 			convert_ctx_access = bpf_xdp_sock_convert_ctx_access;
+			insn_off_in_patch = 0;
 			break;
 		case PTR_TO_BTF_ID:
 		case PTR_TO_BTF_ID | PTR_UNTRUSTED:
@@ -971,7 +981,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
 						       size * 8, 0);
 
 patch_insn_buf:
-		new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+		new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt,
+					       insn_off_in_patch);
 		if (!new_prog)
 			return -ENOMEM;
 
@@ -1464,7 +1475,7 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat
 	 * ones for the hidden subprog. Hence all of the adjustment operations
 	 * in bpf_patch_insn_data are no-ops.
 	 */
-	prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len);
+	prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len, -1);
 	if (!prog)
 		return -ENOMEM;
 	env->prog = prog;
@@ -1549,7 +1560,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 
 			cnt = patch - insn_buf;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, 0);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1569,6 +1581,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			bool is_sdiv = isdiv && insn->off == 1;
 			bool is_smod = !isdiv && insn->off == 1;
 			struct bpf_insn *patch = insn_buf;
+			s32 insn_off_in_patch;
 
 			if (is_sdiv) {
 				/* [R,W]x sdiv 0 -> 0
@@ -1595,6 +1608,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
 				*patch++ = *insn;
 				cnt = patch - insn_buf;
+				insn_off_in_patch = cnt - 1;
 			} else if (is_smod) {
 				/* [R,W]x mod 0 -> [R,W]x */
 				/* [R,W]x mod -1 -> 0 */
@@ -1611,6 +1625,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				*patch++ = BPF_MOV32_IMM(insn->dst_reg, 0);
 				*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
 				*patch++ = *insn;
+				insn_off_in_patch = patch - insn_buf - 1;
 
 				if (!is64) {
 					*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
@@ -1626,12 +1641,14 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
 				*patch++ = *insn;
 				cnt = patch - insn_buf;
+				insn_off_in_patch = cnt - 1;
 			} else {
 				/* [R,W]x mod 0 -> [R,W]x */
 				*patch++ = BPF_RAW_INSN((is64 ? BPF_JMP : BPF_JMP32) |
 							BPF_JEQ | BPF_K, insn->src_reg,
 							0, 1 + (is64 ? 0 : 1), 0);
 				*patch++ = *insn;
+				insn_off_in_patch = patch - insn_buf - 1;
 
 				if (!is64) {
 					*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
@@ -1640,7 +1657,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				cnt = patch - insn_buf;
 			}
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, insn_off_in_patch);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1656,6 +1674,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 		     BPF_MODE(insn->code) == BPF_PROBE_MEMSX)) {
 			struct bpf_insn *patch = insn_buf;
 			u64 uaddress_limit = bpf_arch_uaddress_limit();
+			s32 insn_off_in_patch;
 
 			if (!uaddress_limit)
 				goto next_insn;
@@ -1666,11 +1685,13 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			*patch++ = BPF_ALU64_IMM(BPF_RSH, BPF_REG_AX, 32);
 			*patch++ = BPF_JMP_IMM(BPF_JLE, BPF_REG_AX, uaddress_limit >> 32, 2);
 			*patch++ = *insn;
+			insn_off_in_patch = patch - insn_buf - 1;
 			*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
 			*patch++ = BPF_MOV64_IMM(insn->dst_reg, 0);
 
 			cnt = patch - insn_buf;
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, insn_off_in_patch);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1690,7 +1711,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				return -EFAULT;
 			}
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, 0);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1743,7 +1765,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				*patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1);
 			cnt = patch - insn_buf;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1788,7 +1811,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[6] = BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_AX, stack_off_cnt);
 			cnt = 7;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, 3);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1809,7 +1833,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[3] = BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_AX, stack_off);
 			cnt = 4;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, 2);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1830,7 +1855,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			if (cnt == 0)
 				goto next_insn;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, cnt - 1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1915,7 +1941,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 								 map)->index_mask);
 			insn_buf[2] = *insn;
 			cnt = 3;
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, cnt - 1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1948,7 +1975,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[2] = *insn;
 			cnt = 3;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, cnt - 1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -1967,7 +1995,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[1] = *insn;
 			cnt = 2;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, cnt - 1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2007,8 +2036,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 					return -EFAULT;
 				}
 
-				new_prog = bpf_patch_insn_data(env, i + delta,
-							       insn_buf, cnt);
+				new_prog = bpf_patch_insn_data(
+					env, i + delta, insn_buf, cnt, -1);
 				if (!new_prog)
 					return -ENOMEM;
 
@@ -2091,7 +2120,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			cnt = 3;
 
 			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
-						       cnt);
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2119,7 +2148,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[0] = BPF_ALU32_REG(BPF_XOR, BPF_REG_0, BPF_REG_0);
 			cnt = 1;
 #endif
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2137,7 +2167,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[2] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_0, 0);
 			cnt = 3;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2171,7 +2202,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[cnt++] = BPF_JMP_A(1);
 			insn_buf[cnt++] = BPF_MOV64_IMM(BPF_REG_0, -EINVAL);
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2203,7 +2235,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				cnt = 1;
 			}
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2229,7 +2262,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 				cnt = 2;
 			}
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2245,7 +2279,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			/* Load IP address from ctx - 16 */
 			insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -16);
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       1, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2300,7 +2335,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[10] = BPF_MOV64_IMM(BPF_REG_0, -ENOENT);
 			cnt = 11;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2318,7 +2354,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			insn_buf[1] = BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_0, 0);
 			cnt = 2;
 
-			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+						       cnt, -1);
 			if (!new_prog)
 				return -ENOMEM;
 
@@ -2386,7 +2423,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 		/* Copy first actual insn to preserve it */
 		insn_buf[cnt++] = env->prog->insnsi[subprog_start];
 
-		new_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt);
+		new_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt, cnt - 1);
 		if (!new_prog)
 			return -ENOMEM;
 		env->prog = prog = new_prog;
@@ -2485,7 +2522,7 @@ static struct bpf_prog *inline_bpf_loop(struct bpf_verifier_env *env,
 	insn_buf[cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_10, r8_offset);
 
 	*total_cnt = cnt;
-	new_prog = bpf_patch_insn_data(env, position, insn_buf, cnt);
+	new_prog = bpf_patch_insn_data(env, position, insn_buf, cnt, -1);
 	if (!new_prog)
 		return new_prog;
 

-- 
2.54.0


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

* [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:10   ` sashiko-bot
  2026-07-08  8:00 ` [PATCH bpf-next v4 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  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 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          | 32 +++++++++++++++++++++++++++++++-
 kernel/bpf/verifier.c        |  9 +++++++++
 3 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 76b8b7627a10..868101ef5002 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -723,6 +723,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 f515fc9669ce..feee1f33de9d 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -152,6 +152,18 @@ 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
@@ -172,8 +184,14 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
 	 */
 	data[off].zext_dst = insn_has_def32(insn + off + cnt - 1);
 
-	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);
 		return;
+	}
 	prog_len = new_prog->len;
 
 	memmove(data + off + cnt - 1, data + off,
@@ -183,7 +201,19 @@ 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 = insn_has_def32(insn + i);
+		if (i == off + insn_off_in_patch) {
+			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
+	 */
+	if (is_mem_insn(insn + off + cnt - 1) && insn_off_in_patch != cnt - 1)
+		data[off + cnt - 1].non_stack_access = true;
 
 	/*
 	 * The indirect_target flag of the original instruction was moved to the last of the
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 51f7965d42e3..1246ce8f5f0f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3144,6 +3144,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)
@@ -6333,6 +6338,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 		else
 			coerce_reg_to_size_sx(&regs[value_regno], size);
 	}
+
+	if (!err && reg->type != PTR_TO_STACK)
+		mark_non_stack_access(env, insn_idx);
+
 	return err;
 }
 

-- 
2.54.0


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

* [PATCH bpf-next v4 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08 12:48   ` Andrey Konovalov
  2026-07-08  8:00 ` [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  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 either
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.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
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..e1f3850d2f5a 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 if KASAN
+	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.54.0


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

* [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (2 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:16   ` sashiko-bot
  2026-07-08  8:00 ` [PATCH bpf-next v4 05/10] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Add the emit_kasan_check() function 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:
- ensures that the kasan instrumention is actually needed: if the
  instruction being processed accesses the program stack, we skip the
  instrumentation, as those accesses are already protected with page
  guards
- saves registers. This includes caller-saved registers, but also
  temporary registers, as those were possibly used by the
  affected program. Theoretically, r10 and r11 should be saved as well,
  but the number of called function and their scope being limited, they
  are skipped for the sake of reducing the overhead
- 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
  mov     %rsi,%rdi
  call    0xffffffff81da0a60 <__asan_load8>
  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 v3:
- skip kasan instrumentation if there is no verifier env (cBPF)
- move helper up in the file

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)
---
 arch/x86/net/bpf_jit_comp.c | 95 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index de7515ea1bea..d35f58350d71 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,90 @@ 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, bool accesses_stack_only)
+{
+#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;
+
+	if (accesses_stack_only)
+		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);
+
+	/* mov rdi, addr_reg */
+	EMIT_mov(BPF_REG_1, addr_reg);
+
+	/* add rdi, off (if offset is non-zero) */
+	if (off) {
+		if (is_imm8(off)) {
+			/* add rdi, imm8 */
+			EMIT4(0x48, 0x83, 0xC7, (u8)off);
+		} else {
+			/* add rdi, imm32 */
+			EMIT3_off32(0x48, 0x81, 0xC7, off);
+		}
+	}
+
+	/* Adjust ip to account for the instrumentation generated so far */
+	ip += (prog - *pprog);
+	/* We emit a call, so update call depth counting */
+	ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip);
+	/* call kasan_func */
+	if (emit_call(&prog, kasan_func, ip))
+		return -ERANGE;
+
+	EMIT2(0x41, 0x59);
+	EMIT2(0x41, 0x58);
+	EMIT1(0x5F);
+	EMIT1(0x5E);
+	EMIT1(0x5A);
+	EMIT1(0x59);
+	EMIT1(0x58);
+
+	*pprog = prog;
+#endif /* CONFIG_BPF_JIT_KASAN */
+	return 0;
+}
+
 /* LDX: dst_reg = *(u8*)(src_reg + off) */
 static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
 {

-- 
2.54.0


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

* [PATCH bpf-next v4 05/10] bpf, x86: refactor BPF_ST management in do_jit
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (3 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  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.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- new patch
---
 arch/x86/net/bpf_jit_comp.c | 99 +++++++++++++++++++++++++++------------------
 1 file changed, 60 insertions(+), 39 deletions(-)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index d35f58350d71..959fcaa0cde9 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1410,6 +1410,64 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int
 	*pprog = prog;
 }
 
+static void emit_st(u8 **pprog, struct bpf_insn *insn, int 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,
+				       (u32)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);
@@ -2302,49 +2360,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.54.0


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

* [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (4 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 05/10] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:16   ` sashiko-bot
  2026-07-08  8:00 ` [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  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.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v4:
- refactor BPF_FETCH handling

Changes in v3:
- fix LLVM23 build failure

Changes in v2:
- support BPF_ATOMICS
- support BPF_ST
- make sure to systematically pass correct instruction to kasan check
---
 arch/x86/net/bpf_jit_comp.c | 79 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 63 insertions(+), 16 deletions(-)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 959fcaa0cde9..65a00e84799c 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1576,17 +1576,31 @@ 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:
+		err = emit_kasan_check(env, pprog, src_reg, insn, ip, false,
+				       accesses_stack_only);
+		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:
+		err = emit_kasan_check(env, pprog, dst_reg, insn, ip, true,
+				       accesses_stack_only);
+		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",
@@ -1964,10 +1978,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;
@@ -1984,6 +2000,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 */
@@ -2364,6 +2384,11 @@ 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:
+			err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
+					       true, accesses_stack_only);
+			if (err)
+				return err;
+
 			emit_st(&prog, insn, dst_reg, outgoing_arg_base,
 				outgoing_rsp);
 			break;
@@ -2383,6 +2408,10 @@ 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;
 			}
+			err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
+					       true, accesses_stack_only);
+			if (err)
+				return err;
 			emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
 			break;
 
@@ -2544,6 +2573,12 @@ 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 {
+				err = emit_kasan_check(env, &prog, src_reg,
+						       insn, ip, false,
+						       accesses_stack_only);
+				if (err)
+					return err;
 			}
 			if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX ||
 			    BPF_MODE(insn->code) == BPF_MEMSX)
@@ -2605,15 +2640,16 @@ 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;
+			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.
@@ -2626,7 +2662,17 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 				if (dst_reg == BPF_REG_0)
 					real_dst_reg = BPF_REG_AX;
 
+				ip += 3;
+			}
+			if (!bpf_atomic_is_load_store(insn)) {
+				err = emit_kasan_check(env, &prog, real_dst_reg,
+						       insn, ip, false,
+						       accesses_stack_only);
+				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);
@@ -2658,15 +2704,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.54.0


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

* [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (5 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:19   ` sashiko-bot
  2026-07-08  8:00 ` [PATCH bpf-next v4 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active Alexis Lothoré (eBPF Foundation)
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  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.

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 bdad90f210e4..705a9ab85f3a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -233,6 +233,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.54.0


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

* [PATCH bpf-next v4 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (6 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:00 ` [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
  9 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Add a simple helper checking whether JIT compiler is able to insert
KASAN checks in programs. This will allow to conditionally run
selftests for KASAN checks in JITed programs.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v2:
- fix condition
---
 tools/testing/selftests/bpf/unpriv_helpers.c | 5 +++++
 tools/testing/selftests/bpf/unpriv_helpers.h | 1 +
 2 files changed, 6 insertions(+)

diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index f997d7ec8fd0..11201b65a3d4 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -142,3 +142,8 @@ bool get_unpriv_disabled(void)
 	}
 	return mitigations_off;
 }
+
+bool get_kasan_jit_enabled(void)
+{
+	return config_contains("CONFIG_BPF_JIT_KASAN=y") == 1;
+}
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h
index 151f67329665..bc5f4c953c9d 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.h
+++ b/tools/testing/selftests/bpf/unpriv_helpers.h
@@ -5,3 +5,4 @@
 #define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled"
 
 bool get_unpriv_disabled(void);
+bool get_kasan_jit_enabled(void);

-- 
2.54.0


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

* [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (7 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:16   ` sashiko-bot
  2026-07-08  8:00 ` [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
	linux-kselftest, Alexis Lothoré (eBPF Foundation)

Move set_bpf_jit_harden to testing helpers so that other selftests can
change the hardening configuration without re-implementing a helper.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- new patch
---
 .../selftests/bpf/prog_tests/bpf_insn_array.c      | 41 ++--------------------
 tools/testing/selftests/bpf/testing_helpers.c      | 32 +++++++++++++++++
 tools/testing/selftests/bpf/testing_helpers.h      |  1 +
 3 files changed, 36 insertions(+), 38 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
index 0222a9a5d076..37c405fc2b0d 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -227,42 +227,6 @@ static void check_incorrect_index(void)
 	check_mid_insn_index();
 }
 
-static int set_bpf_jit_harden(char *level)
-{
-	char old_level;
-	int err = -1;
-	int fd = -1;
-
-	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
-	if (fd < 0) {
-		ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
-		return -1;
-	}
-
-	err = read(fd, &old_level, 1);
-	if (err != 1) {
-		ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
-		err = -1;
-		goto end;
-	}
-
-	lseek(fd, 0, SEEK_SET);
-
-	err = write(fd, level, 1);
-	if (err != 1) {
-		ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
-		err = -1;
-		goto end;
-	}
-
-	err = 0;
-	*level = old_level;
-end:
-	if (fd >= 0)
-		close(fd);
-	return err;
-}
-
 static void check_blindness(void)
 {
 	struct bpf_insn insns[] = {
@@ -272,7 +236,7 @@ static void check_blindness(void)
 		BPF_MOV64_IMM(BPF_REG_0, 1),
 		BPF_EXIT_INSN(),
 	};
-	int prog_fd = -1, map_fd;
+	int prog_fd = -1, map_fd, ret;
 	struct bpf_insn_array_value val = {};
 	char bpf_jit_harden = '@'; /* non-exizsting value */
 	int i;
@@ -291,7 +255,8 @@ static void check_blindness(void)
 		goto cleanup;
 
 	bpf_jit_harden = '2';
-	if (set_bpf_jit_harden(&bpf_jit_harden)) {
+	ret = set_bpf_jit_harden(&bpf_jit_harden);
+	if (!ASSERT_OK(ret, "set bpf_jit_harden")) {
 		bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
 		goto cleanup;
 	}
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index c970e7793dfc..737f668b35e2 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -519,6 +519,38 @@ bool is_jit_enabled(void)
 	return enabled;
 }
 
+int set_bpf_jit_harden(char *level)
+{
+	char old_level;
+	int err = -1;
+	int fd = -1;
+
+	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
+	if (fd < 0)
+		return -1;
+
+	err = read(fd, &old_level, 1);
+	if (err != 1) {
+		err = -1;
+		goto end;
+	}
+
+	lseek(fd, 0, SEEK_SET);
+
+	err = write(fd, level, 1);
+	if (err != 1) {
+		err = -1;
+		goto end;
+	}
+
+	err = 0;
+	*level = old_level;
+end:
+	if (fd >= 0)
+		close(fd);
+	return err;
+}
+
 int stack_mprotect(void)
 {
 	void *buf;
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index 2edc6fb7fc52..e00642afe86f 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -59,6 +59,7 @@ struct bpf_insn;
 int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt);
 int testing_prog_flags(void);
 bool is_jit_enabled(void);
+int set_bpf_jit_harden(char *level);
 int stack_mprotect(void);
 
 #endif /* __TESTING_HELPERS_H */

-- 
2.54.0


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

* [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs
  2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
                   ` (8 preceding siblings ...)
  2026-07-08  8:00 ` [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:00 ` Alexis Lothoré (eBPF Foundation)
  2026-07-08  8:28   ` sashiko-bot
  9 siblings, 1 reply; 26+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-08  8:00 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
  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)

A few of those tests depends on cpuv4 (load_acquire and store_release).

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

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
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     | 425 +++++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan.c          | 394 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/kasan_harden.c   |  41 ++
 .../testing/selftests/bpf/test_kmods/bpf_testmod.c |  22 ++
 4 files changed, 882 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..f006b92ee131
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * Tests validating that KASAN reports are properly instrumented and
+ * generated on a wide variety of instructions. The running kernel needs
+ * kasan_multi_shot to run multiple kasan-generating subtests at once
+ */
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include <unpriv_helpers.h>
+#include "kasan.skel.h"
+#include "kasan_harden.skel.h"
+
+#define SUBTEST_NAME_MAX_LEN	128
+#define PROG_NAME_MAX_LEN	128
+
+#define MAX_LOG_SIZE		(8 * 1024)
+#define READ_CHUNK_SIZE		256
+
+#define KASAN_PATTERN_SLAB_UAF "BUG: KASAN: slab-use-after-free " \
+	"in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_REPORT "%s of size %d at addr"
+
+static char klog_buffer[MAX_LOG_SIZE];
+static char record[MAX_LOG_SIZE];
+
+struct test_spec {
+	char *prog_type;
+	bool is_write;
+	bool only_32_or_64;
+	bool needs_load_acq_store_rel;
+	bool skip_multi_size_testing;
+	bool skip_on_stack_testing;
+	int run_size;
+	bool expect_no_report;
+	bool rnd_hi32;
+};
+
+struct kasan_write_val {
+	__u8 data_1;
+	__u16 data_2;
+	__u32 data_4;
+	__u64 data_8;
+};
+
+struct test_ctx {
+	__u8  prog_tag[BPF_TAG_SIZE];
+	struct bpf_object *obj;
+	int *access_size;
+	bool skip_load_acq_store_rel;
+	struct bpf_program *prog;
+	char prog_name[SUBTEST_NAME_MAX_LEN];
+	int klog_fd;
+};
+
+static int open_kernel_logs(void)
+{
+	int fd;
+
+	fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+	return fd;
+}
+
+static void skip_kernel_logs(int fd)
+{
+	lseek(fd, 0, SEEK_END);
+}
+
+static int read_kernel_logs(int fd, char *buf, size_t max_len)
+{
+	size_t total = 0;
+	ssize_t n;
+
+	buf[0] = '\0';
+	while (1) {
+		char *msg, *eol;
+		size_t len;
+
+		n = read(fd, record, sizeof(record) - 1);
+		if (n < 0) {
+			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)
+{
+	char access_log[READ_CHUNK_SIZE];
+	char *kasan_report_start;
+	int nsize;
+
+	snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_SLAB_UAF,
+		 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);
+	/*
+	 * 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 (!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);
+	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",
+			 test->prog_type);
+		strncpy(ctx->prog_name, test->prog_type, PROG_NAME_MAX_LEN);
+	} 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,
+			 on_stack ? "on_stack" : "not_on_stack");
+	}
+
+	if (!test__start_subtest(subtest_name))
+		return;
+
+	if (test->needs_load_acq_store_rel && ctx->skip_load_acq_store_rel) {
+		test__skip();
+		return;
+	}
+
+	exec_subtest(ctx, test, access_size, on_stack);
+}
+
+static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test,
+				  int size)
+{
+	run_subtest_with_size_and_location(ctx, test, size, false);
+	if (!test->skip_on_stack_testing)
+		run_subtest_with_size_and_location(ctx, test, size, true);
+}
+
+static void run_subtest(struct test_ctx *ctx, struct test_spec *test)
+{
+	if (test->skip_multi_size_testing) {
+		run_subtest_with_size(ctx, test, test->run_size);
+		return;
+	}
+
+	if (!test->only_32_or_64) {
+		run_subtest_with_size(ctx, test, 1);
+		run_subtest_with_size(ctx, test, 2);
+	}
+	run_subtest_with_size(ctx, test, 4);
+	run_subtest_with_size(ctx, test, 8);
+}
+
+static void run_blinding_subtest(void)
+{
+	struct test_spec blinding_spec = {
+		.prog_type = "st_blinded",
+		.is_write = true,
+	};
+	char bpf_jit_harden = '2';
+	struct kasan_harden *skel;
+	struct test_ctx *ctx;
+
+	if (!test__start_subtest("st_blinded"))
+		return;
+
+	ctx = calloc(1, sizeof(*ctx));
+	if (!ASSERT_OK_PTR(ctx, "alloc blinding ctx"))
+		return;
+	ctx->klog_fd = -1;
+
+	if (set_bpf_jit_harden(&bpf_jit_harden))
+		goto free_ctx;
+
+	skel = kasan_harden__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open and load blinded prog"))
+		goto restore;
+
+	ctx->klog_fd = open_kernel_logs();
+	if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+		goto destroy;
+
+	ctx->obj = skel->obj;
+	strncpy(ctx->prog_name, "st_blinded", PROG_NAME_MAX_LEN);
+
+	exec_subtest(ctx, &blinding_spec, 1, false);
+
+destroy:
+	close(ctx->klog_fd);
+	kasan_harden__destroy(skel);
+restore:
+	set_bpf_jit_harden(&bpf_jit_harden);
+free_ctx:
+	free(ctx);
+}
+
+static struct test_spec tests[] = {
+	{
+		.prog_type = "st",
+		.is_write = true
+	},
+	{
+		.prog_type = "stx",
+		.is_write = true
+	},
+	{
+		.prog_type = "ldx",
+		.is_write = false
+	},
+	{
+		.prog_type = "simple_atomic",
+		.is_write = false,
+		.only_32_or_64 = true
+	},
+	{
+		.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,
+		.skip_on_stack_testing = true,
+		.run_size = 4,
+		.rnd_hi32 = true
+	},
+	{
+		.prog_type = "ldx_patched_on_stack",
+		.is_write = false,
+		.skip_multi_size_testing = true,
+		.skip_on_stack_testing = true,
+		.run_size = 4,
+		.expect_no_report = true,
+		.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
+	}
+};
+
+void test_kasan(void)
+{
+	struct kasan_write_val val;
+	struct test_spec *test;
+	struct test_ctx *ctx;
+	struct kasan *skel;
+	__u32 key = 0;
+	int i, ret;
+
+	ctx = calloc(1, sizeof(struct test_ctx));
+	if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
+		return;
+
+	if (!is_jit_enabled() || !get_kasan_jit_enabled()) {
+		test__skip();
+		goto end;
+	}
+
+	skel = kasan__open();
+	if (!ASSERT_OK_PTR(skel, "open prog"))
+		goto end;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		struct bpf_program *prog;
+
+		if (!tests[i].rnd_hi32)
+			continue;
+
+		prog = bpf_object__find_program_by_name(skel->obj,
+							tests[i].prog_type);
+		if (!ASSERT_OK_PTR(prog, "find rnd_hi32 prog"))
+			goto destroy;
+		bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+	}
+
+	if (!ASSERT_OK(kasan__load(skel), "load prog"))
+		goto destroy;
+
+	ctx->obj = skel->obj;
+	ctx->access_size = &skel->bss->access_size;
+	ctx->skip_load_acq_store_rel = skel->data->skip_load_acq_store_rel_tests;
+
+	ctx->klog_fd = open_kernel_logs();
+	if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+		goto destroy;
+
+	/* Fill map with recognizable values */
+	ret = bpf_map__lookup_elem(skel->maps.test_map, &key, sizeof(key),
+				   &val, sizeof(val), 0);
+	if (!ASSERT_OK(ret, "get map"))
+		goto close;
+	val.data_1 = 0xAA;
+	val.data_2 = 0xBBBB;
+	val.data_4 = 0xCCCCCCCC;
+	val.data_8 = 0xDDDDDDDDDDDDDDDD;
+	ret = bpf_map__update_elem(skel->maps.test_map, &key, sizeof(key),
+				   &val, sizeof(val), 0);
+	if (!ASSERT_OK(ret, "set map"))
+		goto close;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		test = &tests[i];
+		run_subtest(ctx, test);
+	}
+
+	/*
+	 * Blinding subtest is handled differently as it needs the
+	 * corresponding program to be loaded with bpf_jit_harden raised
+	 */
+	run_blinding_subtest();
+
+close:
+	close(ctx->klog_fd);
+destroy:
+	kasan__destroy(skel);
+end:
+	free(ctx);
+}
diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
new file mode 100644
index 000000000000..82ddce3059ae
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include <stdbool.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;
+
+int access_size;
+
+struct kasan_write_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_write_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int st_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int stx_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+	switch (access_size) {
+	case 1:
+		val.data_1 = access_size;
+		break;
+	case 2:
+		val.data_2 = access_size;
+		break;
+	case 4:
+		val.data_4 = access_size;
+		break;
+	case 8:
+		val.data_8 = access_size;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int stx_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_val));
+	switch (access_size) {
+	case 1:
+		val->data_1 = access_size;
+		break;
+	case 2:
+		val->data_2 = access_size;
+		break;
+	case 4:
+		val->data_4 = access_size;
+		break;
+	case 8:
+		val->data_8 = access_size;
+		break;
+	}
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_val));
+	__sink(val->data_4);
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+
+	return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+	__sink(val.data_4);
+	bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_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_write_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_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int load_acquire_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_val val;
+
+	bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_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_write_val));
+	return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_not_on_stack(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_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_write_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_write_val stack_val = {};
+	struct kasan_write_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;
+}
+
+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..808d7da64421
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct kasan_write_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_write_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_blinded(struct __sk_buff *skb)
+{
+	struct kasan_write_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_write_val));
+	val->data_1 = 0xAA;
+	bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 30f1cd23093c..5d6a834f7d75 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -271,6 +271,26 @@ __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_ctx *
 bpf_testmod_ctx_create(int *err)
 {
@@ -740,6 +760,8 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)
 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_KFUNCS_END(bpf_testmod_common_kfunc_ids)
 
 BTF_ID_LIST(bpf_testmod_dtor_ids)

-- 
2.54.0


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

* Re: [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack
  2026-07-08  8:00 ` [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:10   ` sashiko-bot
  2026-07-08 14:15     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:10 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] In `adjust_insn_aux_data()`, when replacing an instruction with a single new instruction (`cnt == 1`), `data[off].non_stack_access` is incorrectly set by evaluating `is_mem_insn(insn)` instead of `is_mem_insn(insn + off)`.
--

--- Patch [2]: [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack ---
commit 764c11844e954d2fc0370d170803b0deb684733a
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

bpf: mark instructions accessing program stack

This commit introduces tracking of instructions that access non-stack memory
to help JIT compilers emit KASAN checks. It adds logic to mark BPF
instructions as stack-only by default and overrides this if any state
indicates non-stack access, including adjustments during verifier patching.

> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
> index f515fc9669ce2..feee1f33de9d5 100644
> --- a/kernel/bpf/fixups.c
> +++ b/kernel/bpf/fixups.c

[ ... ]

> @@ -172,8 +184,14 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
>  	 */
>  	data[off].zext_dst = insn_has_def32(insn + off + cnt - 1);
>  
> -	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);

[Severity: High]
Should this check be using is_mem_insn(insn + off) instead of
is_mem_insn(insn)?

Since insn points to the start of the program (new_prog->insnsi), calling
is_mem_insn(insn) evaluates the very first instruction of the program rather
than the newly patched instruction at the targeted offset.

If the first instruction happens to not be a memory access, will this
incorrectly mark the patched instruction as not accessing non-stack memory
and cause us to miss KASAN instrumentation?

>  		return;
> +	}
>  	prog_len = new_prog->len;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=2

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

* Re: [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs
  2026-07-08  8:00 ` [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:16   ` sashiko-bot
  2026-07-08 14:20     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:16 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] emit_kasan_check() fails to save caller-saved registers r10 (BPF_REG_AX) and r11 (AUX_REG), causing them to be clobbered by C KASAN helpers.
- [Low] The static function emit_kasan_check() is defined but never called, leading to a -Wunused-function compiler warning and build failure.
--

--- Patch [4]: [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs ---
commit 702ec90119cefcab13a29595976ba78253e74ce6
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

bpf, x86: add helper to emit kasan checks in x86 JITed programs

This commit adds the emit_kasan_check() function for x86 JIT-compiled
BPF programs. It emits KASAN shadow memory checks before memory accesses
by saving state, computing the address, and calling C KASAN helpers.

> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index de7515ea1beae..d35f58350d710 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -1110,6 +1121,90 @@ 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, bool accesses_stack_only)
> +{

[Severity: Low]
Does this introduce a -Wunused-function compiler warning? The static function
emit_kasan_check() is added here but does not appear to be called anywhere in
this patch. This could cause build failures on setups with -Werror, breaking
git bisectability.

> +#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;
> +
> +	if (accesses_stack_only)
> +		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);

[Severity: High]
Could omitting r10 and r11 here lead to data corruption? 

The commit message mentions skipping them to reduce overhead, but the called
C functions (__asan_loadX, __asan_storeX) follow the System V AMD64 ABI and
might clobber caller-saved registers like r10 and r11. 

In the x86 BPF JIT, r10 is mapped to BPF_REG_AX, which is used across
instructions to hold state (such as during constant blinding rewrites where
BPF_ST_MEM is transformed into BPF_STX_MEM using BPF_REG_AX as its source).

Would this result in storing garbage data when the C helper clobbers
BPF_REG_AX?

> +
> +	/* mov rdi, addr_reg */
> +	EMIT_mov(BPF_REG_1, addr_reg);
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=4

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

* Re: [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers
  2026-07-08  8:00 ` [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:16   ` sashiko-bot
  2026-07-08 14:43     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:16 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The refactored `set_bpf_jit_harden` function has been moved to a common helper file and stripped of its internal `ASSERT_FAIL` calls. However, its usage in the cleanup path of `check_blindness()` was not updated to check the return value. Consequently, if restoring the sysctl fails during cleanup, the test will silently leave the system-wide `bpf_jit_harden` configuration in an altered state.
--

--- Patch [9]: [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers ---
commit 806a4cb4e9c458805fd71ac7ae0f57e00e8af6bc
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

selftests/bpf: move bpf_jit_harden helper into testing_helpers

Move set_bpf_jit_harden to testing helpers so that other selftests can
change the hardening configuration without re-implementing a helper.

> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> index 0222a9a5d0761..37c405fc2b0d2 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> @@ -227,42 +227,6 @@ static void check_incorrect_index(void)
>  	check_mid_insn_index();
>  }
>  
> -static int set_bpf_jit_harden(char *level)
> -{
> -	char old_level;
> -	int err = -1;
> -	int fd = -1;
> -
> -	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
> -	if (fd < 0) {
> -		ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
> -		return -1;
> -	}

[Severity: Medium]
Since this function's internal ASSERT_FAIL() calls were removed when moving it
to testing_helpers.c, are we missing an error check in the cleanup path of
check_blindness() where it restores the sysctl?

tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c:check_blindness() {
    ...
cleanup:
    /* restore the old one */
    if (bpf_jit_harden != '@')
        set_bpf_jit_harden(&bpf_jit_harden);
    ...
}

If restoring the sysctl fails during cleanup, this will silently leave the
system-wide bpf_jit_harden configuration in an altered state. Should the return
value of set_bpf_jit_harden() in the cleanup path be checked and asserted?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=9

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

* Re: [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs
  2026-07-08  8:00 ` [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:16   ` sashiko-bot
  2026-07-08 14:29     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:16 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] `emit_kasan_check` fails to save and restore the caller-saved registers %r10 (`BPF_REG_AX`) and %r11 (`AUX_REG`). This corrupts state during atomic fetch operations and constant blinding, potentially leading to arbitrary memory corruption or kernel crashes.
- [Low] Atomic read-modify-write (RMW) operations are incorrectly instrumented as KASAN reads instead of KASAN writes.
--

--- Patch [6]: [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs ---
commit ea72c3645f886fbb17349184882dfa35cd20282f
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

bpf, x86: emit KASAN checks into x86 JITed programs

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.

> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index 959fcaa0cde9a..65a00e84799cd 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -2605,15 +2640,16 @@ 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;
> +			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.
> @@ -2626,7 +2662,17 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
>  				if (dst_reg == BPF_REG_0)
>  					real_dst_reg = BPF_REG_AX;
>  
> +				ip += 3;
> +			}
> +			if (!bpf_atomic_is_load_store(insn)) {
> +				err = emit_kasan_check(env, &prog, real_dst_reg,
> +						       insn, ip, false,
> +						       accesses_stack_only);

[Severity: Critical]
Does this KASAN check clobber %r10 (BPF_REG_AX) and %r11 (AUX_REG)?

If this is an atomic fetch operation and BPF_REG_0 is used, the JIT saves
BPF_REG_0 into BPF_REG_AX (%r10). However, the emit_kasan_check() function
emits a C function call without saving %r10 or %r11:

arch/x86/net/bpf_jit_comp.c:emit_kasan_check() {
	...
	/* Save r8 */
	EMIT2(0x41, 0x50);
	/* Save r9 */
	EMIT2(0x41, 0x51);

	/* mov rdi, addr_reg */
	EMIT_mov(BPF_REG_1, addr_reg);
	...
	/* call kasan_func */
	if (emit_call(&prog, kasan_func, ip))
		return -ERANGE;
	...
}

Since C functions can clobber %r10 under the System V ABI, could the
subsequent use of real_dst_reg (which points to %r10) lead to wild memory
accesses?

[Severity: Low]
Are atomic RMW operations incorrectly instrumented as KASAN reads?

The 6th argument to emit_kasan_check() is is_write. Here it is hardcoded to
false for mutating atomic RMW instructions. Should this be true instead, so
that KASAN diagnostics report out-of-bounds writes rather than out-of-bounds
reads?

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

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=6

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

* Re: [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86
  2026-07-08  8:00 ` [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:19   ` sashiko-bot
  2026-07-08 14:33     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:19 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] The KASAN check emitted by the x86 BPF JIT clobbers the caller-saved registers r10 (BPF_REG_AX) and r11 (AUX_REG), leading to memory corruption.
--

--- Patch [7]: [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86 ---
commit 4ee1a38a02c8227bc35c998032951cf3c95c2d7d
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

bpf, x86: enable KASAN for JITed programs on x86

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

> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
> index bdad90f210e4b..705a9ab85f3a9 100644
> --- a/arch/x86/Kconfig
> +++ b/arch/x86/Kconfig
> @@ -233,6 +233,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
[Severity: Critical]
Does enabling this feature introduce memory corruption by exposing a bug in
emit_kasan_check()?

Looking at arch/x86/net/bpf_jit_comp.c:emit_kasan_check(), caller-saved
registers (rax through r9) are pushed, but r10 (BPF_REG_AX) and r11 (AUX_REG)
are explicitly missing. This exposes them to clobbering by the KASAN C
function call:

arch/x86/net/bpf_jit_comp.c:emit_kasan_check() {
    ...
	/* Save r8 */
	EMIT2(0x41, 0x50);
	/* Save r9 */
	EMIT2(0x41, 0x51);

	/* mov rdi, addr_reg */
    ...
}

For BPF_STX, if constant blinding is used, src_reg is BPF_REG_AX (r10).
The value in src_reg is corrupted by emit_kasan_check() before it is stored
by emit_stx():

arch/x86/net/bpf_jit_comp.c:do_jit() {
    ...
			err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
					       true, accesses_stack_only);
			if (err)
				return err;
			emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
    ...
}

Additionally, in atomic fetch operations, BPF_REG_0 is moved into BPF_REG_AX.
The pointer (real_dst_reg) might become BPF_REG_AX, which is passed to
emit_kasan_check() and then used afterwards, resulting in using a corrupted
pointer:

arch/x86/net/bpf_jit_comp.c:do_jit() {
    ...
				/* Will need RAX as a CMPXCHG operand so save R0 */
				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 += 3;
			}
			if (!bpf_atomic_is_load_store(insn)) {
				err = emit_kasan_check(env, &prog, real_dst_reg,
						       insn, ip, false,
						       accesses_stack_only);
    ...
}

Unprivileged users loading BPF programs with constant blinding might trigger
arbitrary memory reads or writes due to this clobbering.

>  	select HAVE_EFFICIENT_UNALIGNED_ACCESS
>  	select HAVE_EISA			if X86_32
>  	select HAVE_EXIT_THREAD

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=7

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

* Re: [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program
  2026-07-08  8:00 ` [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:19   ` sashiko-bot
  2026-07-08 14:11     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:19 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Incorrect or inconsistent calculation of `insn_off_in_patch` causes loss or misassignment of `aux_data`.
- [Medium] A single `insn_off_in_patch` integer is insufficient to propagate KASAN memory markings to patches that emit multiple memory accesses.
--

--- Patch [1]: [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program ---
commit 6ebaaf2368d2745f393ec38eb53d5857d5ec62b8
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

bpf: propagate original instruction offset when patching program

When the verifier patches an ebpf program with bpf_patch_insn_data, it
then calls adjust_insn_aux_data to make sure that insn_aux_data takes
into account the newly inserted patch.

> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
> index 12a8a4eb757f1..f515fc9669ce2 100644
> --- a/kernel/bpf/fixups.c
> +++ b/kernel/bpf/fixups.c

[ ... ]

> @@ -856,16 +862,20 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
>  			if (!ops->convert_ctx_access)
>  				continue;
>  			convert_ctx_access = ops->convert_ctx_access;
> +			insn_off_in_patch = 0;

[Severity: Medium]
Will this properly track context accesses that are converted into multiple
chained memory loads?

By relying on a single integer to track the offset, it seems we might fail to
account for multi-load patches emitted by ops->convert_ctx_access() (for
example, reading skb->dev followed by dev->ifindex). The secondary memory
accesses will receive zeroed aux_data and lack the required KASAN
non_stack_access marking.

Additionally, since the original instruction is completely replaced here, does
it make sense to unconditionally assign its aux_data to the first generated
instruction, which might not even be the actual memory access?

>  			break;

[ ... ]

> @@ -1464,7 +1475,7 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat
>  	 * ones for the hidden subprog. Hence all of the adjustment operations
>  	 * in bpf_patch_insn_data are no-ops.
>  	 */
> -	prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len);
> +	prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len, -1);

[Severity: Medium]
Is it intentional to pass -1 for the offset here?

Since the original instruction is explicitly preserved in the patch buffer
in bpf_do_misc_fixups():

	*patch++ = env->prog->insnsi[insn_cnt - 1];

Passing -1 to bpf_patch_insn_data() will cause the aux_data associated with
that instruction to be lost.

[ ... ]

> @@ -1743,7 +1765,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
>  				*patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1);
>  			cnt = patch - insn_buf;
>  
> -			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
> +			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
> +						       cnt, -1);

[Severity: Medium]
In this pointer arithmetic rewrite, the original ALU instruction is preserved
in the patch buffer (*patch++ = *insn).

Should we calculate and pass its exact offset inside the patch buffer instead
of passing -1, which discards its aux_data?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=1

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

* Re: [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs
  2026-07-08  8:00 ` [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-08  8:28   ` sashiko-bot
  2026-07-08 14:47     ` Alexis Lothoré
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-07-08  8:28 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation); +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Test fails spuriously on default kernels due to missing verification of the kasan_multi_shot requirement.
- [Low] The variable `nsize` is assigned but never used, constituting dead code.
--

--- Patch [10]: [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs ---
commit c15c25b993ae0bc70091046fa0c6b2bb1e71d099
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

selftests/bpf: add tests to validate KASAN on JIT programs

This patch adds a basic test runner to load and run BPF programs that
trigger memory management bugs to validate KASAN instrumentation.
It runs various memory access tests and checks kernel logs for the expected
KASAN splat, requiring the kernel to be configured with kasan_multi_shot.

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

[ ... ]

> +static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
> +					     bool is_write, int size)
> +{
> +	char access_log[READ_CHUNK_SIZE];
> +	char *kasan_report_start;
> +	int nsize;
> +
> +	snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_SLAB_UAF,
> +		 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);

[ ... ]

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

[Severity: Low]
This isn't a bug, but the variable nsize is assigned here and never used.
Should this assignment be removed to avoid dead code?

> +
> +	if (!strstr(kasan_report_start, access_log))
> +		return 1;
> +
> +	return 0;
> +}

[ ... ]

> +void test_kasan(void)
> +{
> +	struct kasan_write_val val;
> +	struct test_spec *test;
> +	struct test_ctx *ctx;
> +	struct kasan *skel;
> +	__u32 key = 0;
> +	int i, ret;
> +
> +	ctx = calloc(1, sizeof(struct test_ctx));
> +	if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
> +		return;
> +
> +	if (!is_jit_enabled() || !get_kasan_jit_enabled()) {
> +		test__skip();
> +		goto end;
> +	}

[Severity: Medium]
Since the commit message notes that the kernel must run with the
kasan_multi_shot configuration, what happens if this parameter is missing?

Could this test fail spuriously on default kernels where multi-shot is
disabled, since it doesn't verify if kasan_multi_shot is active before
triggering multiple reports?

> +
> +	skel = kasan__open();
> +	if (!ASSERT_OK_PTR(skel, "open prog"))
> +		goto end;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708-kasan-v4-0-d5c177ab8227@bootlin.com?part=10

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

* Re: [PATCH bpf-next v4 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
  2026-07-08  8:00 ` [PATCH bpf-next v4 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-08 12:48   ` Andrey Konovalov
  0 siblings, 0 replies; 26+ messages in thread
From: Andrey Konovalov @ 2026-07-08 12:48 UTC (permalink / raw)
  To: Alexis Lothoré (eBPF Foundation)
  Cc: 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, ebpf, Bastien Curutchet,
	Thomas Petazzoni, bpf, linux-kernel, linux-kselftest

On Wed, Jul 8, 2026 at 10:02 AM Alexis Lothoré (eBPF Foundation)
<alexis.lothore@bootlin.com> wrote:
>
> 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 either
> 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.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> ---
> 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..e1f3850d2f5a 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 if KASAN
> +       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.54.0
>

Acked-by: Andrey Konovalov <andreyknvl@gmail.com>

Thanks!

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

* Re: [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program
  2026-07-08  8:19   ` sashiko-bot
@ 2026-07-08 14:11     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:11 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:19 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Incorrect or inconsistent calculation of `insn_off_in_patch` causes loss or misassignment of `aux_data`.
> - [Medium] A single `insn_off_in_patch` integer is insufficient to propagate KASAN memory markings to patches that emit multiple memory accesses.
> --
>
> --- Patch [1]: [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program ---
> commit 6ebaaf2368d2745f393ec38eb53d5857d5ec62b8
> Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>
> bpf: propagate original instruction offset when patching program
>
> When the verifier patches an ebpf program with bpf_patch_insn_data, it
> then calls adjust_insn_aux_data to make sure that insn_aux_data takes
> into account the newly inserted patch.
>
>> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
>> index 12a8a4eb757f1..f515fc9669ce2 100644
>> --- a/kernel/bpf/fixups.c
>> +++ b/kernel/bpf/fixups.c
>
> [ ... ]
>
>> @@ -856,16 +862,20 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
>>  			if (!ops->convert_ctx_access)
>>  				continue;
>>  			convert_ctx_access = ops->convert_ctx_access;
>> +			insn_off_in_patch = 0;
>
> [Severity: Medium]
> Will this properly track context accesses that are converted into multiple
> chained memory loads?
>
> By relying on a single integer to track the offset, it seems we might fail to
> account for multi-load patches emitted by ops->convert_ctx_access() (for
> example, reading skb->dev followed by dev->ifindex). The secondary memory
> accesses will receive zeroed aux_data and lack the required KASAN
> non_stack_access marking.
>
> Additionally, since the original instruction is completely replaced here, does
> it make sense to unconditionally assign its aux_data to the first generated
> instruction, which might not even be the actual memory access?

Hmmm, true, there's an issue here. I have checked many XXX_convert_ctx
accesses and naively assumed that the generated patch was always a
single ldx or stx, but indeed, there are corner cases:
- bpf_convert_ctx: __skb->ifindex becomes LDX + JPM + LDX
- bpf_convert_ctx: __skb->pkt_type becomes LDX + AND
- bpf_convert_ctx: __skb->queue_mapping becomes (JMP +) ST or STX
- and many more

For all of those XXX_convert_ctx_accesses, the accessed memory should
always be checked with KASAN since we are dealing with ctx pointers, so
it may be simpler just to pass -1 as insn_off_patches to
bpf_patch_insn_data for all of those, so that any LDX/STX/ST in the new
patch is instrumented.

>> @@ -1464,7 +1475,7 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat
>>  	 * ones for the hidden subprog. Hence all of the adjustment operations
>>  	 * in bpf_patch_insn_data are no-ops.
>>  	 */
>> -	prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len);
>> +	prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len, -1);
>
> [Severity: Medium]
> Is it intentional to pass -1 for the offset here?
>
> Since the original instruction is explicitly preserved in the patch buffer
> in bpf_do_misc_fixups():
>
> 	*patch++ = env->prog->insnsi[insn_cnt - 1];
>
> Passing -1 to bpf_patch_insn_data() will cause the aux_data associated with
> that instruction to be lost.

True, but: unless I am missing some corner cases, eBPF programs end with BPF_EXIT,
so it is pointless to forward an original instruction offset. But for
the sake of separating concerns, I'll update this to pass 0 instead of
-1.

>> @@ -1743,7 +1765,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
>>  				*patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1);
>>  			cnt = patch - insn_buf;
>>  
>> -			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
>> +			new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
>> +						       cnt, -1);
>
> [Severity: Medium]
> In this pointer arithmetic rewrite, the original ALU instruction is preserved
> in the patch buffer (*patch++ = *insn).
>
> Should we calculate and pass its exact offset inside the patch buffer instead
> of passing -1, which discards its aux_data?

Ditto: having the exact instruction offset for this case is not useful
as it is not a load or store, but I'll still update the offset value (-1
-> cnt - 1)

Alexis

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


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

* Re: [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack
  2026-07-08  8:10   ` sashiko-bot
@ 2026-07-08 14:15     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:15 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:10 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] In `adjust_insn_aux_data()`, when replacing an instruction with a single new instruction (`cnt == 1`), `data[off].non_stack_access` is incorrectly set by evaluating `is_mem_insn(insn)` instead of `is_mem_insn(insn + off)`.
> --
>
> --- Patch [2]: [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack ---
> commit 764c11844e954d2fc0370d170803b0deb684733a
> Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>
> bpf: mark instructions accessing program stack
>
> This commit introduces tracking of instructions that access non-stack memory
> to help JIT compilers emit KASAN checks. It adds logic to mark BPF
> instructions as stack-only by default and overrides this if any state
> indicates non-stack access, including adjustments during verifier patching.
>
>> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
>> index f515fc9669ce2..feee1f33de9d5 100644
>> --- a/kernel/bpf/fixups.c
>> +++ b/kernel/bpf/fixups.c
>
> [ ... ]
>
>> @@ -172,8 +184,14 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
>>  	 */
>>  	data[off].zext_dst = insn_has_def32(insn + off + cnt - 1);
>>  
>> -	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);
>
> [Severity: High]
> Should this check be using is_mem_insn(insn + off) instead of
> is_mem_insn(insn)?

Aouch, true, my mistake.

> Since insn points to the start of the program (new_prog->insnsi), calling
> is_mem_insn(insn) evaluates the very first instruction of the program rather
> than the newly patched instruction at the targeted offset.
>
> If the first instruction happens to not be a memory access, will this
> incorrectly mark the patched instruction as not accessing non-stack memory
> and cause us to miss KASAN instrumentation?
>
>>  		return;
>> +	}
>>  	prog_len = new_prog->len;




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


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

* Re: [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs
  2026-07-08  8:16   ` sashiko-bot
@ 2026-07-08 14:20     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:20 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:16 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] emit_kasan_check() fails to save caller-saved registers r10 (BPF_REG_AX) and r11 (AUX_REG), causing them to be clobbered by C KASAN helpers.
> - [Low] The static function emit_kasan_check() is defined but never called, leading to a -Wunused-function compiler warning and build failure.

[...]

>> +static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
>> +			    u32 addr_reg, struct bpf_insn *insn, u8 *ip,
>> +			    bool is_write, bool accesses_stack_only)
>> +{
>
> [Severity: Low]
> Does this introduce a -Wunused-function compiler warning? The static function
> emit_kasan_check() is added here but does not appear to be called anywhere in
> this patch. This could cause build failures on setups with -Werror, breaking
> git bisectability.

True, that standalone definition was intended to ease the review. I'll
keep it as is, unless anyone requires this to be squashed in the commit
actually calling the new helper.

[...]

>> +	/* 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);
>
> [Severity: High]
> Could omitting r10 and r11 here lead to data corruption? 
>
> The commit message mentions skipping them to reduce overhead, but the called
> C functions (__asan_loadX, __asan_storeX) follow the System V AMD64 ABI and
> might clobber caller-saved registers like r10 and r11. 
>
> In the x86 BPF JIT, r10 is mapped to BPF_REG_AX, which is used across
> instructions to hold state (such as during constant blinding rewrites where
> BPF_ST_MEM is transformed into BPF_STX_MEM using BPF_REG_AX as its source).
>
> Would this result in storing garbage data when the C helper clobbers
> BPF_REG_AX?

Already discussed multiple time in the previous revisions.

Alexis
>
>> +
>> +	/* mov rdi, addr_reg */
>> +	EMIT_mov(BPF_REG_1, addr_reg);
> [ ... ]




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


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

* Re: [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs
  2026-07-08  8:16   ` sashiko-bot
@ 2026-07-08 14:29     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:29 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:16 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] `emit_kasan_check` fails to save and restore the caller-saved registers %r10 (`BPF_REG_AX`) and %r11 (`AUX_REG`). This corrupts state during atomic fetch operations and constant blinding, potentially leading to arbitrary memory corruption or kernel crashes.
> - [Low] Atomic read-modify-write (RMW) operations are incorrectly instrumented as KASAN reads instead of KASAN writes.

[...]

>> @@ -2626,7 +2662,17 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
>>  				if (dst_reg == BPF_REG_0)
>>  					real_dst_reg = BPF_REG_AX;
>>  
>> +				ip += 3;
>> +			}
>> +			if (!bpf_atomic_is_load_store(insn)) {
>> +				err = emit_kasan_check(env, &prog, real_dst_reg,
>> +						       insn, ip, false,
>> +						       accesses_stack_only);
>
> [Severity: Critical]
> Does this KASAN check clobber %r10 (BPF_REG_AX) and %r11 (AUX_REG)?
>
> If this is an atomic fetch operation and BPF_REG_0 is used, the JIT saves
> BPF_REG_0 into BPF_REG_AX (%r10). However, the emit_kasan_check() function
> emits a C function call without saving %r10 or %r11:
>
> arch/x86/net/bpf_jit_comp.c:emit_kasan_check() {
> 	...
> 	/* Save r8 */
> 	EMIT2(0x41, 0x50);
> 	/* Save r9 */
> 	EMIT2(0x41, 0x51);
>
> 	/* mov rdi, addr_reg */
> 	EMIT_mov(BPF_REG_1, addr_reg);
> 	...
> 	/* call kasan_func */
> 	if (emit_call(&prog, kasan_func, ip))
> 		return -ERANGE;
> 	...
> }
>
> Since C functions can clobber %r10 under the System V ABI, could the
> subsequent use of real_dst_reg (which points to %r10) lead to wild memory
> accesses?

Already discussed multiple times in the previous revisions


> [Severity: Low]
> Are atomic RMW operations incorrectly instrumented as KASAN reads?
>
> The 6th argument to emit_kasan_check() is is_write. Here it is hardcoded to
> false for mutating atomic RMW instructions. Should this be true instead, so
> that KASAN diagnostics report out-of-bounds writes rather than out-of-bounds
> reads?

It sounded logical to me to flag a RMW operation as a read access, as it
is the first thing performed by the op, but I can change it to a write
it it makes more sense. It should just change the wording in the report
anyway.

Alexis

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


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

* Re: [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86
  2026-07-08  8:19   ` sashiko-bot
@ 2026-07-08 14:33     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:33 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:19 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

[...]

>> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
>> index bdad90f210e4b..705a9ab85f3a9 100644
>> --- a/arch/x86/Kconfig
>> +++ b/arch/x86/Kconfig
>> @@ -233,6 +233,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
> [Severity: Critical]
> Does enabling this feature introduce memory corruption by exposing a bug in
> emit_kasan_check()?
>
> Looking at arch/x86/net/bpf_jit_comp.c:emit_kasan_check(), caller-saved
> registers (rax through r9) are pushed, but r10 (BPF_REG_AX) and r11 (AUX_REG)
> are explicitly missing. This exposes them to clobbering by the KASAN C
> function call:
>
> arch/x86/net/bpf_jit_comp.c:emit_kasan_check() {
>     ...
> 	/* Save r8 */
> 	EMIT2(0x41, 0x50);
> 	/* Save r9 */
> 	EMIT2(0x41, 0x51);
>
> 	/* mov rdi, addr_reg */
>     ...
> }
>
> For BPF_STX, if constant blinding is used, src_reg is BPF_REG_AX (r10).
> The value in src_reg is corrupted by emit_kasan_check() before it is stored
> by emit_stx():
>
> arch/x86/net/bpf_jit_comp.c:do_jit() {
>     ...
> 			err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
> 					       true, accesses_stack_only);
> 			if (err)
> 				return err;
> 			emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
>     ...
> }
>
> Additionally, in atomic fetch operations, BPF_REG_0 is moved into BPF_REG_AX.
> The pointer (real_dst_reg) might become BPF_REG_AX, which is passed to
> emit_kasan_check() and then used afterwards, resulting in using a corrupted
> pointer:
>
> arch/x86/net/bpf_jit_comp.c:do_jit() {
>     ...
> 				/* Will need RAX as a CMPXCHG operand so save R0 */
> 				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 += 3;
> 			}
> 			if (!bpf_atomic_is_load_store(insn)) {
> 				err = emit_kasan_check(env, &prog, real_dst_reg,
> 						       insn, ip, false,
> 						       accesses_stack_only);
>     ...
> }
>
> Unprivileged users loading BPF programs with constant blinding might trigger
> arbitrary memory reads or writes due to this clobbering.

Already discussed multiple times in previous revisions.

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


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

* Re: [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers
  2026-07-08  8:16   ` sashiko-bot
@ 2026-07-08 14:43     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:43 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:16 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The refactored `set_bpf_jit_harden` function has been moved to a common helper file and stripped of its internal `ASSERT_FAIL` calls. However, its usage in the cleanup path of `check_blindness()` was not updated to check the return value. Consequently, if restoring the sysctl fails during cleanup, the test will silently leave the system-wide `bpf_jit_harden` configuration in an altered state.
> --
>
> --- Patch [9]: [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers ---
> commit 806a4cb4e9c458805fd71ac7ae0f57e00e8af6bc
> Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>
> selftests/bpf: move bpf_jit_harden helper into testing_helpers
>
> Move set_bpf_jit_harden to testing helpers so that other selftests can
> change the hardening configuration without re-implementing a helper.
>
>> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
>> index 0222a9a5d0761..37c405fc2b0d2 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
>> @@ -227,42 +227,6 @@ static void check_incorrect_index(void)
>>  	check_mid_insn_index();
>>  }
>>  
>> -static int set_bpf_jit_harden(char *level)
>> -{
>> -	char old_level;
>> -	int err = -1;
>> -	int fd = -1;
>> -
>> -	fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
>> -	if (fd < 0) {
>> -		ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
>> -		return -1;
>> -	}
>
> [Severity: Medium]
> Since this function's internal ASSERT_FAIL() calls were removed when moving it
> to testing_helpers.c, are we missing an error check in the cleanup path of
> check_blindness() where it restores the sysctl?
>
> tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c:check_blindness() {
>     ...
> cleanup:
>     /* restore the old one */
>     if (bpf_jit_harden != '@')
>         set_bpf_jit_harden(&bpf_jit_harden);
>     ...
> }
>
> If restoring the sysctl fails during cleanup, this will silently leave the
> system-wide bpf_jit_harden configuration in an altered state. Should the return
> value of set_bpf_jit_harden() in the cleanup path be checked and asserted?

I guess I can add an extra ASSERT_OK in the cleanup path to at least get
a log if the setting restore fails.

Alexis

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


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

* Re: [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs
  2026-07-08  8:28   ` sashiko-bot
@ 2026-07-08 14:47     ` Alexis Lothoré
  0 siblings, 0 replies; 26+ messages in thread
From: Alexis Lothoré @ 2026-07-08 14:47 UTC (permalink / raw)
  To: sashiko-reviews, Alexis Lothoré (eBPF Foundation); +Cc: bpf

On Wed Jul 8, 2026 at 10:28 AM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Test fails spuriously on default kernels due to missing verification of the kasan_multi_shot requirement.
> - [Low] The variable `nsize` is assigned but never used, constituting dead code.
> --
>
> --- Patch [10]: [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs ---
> commit c15c25b993ae0bc70091046fa0c6b2bb1e71d099
> Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>
> selftests/bpf: add tests to validate KASAN on JIT programs
>
> This patch adds a basic test runner to load and run BPF programs that
> trigger memory management bugs to validate KASAN instrumentation.
> It runs various memory access tests and checks kernel logs for the expected
> KASAN splat, requiring the kernel to be configured with kasan_multi_shot.
>
>> diff --git a/tools/testing/selftests/bpf/prog_tests/kasan.c b/tools/testing/selftests/bpf/prog_tests/kasan.c
>> new file mode 100644
>> index 0000000000000..f006b92ee131a
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
>
> [ ... ]
>
>> +static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
>> +					     bool is_write, int size)
>> +{
>> +	char access_log[READ_CHUNK_SIZE];
>> +	char *kasan_report_start;
>> +	int nsize;
>> +
>> +	snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_SLAB_UAF,
>> +		 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);
>
> [ ... ]
>
>> +	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);
>
> [Severity: Low]
> This isn't a bug, but the variable nsize is assigned here and never used.
> Should this assignment be removed to avoid dead code?

I'll rather add an ASSERT on it.

>> +
>> +	if (!strstr(kasan_report_start, access_log))
>> +		return 1;
>> +
>> +	return 0;
>> +}
>
> [ ... ]
>
>> +void test_kasan(void)
>> +{
>> +	struct kasan_write_val val;
>> +	struct test_spec *test;
>> +	struct test_ctx *ctx;
>> +	struct kasan *skel;
>> +	__u32 key = 0;
>> +	int i, ret;
>> +
>> +	ctx = calloc(1, sizeof(struct test_ctx));
>> +	if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
>> +		return;
>> +
>> +	if (!is_jit_enabled() || !get_kasan_jit_enabled()) {
>> +		test__skip();
>> +		goto end;
>> +	}
>
> [Severity: Medium]
> Since the commit message notes that the kernel must run with the
> kasan_multi_shot configuration, what happens if this parameter is missing?
>
> Could this test fail spuriously on default kernels where multi-shot is
> disabled, since it doesn't verify if kasan_multi_shot is active before
> triggering multiple reports?

The current test design assumes that kasan_multi_shot is not enabled by
the test itself but by the CI runner. This is actually already the case
for the BPF CI. But maybe having a SKIP instead of FAIL is more friendly
for anyone trying to run the tests and forgetting to enable
kasan_multi_shot (some additional doc have been requested and added
since first revision, but I guess the check would not hurt).

Alexis

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


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

end of thread, other threads:[~2026-07-08 14:47 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-08  8:00 [PATCH bpf-next v4 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-08  8:00 ` [PATCH bpf-next v4 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
2026-07-08  8:19   ` sashiko-bot
2026-07-08 14:11     ` Alexis Lothoré
2026-07-08  8:00 ` [PATCH bpf-next v4 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
2026-07-08  8:10   ` sashiko-bot
2026-07-08 14:15     ` Alexis Lothoré
2026-07-08  8:00 ` [PATCH bpf-next v4 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-08 12:48   ` Andrey Konovalov
2026-07-08  8:00 ` [PATCH bpf-next v4 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
2026-07-08  8:16   ` sashiko-bot
2026-07-08 14:20     ` Alexis Lothoré
2026-07-08  8:00 ` [PATCH bpf-next v4 05/10] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
2026-07-08  8:00 ` [PATCH bpf-next v4 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-08  8:16   ` sashiko-bot
2026-07-08 14:29     ` Alexis Lothoré
2026-07-08  8:00 ` [PATCH bpf-next v4 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
2026-07-08  8:19   ` sashiko-bot
2026-07-08 14:33     ` Alexis Lothoré
2026-07-08  8:00 ` [PATCH bpf-next v4 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active Alexis Lothoré (eBPF Foundation)
2026-07-08  8:00 ` [PATCH bpf-next v4 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
2026-07-08  8:16   ` sashiko-bot
2026-07-08 14:43     ` Alexis Lothoré
2026-07-08  8:00 ` [PATCH bpf-next v4 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
2026-07-08  8:28   ` sashiko-bot
2026-07-08 14:47     ` Alexis Lothoré

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