* [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs
@ 2026-08-21 22:39 Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
` (8 more replies)
0 siblings, 9 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Hello,
this is v7 of the series aiming to bring basic support for KASAN checks
to BPF JITed programs. This new revision just makes the series rebased
on top of current bpf-next_base.
Please note that with the recent kernel splat detector introduced in
kernel-patches/vmtest through commit 8109e5928cd0 ("ci: own the kernel
splat matching (#509)"), CI runs on this series will fail with the
following error.
Error: kernel splat detected: [ 100.689549] BUG: KASAN: \
slab-use-after-free in \
bpf_prog_eba69524d1d949b1_st_not_on_stack+0x12f/0x17a \
which is pretty normal, as the new selftests introduced by this series
are expected to trigger KASAN splats (the test_progs part will pass,
only the kernel_splats part will trigger errors). The right fix may be
to just put a list of regex matching all the kasan subtests progs in the
relevant SPLAT_ALLOWLIST file; something like:
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_(st|ld)(x)?(_not)?_on_stack\+
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_simple_atomic(_fetch)?(_not)_on_stack\+
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_ldx_patched(_not)?_on_stack\+
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_verifier_paths_stack_and_non_stack\+
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_ldx_oob\+
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_st_blinded\+
BUG: KASAN: slab-(out-of-bounds|use-after-free) in bpf_prog_([a-f0-9]){16}_(load_acquire|store_release)(_not)?_on_stack\+
I can take care of opening the relevant PR in kernel-patches/vmtest with
this if it ends up being the correct solution, but it will make sense to
do so only once the selftests list is validated (but before merging the
series).
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 v7:
- Rebase series on top of current bpf-next_base, fixed conflict with
7ce090afbf72 ("bpf: Infer zext_dst based on static register liveness
analysis")
- Link to v6: https://patch.msgid.link/20260804-kasan-v6-0-549ef845f491@bootlin.com
Changes in v6:
- dropped instruction original offset tracking
- when patching instructions, track former non_stack_access flag by
passing original insn to adjust_insn_aux_data
- drop unecessary dep on CONFIG_KASAN in Kconfig
- fold patch adding the emit_kasan_helper into the patch actually
calling it, to avoid an unused static function warning
- move stack access check out of emit_kasan_check
- replace hardcoded ip value by a computed value
- add OoB testing
- add fix commit to make cmdline_contains stricter
- Link to v5: https://patch.msgid.link/20260709-kasan-v5-0-1c64af8e4e1e@bootlin.com
Changes in v5:
- fixed a few instruction offset for generated fixups
- fix insn marking for single insn patches
- enforce more checks in tests
- skip tests if kasan_multi_shot isn't enabled
- Link to v4: https://patch.msgid.link/20260708-kasan-v4-0-d5c177ab8227@bootlin.com
Changes in v4:
- fix insn_offs_in_patch leakage in bpf_convert_ctx_access
- handle BPF_ATOMIC in is_mem_insn
- correctly mark fixup instructions if a single insn is generated
- clarify new kconfig (Andrey) and drop VMAP_STACK dep
- refactor BPF_FETCH atomic handling in JIT loop
- make kernel log reading resilient to unrelated, interleaved logs in
the selftests
- make new test kfuncs depend on BPF_JIT_KASAN rather than KASAN_GENERIC
- Link to v3: https://patch.msgid.link/20260701-kasan-v3-0-bd09bb942d86@bootlin.com
Changes in v3:
- Do not insert KASAN instrumentation when dealing with cBPF
- Fix stack-accessing insn tracking for verifier patches, as original
instruction location in the generated patch may vary
- drop cBPF support for stack-accessing insn marking
- make sure to flag correctly memory access if different verifier states
involve different memory types (eg: stack in one path, non-stack in
another path)
- refactor BPF_ST handling in x86 JIT compiler
- improve tests coverage (cover instrumentation for a few patches
emitted by the verifier)
- Link to v2: https://patch.msgid.link/20260604-kasan-v2-0-c066e627fda8@bootlin.com
Changes in v2:
- declare asan functions as extern in JIT compiler rather than exposing
them in kasan header
- invert stack-accessing instructions marking to make sure not to skip
instructions that could end up accessing to-be-checked memory
- fix stack accesses marking when verifier patches instructions
- add best effort marking for cBPF
- add missing call depth accounting in jited instrumentation
- skip unused registers in kasan instrumentation save/restore
- remove faulty stack align in kasan instrumentation
- drop commit skipping some jit-related tests
- cover missing instructions: BPF_ST and atomics
- completely rework tests: directly tune shadow memory, increase
coverage, do not consume kernel logs
- Link to v1: https://patch.msgid.link/20260413-kasan-v1-0-1a5831230821@bootlin.com
To: Alexei Starovoitov <ast@kernel.org>
To: Daniel Borkmann <daniel@iogearbox.net>
To: John Fastabend <john.fastabend@gmail.com>
To: Andrii Nakryiko <andrii@kernel.org>
To: Martin KaFai Lau <martin.lau@linux.dev>
To: Eduard Zingerman <eddyz87@gmail.com>
To: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: Song Liu <song@kernel.org>
To: Yonghong Song <yonghong.song@linux.dev>
To: Jiri Olsa <jolsa@kernel.org>
To: Thomas Gleixner <tglx@kernel.org>
To: Borislav Petkov <bp@alien8.de>
To: Dave Hansen <dave.hansen@linux.intel.com>
To: x86@kernel.org
To: "H. Peter Anvin" <hpa@zytor.com>
To: Shuah Khan <shuah@kernel.org>
To: Ingo Molnar <mingo@redhat.com>
To: Andrey Konovalov <andreyknvl@gmail.com>
To: Emil Tsalapatis <emil@etsalapatis.com>
To: Ihor Solodrai <ihor.solodrai@linux.dev>
To: Yafang Shao <laoar.shao@gmail.com>
Cc: ebpf@linuxfoundation.org
Cc: Bastien Curutchet <bastien.curutchet@bootlin.com>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Cc: bpf@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org
---
Alexis Lothoré (eBPF Foundation) (9):
bpf: mark instructions accessing program stack
bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
bpf, x86: refactor BPF_ST management in do_jit
bpf, x86: emit KASAN checks in x86 JITed programs
bpf, x86: enable KASAN for JITed programs on x86
selftests/bpf: make cmdline_contains stricter
selftests/bpf: add helpers for KASAN in JIT testing
selftests/bpf: 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 | 282 ++++++++++---
include/linux/bpf_verifier.h | 2 +
kernel/bpf/Kconfig | 17 +
kernel/bpf/fixups.c | 45 +-
kernel/bpf/verifier.c | 9 +
.../selftests/bpf/prog_tests/bpf_insn_array.c | 44 +-
tools/testing/selftests/bpf/prog_tests/kasan.c | 454 ++++++++++++++++++++
tools/testing/selftests/bpf/progs/kasan.c | 462 +++++++++++++++++++++
tools/testing/selftests/bpf/progs/kasan_harden.c | 41 ++
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 55 +++
tools/testing/selftests/bpf/testing_helpers.c | 32 ++
tools/testing/selftests/bpf/testing_helpers.h | 1 +
tools/testing/selftests/bpf/unpriv_helpers.c | 19 +-
tools/testing/selftests/bpf/unpriv_helpers.h | 2 +
15 files changed, 1366 insertions(+), 100 deletions(-)
---
base-commit: 22b638a25e36956312a8cc9ba80b9464ccb96e76
change-id: 20260126-kasan-fcd68f64cd7b
Best regards,
--
Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 22:54 ` sashiko-bot
2026-08-21 23:24 ` bot+bpf-ci
2026-08-21 22:39 ` [PATCH bpf-next v7 2/9] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
` (7 subsequent siblings)
8 siblings, 2 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
In order to prepare to emit KASAN checks in JITed programs, JIT
compilers need to be aware about whether some load/store instructions
are targeting the bpf program stack, as those should not be monitored
(we already have guard pages for that, and it is difficult anyway to
correctly monitor any kind of data passed on stack).
To support this need, make the BPF verifier mark the instructions
depending on whether they could access or not memory other than stack.
As different states in the verifier could lead to different memory types
for the same access, just marking an instruction as accessing stack only
is not enough (it could be some other memory type in another verifier
state), so the algorithm rather sets by default any load/store
instruction as stack only, and if _any_ state leads to any memory access
type other than PTR_TO_STACK, it overrides this setting. It also takes
care about shifting back the instruction marking in adjust_insn_aux_data
if the verifier patches instructions. However, if the verifier generates
new BPF_ST/BPF_STX/BPF_LDX while patching some instructions, those new
ones are systematically marked as non-stack-accessing: this may
over-instrument a few memory accessing instructions, but it allows
making sure that we will not miss accidentally any.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v6:
- drop original instruction offset tracking, but add a best effort
original insn marking preservation
- update comment style
Changes in v5:
- fix incorrect marking for single instruction patch
Changes in v4:
- include BPF_ATOMIC in is_mem_insn
- correctly mark instructions in adjust_insn_aux_data if patch generates
a single instruction (ie replace an instruction)
Changes in v3:
- drop getter
- drop cBPF handling
- update marking shifting logic to track more precisely orignal
instructions
- systematically mark newly generated instructions as non-stack
accessing
Changes in v2:
- invert marking logic to cover possible different reg types when the
verifier covers different states
- add a best-effort processing for classical bpf programs, inspecting
directly src and dst registers since we don't have verifier env
- make sure to keep marking in sync with prog when it is patched by
verifier
---
include/linux/bpf_verifier.h | 2 ++
kernel/bpf/fixups.c | 45 +++++++++++++++++++++++++++++++++++++++++---
kernel/bpf/verifier.c | 9 +++++++++
3 files changed, 53 insertions(+), 3 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 004b06785521..412a13a664fa 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -725,6 +725,8 @@ struct bpf_insn_aux_data {
u16 const_reg_map_mask;
u16 const_reg_subprog_mask;
u32 const_reg_vals[10];
+ /* instruction can access non-stack memory */
+ bool non_stack_access;
};
#define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 65b441e4a351..edcb0cbbb13d 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -208,12 +208,25 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
}
#endif
+static bool is_mem_insn(struct bpf_insn *insn)
+{
+ if (BPF_CLASS(insn->code) != BPF_ST &&
+ BPF_CLASS(insn->code) != BPF_STX &&
+ BPF_CLASS(insn->code) != BPF_LDX)
+ return false;
+
+ return (BPF_MODE(insn->code) == BPF_MEM ||
+ BPF_MODE(insn->code) == BPF_MEMSX ||
+ BPF_MODE(insn->code) == BPF_ATOMIC);
+}
+
/* single env->prog->insni[off] instruction was replaced with the range
* insni[off, off + cnt). Adjust corresponding insn_aux_data by copying
* [0, off) and [off, end) to new locations, so the patched range stays zero
*/
static void adjust_insn_aux_data(struct bpf_verifier_env *env,
- struct bpf_prog *new_prog, u32 off, u32 cnt)
+ struct bpf_prog *new_prog, u32 off, u32 cnt,
+ struct bpf_insn *original_insn)
{
struct bpf_insn_aux_data *data = env->insn_aux_data;
struct bpf_insn *insn = new_prog->insnsi;
@@ -227,8 +240,15 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
*/
data[off].zext_dst = bpf_insn_def32(new_prog, insn + off + cnt - 1) >= 0;
- if (cnt == 1)
+ if (cnt == 1) {
+ /*
+ * A non-memory accessing insn could have been replaced by a
+ * memory accessing insn, systematically mark it for non-stack
+ * access
+ */
+ data[off].non_stack_access = is_mem_insn(insn + off);
return;
+ }
prog_len = new_prog->len;
env->insn_aux_data_len = prog_len;
@@ -239,8 +259,25 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
/* Expand insni[off]'s seen count to the patched range. */
data[i].seen = old_seen;
data[i].zext_dst = bpf_insn_def32(new_prog, insn + i) >= 0;
+ if (!memcmp(insn + i, original_insn, sizeof(struct bpf_insn))) {
+ data[i].non_stack_access =
+ data[off + cnt - 1].non_stack_access;
+ data[off + cnt - 1].non_stack_access = false;
+ } else if (is_mem_insn(insn + i)) {
+ data[i].non_stack_access = true;
+ }
}
+ /*
+ * Last slot instruction could be a newly generated
+ * BPF_ST/BPF_LDX/BPF_STX, systematically mark it for non-stack access
+ * if it is not the original instruction, otherwise keep the
+ * original marking
+ */
+ if (is_mem_insn(insn + off + cnt - 1) &&
+ memcmp(insn + off + cnt - 1, original_insn, sizeof(struct bpf_insn)))
+ data[off + cnt - 1].non_stack_access = true;
+
/*
* The indirect_target flag of the original instruction was moved to the last of the
* new instructions by the above memmove and memset, but the indirect jump target is
@@ -306,6 +343,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
{
struct bpf_prog *new_prog;
struct bpf_insn_aux_data *new_data = NULL;
+ struct bpf_insn original_insn;
if (len > 1) {
new_data = vrealloc(env->insn_aux_data,
@@ -318,6 +356,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
env->insn_aux_data = new_data;
}
+ memcpy(&original_insn, env->prog->insnsi + off, sizeof(struct bpf_insn));
new_prog = bpf_patch_insn_single(env->prog, off, patch, len);
if (IS_ERR(new_prog)) {
if (PTR_ERR(new_prog) == -ERANGE)
@@ -326,7 +365,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
env->insn_aux_data[off].orig_idx);
return NULL;
}
- adjust_insn_aux_data(env, new_prog, off, len);
+ adjust_insn_aux_data(env, new_prog, off, len, &original_insn);
adjust_subprog_starts(env, off, len);
adjust_insn_arrays(env, off, len);
adjust_poke_descs(new_prog, off, len);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e036ae20bf6b..040af75d9550 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3272,6 +3272,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
env->insn_aux_data[idx].indirect_target = true;
}
+static void mark_non_stack_access(struct bpf_verifier_env *env, int idx)
+{
+ env->insn_aux_data[idx].non_stack_access = true;
+}
+
#define LR_FRAMENO_BITS 4
#define LR_SPI_BITS 6
#define LR_ENTRY_BITS (LR_SPI_BITS + LR_FRAMENO_BITS + 1)
@@ -6654,6 +6659,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
clear_scalar_id(®s[value_regno]);
}
}
+
+ if (!err && reg->type != PTR_TO_STACK)
+ mark_non_stack_access(env, insn_idx);
+
return err;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 2/9] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 3/9] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
` (6 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add a new Kconfig option CONFIG_BPF_JIT_KASAN that automatically enables
generic KASAN (Kernel Address SANitizer) memory access checks for
JIT-compiled BPF programs as well, when both KASAN (and more
specifically, generic KASAN with KASAN_VMALLOC) and JIT compiler are
enabled. This new Kconfig is not a user selectable one: it is
automatically enabled if KASAN is enabled on a compatible platform. When
enabled, the JIT compiler will emit shadow memory checks before memory
loads and stores to detect use-after-free or out-of-bounds accesses at
runtime. The option is gated behind HAVE_EBPF_JIT_KASAN, as it needs
proper arch-specific implementation.
Acked-by: Andrey Konovalov <andreyknvl@gmail.com>
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v6:
- fix commit msg
- get rid of "if KASAN" when setting default value, already implied by
KASAN_GENERIC
Changes in v4:
- reorganize dependencies (Andrey)
- drop VMAP_STACK dependency
Changes in v2:
- add dependency on kasan for vmalloc and vmalloc'ed stack
---
kernel/bpf/Kconfig | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
index eb3de35734f0..d7d25477ef48 100644
--- a/kernel/bpf/Kconfig
+++ b/kernel/bpf/Kconfig
@@ -17,6 +17,10 @@ config HAVE_CBPF_JIT
config HAVE_EBPF_JIT
bool
+# KASAN support for JIT compiler
+config HAVE_EBPF_JIT_KASAN
+ bool
+
# Used by archs to tell that they want the BPF JIT compiler enabled by
# default for kernels that were compiled with BPF JIT support.
config ARCH_WANT_DEFAULT_BPF_JIT
@@ -101,4 +105,17 @@ config BPF_LSM
If you are unsure how to answer this question, answer N.
+config BPF_JIT_KASAN
+ bool
+ depends on HAVE_EBPF_JIT_KASAN
+ depends on KASAN_GENERIC
+ depends on KASAN_VMALLOC
+ depends on BPF_JIT
+ default y
+ help
+ Makes JIT compiler insert generic outline KASAN checks in BPF
+ programs when they are inserted in the kernel. This feature is
+ automatically enabled if the needed set of KASAN and BPF
+ configuration options is enabled.
+
endmenu # "BPF subsystem"
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 3/9] bpf, x86: refactor BPF_ST management in do_jit
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 2/9] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
` (5 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
In order to prepare for KASAN checks insertion before every
memory-related load or store, group all BPF_ST instructions that indeed
access memory in a single helper to allow instrumenting those in one
call, rather than having to instrument all cases individually.
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v7:
- align dst_reg type with same type as other emit helpers
- fix line wrapping
- add missing doc on top of new helper
- remove unneeded cast
Changes in v3:
- new patch
---
arch/x86/net/bpf_jit_comp.c | 98 +++++++++++++++++++++++++++------------------
1 file changed, 59 insertions(+), 39 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 48429fae0641..13ba3232993a 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1315,6 +1315,63 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int
*pprog = prog;
}
+/* ST: *(u8*)(dst_reg + off) = imm */
+static void emit_st(u8 **pprog, struct bpf_insn *insn, u32 dst_reg,
+ s32 outgoing_arg_base, u16 outgoing_rsp)
+{
+ s32 imm32 = insn->imm;
+ u8 *prog = *pprog;
+ s32 insn_off;
+
+ switch (BPF_SIZE(insn->code)) {
+ case BPF_B:
+ if (is_ereg(dst_reg))
+ EMIT2(0x41, 0xC6);
+ else
+ EMIT1(0xC6);
+ break;
+ case BPF_H:
+ if (is_ereg(dst_reg))
+ EMIT3(0x66, 0x41, 0xC7);
+ else
+ EMIT2(0x66, 0xC7);
+ break;
+ case BPF_W:
+ if (is_ereg(dst_reg))
+ EMIT2(0x41, 0xC7);
+ else
+ EMIT1(0xC7);
+ break;
+ case BPF_DW:
+ if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
+ /* Arg 6: store immediate in r9 register */
+ emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, imm32);
+ *pprog = prog;
+ return;
+ }
+ EMIT2(add_1mod(0x48, dst_reg), 0xC7);
+ break;
+ }
+
+ insn_off = insn->off;
+ if (dst_reg == BPF_REG_PARAMS) {
+ /*
+ * Args 7+: reverse BPF negative offsets to
+ * x86 positive rsp offsets.
+ * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
+ */
+ insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
+ dst_reg = BPF_REG_FP;
+ }
+ if (is_imm8(insn_off))
+ EMIT2(add_1reg(0x40, dst_reg), insn_off);
+ else
+ EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
+
+ EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+ *pprog = prog;
+}
+
static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm)
{
emit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm);
@@ -2250,49 +2307,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
EMIT_LFENCE();
break;
- /* ST: *(u8*)(dst_reg + off) = imm */
case BPF_ST | BPF_MEM | BPF_B:
- if (is_ereg(dst_reg))
- EMIT2(0x41, 0xC6);
- else
- EMIT1(0xC6);
- goto st;
case BPF_ST | BPF_MEM | BPF_H:
- if (is_ereg(dst_reg))
- EMIT3(0x66, 0x41, 0xC7);
- else
- EMIT2(0x66, 0xC7);
- goto st;
case BPF_ST | BPF_MEM | BPF_W:
- if (is_ereg(dst_reg))
- EMIT2(0x41, 0xC7);
- else
- EMIT1(0xC7);
- goto st;
case BPF_ST | BPF_MEM | BPF_DW:
- if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
- /* Arg 6: store immediate in r9 register */
- emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32);
- break;
- }
- EMIT2(add_1mod(0x48, dst_reg), 0xC7);
-
-st: insn_off = insn->off;
- if (dst_reg == BPF_REG_PARAMS) {
- /*
- * Args 7+: reverse BPF negative offsets to
- * x86 positive rsp offsets.
- * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
- */
- insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
- dst_reg = BPF_REG_FP;
- }
- if (is_imm8(insn_off))
- EMIT2(add_1reg(0x40, dst_reg), insn_off);
- else
- EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
-
- EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+ emit_st(&prog, insn, dst_reg, outgoing_arg_base,
+ outgoing_rsp);
break;
/* STX: *(u8*)(dst_reg + off) = src_reg */
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (2 preceding siblings ...)
2026-08-21 22:39 ` [PATCH bpf-next v7 3/9] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 23:24 ` bot+bpf-ci
2026-08-21 23:33 ` sashiko-bot
2026-08-21 22:39 ` [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
` (4 subsequent siblings)
8 siblings, 2 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Insert KASAN shadow memory checks before memory load and store
operations in JIT-compiled BPF programs. This helps detect memory safety
bugs such as use-after-free and out-of-bounds accesses at runtime.
The main instructions being targeted are BPF_ST, BPF_STX and BPF_LDX,
but not all of them are being instrumented:
- if the load/store instruction is in fact accessing the program stack,
emit_kasan_check silently skips the instrumentation, as we can already
benefit from guard pages to monitor stack accesses.
- if the load/store instruction is a BPF_PROBE_MEM or a BPF_PROBE_ATOMIC
instruction, we do not instrument it, as the passed address can fault
(hence the custom fault management with BPF_PROBE_XXX instructions),
and so the corresponding kasan check could fault as well.
To support those new instructions insertion, create the
emit_kasan_check() helper that emits KASAN shadow memory checks before
memory accesses in JIT-compiled BPF programs. The implementation relies
on the existing __asan_{load,store}X functions from KASAN subsystem. The
helper:
- saves registers. This includes caller-saved registers, but also
temporary registers, as those were possibly used by the
affected program. 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 v6:
- add a comment about r10/r11 being skipped in emit_kasan_check
- merge the commit defining the helper into the commit actually using
it
- move non_stack_access check out of the emit_kasan_check helper
- replace hardcoded ip increment with actual computation
Changes in v5:
- (from former split commit) change access type (read -> write) for
atomic RMW check
Changes in v4:
- (from former split commit) refactor BPF_FETCH handling
Changes in v3:
- skip kasan instrumentation if there is no verifier env (cBPF)
- move helper up in the file
- (from former split commit) fix LLVM23 build failure
Changes in v2:
- move asan functions declaration directly into jit compiler, and guard
them with IS_ENABLED
- remove faulty stack alignment, no arg is passed to kasan funcs on the
stack anyway
- make sure to emit call depth accounting code
- do not save unneeded registers
- update helper signature to let caller configure some values (eg:
is_write)
- (from former split commit) support BPF_ATOMICS
- (from former split commit) support BPF_ST
- (from former split commit) make sure to systematically pass correct
instruction to kasan check
---
arch/x86/net/bpf_jit_comp.c | 188 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 171 insertions(+), 17 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 13ba3232993a..8134d028a2f2 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -21,6 +21,17 @@
#include <asm/unwind.h>
#include <asm/cfi.h>
+#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)
+void __asan_load1(void *p);
+void __asan_store1(void *p);
+void __asan_load2(void *p);
+void __asan_store2(void *p);
+void __asan_load4(void *p);
+void __asan_store4(void *p);
+void __asan_load8(void *p);
+void __asan_store8(void *p);
+#endif
+
static bool all_callee_regs_used[4] = {true, true, true, true};
static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len)
@@ -1110,6 +1121,92 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
*pprog = prog;
}
+static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
+ u32 addr_reg, struct bpf_insn *insn, u8 *ip,
+ bool is_write)
+{
+#ifdef CONFIG_BPF_JIT_KASAN
+ u32 bpf_size = BPF_SIZE(insn->code);
+ s32 off = insn->off;
+ u8 *prog = *pprog;
+ void *kasan_func;
+
+ if (!env)
+ return 0;
+
+ /* Derive KASAN check function from access type and size */
+ switch (bpf_size) {
+ case BPF_B:
+ kasan_func = is_write ? __asan_store1 : __asan_load1;
+ break;
+ case BPF_H:
+ kasan_func = is_write ? __asan_store2 : __asan_load2;
+ break;
+ case BPF_W:
+ kasan_func = is_write ? __asan_store4 : __asan_load4;
+ break;
+ case BPF_DW:
+ kasan_func = is_write ? __asan_store8 : __asan_load8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Save rax */
+ EMIT1(0x50);
+ /* Save rcx */
+ EMIT1(0x51);
+ /* Save rdx */
+ EMIT1(0x52);
+ /* Save rsi */
+ EMIT1(0x56);
+ /* Save rdi */
+ EMIT1(0x57);
+ /* Save r8 */
+ EMIT2(0x41, 0x50);
+ /* Save r9 */
+ EMIT2(0x41, 0x51);
+ /*
+ * SystemV ABI states that we should also save r10/r11, but in
+ * practice those registers are _not_ used by the limited set of
+ * kasan helpers we are calling here, so that's fine not to save those.
+ */
+
+ /* mov rdi, addr_reg */
+ EMIT_mov(BPF_REG_1, addr_reg);
+
+ /* add rdi, off (if offset is non-zero) */
+ if (off) {
+ if (is_imm8(off)) {
+ /* add rdi, imm8 */
+ EMIT4(0x48, 0x83, 0xC7, (u8)off);
+ } else {
+ /* add rdi, imm32 */
+ EMIT3_off32(0x48, 0x81, 0xC7, off);
+ }
+ }
+
+ /* Adjust ip to account for the instrumentation generated so far */
+ ip += (prog - *pprog);
+ /* We emit a call, so update call depth counting */
+ ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip);
+ /* call kasan_func */
+ if (emit_call(&prog, kasan_func, ip))
+ return -ERANGE;
+
+ EMIT2(0x41, 0x59);
+ EMIT2(0x41, 0x58);
+ EMIT1(0x5F);
+ EMIT1(0x5E);
+ EMIT1(0x5A);
+ EMIT1(0x59);
+ EMIT1(0x58);
+
+ *pprog = prog;
+#endif /* CONFIG_BPF_JIT_KASAN */
+ return 0;
+}
+
/* LDX: dst_reg = *(u8*)(src_reg + off) */
static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
{
@@ -1480,17 +1577,35 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size,
return 0;
}
-static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg,
- u32 src_reg, s16 off, u8 bpf_size)
+static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog,
+ struct bpf_insn *insn, u8 *ip, u32 dst_reg,
+ u32 src_reg, bool accesses_stack_only)
{
+ u32 atomic_op = insn->imm;
+ int err;
+
switch (atomic_op) {
case BPF_LOAD_ACQ:
+ if (!accesses_stack_only) {
+ err = emit_kasan_check(env, pprog, src_reg, insn, ip,
+ false);
+ if (err)
+ return err;
+ }
/* dst_reg = smp_load_acquire(src_reg + off16) */
- emit_ldx(pprog, bpf_size, dst_reg, src_reg, off);
+ emit_ldx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+ insn->off);
break;
case BPF_STORE_REL:
+ if (!accesses_stack_only) {
+ err = emit_kasan_check(env, pprog, dst_reg, insn, ip,
+ true);
+ if (err)
+ return err;
+ }
/* smp_store_release(dst_reg + off16, src_reg) */
- emit_stx(pprog, bpf_size, dst_reg, src_reg, off);
+ emit_stx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+ insn->off);
break;
default:
pr_err("bpf_jit: unknown atomic load/store opcode %02x\n",
@@ -1911,10 +2026,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
const s32 imm32 = insn->imm;
u32 dst_reg = insn->dst_reg;
u32 src_reg = insn->src_reg;
+ bool accesses_stack_only;
u8 b2 = 0, b3 = 0;
u8 *start_of_ldx;
s64 jmp_offset;
s32 insn_off;
+ int insn_idx;
u8 jmp_cond;
u8 *func;
int nops;
@@ -1931,6 +2048,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
EMIT_ENDBR();
ip = image + addrs[i - 1] + (prog - temp);
+ insn_idx = i - 1 + bpf_prog->aux->subprog_start;
+ accesses_stack_only =
+ env ? !env->insn_aux_data[insn_idx].non_stack_access :
+ false;
switch (insn->code) {
/* ALU */
@@ -2311,6 +2432,13 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
case BPF_ST | BPF_MEM | BPF_H:
case BPF_ST | BPF_MEM | BPF_W:
case BPF_ST | BPF_MEM | BPF_DW:
+ if (!accesses_stack_only) {
+ err = emit_kasan_check(env, &prog, dst_reg,
+ insn, ip, true);
+ if (err)
+ return err;
+ }
+
emit_st(&prog, insn, dst_reg, outgoing_arg_base,
outgoing_rsp);
break;
@@ -2330,6 +2458,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
dst_reg = BPF_REG_FP;
}
+ if (!accesses_stack_only) {
+ err = emit_kasan_check(env, &prog, dst_reg,
+ insn, ip, true);
+ if (err)
+ return err;
+ }
emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
break;
@@ -2511,6 +2645,11 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
/* populate jmp_offset for JAE above to jump to start_of_ldx */
start_of_ldx = prog;
end_of_jmp[-1] = start_of_ldx - end_of_jmp;
+ } else if (!accesses_stack_only) {
+ err = emit_kasan_check(env, &prog, src_reg,
+ insn, ip, false);
+ if (err)
+ return err;
}
if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX ||
BPF_MODE(insn->code) == BPF_MEMSX)
@@ -2572,28 +2711,42 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
}
fallthrough;
case BPF_STX | BPF_ATOMIC | BPF_W:
- case BPF_STX | BPF_ATOMIC | BPF_DW:
- if (insn->imm == (BPF_AND | BPF_FETCH) ||
- insn->imm == (BPF_OR | BPF_FETCH) ||
- insn->imm == (BPF_XOR | BPF_FETCH)) {
- bool is64 = BPF_SIZE(insn->code) == BPF_DW;
- u32 real_src_reg = src_reg;
- u32 real_dst_reg = dst_reg;
- u8 *branch_target;
-
+ case BPF_STX | BPF_ATOMIC | BPF_DW: {
+ bool is64 = BPF_SIZE(insn->code) == BPF_DW;
+ u32 real_src_reg = src_reg;
+ u32 real_dst_reg = dst_reg;
+ u8 *branch_target;
+ u8 *pprog;
+ bool is_atomic_fetch =
+ (insn->imm == (BPF_AND | BPF_FETCH) ||
+ insn->imm == (BPF_OR | BPF_FETCH) ||
+ insn->imm == (BPF_XOR | BPF_FETCH));
+ if (is_atomic_fetch) {
/*
* Can't be implemented with a single x86 insn.
* Need to do a CMPXCHG loop.
*/
/* Will need RAX as a CMPXCHG operand so save R0 */
+ pprog = prog;
emit_mov_reg(&prog, true, BPF_REG_AX, BPF_REG_0);
if (src_reg == BPF_REG_0)
real_src_reg = BPF_REG_AX;
if (dst_reg == BPF_REG_0)
real_dst_reg = BPF_REG_AX;
-
+ ip += (prog - pprog);
+ }
+ if (!bpf_atomic_is_load_store(insn)) {
+ if (!accesses_stack_only) {
+ err = emit_kasan_check(env, &prog,
+ real_dst_reg,
+ insn, ip, true);
+ if (err)
+ return err;
+ }
branch_target = prog;
+ }
+ if (is_atomic_fetch) {
/* Load old value */
emit_ldx(&prog, BPF_SIZE(insn->code),
BPF_REG_0, real_dst_reg, insn->off);
@@ -2625,15 +2778,16 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
}
if (bpf_atomic_is_load_store(insn))
- err = emit_atomic_ld_st(&prog, insn->imm, dst_reg, src_reg,
- insn->off, BPF_SIZE(insn->code));
+ err = emit_atomic_ld_st(env, &prog, insn, ip,
+ dst_reg, src_reg,
+ accesses_stack_only);
else
err = emit_atomic_rmw(&prog, insn->imm, dst_reg, src_reg,
insn->off, BPF_SIZE(insn->code));
if (err)
return err;
break;
-
+ }
case BPF_STX | BPF_PROBE_ATOMIC | BPF_B:
case BPF_STX | BPF_PROBE_ATOMIC | BPF_H:
if (!bpf_atomic_is_load_store(insn)) {
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (3 preceding siblings ...)
2026-08-21 22:39 ` [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 22:55 ` sashiko-bot
2026-08-21 22:39 ` [PATCH bpf-next v7 6/9] selftests/bpf: make cmdline_contains stricter Alexis Lothoré (eBPF Foundation)
` (3 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Mark x86 as supporting KASAN checks in JITed programs so that the
corresponding JIT compiler inserts checks on the translated
instructions.
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
arch/x86/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 48ccc3e6059d..745890d91e99 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -234,6 +234,7 @@ config X86
select HAVE_SAMPLE_FTRACE_DIRECT if X86_64
select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64
select HAVE_EBPF_JIT
+ select HAVE_EBPF_JIT_KASAN if X86_64
select HAVE_EFFICIENT_UNALIGNED_ACCESS
select HAVE_EISA if X86_32
select HAVE_EXIT_THREAD
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 6/9] selftests/bpf: make cmdline_contains stricter
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (4 preceding siblings ...)
2026-08-21 22:39 ` [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 7/9] selftests/bpf: add helpers for KASAN in JIT testing Alexis Lothoré (eBPF Foundation)
` (2 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
cmdline_contains is used by BPF selftests to check the presence of
specific kernel commandline parameters, but it currently suffers from
two issues:
- the read commandline isn't NULL terminated right after the read data
but only at the end of the buffer, leaving uninitialized bytes that
are then possibly tokenized
- the comparison of found tokens is done based on the size of found
token. This could lead to too-short-but-matching tokens to wrongly
match the search pattern.
Enforce stricter checks in cmdline_contains to avoid accidental matches.
Fixes: 399f6185a1c0 ("selftests/bpf: Fix selftests broken by mitigations=off")
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v7:
- add missing Fixes tag
- drop unneeded size check, already done by strcmp
---
tools/testing/selftests/bpf/unpriv_helpers.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index f997d7ec8fd0..9dadcacaef0c 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -72,8 +72,8 @@ static int config_contains(const char *pat)
static bool cmdline_contains(const char *pat)
{
+ int fd, cnt, ret = false;
char cmdline[4096], *c;
- int fd, ret = false;
fd = open("/proc/cmdline", O_RDONLY);
if (fd < 0) {
@@ -81,14 +81,15 @@ static bool cmdline_contains(const char *pat)
return false;
}
- if (read(fd, cmdline, sizeof(cmdline) - 1) < 0) {
+ cnt = read(fd, cmdline, sizeof(cmdline) - 1);
+ if (cnt < 0) {
perror("read /proc/cmdline");
goto out;
}
- cmdline[sizeof(cmdline) - 1] = '\0';
+ cmdline[cnt] = '\0';
for (c = strtok(cmdline, " \n"); c; c = strtok(NULL, " \n")) {
- if (strncmp(c, pat, strlen(c)))
+ if (strcmp(c, pat))
continue;
ret = true;
break;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 7/9] selftests/bpf: add helpers for KASAN in JIT testing
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (5 preceding siblings ...)
2026-08-21 22:39 ` [PATCH bpf-next v7 6/9] selftests/bpf: make cmdline_contains stricter Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 8/9] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 9/9] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
8 siblings, 0 replies; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add two simple helpers to allow checking whether KASAN for eBPF tests
should be executed:
- one helper to check if BPF_JIT_KASAN is enabled in kernel
configuration
- one helper to check if the kernel is running with kasan_multi_shot
(otherwise only the first test will be able to trigger a report)
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v5:
- add kasan_multi_shot helper
Changes in v2:
- fix condition
---
tools/testing/selftests/bpf/unpriv_helpers.c | 10 ++++++++++
tools/testing/selftests/bpf/unpriv_helpers.h | 2 ++
2 files changed, 12 insertions(+)
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index 9dadcacaef0c..2c8c5edb8751 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -143,3 +143,13 @@ bool get_unpriv_disabled(void)
}
return mitigations_off;
}
+
+bool get_kasan_jit_enabled(void)
+{
+ return config_contains("CONFIG_BPF_JIT_KASAN=y") == 1;
+}
+
+bool get_kasan_multi_shot_enabled(void)
+{
+ return cmdline_contains("kasan_multi_shot");
+}
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h
index 151f67329665..a7ceb51577cd 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.h
+++ b/tools/testing/selftests/bpf/unpriv_helpers.h
@@ -5,3 +5,5 @@
#define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled"
bool get_unpriv_disabled(void);
+bool get_kasan_jit_enabled(void);
+bool get_kasan_multi_shot_enabled(void);
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 8/9] selftests/bpf: move bpf_jit_harden helper into testing_helpers
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (6 preceding siblings ...)
2026-08-21 22:39 ` [PATCH bpf-next v7 7/9] selftests/bpf: add helpers for KASAN in JIT testing Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 23:13 ` bot+bpf-ci
2026-08-21 22:39 ` [PATCH bpf-next v7 9/9] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
8 siblings, 1 reply; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Move set_bpf_jit_harden to testing helpers so that other selftests can
change the hardening configuration without re-implementing a helper.
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v5:
- check set_bpf_jit_harden return code in cleanup path
Changes in v3:
- new patch
---
.../selftests/bpf/prog_tests/bpf_insn_array.c | 44 +++-------------------
tools/testing/selftests/bpf/testing_helpers.c | 32 ++++++++++++++++
tools/testing/selftests/bpf/testing_helpers.h | 1 +
3 files changed, 38 insertions(+), 39 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..815f3e04540f 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -227,42 +227,6 @@ static void check_incorrect_index(void)
check_mid_insn_index();
}
-static int set_bpf_jit_harden(char *level)
-{
- char old_level;
- int err = -1;
- int fd = -1;
-
- fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
- if (fd < 0) {
- ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
- return -1;
- }
-
- err = read(fd, &old_level, 1);
- if (err != 1) {
- ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
- err = -1;
- goto end;
- }
-
- lseek(fd, 0, SEEK_SET);
-
- err = write(fd, level, 1);
- if (err != 1) {
- ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
- err = -1;
- goto end;
- }
-
- err = 0;
- *level = old_level;
-end:
- if (fd >= 0)
- close(fd);
- return err;
-}
-
static void check_blindness(void)
{
struct bpf_insn insns[] = {
@@ -272,7 +236,7 @@ static void check_blindness(void)
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
};
- int prog_fd = -1, map_fd;
+ int prog_fd = -1, map_fd, ret;
struct bpf_insn_array_value val = {};
char bpf_jit_harden = '@'; /* non-exizsting value */
int i;
@@ -291,7 +255,8 @@ static void check_blindness(void)
goto cleanup;
bpf_jit_harden = '2';
- if (set_bpf_jit_harden(&bpf_jit_harden)) {
+ ret = set_bpf_jit_harden(&bpf_jit_harden);
+ if (!ASSERT_OK(ret, "set bpf_jit_harden")) {
bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
goto cleanup;
}
@@ -313,7 +278,8 @@ static void check_blindness(void)
cleanup:
/* restore the old one */
if (bpf_jit_harden != '@')
- set_bpf_jit_harden(&bpf_jit_harden);
+ ASSERT_OK(set_bpf_jit_harden(&bpf_jit_harden),
+ "restore hardening configuration");
close(prog_fd);
close(map_fd);
diff --git a/tools/testing/selftests/bpf/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.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH bpf-next v7 9/9] selftests/bpf: add tests to validate KASAN on JIT programs
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (7 preceding siblings ...)
2026-08-21 22:39 ` [PATCH bpf-next v7 8/9] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:39 ` Alexis Lothoré (eBPF Foundation)
2026-08-21 23:36 ` bot+bpf-ci
8 siblings, 1 reply; 17+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-08-21 22:39 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov,
Emil Tsalapatis, Ihor Solodrai, Yafang Shao
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add a basic KASAN test runner that loads and test-run programs that can
trigger memory management bugs. The test captures kernel logs and ensure
that the expected KASAN splat is emitted by searching for the
corresponding first lines in the report, hence validated that the needed
instrumentation has been inserted by the JIT compiler before the
relevant memory accesses. To allow each test to trigger the expected
report, the kernel must run with the kasan_multi_shot configuration.
The runner covers different cases and settings: in the nominal case, it
validates kasan reports on basic instructions (on all supported accesses
sizes) but also when report _should not_ be emitted (eg: for accesses on
program stack). The runner also comes with a few specialized tests that
are then not executed for all sizes/locations:
- specific atomic ops
- test for instructions involving different verifier states, with some
states flagging memory as stack, and other states as non-stack memory
- tests that validate the stack marking shifting when a patch is emitted
by the verifier (zext/rnd_hi32, constant blindind).
Most of those tests are able to trigger kasan reports by altering the
shadow memory (triggering faulty accesses is otherwise complex, because
of the verifier). A few tests trigger actual faulty accesses (eg
out-of-bound accesses)
A few of those tests depends on cpuv4 (load_acquire and store_release).
# ./test_progs -a kasan
#171/1 kasan/st_1_not_on_stack:OK
#171/2 kasan/st_1_on_stack:OK
#171/3 kasan/st_2_not_on_stack:OK
#171/4 kasan/st_2_on_stack:OK
#171/5 kasan/st_4_not_on_stack:OK
#171/6 kasan/st_4_on_stack:OK
#171/7 kasan/st_8_not_on_stack:OK
#171/8 kasan/st_8_on_stack:OK
#171/9 kasan/stx_1_not_on_stack:OK
#171/10 kasan/stx_1_on_stack:OK
#171/11 kasan/stx_2_not_on_stack:OK
#171/12 kasan/stx_2_on_stack:OK
#171/13 kasan/stx_4_not_on_stack:OK
#171/14 kasan/stx_4_on_stack:OK
#171/15 kasan/stx_8_not_on_stack:OK
#171/16 kasan/stx_8_on_stack:OK
#171/17 kasan/ldx_1_not_on_stack:OK
#171/18 kasan/ldx_1_on_stack:OK
#171/19 kasan/ldx_2_not_on_stack:OK
#171/20 kasan/ldx_2_on_stack:OK
#171/21 kasan/ldx_4_not_on_stack:OK
#171/22 kasan/ldx_4_on_stack:OK
#171/23 kasan/ldx_8_not_on_stack:OK
#171/24 kasan/ldx_8_on_stack:OK
#171/25 kasan/simple_atomic_4_not_on_stack:OK
#171/26 kasan/simple_atomic_4_on_stack:OK
#171/27 kasan/simple_atomic_8_not_on_stack:OK
#171/28 kasan/simple_atomic_8_on_stack:OK
#171/29 kasan/simple_atomic_fetch:OK
#171/30 kasan/simple_atomic_fetch:OK
#171/31 kasan/load_acquire_1_not_on_stack:SKIP
#171/32 kasan/load_acquire_1_on_stack:SKIP
#171/33 kasan/load_acquire_2_not_on_stack:SKIP
#171/34 kasan/load_acquire_2_on_stack:SKIP
#171/35 kasan/load_acquire_4_not_on_stack:SKIP
#171/36 kasan/load_acquire_4_on_stack:SKIP
#171/37 kasan/load_acquire_8_not_on_stack:SKIP
#171/38 kasan/load_acquire_8_on_stack:SKIP
#171/39 kasan/store_release_1_not_on_stack:SKIP
#171/40 kasan/store_release_1_on_stack:SKIP
#171/41 kasan/store_release_2_not_on_stack:SKIP
#171/42 kasan/store_release_2_on_stack:SKIP
#171/43 kasan/store_release_4_not_on_stack:SKIP
#171/44 kasan/store_release_4_on_stack:SKIP
#171/45 kasan/store_release_8_not_on_stack:SKIP
#171/46 kasan/store_release_8_on_stack:SKIP
#171/47 kasan/ldx_patched:OK
#171/48 kasan/ldx_patched:OK
#171/49 kasan/verifier_paths_stack_and_non_stack:OK
#171/50 kasan/ldx_oob_1_not_on_stack:OK
#171/51 kasan/ldx_oob_2_not_on_stack:OK
#171/52 kasan/ldx_oob_4_not_on_stack:OK
#171/53 kasan/ldx_oob_8_not_on_stack:OK
#171/54 kasan/st_blinded:OK
#171 kasan:OK (SKIP: 16/54)
Summary: 1/38 PASSED, 16 SKIPPED, 0 FAILED
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v6:
- rename test struct in kasan program
- add oob testing
- add atomic fetch tests
- refactor a bit the test runner so that ldx_patched tests are not
treated as special cases
- break kernel log reading loop immediately if no bytes are read
Changes in v5:
- check snprintf return codes when checking kernel logs
- skip tests in kasan_multi_shot is not enabled
- update simple atomic test expected access type (read -> write)
Changes in v4:
- prevent interleaved kernel logs from breaking kasan report search
- make new kfuncs in bpf_testmod depend on BPF_JIT_KASAN rather than
KASAN_GENERIC
- initialize buffer passed to bpf_prog_test_run_opts
Changes in v3:
- increase kernel log buffer size
- fix comment style
- check bpf_program__fd return code
- document kasan_multi_shot
- fix copy-paste mistakes on poisoning/unpoisoning sequences
- add test for patch due to constant blinding
Changes in v2:
- simplify tests by just manually poisoning test areas with a dedicated
kfunc
- introduce one prog per covered instruction family
- make sure that tests do not consume kernel logs (use /dev/kmgs rather
than klogctl)
- add tests for stack accesses:
- marking correctly set when there are diverging verifier states
leading to different memory types
- marking kept in sync with prog when it is patched
---
tools/testing/selftests/bpf/prog_tests/kasan.c | 454 ++++++++++++++++++++
tools/testing/selftests/bpf/progs/kasan.c | 462 +++++++++++++++++++++
tools/testing/selftests/bpf/progs/kasan_harden.c | 41 ++
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 55 +++
4 files changed, 1012 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..2b424767a0f3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * Tests validating that KASAN reports are properly instrumented and
+ * generated on a wide variety of instructions. The running kernel needs
+ * kasan_multi_shot to run multiple kasan-generating subtests at once
+ */
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include <unpriv_helpers.h>
+#include "kasan.skel.h"
+#include "kasan_harden.skel.h"
+
+#define SUBTEST_NAME_MAX_LEN 128
+#define PROG_NAME_MAX_LEN 128
+
+#define MAX_LOG_SIZE (8 * 1024)
+#define READ_CHUNK_SIZE 256
+
+#define KASAN_PATTERN_SLAB_UAF "BUG: KASAN: slab-use-after-free " \
+ "in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_SLAB_OOB "BUG: KASAN: slab-out-of-bounds " \
+ "in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_REPORT "%s of size %d at addr"
+
+static char klog_buffer[MAX_LOG_SIZE];
+static char record[MAX_LOG_SIZE];
+
+struct test_spec {
+ char *prog_type;
+ bool is_write;
+ bool only_32_or_64;
+ bool needs_load_acq_store_rel;
+ bool skip_multi_size_testing;
+ bool skip_on_stack_testing;
+ int run_size;
+ bool expect_no_report;
+ bool rnd_hi32;
+ bool is_oob;
+};
+
+struct kasan_write_val {
+ __u8 data_1;
+ __u16 data_2;
+ __u32 data_4;
+ __u64 data_8;
+};
+
+struct test_ctx {
+ __u8 prog_tag[BPF_TAG_SIZE];
+ struct bpf_object *obj;
+ int *access_size;
+ bool skip_load_acq_store_rel;
+ struct bpf_program *prog;
+ char prog_name[SUBTEST_NAME_MAX_LEN];
+ int klog_fd;
+};
+
+static int open_kernel_logs(void)
+{
+ int fd;
+
+ fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+ return fd;
+}
+
+static void skip_kernel_logs(int fd)
+{
+ lseek(fd, 0, SEEK_END);
+}
+
+static int read_kernel_logs(int fd, char *buf, size_t max_len)
+{
+ size_t total = 0;
+ ssize_t n;
+
+ buf[0] = '\0';
+ while (1) {
+ char *msg, *eol;
+ size_t len;
+
+ n = read(fd, record, sizeof(record) - 1);
+ if (n == 0)
+ break;
+
+ if (n < 0) {
+ if (errno == EAGAIN)
+ break;
+ return n;
+ }
+ record[n] = '\0';
+
+ /*
+ * Each kmsg record starts with some metadata, separated
+ * from the actual content by a semi-colon
+ */
+ msg = strchr(record, ';');
+ if (!msg)
+ continue;
+ msg++;
+ eol = strchr(msg, '\n');
+ if (eol)
+ *eol = '\0';
+
+ len = strlen(msg);
+ if (total + len + 2 > max_len)
+ break;
+ memcpy(buf + total, msg, len);
+ total += len;
+ buf[total++] = '\n';
+ buf[total] = '\0';
+ }
+
+ return total;
+}
+
+static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
+ bool is_write, int size,
+ bool is_oob)
+{
+ char access_log[READ_CHUNK_SIZE];
+ const char *pattern;
+ char *kasan_report_start;
+ int nsize;
+
+ pattern = is_oob ? KASAN_PATTERN_SLAB_OOB : KASAN_PATTERN_SLAB_UAF;
+ nsize = snprintf(access_log, READ_CHUNK_SIZE, pattern,
+ ctx->prog_tag[0], ctx->prog_tag[1], ctx->prog_tag[2],
+ ctx->prog_tag[3], ctx->prog_tag[4], ctx->prog_tag[5],
+ ctx->prog_tag[6], ctx->prog_tag[7], ctx->prog_name);
+ if (!ASSERT_GE(nsize, 0, "format kasan access header line"))
+ return nsize;
+ /*
+ * Searched kasan report is valid if
+ * - it contains the expected kasan pattern
+ * - the description of the faulty access is found somewhere
+ * after the header (not necessarily on the very next line,
+ * because other kernel messages may interleave)
+ * - faulty access properties match the tested type and size
+ */
+ kasan_report_start = strstr(buf, access_log);
+
+ if (!kasan_report_start)
+ return 1;
+
+ nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT,
+ is_write ? "Write" : "Read", size);
+ if (!ASSERT_GE(nsize, 0, "format kasan access report line"))
+ return nsize;
+
+ if (!strstr(kasan_report_start, access_log))
+ return 1;
+
+ return 0;
+}
+
+static void exec_subtest(struct test_ctx *ctx, struct test_spec *test,
+ int access_size, bool on_stack)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ struct bpf_prog_info info;
+ uint8_t buf[ETH_HLEN] = {0};
+ int ret, prog_fd;
+ __u32 info_len;
+
+ ctx->prog = bpf_object__find_program_by_name(ctx->obj,
+ ctx->prog_name);
+ if (!ASSERT_OK_PTR(ctx->prog, "find test prog"))
+ return;
+
+ info_len = sizeof(info);
+ memset(&info, 0, info_len);
+ prog_fd = bpf_program__fd(ctx->prog);
+ if (!ASSERT_OK_FD(prog_fd, "get prog fd"))
+ return;
+ ret = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len);
+ if (!ASSERT_OK(ret, "fetch loaded program info"))
+ return;
+ memcpy(ctx->prog_tag, info.tag, BPF_TAG_SIZE);
+
+ skip_kernel_logs(ctx->klog_fd);
+
+ topts.sz = sizeof(struct bpf_test_run_opts);
+ topts.data_size_in = ETH_HLEN;
+ topts.data_in = buf;
+ if (ctx->access_size)
+ *ctx->access_size = access_size;
+ ret = bpf_prog_test_run_opts(bpf_program__fd(ctx->prog),
+ &topts);
+ if (!ASSERT_OK(ret, "run prog"))
+ return;
+
+ ret = read_kernel_logs(ctx->klog_fd, klog_buffer, MAX_LOG_SIZE);
+ if (!ASSERT_GE(ret, 0, "read kernel logs"))
+ return;
+
+ ret = check_kasan_report_in_kernel_logs(klog_buffer, ctx,
+ test->is_write, access_size,
+ test->is_oob);
+ if (on_stack || test->expect_no_report)
+ ASSERT_NEQ(ret, 0, "no report should be generated");
+ else
+ ASSERT_OK(ret, "report should be generated");
+}
+
+static void run_subtest_with_size_and_location(struct test_ctx *ctx,
+ struct test_spec *test,
+ int access_size,
+ bool on_stack)
+{
+ char subtest_name[SUBTEST_NAME_MAX_LEN];
+
+ if (test->skip_multi_size_testing) {
+ snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s%s",
+ test->prog_type,
+ test->skip_on_stack_testing ? "" :
+ on_stack ? "_on_stack" :
+ "_not_on_stack");
+ } else {
+ snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s_%d_%s",
+ test->prog_type, access_size,
+ on_stack ? "on_stack" : "not_on_stack");
+ }
+
+ snprintf(ctx->prog_name, PROG_NAME_MAX_LEN, "%s%s", test->prog_type,
+ test->skip_on_stack_testing ? "" :
+ on_stack ? "_on_stack" :
+ "_not_on_stack");
+
+ if (!test__start_subtest(subtest_name))
+ return;
+
+ if (test->needs_load_acq_store_rel && ctx->skip_load_acq_store_rel) {
+ test__skip();
+ return;
+ }
+
+ exec_subtest(ctx, test, access_size, on_stack);
+}
+
+static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test,
+ int size)
+{
+ run_subtest_with_size_and_location(ctx, test, size, false);
+ if (!test->skip_on_stack_testing)
+ run_subtest_with_size_and_location(ctx, test, size, true);
+}
+
+static void run_subtest(struct test_ctx *ctx, struct test_spec *test)
+{
+ if (test->skip_multi_size_testing) {
+ run_subtest_with_size(ctx, test, test->run_size);
+ return;
+ }
+
+ if (!test->only_32_or_64) {
+ run_subtest_with_size(ctx, test, 1);
+ run_subtest_with_size(ctx, test, 2);
+ }
+ run_subtest_with_size(ctx, test, 4);
+ run_subtest_with_size(ctx, test, 8);
+}
+
+static void run_blinding_subtest(void)
+{
+ struct test_spec blinding_spec = {
+ .prog_type = "st_blinded",
+ .is_write = true,
+ };
+ char bpf_jit_harden = '2';
+ struct kasan_harden *skel;
+ struct test_ctx *ctx;
+
+ if (!test__start_subtest("st_blinded"))
+ return;
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (!ASSERT_OK_PTR(ctx, "alloc blinding ctx"))
+ return;
+ ctx->klog_fd = -1;
+
+ if (set_bpf_jit_harden(&bpf_jit_harden))
+ goto free_ctx;
+
+ skel = kasan_harden__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open and load blinded prog"))
+ goto restore;
+
+ ctx->klog_fd = open_kernel_logs();
+ if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+ goto destroy;
+
+ ctx->obj = skel->obj;
+ strncpy(ctx->prog_name, "st_blinded", PROG_NAME_MAX_LEN);
+
+ exec_subtest(ctx, &blinding_spec, 1, false);
+
+destroy:
+ close(ctx->klog_fd);
+ kasan_harden__destroy(skel);
+restore:
+ set_bpf_jit_harden(&bpf_jit_harden);
+free_ctx:
+ free(ctx);
+}
+
+static struct test_spec tests[] = {
+ {
+ .prog_type = "st",
+ .is_write = true
+ },
+ {
+ .prog_type = "stx",
+ .is_write = true
+ },
+ {
+ .prog_type = "ldx",
+ .is_write = false
+ },
+ {
+ .prog_type = "simple_atomic",
+ .is_write = true,
+ .only_32_or_64 = true
+ },
+ {
+ .prog_type = "simple_atomic_fetch",
+ .is_write = true,
+ .skip_multi_size_testing = true,
+ .run_size = 8,
+ },
+ {
+ .prog_type = "load_acquire",
+ .is_write = false,
+ .needs_load_acq_store_rel = true
+ },
+ {
+ .prog_type = "store_release",
+ .is_write = true,
+ .needs_load_acq_store_rel = true
+ },
+ {
+ .prog_type = "ldx_patched",
+ .is_write = false,
+ .skip_multi_size_testing = true,
+ .run_size = 4,
+ .rnd_hi32 = true
+ },
+ {
+ .prog_type = "verifier_paths_stack_and_non_stack",
+ .is_write = true,
+ .skip_multi_size_testing = true,
+ .skip_on_stack_testing = true,
+ .run_size = 1
+ },
+ {
+ .prog_type = "ldx_oob",
+ .is_write = false,
+ .skip_on_stack_testing = true,
+ .is_oob = true
+ },
+};
+
+void test_kasan(void)
+{
+ struct kasan_write_val val;
+ struct test_spec *test;
+ struct test_ctx *ctx;
+ struct kasan *skel;
+ __u32 key = 0;
+ int i, ret;
+
+ ctx = calloc(1, sizeof(struct test_ctx));
+ if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
+ return;
+
+ if (!is_jit_enabled() || !get_kasan_jit_enabled() ||
+ !get_kasan_multi_shot_enabled()) {
+ test__skip();
+ goto end;
+ }
+
+ skel = kasan__open();
+ if (!ASSERT_OK_PTR(skel, "open prog"))
+ goto end;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ char prog_name[SUBTEST_NAME_MAX_LEN];
+ struct bpf_program *prog;
+
+ if (!tests[i].rnd_hi32)
+ continue;
+
+ snprintf(prog_name, SUBTEST_NAME_MAX_LEN, "%s_%s",
+ tests[i].prog_type, "on_stack");
+ prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+ if (!ASSERT_OK_PTR(prog, "find rnd_hi32 on_stack prog"))
+ goto destroy;
+ bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+ snprintf(prog_name, SUBTEST_NAME_MAX_LEN, "%s_%s",
+ tests[i].prog_type, "not_on_stack");
+ prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+ if (!ASSERT_OK_PTR(prog, "find rnd_hi32 not_on_stack prog"))
+ goto destroy;
+ bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+ }
+
+ if (!ASSERT_OK(kasan__load(skel), "load prog"))
+ goto destroy;
+
+ ctx->obj = skel->obj;
+ ctx->access_size = &skel->bss->access_size;
+ ctx->skip_load_acq_store_rel = skel->data->skip_load_acq_store_rel_tests;
+
+ ctx->klog_fd = open_kernel_logs();
+ if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+ goto destroy;
+
+ /* Fill map with recognizable values */
+ ret = bpf_map__lookup_elem(skel->maps.test_map, &key, sizeof(key),
+ &val, sizeof(val), 0);
+ if (!ASSERT_OK(ret, "get map"))
+ goto close;
+ val.data_1 = 0xAA;
+ val.data_2 = 0xBBBB;
+ val.data_4 = 0xCCCCCCCC;
+ val.data_8 = 0xDDDDDDDDDDDDDDDD;
+ ret = bpf_map__update_elem(skel->maps.test_map, &key, sizeof(key),
+ &val, sizeof(val), 0);
+ if (!ASSERT_OK(ret, "set map"))
+ goto close;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ test = &tests[i];
+ run_subtest(ctx, test);
+ }
+
+ /*
+ * Blinding subtest is handled differently as it needs the
+ * corresponding program to be loaded with bpf_jit_harden raised
+ */
+ run_blinding_subtest();
+
+close:
+ close(ctx->klog_fd);
+destroy:
+ kasan__destroy(skel);
+end:
+ free(ctx);
+}
diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
new file mode 100644
index 000000000000..ea29197646b0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <stdbool.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct bpf_testmod_oob {
+ __u8 data;
+ union {
+ __u8 redzone_1;
+ __u16 redzone_2;
+ __u32 redzone_4;
+ __u64 redzone_8;
+ };
+};
+
+extern struct bpf_testmod_oob *bpf_testmod_oob_alloc(void) __ksym;
+extern void bpf_testmod_oob_free(struct bpf_testmod_oob *oob) __ksym;
+
+int access_size;
+
+struct kasan_test_val {
+ __u8 data_1;
+ __u16 data_2;
+ __u32 data_4;
+ __u64 data_8;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct kasan_test_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ val.data_1 = 0xAA;
+ break;
+ case 2:
+ val.data_2 = 0xAA;
+ break;
+ case 4:
+ val.data_4 = 0xAA;
+ break;
+ case 8:
+ val.data_8 = 0xAA;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int st_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ val->data_1 = 0xAA;
+ break;
+ case 2:
+ val->data_2 = 0xAA;
+ break;
+ case 4:
+ val->data_4 = 0xAA;
+ break;
+ case 8:
+ val->data_8 = 0xAA;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int stx_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ val.data_1 = access_size;
+ break;
+ case 2:
+ val.data_2 = access_size;
+ break;
+ case 4:
+ val.data_4 = access_size;
+ break;
+ case 8:
+ val.data_8 = access_size;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int stx_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ val->data_1 = access_size;
+ break;
+ case 2:
+ val->data_2 = access_size;
+ break;
+ case 4:
+ val->data_4 = access_size;
+ break;
+ case 8:
+ val->data_8 = access_size;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ __sink(val.data_1);
+ break;
+ case 2:
+ __sink(val.data_2);
+ break;
+ case 4:
+ __sink(val.data_4);
+ break;
+ case 8:
+ __sink(val.data_8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ __sink(val->data_1);
+ break;
+ case 2:
+ __sink(val->data_2);
+ break;
+ case 4:
+ __sink(val->data_4);
+ break;
+ case 8:
+ __sink(val->data_8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ __sink(val->data_4);
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ __sink(val.data_4);
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+
+ return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 4:
+ __sync_fetch_and_add(&val.data_4, 4);
+ break;
+ case 8:
+ __sync_fetch_and_add(&val.data_8, 8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 4:
+ __sync_fetch_and_add(&val->data_4, 4);
+ break;
+ case 8:
+ __sync_fetch_and_add(&val->data_8, 8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_fetch_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ __sync_fetch_and_or(&val.data_8, 8);
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_fetch_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ __sync_fetch_and_or(&val->data_8, 8);
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL
+bool skip_load_acq_store_rel_tests SEC(".data") = 0;
+
+SEC("tcx/ingress")
+int load_acquire_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ __atomic_load_n(&val.data_1, __ATOMIC_ACQUIRE);
+ break;
+ case 2:
+ __atomic_load_n(&val.data_2, __ATOMIC_ACQUIRE);
+ break;
+ case 4:
+ __atomic_load_n(&val.data_4, __ATOMIC_ACQUIRE);
+ break;
+ case 8:
+ __atomic_load_n(&val.data_8, __ATOMIC_ACQUIRE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int load_acquire_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ __atomic_load_n(&val->data_1, __ATOMIC_ACQUIRE);
+ break;
+ case 2:
+ __atomic_load_n(&val->data_2, __ATOMIC_ACQUIRE);
+ break;
+ case 4:
+ __atomic_load_n(&val->data_4, __ATOMIC_ACQUIRE);
+ break;
+ case 8:
+ __atomic_load_n(&val->data_8, __ATOMIC_ACQUIRE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ __atomic_store_n(&val.data_1, 0xAA, __ATOMIC_RELEASE);
+ break;
+ case 2:
+ __atomic_store_n(&val.data_2, 0xBBBB, __ATOMIC_RELEASE);
+ break;
+ case 4:
+ __atomic_store_n(&val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+ break;
+ case 8:
+ __atomic_store_n(&val.data_8, 0xDDDDDDDDDDDDDDDD,
+ __ATOMIC_RELEASE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ switch (access_size) {
+ case 1:
+ __atomic_store_n(&val->data_1, 0xAA, __ATOMIC_RELEASE);
+ break;
+ case 2:
+ __atomic_store_n(&val->data_2, 0xBBBB, __ATOMIC_RELEASE);
+ break;
+ case 4:
+ __atomic_store_n(&val->data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+ break;
+ case 8:
+ __atomic_store_n(&val->data_8, 0xDDDDDDDDDDDDDDDD,
+ __ATOMIC_RELEASE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+ return 0;
+}
+#else
+bool skip_load_acq_store_rel_tests SEC(".data") = 1;
+#endif
+
+SEC("tcx/ingress")
+int verifier_paths_stack_and_non_stack(struct __sk_buff *skb)
+{
+ struct kasan_test_val stack_val = {};
+ struct kasan_test_val *val;
+ void *ptr;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ if (access_size)
+ ptr = val;
+ else
+ ptr = &stack_val;
+
+ bpf_kfunc_kasan_poison(val, sizeof(*val));
+ *(__u8 *)ptr = 0xAA;
+ bpf_kfunc_kasan_unpoison(val, sizeof(*val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_oob(struct __sk_buff *skb)
+{
+ struct bpf_testmod_oob *val;
+ struct kasan_test_val volatile tmp;
+
+ val = bpf_testmod_oob_alloc();
+ if (!val)
+ return 0;
+
+ switch (access_size) {
+ case 1:
+ tmp.data_1 = (__u8)val->redzone_1;
+ break;
+ case 2:
+ tmp.data_2 = (__u16)val->redzone_2;
+ break;
+ case 4:
+ tmp.data_4 = (__u32)val->redzone_4;
+ break;
+ case 8:
+ tmp.data_8 = (__u64)val->redzone_8;
+ break;
+ }
+ bpf_testmod_oob_free(val);
+ return tmp.data_1;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kasan_harden.c b/tools/testing/selftests/bpf/progs/kasan_harden.c
new file mode 100644
index 000000000000..a2756bbfd529
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct kasan_test_val {
+ __u8 data_1;
+ __u16 data_2;
+ __u32 data_4;
+ __u64 data_8;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct kasan_test_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_blinded(struct __sk_buff *skb)
+{
+ struct kasan_test_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
+ val->data_1 = 0xAA;
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
+
+ return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 850cf4f830c4..f5e89ce0ff32 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -75,6 +75,16 @@ union bpf_testmod_union_arg_2 {
struct bpf_testmod_struct_arg_2 arg;
};
+struct bpf_testmod_oob {
+ __u8 data;
+ union {
+ __u8 redzone_1;
+ __u16 redzone_2;
+ __u32 redzone_4;
+ __u64 redzone_8;
+ };
+};
+
__bpf_hook_start();
noinline int
@@ -336,6 +346,47 @@ __bpf_kfunc void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member
*/
}
+#ifdef CONFIG_BPF_JIT_KASAN
+
+extern void kasan_poison(const void *addr, size_t size, u8 value, bool init);
+
+#define KASAN_SLAB_FREE 0xFB
+
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz)
+{
+ kasan_poison(mem, mem__sz, KASAN_SLAB_FREE, false);
+}
+
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz)
+{
+ kasan_poison(mem, mem__sz, 0x00, false);
+}
+#else
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) { }
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) { }
+#endif
+
+__bpf_kfunc struct bpf_testmod_oob *bpf_testmod_oob_alloc(void)
+{
+ struct bpf_testmod_oob *p;
+
+ /*
+ * Only allocate size of data (and so, voluntarily use kmalloc
+ * instead of kmalloc_obj), not the rest of the structure, so
+ * that programs under test trying to access the rest of the
+ * structure trigger OoB accesses
+ */
+ p = kmalloc(sizeof(p->data), GFP_ATOMIC);
+ if (!p)
+ return NULL;
+ return p;
+}
+
+__bpf_kfunc void bpf_testmod_oob_free(struct bpf_testmod_oob *oob)
+{
+ kfree(oob);
+}
+
__bpf_kfunc struct bpf_testmod_ctx *
bpf_testmod_ctx_create(int *err)
{
@@ -869,6 +920,10 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_multislot)
BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison)
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_unpoison)
+BTF_ID_FLAGS(func, bpf_testmod_oob_alloc, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_testmod_oob_free, KF_RELEASE)
BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
BTF_ID_LIST(bpf_testmod_dtor_ids)
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack
2026-08-21 22:39 ` [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:54 ` sashiko-bot
2026-08-21 23:24 ` bot+bpf-ci
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-08-21 22:54 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:
- [Low] The `adjust_insn_aux_data()` logic leaks the `non_stack_access` flag onto non-memory instructions when a patch entirely drops the original instruction.
- [Low] Duplicated original instructions in a patch sequence lose their `non_stack_access` marking.
--
--- Patch [1]: [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack ---
commit e45a03bdb90ce436777e9a9558206c04d0705394
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf: mark instructions accessing program stack
This commit adds tracking in the BPF verifier for load/store instructions
that access memory other than the BPF program stack. It modifies instruction
patching logic to retain or conservatively mark these instructions to aid
JIT compilers in emitting KASAN checks.
> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
> index 65b441e4a3517..edcb0cbbb13de 100644
> --- a/kernel/bpf/fixups.c
> +++ b/kernel/bpf/fixups.c
[ ... ]
> @@ -239,8 +259,25 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
> /* Expand insni[off]'s seen count to the patched range. */
> data[i].seen = old_seen;
> data[i].zext_dst = bpf_insn_def32(new_prog, insn + i) >= 0;
> + if (!memcmp(insn + i, original_insn, sizeof(struct bpf_insn))) {
> + data[i].non_stack_access =
> + data[off + cnt - 1].non_stack_access;
> + data[off + cnt - 1].non_stack_access = false;
[Severity: Low]
Does this logic correctly handle patch sequences that might duplicate the
original instruction?
If a patch duplicates the original instruction, the first match correctly
inherits the non_stack_access marking, but then actively clears the source
value by setting it to false here.
Any subsequent duplicated original instruction would then inherit a cleared
false marking, leading to missing KASAN runtime instrumentation checks for
that memory access.
> + } else if (is_mem_insn(insn + i)) {
> + data[i].non_stack_access = true;
> + }
> }
>
> + /*
> + * Last slot instruction could be a newly generated
> + * BPF_ST/BPF_LDX/BPF_STX, systematically mark it for non-stack access
> + * if it is not the original instruction, otherwise keep the
> + * original marking
> + */
> + if (is_mem_insn(insn + off + cnt - 1) &&
> + memcmp(insn + off + cnt - 1, original_insn, sizeof(struct bpf_insn)))
> + data[off + cnt - 1].non_stack_access = true;
[Severity: Low]
Can the non_stack_access flag leak onto non-memory instructions if a patch
entirely drops the original instruction?
A preceding memmove copies the original instruction's marking to the final
patched instruction's slot:
memmove(data + off + cnt - 1, data + off,
sizeof(struct bpf_insn_aux_data) * (prog_len - off - cnt + 1));
If the original memory instruction is replaced by a sequence that does not
contain the exact original instruction and ends in a non-memory instruction,
this final check fails to reset the inherited true flag back to false since it
only evaluates is_mem_insn().
While the leaked flag is currently benign because JIT compilers exclusively
evaluate the flag for memory instructions, it seems like an unintended state
for the verifier auxiliary data.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822-kasan-v7-0-99afee6ef7fd@bootlin.com?part=1
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86
2026-08-21 22:39 ` [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 22:55 ` sashiko-bot
0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-08-21 22:55 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] Caller-saved register R10 (BPF_REG_AX) is clobbered by KASAN checks during BPF atomic fetch operations and constant blinding, causing silent memory corruption.
--
--- Patch [5]: [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86 ---
commit 0c36ddb8d38e85fd1cfe5d79b5382a25c0f86a21
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf, x86: enable KASAN for JITed programs on x86
This commit marks x86 as supporting KASAN checks in JITed programs, causing
the JIT compiler to insert checks on translated instructions.
> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
> index 48ccc3e6059d0..745890d91e99a 100644
> --- a/arch/x86/Kconfig
> +++ b/arch/x86/Kconfig
> @@ -234,6 +234,7 @@ config X86
> select HAVE_SAMPLE_FTRACE_DIRECT if X86_64
> select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64
> select HAVE_EBPF_JIT
> + select HAVE_EBPF_JIT_KASAN if X86_64
[Severity: High]
Does enabling this feature expose a register clobbering issue in how KASAN
checks are emitted?
Looking at emit_kasan_check() in arch/x86/net/bpf_jit_comp.c, it deliberately
skips saving R10 (which is mapped to BPF_REG_AX) before calling the KASAN
helper:
/*
* SystemV ABI states that we should also save r10/r11, but in
* practice those registers are _not_ used by the limited set of
* kasan helpers we are calling here, so that's fine not to save those.
*/
However, KASAN helpers are standard C functions following the System V AMD64
ABI, which are allowed to clobber R10. Additionally, if KASAN detects an
issue, kasan_report() is called, which also clobbers R10.
Meanwhile, do_jit() relies on BPF_REG_AX (R10) being preserved across KASAN
checks, such as during atomic fetch operations:
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;
If R10 is clobbered during the KASAN check, won't the subsequent atomic or
memory instruction use a corrupted R10 as a memory address or source value,
potentially leading to silent memory corruption or crashes?
> select HAVE_EFFICIENT_UNALIGNED_ACCESS
> select HAVE_EISA if X86_32
> select HAVE_EXIT_THREAD
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822-kasan-v7-0-99afee6ef7fd@bootlin.com?part=5
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 8/9] selftests/bpf: move bpf_jit_harden helper into testing_helpers
2026-08-21 22:39 ` [PATCH bpf-next v7 8/9] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 23:13 ` bot+bpf-ci
0 siblings, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-21 23:13 UTC (permalink / raw)
To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
ihor.solodrai, laoar.shao
Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 5684 bytes --]
> 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..815f3e04540f 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
[ ... ]
> @@ -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")) {
This isn't a bug, but could the first call just be
`if (!ASSERT_OK(set_bpf_jit_harden(&bpf_jit_harden), "set bpf_jit_harden"))`
instead? It would match the restore call below and drop the extra `ret`
local:
> cleanup:
> /* restore the old one */
> if (bpf_jit_harden != '@')
> - set_bpf_jit_harden(&bpf_jit_harden);
> + ASSERT_OK(set_bpf_jit_harden(&bpf_jit_harden),
> + "restore hardening configuration");
> 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;
> +}
tools/testing/selftests/bpf/ already has a shared sysctl helper with these
semantics. Is there a reason to add a second sysctl read-modify-write
implementation instead of wrapping the existing one?
sysctl_helpers.h (same directory) exports:
int sysctl_set(const char *sysctl_path, char *old_val,
const char *new_val);
int sysctl_set_or_fail(const char *sysctl_path, char *old_val,
const char *new_val);
and sysctl_helpers.c implements 'open the sysctl, save the previous value
into old_val, write new_val, return -errno on failure' - the same contract
the new set_bpf_jit_harden() open-codes with
open()/read()/lseek()/write()/close(). sysctl_set_or_fail() additionally
emits the PRINT_FAIL diagnostic that the moved helper drops.
sysctl_helpers.c is already linked into test_progs (Makefile
TRUNNER_EXTRA_SOURCES) and is already used by three prog_tests
(test_sysctl.c, token.c, unpriv_bpf_disabled.c). The only consumers of
set_bpf_jit_harden() are test_progs subtests (prog_tests/bpf_insn_array.c
here, and prog_tests/kasan.c added by the follow-up commit 7af63bad7fc6), so
sysctl_set() is reachable from every call site.
Since this patch's stated purpose is precisely to provide a shared way to
change the hardening configuration, landing a second sysctl
set-and-restore implementation means a corner-case fix in one (short
read/write handling, values longer than one byte, error reporting) will not
propagate to the other.
The immediately preceding function in the same file, is_jit_enabled() at
testing_helpers.c:505, already hand-opens /proc/sys/net/core/bpf_jit_enable
with open()/read()/close(), so the new code matches its neighbour. Also,
the patch is a pure code motion - the body is byte-identical to the version
removed from bpf_insn_array.c apart from the deleted ASSERT_FAIL calls - so
it does not newly introduce the hand-rolled I/O, it only relocates it.
sysctl_set() reads the old value with an unbounded fscanf(fp, "%s",
old_val), which would require the caller to pass a sized buffer rather than
the single char the callers use today, so a straight substitution is not
free.
Worth asking whether set_bpf_jit_harden() should be a thin wrapper over
sysctl_set("/proc/sys/net/core/bpf_jit_harden", ...) instead?
---
This isn't a bug, but the patch is not a pure move: the three ASSERT_FAIL()
calls that reported which of open/read/write failed (with errno) are dropped
from the helper, and two ASSERT_OK() checks are added at the call sites
instead.
The reason that rework is required is not obvious from the diff -
testing_helpers.c contains no ASSERT_*/PRINT_FAIL uses at all, and
testing_helpers.o is linked into standalone binaries (test_maps, test_tag,
test_sockmap, bench) that do not provide the test_progs failure machinery,
which is why network_helpers.c's users have to define stub test__fail() in
xdp_features.c, xskxceiver.c and xdp_hw_metadata.c.
A reader also loses the ability to tell open/read/write apart, while the
retained comment at the call site still says "open, read or write failed =>
no write was done".
Would it be worth a sentence noting that the ASSERT_FAIL() diagnostics had
to move to the callers because testing_helpers.c is linked into binaries
without the test_progs assertion machinery?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32534828209
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack
2026-08-21 22:39 ` [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
2026-08-21 22:54 ` sashiko-bot
@ 2026-08-21 23:24 ` bot+bpf-ci
1 sibling, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-21 23:24 UTC (permalink / raw)
To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
ihor.solodrai, laoar.shao
Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2509 bytes --]
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 004b06785521..412a13a664fa 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -725,6 +725,8 @@ struct bpf_insn_aux_data {
> u16 const_reg_map_mask;
> u16 const_reg_subprog_mask;
> u32 const_reg_vals[10];
> + /* instruction can access non-stack memory */
> + bool non_stack_access;
> };
[ ... ]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index e036ae20bf6b..040af75d9550 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -3272,6 +3272,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
> env->insn_aux_data[idx].indirect_target = true;
> }
>
> +static void mark_non_stack_access(struct bpf_verifier_env *env, int idx)
> +{
> + env->insn_aux_data[idx].non_stack_access = true;
> +}
> +
[ ... ]
> @@ -6654,6 +6659,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
> clear_scalar_id(®s[value_regno]);
> }
> }
> +
> + if (!err && reg->type != PTR_TO_STACK)
> + mark_non_stack_access(env, insn_idx);
> +
> return err;
> }
Does this test read the correct register type? For a load instruction where
dst_reg == src_reg (e.g., rX = *(u64 *)(rX + off)), check_mem_access()
receives reg = ®s[src_reg] and value_regno = dst_reg, so reg points to
the same slot that gets overwritten by the load.
When the pointer is PTR_TO_STACK, check_stack_read() runs:
check_stack_read_fixed_off() {
state->regs[dst_regno] = *reg; /* or mark_reg_stack_read() */
}
By the time execution reaches the new test, reg->type is the loaded value's
type (typically SCALAR_VALUE), not PTR_TO_STACK. The condition evaluates to
true and marks a stack-only access as non-stack.
Both check_load_mem() and check_store_reg() snapshot the pointer type before
calling check_mem_access():
/* verifier.c check_load_mem */
src_reg_type = regs[insn->src_reg].type;
err = check_mem_access(env, env->insn_idx, ...);
/* verifier.c check_store_reg */
dst_reg_type = regs[insn->dst_reg].type;
err = check_mem_access(env, env->insn_idx, ...);
Could check_mem_access() snapshot reg->type at entry and test that snapshot
instead?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32534828209
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs
2026-08-21 22:39 ` [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 23:24 ` bot+bpf-ci
2026-08-21 23:33 ` sashiko-bot
1 sibling, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-21 23:24 UTC (permalink / raw)
To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
ihor.solodrai, laoar.shao
Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1180 bytes --]
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index 13ba3232993a..8134d028a2f2 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
> +
This isn't a bug, but these eight prototypes duplicate declarations from
mm/kasan/kasan.h. Would it be worth exporting them from a shared header
such as include/linux/kasan.h under CONFIG_KASAN_GENERIC so each JIT does
not have to re-declare them? Also, this uses #if IS_ENABLED() while the
matching guard in emit_kasan_check() uses #ifdef CONFIG_BPF_JIT_KASAN.
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32534828209
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs
2026-08-21 22:39 ` [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-21 23:24 ` bot+bpf-ci
@ 2026-08-21 23:33 ` sashiko-bot
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-08-21 23:33 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] `emit_kasan_check` fails to save the caller-saved register %r10 (BPF_REG_AX), which corrupts BPF_REG_0 and memory pointers during atomic fetch operations.
--
--- Patch [4]: [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs ---
commit 6bca50db6e9af9416363fe2e60f4e42f924950c9
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf, x86: emit KASAN checks in 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 13ba3232993ac..8134d028a2f2c 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -1110,6 +1121,92 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
> *pprog = prog;
> }
>
> +static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
> + u32 addr_reg, struct bpf_insn *insn, u8 *ip,
> + bool is_write)
> +{
> +#ifdef CONFIG_BPF_JIT_KASAN
> + u32 bpf_size = BPF_SIZE(insn->code);
> + s32 off = insn->off;
> + u8 *prog = *pprog;
> + void *kasan_func;
> +
> + if (!env)
> + return 0;
> +
> + /* Derive KASAN check function from access type and size */
> + switch (bpf_size) {
> + case BPF_B:
> + kasan_func = is_write ? __asan_store1 : __asan_load1;
> + break;
> + case BPF_H:
> + kasan_func = is_write ? __asan_store2 : __asan_load2;
> + break;
> + case BPF_W:
> + kasan_func = is_write ? __asan_store4 : __asan_load4;
> + break;
> + case BPF_DW:
> + kasan_func = is_write ? __asan_store8 : __asan_load8;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /* Save rax */
> + EMIT1(0x50);
> + /* Save rcx */
> + EMIT1(0x51);
> + /* Save rdx */
> + EMIT1(0x52);
> + /* Save rsi */
> + EMIT1(0x56);
> + /* Save rdi */
> + EMIT1(0x57);
> + /* Save r8 */
> + EMIT2(0x41, 0x50);
> + /* Save r9 */
> + EMIT2(0x41, 0x51);
> + /*
> + * SystemV ABI states that we should also save r10/r11, but in
> + * practice those registers are _not_ used by the limited set of
> + * kasan helpers we are calling here, so that's fine not to save those.
> + */
> +
> + /* mov rdi, addr_reg */
[Severity: High]
Is it safe to skip saving %r10 and %r11 here?
In the x86 BPF JIT, %r10 is mapped to BPF_REG_AX. During atomic fetch
operations, BPF_REG_0 is stashed into BPF_REG_AX before calling
emit_kasan_check():
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;
if (!bpf_atomic_is_load_store(insn)) {
if (!accesses_stack_only) {
err = emit_kasan_check(env, &prog, real_dst_reg, insn, ip, true);
...
}
Since the KASAN helpers are standard C functions, they are permitted by the
System V AMD64 ABI to clobber caller-saved registers like %r10 (especially if
kasan_report is called).
If %r10 is clobbered, could this corrupt the stashed BPF_REG_0 and potentially
cause a crash when real_dst_reg is used as a memory pointer in the subsequent
emit_ldx() call?
arch/x86/net/bpf_jit_comp.c:do_jit() {
...
/* Load old value */
emit_ldx(&prog, BPF_SIZE(insn->code), BPF_REG_0, real_dst_reg, insn->off);
...
/* Restore R0 after clobbering RAX */
emit_mov_reg(&prog, true, BPF_REG_0, BPF_REG_AX);
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822-kasan-v7-0-99afee6ef7fd@bootlin.com?part=4
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v7 9/9] selftests/bpf: add tests to validate KASAN on JIT programs
2026-08-21 22:39 ` [PATCH bpf-next v7 9/9] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
@ 2026-08-21 23:36 ` bot+bpf-ci
0 siblings, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-21 23:36 UTC (permalink / raw)
To: alexis.lothore, ast, daniel, john.fastabend, andrii, martin.lau,
eddyz87, memxor, song, yonghong.song, jolsa, tglx, bp,
dave.hansen, x86, hpa, shuah, mingo, andreyknvl, emil,
ihor.solodrai, laoar.shao
Cc: ebpf, bastien.curutchet, thomas.petazzoni, bpf, linux-kernel,
linux-kselftest, alexis.lothore, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 16550 bytes --]
> Add a basic KASAN test runner that loads and test-run programs that can
> trigger memory management bugs. The test captures kernel logs and ensure
> that the expected KASAN splat is emitted by searching for the
> corresponding first lines in the report, hence validated that the needed
> instrumentation has been inserted by the JIT compiler before the
> relevant memory accesses. To allow each test to trigger the expected
> report, the kernel must run with the kasan_multi_shot configuration.
>
> The runner covers different cases and settings: in the nominal case, it
> validates kasan reports on basic instructions (on all supported accesses
> sizes) but also when report _should not_ be emitted (eg: for accesses on
> program stack). The runner also comes with a few specialized tests that
> are then not executed for all sizes/locations:
> - specific atomic ops
> - test for instructions involving different verifier states, with some
> states flagging memory as stack, and other states as non-stack memory
> - tests that validate the stack marking shifting when a patch is emitted
> by the verifier (zext/rnd_hi32, constant blindind).
> Most of those tests are able to trigger kasan reports by altering the
> shadow memory (triggering faulty accesses is otherwise complex, because
> of the verifier). A few tests trigger actual faulty accesses (eg
> out-of-bound accesses)
>
> A few of those tests depends on cpuv4 (load_acquire and store_release).
>
> # ./test_progs -a kasan
> #171/1 kasan/st_1_not_on_stack:OK
> #171/2 kasan/st_1_on_stack:OK
[ ... ]
> #171/29 kasan/simple_atomic_fetch:OK
> #171/30 kasan/simple_atomic_fetch:OK
The test output pasted here does not match the code being committed. It
shows two subtests both named kasan/simple_atomic_fetch (entries #171/29
and #171/30). With the code in this patch, the simple_atomic_fetch spec
has skip_on_stack_testing = false, so run_subtest_with_size_and_location()
applies the location suffix:
snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s%s",
test->prog_type,
test->skip_on_stack_testing ? "" :
on_stack ? "_on_stack" :
"_not_on_stack");
The actual names would be simple_atomic_fetch_not_on_stack and
simple_atomic_fetch_on_stack. The same duplication appears for ldx_patched
(#171/47 and #171/48). Regenerating the test output against this version
of the code would remove the duplicate names, which is important because
duplicate subtest names would make test_progs -t kasan/<name> unable to
select individual cases.
> #171/47 kasan/ldx_patched:OK
> #171/48 kasan/ldx_patched:OK
[ ... ]
> 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..2b424767a0f3
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
[ ... ]
> +#define SUBTEST_NAME_MAX_LEN 128
> +#define PROG_NAME_MAX_LEN 128
[ ... ]
> +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;
> +};
The prog_name buffer is declared with SUBTEST_NAME_MAX_LEN but is written
with PROG_NAME_MAX_LEN as the bound (at run_subtest_with_size_and_location
and run_blinding_subtest). Today both macros are 128, but they are
independent defines with no static assertion linking them. If
PROG_NAME_MAX_LEN were raised (or SUBTEST_NAME_MAX_LEN lowered), the two
writers would overflow into the next struct member. Could the buffer be
sized with the same macro used to bound the writes, or would
sizeof(ctx->prog_name) work at the call sites?
[ ... ]
> +static void run_subtest_with_size_and_location(struct test_ctx *ctx,
> + struct test_spec *test,
> + int access_size,
> + bool on_stack)
> +{
> + char subtest_name[SUBTEST_NAME_MAX_LEN];
> +
> + if (test->skip_multi_size_testing) {
> + snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s%s",
> + test->prog_type,
> + test->skip_on_stack_testing ? "" :
> + on_stack ? "_on_stack" :
> + "_not_on_stack");
> + } else {
> + snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s_%d_%s",
> + test->prog_type, access_size,
> + on_stack ? "on_stack" : "not_on_stack");
> + }
> +
> + snprintf(ctx->prog_name, PROG_NAME_MAX_LEN, "%s%s", test->prog_type,
> + test->skip_on_stack_testing ? "" :
> + on_stack ? "_on_stack" :
> + "_not_on_stack");
[ ... ]
> +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;
run_blinding_subtest() starts the subtest with test__start_subtest() but
then bails out on set_bpf_jit_harden() failure without recording the
result:
if (!test__start_subtest("st_blinded"))
return;
...
if (set_bpf_jit_harden(&bpf_jit_harden))
goto free_ctx;
There is no ASSERT_*() and no test__skip() on that path, so
test__end_subtest() sees error_cnt == 0 and skipped == false and takes the
success branch. The subtest reports OK when the blinded program was never
loaded or run. set_bpf_jit_harden() returns -1 when
open("/proc/sys/net/core/bpf_jit_harden", O_RDWR|O_NONBLOCK) fails or the
read/write fails, which happens in containers or VMs with /proc/sys
read-only, or without privileges to write that sysctl. The only other
caller of this helper, check_blindness() in prog_tests/bpf_insn_array.c,
treats the identical failure as an assertion. Should this do the same
(ASSERT_OK, or an explicit test__skip() with a reason)?
[ ... ]
> +void test_kasan(void)
> +{
> + struct kasan_write_val val;
> + struct test_spec *test;
> + struct test_ctx *ctx;
> + struct kasan *skel;
> + __u32 key = 0;
> + int i, ret;
> +
> + ctx = calloc(1, sizeof(struct test_ctx));
> + if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
> + return;
> +
> + if (!is_jit_enabled() || !get_kasan_jit_enabled() ||
> + !get_kasan_multi_shot_enabled()) {
> + test__skip();
> + goto end;
> + }
test_kasan() is registered as a parallel-capable test (the entry point is
test_kasan, not serial_test_kasan), yet through run_blinding_subtest() it
does a read-modify-write swap of the machine-global sysctl
net.core.bpf_jit_harden:
char bpf_jit_harden = '2';
...
if (set_bpf_jit_harden(&bpf_jit_harden)) /* writes '2' */
goto free_ctx;
...
set_bpf_jit_harden(&bpf_jit_harden); /* restores old value */
Under test_progs -j, other worker processes JIT-compile programs
concurrently, so the raised hardening level leaks into unrelated tests
while the window is open. Constant blinding rewrites the emitted JIT
image, which breaks any test that inspects jited output.
More concretely, this patch adds a second racer on that sysctl:
check_blindness() in prog_tests/bpf_insn_array.c performs the same
read-modify-write swap, and test_bpf_insn_array is also a plain test_.
Interleaving the two loses a restore permanently, e.g.
A reads 0, A writes 2
B reads 2, B writes 2
A restores 0
B restores 2 <- bpf_jit_harden left at 2 after test_progs exits
leaving JIT hardening enabled system-wide after the run, which then
perturbs every subsequent test invocation on that machine. The test also
scans the global /dev/kmsg ring, which other concurrently running tests
are free to write into. Renaming the entry point to serial_test_kasan()
would close both the cross-test interference and the lost-restore
interleaving.
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
> new file mode 100644
> index 000000000000..ea29197646b0
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/kasan.c
[ ... ]
> +SEC("tcx/ingress")
> +int stx_on_stack(struct __sk_buff *skb)
> +{
> + struct kasan_test_val val;
> +
> + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
> + switch (access_size) {
> + case 1:
> + val.data_1 = access_size;
> + break;
> + case 2:
> + val.data_2 = access_size;
> + break;
> + case 4:
> + val.data_4 = access_size;
> + break;
> + case 8:
> + val.data_8 = access_size;
> + break;
> + }
> + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
> + return 0;
> +}
In stx_on_stack and stx_not_on_stack, the value stored is access_size -
the same variable the enclosing switch selects on. Inside case 1: the
compiler knows access_size == 1, so the store degenerates to a
store-immediate.
Specifically, access_size is a plain global in .bss, the switch load and
the re-load in the case arm are separated by no call or store, so GVN CSEs
the second load into the switch condition, and GVN's switch handling
propagates the case value into the successor. The store operand becomes
the literal 1/2/4/8, and clang emits BPF_ST|BPF_MEM, not BPF_STX|BPF_MEM.
That is exactly the distinction the st_* vs stx_* program pair is meant to
cover: arch/x86/net/bpf_jit_comp.c gains two independent emit_kasan_check()
call sites in this series, one under case BPF_ST | BPF_MEM and one under
case BPF_STX | BPF_MEM. If the stx programs compile to BPF_ST they merely
re-run the st tests, and no program in this file covers BPF_STX|BPF_MEM
instrumentation. Nothing fails because the emitted BPF_ST is instrumented
with the same __asan_storeN, so the loss of coverage is invisible. Storing
a value the compiler cannot correlate with the switch selector would pin
the BPF_STX encoding.
[ ... ]
> +SEC("tcx/ingress")
> +int simple_atomic_fetch_on_stack(struct __sk_buff *skb)
> +{
> + struct kasan_test_val val;
> +
> + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_test_val));
> + __sync_fetch_and_or(&val.data_8, 8);
> + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_test_val));
> + return 0;
> +}
> +
> +SEC("tcx/ingress")
> +int simple_atomic_fetch_not_on_stack(struct __sk_buff *skb)
> +{
> + struct kasan_test_val *val;
> + __u32 key = 0;
> +
> + val = bpf_map_lookup_elem(&test_map, &key);
> + if (!val)
> + return 0;
> +
> + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
> + __sync_fetch_and_or(&val->data_8, 8);
> + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
> + return 0;
> +}
The simple_atomic_fetch_{on,not_on}_stack programs discard the result of
__sync_fetch_and_or(), so clang emits the noreturn atomic form
BPF_ATOMIC|BPF_OR, not BPF_OR|BPF_FETCH. The in-tree convention proves
this: progs/atomics.c deliberately keeps two variables per operation,
or64_result = __sync_fetch_and_or(&or64_value, ...) for the fetching
encoding and a separate __sync_fetch_and_or(&or_noreturn_value, ...) whose
only difference is that the result is dropped.
That matters because the fetching encoding is the only thing these two
subtests exist for. In arch/x86/net/bpf_jit_comp.c the fetch case is a
separate, delicate code path:
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) {
pprog = prog;
emit_mov_reg(&prog, true, BPF_REG_AX, BPF_REG_0);
...
ip += (prog - pprog);
}
The CMPXCHG-loop retry target is moved to sit after the injected KASAN
call, and ip is manually re-adjusted so that emit_call() computes a
correct relative displacement. None of that is exercised: with BPF_OR (no
FETCH) is_atomic_fetch is false, so the extra ip accounting and the
branch_target placement are never reached. The subtest still passes,
because a non-fetch 8-byte atomic OR is instrumented with __asan_store8
and produces the same Write of size 8 report the runner greps for, so the
gap is silent. Consuming the result (__sink(__sync_fetch_and_or(...)) as
the ldx programs in this same file already do) is what forces the
BPF_OR|BPF_FETCH encoding the test is named after.
[ ... ]
> +SEC("tcx/ingress")
> +int ldx_oob(struct __sk_buff *skb)
> +{
> + struct bpf_testmod_oob *val;
> + struct kasan_test_val volatile tmp;
> +
> + val = bpf_testmod_oob_alloc();
> + if (!val)
> + return 0;
> +
> + switch (access_size) {
> + case 1:
> + tmp.data_1 = (__u8)val->redzone_1;
> + break;
> + case 2:
> + tmp.data_2 = (__u16)val->redzone_2;
> + break;
> + case 4:
> + tmp.data_4 = (__u32)val->redzone_4;
> + break;
> + case 8:
> + tmp.data_8 = (__u64)val->redzone_8;
> + break;
> + }
> + bpf_testmod_oob_free(val);
> + return tmp.data_1;
> +}
ldx_oob() returns tmp.data_1, but tmp.data_1 is only ever assigned on the
access_size == 1 path. In struct kasan_test_val the fields sit at offsets
0, 2, 4 and 8, so the case 2/4/8 stores never touch offset 0. The runner
exercises exactly those sizes: the ldx_oob spec has
skip_on_stack_testing = true and no skip_multi_size_testing, so
run_subtest() calls run_subtest_with_size() for sizes 1, 2, 4 and 8
(subtests ldx_oob_2/4/8_not_on_stack in the commit log). Because tmp is
declared volatile, the compiler cannot substitute the stored value or elide
the load, so on those three subtests the program performs a real read of an
uninitialized BPF stack slot and returns whatever was left there.
Verification of this program depends on env->allow_uninit_stack. All four
switch arms are explored at load time, so the verifier sees a path where
the 8-byte slot at fp-16 is STACK_INVALID when it is read;
check_stack_read_fixed_off() rejects that unless allow_uninit_stack is set,
and bpf_allow_uninit_stack() requires CAP_PERFMON. The program therefore
only loads for a sufficiently privileged caller.
The intent is clearly just to keep the load from being optimized away, and
the sibling programs in this same file already use the established idiom
for that - __sink(val->data_1) etc. Using __sink() here would drop the
volatile stack struct, the uninitialized read and the CAP_PERFMON
dependency in one go.
[ ... ]
> 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..a2756bbfd529
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
A subsystem pattern flags this as potentially concerning: progs/kasan_harden.c
is a new 42-line BPF object file whose only program, st_blinded(), is a
verbatim copy of the access_size == 1 arm of st_not_on_stack() in
progs/kasan.c:
val = bpf_map_lookup_elem(&test_map, &key);
if (!val)
return 0;
bpf_kfunc_kasan_poison(val, sizeof(struct kasan_test_val));
val->data_1 = 0xAA;
bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_test_val));
The new file also re-declares struct kasan_test_val, the test_map ARRAY
map and both bpf_kfunc_kasan_poison/unpoison externs, all of which already
exist in progs/kasan.c. Nothing in the program text differs from the
existing test; the only new variable is on the runner side, where
run_blinding_subtest() raises the bpf_jit_harden sysctl to 2 before
loading it so the BPF_ST|BPF_MEM imm store gets rewritten by
bpf_jit_blind_insn(). That makes this a second copy of an existing case,
kept in sync by hand, that exists purely to be loaded under a different
sysctl value.
In BPF selftests one skeleton per progs/*.c is the normal structure, and
the blinding subtest genuinely needs a separately-loaded object:
bpf_jit_harden must be raised before load, and reloading the whole kasan
object (14 programs) under raised harden would slow the test and change
the JIT for every other program in it. A dedicated single-program object
is the cheapest way to get that. The duplicated struct/map/extern
declarations are also unavoidable without introducing a shared header,
since each progs/*.c is compiled independently. The concrete downside is
only maintenance: if struct kasan_test_val or the st_* body changes in
progs/kasan.c, this copy must be updated in lockstep or the two tests
quietly diverge. Could a comment pointing at st_not_on_stack help track
the relationship?
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32534828209
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-08-21 23:36 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 22:39 [PATCH bpf-next v7 0/9] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 1/9] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
2026-08-21 22:54 ` sashiko-bot
2026-08-21 23:24 ` bot+bpf-ci
2026-08-21 22:39 ` [PATCH bpf-next v7 2/9] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 3/9] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 4/9] bpf, x86: emit KASAN checks in x86 JITed programs Alexis Lothoré (eBPF Foundation)
2026-08-21 23:24 ` bot+bpf-ci
2026-08-21 23:33 ` sashiko-bot
2026-08-21 22:39 ` [PATCH bpf-next v7 5/9] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
2026-08-21 22:55 ` sashiko-bot
2026-08-21 22:39 ` [PATCH bpf-next v7 6/9] selftests/bpf: make cmdline_contains stricter Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 7/9] selftests/bpf: add helpers for KASAN in JIT testing Alexis Lothoré (eBPF Foundation)
2026-08-21 22:39 ` [PATCH bpf-next v7 8/9] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
2026-08-21 23:13 ` bot+bpf-ci
2026-08-21 22:39 ` [PATCH bpf-next v7 9/9] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
2026-08-21 23:36 ` bot+bpf-ci
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.