* [PATCH bpf-next v3 01/10] bpf: propagate original instruction offset when patching program
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:20 ` sashiko-bot
2026-07-01 10:02 ` [PATCH bpf-next v3 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
` (8 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
When the verifier patches an ebpf program with bpf_patch_insn_data, it
then calls adjust_insn_aux_data to make sure that insn_aux_data takes
into account the newly inserted patch. Some of the data offset is pretty
straightforward to deduce, it is for example the case for
indirect_target, as any patch affecting indirect calls will
systematically move the original instruction to the end of the new
patch.
In order to introduce KASAN support for eBPF JIT, we need to mark any
load/store instruction that access non-stack memory, but updating this
new marking after a patch is not as straightforward as for indirect
calls: the original BPF_ST/BPF_STX/BPF_LDX can be at the beginning, at
the end or somewhere in the middle of the new patch: we then need some
additional info to properly update this marking.
Add a new parameter to bpf_patch_insn_data and adjust_insn_aux_data to
convey the info about the new location in the patch of the original
instruction. This info does not always make sense depending on the
generated patch (eg, some bpf helpers being inlined by the verifier, and
so turned into multiple new instructions without any BPF_CALL remaning),
the parameter can then be set to -1. It will be used in next patches to
properly handle insn_aux_data adjustment for the new KASAN
instrumentation
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- new patch
---
include/linux/filter.h | 10 +++--
kernel/bpf/core.c | 2 +-
kernel/bpf/fixups.c | 105 +++++++++++++++++++++++++++++++++----------------
3 files changed, 79 insertions(+), 38 deletions(-)
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 67d337ede91b..912b91c65e0c 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1209,13 +1209,17 @@ struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off,
#ifdef CONFIG_BPF_SYSCALL
struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
- const struct bpf_insn *patch, u32 len);
+ const struct bpf_insn *patch, u32 len,
+ s32 insn_off_in_patch);
struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env);
void bpf_restore_insn_aux_data(struct bpf_verifier_env *env,
struct bpf_insn_aux_data *orig_insn_aux);
#else
-static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
- const struct bpf_insn *patch, u32 len)
+static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env,
+ u32 off,
+ const struct bpf_insn *patch,
+ u32 len,
+ s32 insn_off_in_patch)
{
return ERR_PTR(-ENOTSUPP);
}
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 649cce41e13f..20dd3dd17225 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1542,7 +1542,7 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp
continue;
if (env)
- tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten);
+ tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten, rewritten - 1);
else
tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten);
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 3cf2cc6e3ab6..1f340211b65c 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -157,7 +157,8 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
* [0, off) and [off, end) to new locations, so the patched range stays zero
*/
static void adjust_insn_aux_data(struct bpf_verifier_env *env,
- struct bpf_prog *new_prog, u32 off, u32 cnt)
+ struct bpf_prog *new_prog, u32 off, u32 cnt,
+ s32 insn_off_in_patch)
{
struct bpf_insn_aux_data *data = env->insn_aux_data;
struct bpf_insn *insn = new_prog->insnsi;
@@ -245,7 +246,8 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)
}
struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
- const struct bpf_insn *patch, u32 len)
+ const struct bpf_insn *patch, u32 len,
+ s32 insn_off_in_patch)
{
struct bpf_prog *new_prog;
struct bpf_insn_aux_data *new_data = NULL;
@@ -269,7 +271,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
env->insn_aux_data[off].orig_idx);
return NULL;
}
- adjust_insn_aux_data(env, new_prog, off, len);
+ adjust_insn_aux_data(env, new_prog, off, len, insn_off_in_patch);
adjust_subprog_starts(env, off, len);
adjust_insn_arrays(env, off, len);
adjust_poke_descs(new_prog, off, len);
@@ -668,7 +670,7 @@ int bpf_opt_subreg_zext_lo32_rnd_hi32(struct bpf_verifier_env *env,
patch = zext_patch;
patch_len = 2;
apply_patch_buffer:
- new_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len);
+ new_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len, 0);
if (!new_prog)
return -ENOMEM;
env->prog = new_prog;
@@ -695,6 +697,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
struct bpf_insn *insn_buf = env->insn_buf;
struct bpf_insn *insn;
u32 target_size, size_default, off;
+ s32 insn_off_in_patch = -1;
struct bpf_prog *new_prog;
enum bpf_access_type type;
bool is_narrower_load;
@@ -713,7 +716,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
insn_buf[cnt++] = BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_1,
-subprogs[0].stack_depth);
insn_buf[cnt++] = env->prog->insnsi[0];
- new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt, 1);
if (!new_prog)
return -ENOMEM;
env->prog = new_prog;
@@ -736,7 +739,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
verifier_bug(env, "prologue is too long");
return -EFAULT;
} else if (cnt) {
- new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt,
+ cnt - 1);
if (!new_prog)
return -ENOMEM;
@@ -768,7 +772,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
*patch++ = BPF_ST_NOSPEC();
*patch++ = *insn;
cnt = patch - insn_buf;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, cnt - 1);
if (!new_prog)
return -ENOMEM;
@@ -841,7 +846,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
*patch++ = *insn;
*patch++ = BPF_ST_NOSPEC();
cnt = patch - insn_buf;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, 0);
if (!new_prog)
return -ENOMEM;
@@ -856,16 +862,20 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
if (!ops->convert_ctx_access)
continue;
convert_ctx_access = ops->convert_ctx_access;
+ insn_off_in_patch = 0;
break;
case PTR_TO_SOCKET:
case PTR_TO_SOCK_COMMON:
convert_ctx_access = bpf_sock_convert_ctx_access;
+ insn_off_in_patch = 0;
break;
case PTR_TO_TCP_SOCK:
convert_ctx_access = bpf_tcp_sock_convert_ctx_access;
+ insn_off_in_patch = 0;
break;
case PTR_TO_XDP_SOCK:
convert_ctx_access = bpf_xdp_sock_convert_ctx_access;
+ insn_off_in_patch = 0;
break;
case PTR_TO_BTF_ID:
case PTR_TO_BTF_ID | PTR_UNTRUSTED:
@@ -971,7 +981,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
size * 8, 0);
patch_insn_buf:
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt,
+ insn_off_in_patch);
if (!new_prog)
return -ENOMEM;
@@ -1464,7 +1475,7 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat
* ones for the hidden subprog. Hence all of the adjustment operations
* in bpf_patch_insn_data are no-ops.
*/
- prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len);
+ prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len, -1);
if (!prog)
return -ENOMEM;
env->prog = prog;
@@ -1549,7 +1560,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
cnt = patch - insn_buf;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, 0);
if (!new_prog)
return -ENOMEM;
@@ -1569,6 +1581,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
bool is_sdiv = isdiv && insn->off == 1;
bool is_smod = !isdiv && insn->off == 1;
struct bpf_insn *patch = insn_buf;
+ s32 insn_off_in_patch;
if (is_sdiv) {
/* [R,W]x sdiv 0 -> 0
@@ -1595,6 +1608,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
*patch++ = *insn;
cnt = patch - insn_buf;
+ insn_off_in_patch = cnt - 1;
} else if (is_smod) {
/* [R,W]x mod 0 -> [R,W]x */
/* [R,W]x mod -1 -> 0 */
@@ -1611,6 +1625,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
*patch++ = BPF_MOV32_IMM(insn->dst_reg, 0);
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
*patch++ = *insn;
+ insn_off_in_patch = patch - insn_buf - 1;
if (!is64) {
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
@@ -1626,12 +1641,14 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
*patch++ = *insn;
cnt = patch - insn_buf;
+ insn_off_in_patch = cnt - 1;
} else {
/* [R,W]x mod 0 -> [R,W]x */
*patch++ = BPF_RAW_INSN((is64 ? BPF_JMP : BPF_JMP32) |
BPF_JEQ | BPF_K, insn->src_reg,
0, 1 + (is64 ? 0 : 1), 0);
*patch++ = *insn;
+ insn_off_in_patch = patch - insn_buf - 1;
if (!is64) {
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
@@ -1640,7 +1657,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
cnt = patch - insn_buf;
}
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, insn_off_in_patch);
if (!new_prog)
return -ENOMEM;
@@ -1656,6 +1674,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
BPF_MODE(insn->code) == BPF_PROBE_MEMSX)) {
struct bpf_insn *patch = insn_buf;
u64 uaddress_limit = bpf_arch_uaddress_limit();
+ s32 insn_off_in_patch;
if (!uaddress_limit)
goto next_insn;
@@ -1666,11 +1685,13 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
*patch++ = BPF_ALU64_IMM(BPF_RSH, BPF_REG_AX, 32);
*patch++ = BPF_JMP_IMM(BPF_JLE, BPF_REG_AX, uaddress_limit >> 32, 2);
*patch++ = *insn;
+ insn_off_in_patch = patch - insn_buf - 1;
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
*patch++ = BPF_MOV64_IMM(insn->dst_reg, 0);
cnt = patch - insn_buf;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, insn_off_in_patch);
if (!new_prog)
return -ENOMEM;
@@ -1690,7 +1711,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
return -EFAULT;
}
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, 0);
if (!new_prog)
return -ENOMEM;
@@ -1743,7 +1765,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
*patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1);
cnt = patch - insn_buf;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -1788,7 +1811,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[6] = BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_AX, stack_off_cnt);
cnt = 7;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, 3);
if (!new_prog)
return -ENOMEM;
@@ -1809,7 +1833,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[3] = BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_AX, stack_off);
cnt = 4;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, 2);
if (!new_prog)
return -ENOMEM;
@@ -1830,7 +1855,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
if (cnt == 0)
goto next_insn;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, cnt - 1);
if (!new_prog)
return -ENOMEM;
@@ -1915,7 +1941,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
map)->index_mask);
insn_buf[2] = *insn;
cnt = 3;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, cnt - 1);
if (!new_prog)
return -ENOMEM;
@@ -1948,7 +1975,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[2] = *insn;
cnt = 3;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, cnt - 1);
if (!new_prog)
return -ENOMEM;
@@ -1967,7 +1995,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[1] = *insn;
cnt = 2;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, cnt - 1);
if (!new_prog)
return -ENOMEM;
@@ -2007,8 +2036,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
return -EFAULT;
}
- new_prog = bpf_patch_insn_data(env, i + delta,
- insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(
+ env, i + delta, insn_buf, cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2091,7 +2120,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
cnt = 3;
new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
- cnt);
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2119,7 +2148,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[0] = BPF_ALU32_REG(BPF_XOR, BPF_REG_0, BPF_REG_0);
cnt = 1;
#endif
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2137,7 +2167,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[2] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_0, 0);
cnt = 3;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2171,7 +2202,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[cnt++] = BPF_JMP_A(1);
insn_buf[cnt++] = BPF_MOV64_IMM(BPF_REG_0, -EINVAL);
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2203,7 +2235,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
cnt = 1;
}
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2229,7 +2262,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
cnt = 2;
}
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2245,7 +2279,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
/* Load IP address from ctx - 16 */
insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -16);
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ 1, -1);
if (!new_prog)
return -ENOMEM;
@@ -2300,7 +2335,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[10] = BPF_MOV64_IMM(BPF_REG_0, -ENOENT);
cnt = 11;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2318,7 +2354,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
insn_buf[1] = BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_0, 0);
cnt = 2;
- new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf,
+ cnt, -1);
if (!new_prog)
return -ENOMEM;
@@ -2386,7 +2423,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
/* Copy first actual insn to preserve it */
insn_buf[cnt++] = env->prog->insnsi[subprog_start];
- new_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt, cnt - 1);
if (!new_prog)
return -ENOMEM;
env->prog = prog = new_prog;
@@ -2485,7 +2522,7 @@ static struct bpf_prog *inline_bpf_loop(struct bpf_verifier_env *env,
insn_buf[cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_10, r8_offset);
*total_cnt = cnt;
- new_prog = bpf_patch_insn_data(env, position, insn_buf, cnt);
+ new_prog = bpf_patch_insn_data(env, position, insn_buf, cnt, -1);
if (!new_prog)
return new_prog;
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 01/10] bpf: propagate original instruction offset when patching program
2026-07-01 10:02 ` [PATCH bpf-next v3 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:20 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:20 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] State leakage of `insn_off_in_patch` across loop iterations in `bpf_convert_ctx_accesses`
--
commit 37bd4077be7be826caa9dd6664d363fc86f4d0e5
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf: propagate original instruction offset when patching program
This commit adds a new parameter to bpf_patch_insn_data and
adjust_insn_aux_data to convey the location of the original instruction
within the newly generated patch. This metadata maps the original
instruction to the patch for future KASAN instrumentation.
> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
> index 3cf2cc6e3ab66..1f340211b65c2 100644
> --- a/kernel/bpf/fixups.c
> +++ b/kernel/bpf/fixups.c
> @@ -695,6 +697,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
> struct bpf_insn *insn_buf = env->insn_buf;
> struct bpf_insn *insn;
> u32 target_size, size_default, off;
> + s32 insn_off_in_patch = -1;
> struct bpf_prog *new_prog;
> enum bpf_access_type type;
> bool is_narrower_load;
[ ... ]
> @@ -856,16 +862,20 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
> if (!ops->convert_ctx_access)
> continue;
> convert_ctx_access = ops->convert_ctx_access;
> + insn_off_in_patch = 0;
> break;
[ ... ]
> @@ -971,7 +981,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
> size * 8, 0);
>
> patch_insn_buf:
> - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
> + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt,
> + insn_off_in_patch);
[Severity: Medium]
Does this leak the insn_off_in_patch state across loop iterations?
Because insn_off_in_patch is initialized outside the main instruction
processing loop, if a context access like PTR_TO_CTX sets it to 0, it
is not reset back to -1 for subsequent iterations.
If an epilogue-generating instruction like BPF_EXIT follows later and takes
the goto patch_insn_buf path:
bpf_convert_ctx_accesses() {
...
} else if (insn->code == (BPF_JMP | BPF_EXIT) &&
epilogue_cnt &&
i + delta < subprogs[1].start) {
...
goto patch_insn_buf;
Won't this erroneously reuse the stale 0 value instead of -1? This could
cause adjust_insn_aux_data() to incorrectly map metadata for the epilogue.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v3 02/10] bpf: mark instructions accessing program stack
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:19 ` sashiko-bot
2026-07-01 10:02 ` [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
` (7 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
In order to prepare to emit KASAN checks in JITed programs, JIT
compilers need to be aware about whether some load/store instructions
are targeting the bpf program stack, as those should not be monitored
(we already have guard pages for that, and it is difficult anyway to
correctly monitor any kind of data passed on stack).
To support this need, make the BPF verifier mark the instructions
depending on whether they could access or not memory other than stack.
As different states in the verifier could lead to different memory types
for the same access, just marking an instruction as accessing stack only
is not enough (it could be some other memory type in another verifier
state), so the algorithm rather sets by default any load/store
instruction as stack only, and if _any_ state leads to any memory access
type other than PTR_TO_STACK, it overrides this setting. It also takes
care about shifting back the instruction marking in adjust_insn_aux_data
if the verifier patches instructions. However, if the verifier generates
new BPF_ST/BPF_STX/BPF_LDX while patching some instructions, those new
ones are systematically marked as non-stack-accessing: this may
over-instrument a few memory accessing instructions, but it allows
making sure that we will not miss accidentally any.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in 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 | 23 +++++++++++++++++++++++
kernel/bpf/verifier.c | 9 +++++++++
3 files changed, 34 insertions(+)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 76b8b7627a10..868101ef5002 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -723,6 +723,8 @@ struct bpf_insn_aux_data {
u16 const_reg_map_mask;
u16 const_reg_subprog_mask;
u32 const_reg_vals[10];
+ /* instruction can access non-stack memory */
+ bool non_stack_access;
};
#define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 1f340211b65c..0b58a02a7179 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -152,6 +152,17 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
}
#endif
+static bool is_mem_insn(struct bpf_insn *insn)
+{
+ if (BPF_MODE(insn->code) != BPF_MEM &&
+ BPF_MODE(insn->code) != BPF_MEMSX)
+ return false;
+
+ return BPF_CLASS(insn->code) == BPF_ST ||
+ BPF_CLASS(insn->code) == BPF_STX ||
+ BPF_CLASS(insn->code) == BPF_LDX;
+}
+
/* 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
@@ -183,7 +194,19 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
/* Expand insni[off]'s seen count to the patched range. */
data[i].seen = old_seen;
data[i].zext_dst = insn_has_def32(insn + i);
+ if (i == off + insn_off_in_patch) {
+ data[i].non_stack_access = data[off + cnt - 1].non_stack_access;
+ data[off + cnt - 1].non_stack_access = false;
+ } else if (is_mem_insn(insn + i)) {
+ data[i].non_stack_access = true;
+ }
}
+ /*
+ * Last slot instruction could be a newly generated
+ * BPF_ST/BPF_LDX/BPF_STX
+ */
+ if (is_mem_insn(insn + off + cnt - 1) && insn_off_in_patch != cnt - 1)
+ data[off + cnt - 1].non_stack_access = true;
/*
* The indirect_target flag of the original instruction was moved to the last of the
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 25aea4271cd0..e24545dcb4f9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3144,6 +3144,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx)
env->insn_aux_data[idx].indirect_target = true;
}
+static void mark_non_stack_access(struct bpf_verifier_env *env, int idx)
+{
+ env->insn_aux_data[idx].non_stack_access = true;
+}
+
#define LR_FRAMENO_BITS 4
#define LR_SPI_BITS 6
#define LR_ENTRY_BITS (LR_SPI_BITS + LR_FRAMENO_BITS + 1)
@@ -6355,6 +6360,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
else
coerce_reg_to_size_sx(®s[value_regno], size);
}
+
+ if (!err && reg->type != PTR_TO_STACK)
+ mark_non_stack_access(env, insn_idx);
+
return err;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 02/10] bpf: mark instructions accessing program stack
2026-07-01 10:02 ` [PATCH bpf-next v3 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:19 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:19 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `is_mem_insn` helper incorrectly excludes `BPF_ATOMIC`, causing inline atomic operations to bypass KASAN checks.
- [Medium] Early return in `adjust_insn_aux_data` bypasses KASAN marking for 1-to-1 patched memory instructions.
--
commit 90d3117a6d0883383f557efe547dcd0c0e5bc53a
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf: mark instructions accessing program stack
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. The verifier marks
instructions depending on whether they access memory other than the stack,
taking care to shift instruction marking when the verifier patches
instructions.
> diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
> index 1f340211b65c2..0b58a02a71795 100644
> --- a/kernel/bpf/fixups.c
> +++ b/kernel/bpf/fixups.c
> @@ -152,6 +152,17 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env,
> }
> #endif
>
> +static bool is_mem_insn(struct bpf_insn *insn)
> +{
> + if (BPF_MODE(insn->code) != BPF_MEM &&
> + BPF_MODE(insn->code) != BPF_MEMSX)
> + return false;
[Severity: High]
Does this logic inadvertently exclude BPF_ATOMIC operations from KASAN bounds
checking?
When bpf_kptr_xchg is inlined into an atomic exchange (via fixup_bpf_calls()),
the verifier replaces the CALL with a BPF_ATOMIC instruction. Because this
function specifically requires BPF_MEM or BPF_MEMSX, it returns false for
BPF_ATOMIC.
Consequently, the newly generated atomic operation inherits
non_stack_access = false from the original CALL instruction. The JIT will then
assume the map value access is a stack access and skip KASAN instrumentation.
Could this be updated to include BPF_ATOMIC?
> +
> + return BPF_CLASS(insn->code) == BPF_ST ||
> + BPF_CLASS(insn->code) == BPF_STX ||
> + BPF_CLASS(insn->code) == BPF_LDX;
> +}
[ ... ]
> @@ -183,7 +194,19 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
> /* Expand insni[off]'s seen count to the patched range. */
> data[i].seen = old_seen;
> data[i].zext_dst = insn_has_def32(insn + i);
> + if (i == off + insn_off_in_patch) {
> + data[i].non_stack_access = data[off + cnt - 1].non_stack_access;
> + data[off + cnt - 1].non_stack_access = false;
> + } else if (is_mem_insn(insn + i)) {
> + data[i].non_stack_access = true;
> + }
> }
> + /*
> + * Last slot instruction could be a newly generated
> + * BPF_ST/BPF_LDX/BPF_STX
> + */
> + if (is_mem_insn(insn + off + cnt - 1) && insn_off_in_patch != cnt - 1)
> + data[off + cnt - 1].non_stack_access = true;
[Severity: Medium]
What happens when a non-memory instruction is replaced with a single memory
instruction (i.e., cnt == 1)?
Looking at adjust_insn_aux_data(), there is an early return if cnt == 1:
data[off].zext_dst = insn_has_def32(insn + off + cnt - 1);
if (cnt == 1)
return;
Since this new logic is placed after the early return, if a non-memory
instruction (like a helper call) is replaced 1-to-1 with a memory instruction
(e.g., BPF_LDX_MEM for bpf_get_func_ip), it skips evaluating is_mem_insn().
The newly generated instruction would then blindly inherit
non_stack_access = false, causing the JIT to skip KASAN bounds checks.
Is it possible to move the 1-to-1 handling before the early return, or
explicitly check it there?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=2
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 01/10] bpf: propagate original instruction offset when patching program Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 02/10] bpf: mark instructions accessing program stack Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:12 ` sashiko-bot
` (2 more replies)
2026-07-01 10:02 ` [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
` (6 subsequent siblings)
9 siblings, 3 replies; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add a new Kconfig option CONFIG_BPF_JIT_KASAN that automatically enables
generic KASAN (Kernel Address SANitizer) memory access checks for
JIT-compiled BPF programs as well, when both KASAN_GENERIC and JIT
compiler are enabled. This new Kconfig is not a user selectable one: it
is either automatically enabled if KASAN is enabled on a compatible
platform, or disabled. 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.
As KASAN instrumentation for eBPF program will depend on the info that
can be accessed during each instruction verification, there may be
instructions that will be instrumented even if they don't really need to
(eg: global subprograms that access caller stack memory passed as
argument). To make sure that those additional checks do not trigger any
crash, make sure that VMAP_STACK is enabled so that programs stack has
shadow memory allocated.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v2:
- add dependency on kasan for vmalloc and vmalloc'ed stack
---
kernel/bpf/Kconfig | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
index eb3de35734f0..a8e004f88b92 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,9 @@ 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
+ default y if BPF_JIT && KASAN_GENERIC && KASAN_VMALLOC && VMAP_STACK
+
endmenu # "BPF subsystem"
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:12 ` sashiko-bot
2026-07-01 10:44 ` bot+bpf-ci
2026-07-01 13:43 ` Andrey Konovalov
2 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:12 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] BPF_JIT_KASAN needlessly restricts its availability by unconditionally requiring VMAP_STACK.
--
commit 23714982e1ca6e827ba19b9f4c74b703b4ad2090
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
This commit introduces CONFIG_BPF_JIT_KASAN to enable generic KASAN memory
access checks for JIT-compiled BPF programs. The JIT compiler emits shadow
memory checks before memory accesses to detect bugs like use-after-free or
out-of-bounds at runtime. The feature is gated behind HAVE_EBPF_JIT_KASAN.
> As KASAN instrumentation for eBPF program will depend on the info that
> can be accessed during each instruction verification, there may be
> instructions that will be instrumented even if they don't really need to
> (eg: global subprograms that access caller stack memory passed as
> argument). To make sure that those additional checks do not trigger any
> crash, make sure that VMAP_STACK is enabled so that programs stack has
> shadow memory allocated.
[Severity: Medium]
Does generic KASAN already map shadow memory for the entire linear mapping?
If VMAP_STACK is disabled, the kernel stack resides in the linear mapping,
which should already have shadow memory allocated. Could this justification
be slightly misunderstanding the default generic KASAN shadow memory coverage?
> diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
> --- a/kernel/bpf/Kconfig
> +++ b/kernel/bpf/Kconfig
[ ... ]
> @@ -101,4 +105,9 @@ 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
> + default y if BPF_JIT && KASAN_GENERIC && KASAN_VMALLOC && VMAP_STACK
[Severity: Medium]
Is it necessary to unconditionally require VMAP_STACK for BPF_JIT_KASAN?
Following up on the commit message note, since generic KASAN covers the
linear mapping where non-VMAP stacks reside, this condition might needlessly
disable a useful security and debugging feature on kernel configurations
where VMAP_STACK happens to be turned off.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=3
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-01 10:12 ` sashiko-bot
@ 2026-07-01 10:44 ` bot+bpf-ci
2026-07-01 13:43 ` Andrey Konovalov
2 siblings, 0 replies; 22+ messages in thread
From: bot+bpf-ci @ 2026-07-01 10:44 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
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: 1956 bytes --]
> 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_GENERIC and JIT
> compiler are enabled. This new Kconfig is not a user selectable one: it
> is either automatically enabled if KASAN is enabled on a compatible
> platform, or disabled. 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.
>
> As KASAN instrumentation for eBPF program will depend on the info that
> can be accessed during each instruction verification, there may be
> instructions that will be instrumented even if they don't really need to
> (eg: global subprograms that access caller stack memory passed as
> argument). To make sure that those additional checks do not trigger any
> crash, make sure that VMAP_STACK is enabled so that programs stack has
> shadow memory allocated.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
This isn't a bug, but the changelog describes the enabling condition as
"KASAN_GENERIC and JIT compiler", while the added default line also
requires KASAN_VMALLOC:
config BPF_JIT_KASAN
bool
depends on HAVE_EBPF_JIT_KASAN
default y if BPF_JIT && KASAN_GENERIC && KASAN_VMALLOC && VMAP_STACK
Could the changelog state the full set of conditions that enable
BPF_JIT_KASAN (including KASAN_VMALLOC) so the described behavior matches
the "default y if" line?
---
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/28510424881
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-01 10:12 ` sashiko-bot
2026-07-01 10:44 ` bot+bpf-ci
@ 2026-07-01 13:43 ` Andrey Konovalov
2 siblings, 0 replies; 22+ messages in thread
From: Andrey Konovalov @ 2026-07-01 13:43 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation)
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, ebpf, Bastien Curutchet,
Thomas Petazzoni, bpf, linux-kernel, linux-kselftest
On Wed, Jul 1, 2026 at 12:03 PM Alexis Lothoré (eBPF Foundation)
<alexis.lothore@bootlin.com> wrote:
>
> Add a new Kconfig option CONFIG_BPF_JIT_KASAN that automatically enables
> generic KASAN (Kernel Address SANitizer) memory access checks for
> JIT-compiled BPF programs as well, when both KASAN_GENERIC and JIT
> compiler are enabled. This new Kconfig is not a user selectable one: it
> is either automatically enabled if KASAN is enabled on a compatible
> platform, or disabled. 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.
>
> As KASAN instrumentation for eBPF program will depend on the info that
> can be accessed during each instruction verification, there may be
> instructions that will be instrumented even if they don't really need to
> (eg: global subprograms that access caller stack memory passed as
> argument). To make sure that those additional checks do not trigger any
> crash, make sure that VMAP_STACK is enabled so that programs stack has
> shadow memory allocated.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> ---
> Changes in v2:
> - add dependency on kasan for vmalloc and vmalloc'ed stack
> ---
> kernel/bpf/Kconfig | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig
> index eb3de35734f0..a8e004f88b92 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,9 @@ 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
> + default y if BPF_JIT && KASAN_GENERIC && KASAN_VMALLOC && VMAP_STACK
I think it make sense to put KASAN_GENERIC into the "depends on"
section, as __asan_load/storeX() are only defined for the Generic
mode. SW_TAGS mode is expected to be soon implemented for x86-64, and
that mode uses different checking function names.
Would also be great to add description for this config option.
Thank you!
> +
> endmenu # "BPF subsystem"
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (2 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 03/10] bpf: add BPF_JIT_KASAN for KASAN instrumentation of JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:16 ` sashiko-bot
2026-07-01 10:44 ` bot+bpf-ci
2026-07-01 10:02 ` [PATCH bpf-next v3 05/10] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
` (5 subsequent siblings)
9 siblings, 2 replies; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add the emit_kasan_check() function that emits KASAN shadow memory
checks before memory accesses in JIT-compiled BPF programs. The
implementation relies on the existing __asan_{load,store}X functions
from KASAN subsystem. The helper:
- ensures that the kasan instrumention is actually needed: if the
instruction being processed accesses the program stack, we skip the
instrumentation, as those accesses are already protected with page
guards
- saves registers. This includes caller-saved registers, but also
temporary registers, as those were possibly used by the
affected program. Theoretically, r10 and r11 should be saved as well,
but the number of called function and their scope being limited, they
are skipped for the sake of reducing the overhead
- computes the accessed address and stores it in %rdi
- calls the relevant function, depending on the instruction being a load
or a store, and the size of the access.
- restores registers
The special care needed when inserting this instrumentation comes at the
cost of a non negligeable increase in JITed code size. For example, a
bare
mov 0x0(%si),rbx # Load in rbx content at address stored in rsi
becomes
push %rax
push %rcx
push %rdx
push %rsi
push %rdi
push %r8
push %r9
mov %rsi,%rdi
call 0xffffffff81da0a60 <__asan_load8>
pop %r9
pop %r8
pop %rdi
pop %rsi
pop %rdx
pop %rcx
pop %rax
mov 0x0(%rsi),rbx
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- skip kasan instrumentation if there is no verifier env (cBPF)
- move helper up in the file
Changes in v2:
- move asan functions declaration directly into jit compiler, and guard
them with IS_ENABLED
- remove faulty stack alignment, no arg is passed to kasan funcs on the
stack anyway
- make sure to emit call depth accounting code
- do not save unneeded registers
- update helper signature to let caller configure some values (eg:
is_write)
---
arch/x86/net/bpf_jit_comp.c | 95 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 054e043ffcd2..68c5f9f94e5e 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -21,6 +21,17 @@
#include <asm/unwind.h>
#include <asm/cfi.h>
+#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)
+void __asan_load1(void *p);
+void __asan_store1(void *p);
+void __asan_load2(void *p);
+void __asan_store2(void *p);
+void __asan_load4(void *p);
+void __asan_store4(void *p);
+void __asan_load8(void *p);
+void __asan_store8(void *p);
+#endif
+
static bool all_callee_regs_used[4] = {true, true, true, true};
static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len)
@@ -1110,6 +1121,90 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
*pprog = prog;
}
+static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
+ u32 addr_reg, struct bpf_insn *insn, u8 *ip,
+ bool is_write, bool accesses_stack_only)
+{
+#ifdef CONFIG_BPF_JIT_KASAN
+ u32 bpf_size = BPF_SIZE(insn->code);
+ s32 off = insn->off;
+ u8 *prog = *pprog;
+ void *kasan_func;
+
+ if (!env)
+ return 0;
+
+ if (accesses_stack_only)
+ return 0;
+
+ /* Derive KASAN check function from access type and size */
+ switch (bpf_size) {
+ case BPF_B:
+ kasan_func = is_write ? __asan_store1 : __asan_load1;
+ break;
+ case BPF_H:
+ kasan_func = is_write ? __asan_store2 : __asan_load2;
+ break;
+ case BPF_W:
+ kasan_func = is_write ? __asan_store4 : __asan_load4;
+ break;
+ case BPF_DW:
+ kasan_func = is_write ? __asan_store8 : __asan_load8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Save rax */
+ EMIT1(0x50);
+ /* Save rcx */
+ EMIT1(0x51);
+ /* Save rdx */
+ EMIT1(0x52);
+ /* Save rsi */
+ EMIT1(0x56);
+ /* Save rdi */
+ EMIT1(0x57);
+ /* Save r8 */
+ EMIT2(0x41, 0x50);
+ /* Save r9 */
+ EMIT2(0x41, 0x51);
+
+ /* mov rdi, addr_reg */
+ EMIT_mov(BPF_REG_1, addr_reg);
+
+ /* add rdi, off (if offset is non-zero) */
+ if (off) {
+ if (is_imm8(off)) {
+ /* add rdi, imm8 */
+ EMIT4(0x48, 0x83, 0xC7, (u8)off);
+ } else {
+ /* add rdi, imm32 */
+ EMIT3_off32(0x48, 0x81, 0xC7, off);
+ }
+ }
+
+ /* Adjust ip to account for the instrumentation generated so far */
+ ip += (prog - *pprog);
+ /* We emit a call, so update call depth counting */
+ ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip);
+ /* call kasan_func */
+ if (emit_call(&prog, kasan_func, ip))
+ return -ERANGE;
+
+ EMIT2(0x41, 0x59);
+ EMIT2(0x41, 0x58);
+ EMIT1(0x5F);
+ EMIT1(0x5E);
+ EMIT1(0x5A);
+ EMIT1(0x59);
+ EMIT1(0x58);
+
+ *pprog = prog;
+#endif /* CONFIG_BPF_JIT_KASAN */
+ return 0;
+}
+
/* LDX: dst_reg = *(u8*)(src_reg + off) */
static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
{
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:16 ` sashiko-bot
2026-07-01 10:44 ` bot+bpf-ci
1 sibling, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:16 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] emit_kasan_check() fails to save BPF_REG_AX (R10), leading to memory corruption when combined with constant blinding.
--
commit 4c7e1a72bec793ca4ed19fd754408dd627715a3c
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf, x86: add helper to emit kasan checks in x86 JITed programs
This patch adds emit_kasan_check() to emit KASAN shadow memory checks before
memory accesses in x86 JIT-compiled BPF programs. The helper saves temporary
registers before calling the KASAN functions, but deliberately omits r10 and
r11 to reduce overhead.
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index 054e043ffcd22..68c5f9f94e5e2 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -1110,6 +1121,90 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
> *pprog = prog;
> }
>
> +static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
> + u32 addr_reg, struct bpf_insn *insn, u8 *ip,
> + bool is_write, bool accesses_stack_only)
> +{
[ ... ]
> + /* Save r8 */
> + EMIT2(0x41, 0x50);
> + /* Save r9 */
> + EMIT2(0x41, 0x51);
> +
[Severity: Critical]
Can this lead to data corruption when BPF constant blinding is enabled?
The commit message notes that r10 and r11 are skipped here to reduce
overhead. However, when constant blinding is enabled, bpf_jit_blind_insn()
in kernel/bpf/core.c translates BPF_ST_MEM into a sequence that uses
BPF_REG_AX (which maps to x86's r10) as the source register:
kernel/bpf/core.c:bpf_jit_blind_insn() {
...
case BPF_ST | BPF_MEM | BPF_DW:
*to++ = BPF_ALU64_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm);
*to++ = BPF_ALU64_IMM(BPF_XOR, BPF_REG_AX, imm_rnd);
*to++ = BPF_STX_MEM(from->code, from->dst_reg, BPF_REG_AX,
from->off);
...
}
When do_jit() processes this BPF_STX_MEM, it first calls emit_kasan_check(),
which invokes the C function __asan_storeX. Since __asan_storeX is a standard
C function, it is permitted by the System V AMD64 ABI to clobber caller-saved
registers like r10.
Upon returning from the KASAN check, r10 might contain garbage. Then do_jit()
calls emit_stx() which will use this clobbered r10 as the data to be written,
potentially writing corrupted data to memory.
Should BPF_REG_AX (r10) be explicitly saved and restored here to prevent this?
> + /* mov rdi, addr_reg */
> + EMIT_mov(BPF_REG_1, addr_reg);
> +
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=4
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
2026-07-01 10:16 ` sashiko-bot
@ 2026-07-01 10:44 ` bot+bpf-ci
1 sibling, 0 replies; 22+ messages in thread
From: bot+bpf-ci @ 2026-07-01 10:44 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
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: 1307 bytes --]
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index 054e043ffcd2..68c5f9f94e5e 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -1110,6 +1121,90 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64)
[ ... ]
> + /* 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);
[ ... ]
> + EMIT2(0x41, 0x59);
> + EMIT2(0x41, 0x58);
> + EMIT1(0x5F);
> + EMIT1(0x5E);
> + EMIT1(0x5A);
> + EMIT1(0x59);
> + EMIT1(0x58);
This isn't a bug, but the save block annotates each push with its target
register (/* Save rax */, /* Save rcx */, ... /* Save r9 */), while the
matching restore block emits the seven pops with no comments at all.
Would adding the mirrored comments here (/* Restore r9 */, /* Restore r8 */,
... /* Restore rax */) make the push/pop symmetry easier to check at a
glance?
---
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/28510424881
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v3 05/10] bpf, x86: refactor BPF_ST management in do_jit
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (3 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 04/10] bpf, x86: add helper to emit kasan checks in x86 " Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
` (4 subsequent siblings)
9 siblings, 0 replies; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
In order to prepare for KASAN checks insertion before every
memory-related load or store, group all BPF_ST instructions that indeed
access memory in a single helper to allow instrumenting those in one
call, rather than having to instrument all cases individually.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- new patch
---
arch/x86/net/bpf_jit_comp.c | 99 +++++++++++++++++++++++++++------------------
1 file changed, 60 insertions(+), 39 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 68c5f9f94e5e..b70cecfec179 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1410,6 +1410,64 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int
*pprog = prog;
}
+static void emit_st(u8 **pprog, struct bpf_insn *insn, int dst_reg,
+ s32 outgoing_arg_base, u16 outgoing_rsp)
+{
+ s32 imm32 = insn->imm;
+ u8 *prog = *pprog;
+ s32 insn_off;
+
+ switch (BPF_SIZE(insn->code)) {
+ case BPF_B:
+ if (is_ereg(dst_reg))
+ EMIT2(0x41, 0xC6);
+ else
+ EMIT1(0xC6);
+ break;
+ case BPF_H:
+ if (is_ereg(dst_reg))
+ EMIT3(0x66, 0x41, 0xC7);
+ else
+ EMIT2(0x66, 0xC7);
+ break;
+ case BPF_W:
+ if (is_ereg(dst_reg))
+ EMIT2(0x41, 0xC7);
+ else
+ EMIT1(0xC7);
+ break;
+ case BPF_DW:
+ if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
+ /* Arg 6: store immediate in r9 register */
+ emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31,
+ (u32)imm32);
+ *pprog = prog;
+ return;
+ }
+ EMIT2(add_1mod(0x48, dst_reg), 0xC7);
+ break;
+ }
+
+ insn_off = insn->off;
+ if (dst_reg == BPF_REG_PARAMS) {
+ /*
+ * Args 7+: reverse BPF negative offsets to
+ * x86 positive rsp offsets.
+ * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
+ */
+ insn_off = outgoing_arg_base - outgoing_rsp -
+ insn_off - 16;
+ dst_reg = BPF_REG_FP;
+ }
+ if (is_imm8(insn_off))
+ EMIT2(add_1reg(0x40, dst_reg), insn_off);
+ else
+ EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
+
+ EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+ *pprog = prog;
+}
+
static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm)
{
emit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm);
@@ -2302,49 +2360,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
EMIT_LFENCE();
break;
- /* ST: *(u8*)(dst_reg + off) = imm */
case BPF_ST | BPF_MEM | BPF_B:
- if (is_ereg(dst_reg))
- EMIT2(0x41, 0xC6);
- else
- EMIT1(0xC6);
- goto st;
case BPF_ST | BPF_MEM | BPF_H:
- if (is_ereg(dst_reg))
- EMIT3(0x66, 0x41, 0xC7);
- else
- EMIT2(0x66, 0xC7);
- goto st;
case BPF_ST | BPF_MEM | BPF_W:
- if (is_ereg(dst_reg))
- EMIT2(0x41, 0xC7);
- else
- EMIT1(0xC7);
- goto st;
case BPF_ST | BPF_MEM | BPF_DW:
- if (dst_reg == BPF_REG_PARAMS && insn->off == -8) {
- /* Arg 6: store immediate in r9 register */
- emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32);
- break;
- }
- EMIT2(add_1mod(0x48, dst_reg), 0xC7);
-
-st: insn_off = insn->off;
- if (dst_reg == BPF_REG_PARAMS) {
- /*
- * Args 7+: reverse BPF negative offsets to
- * x86 positive rsp offsets.
- * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ...
- */
- insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
- dst_reg = BPF_REG_FP;
- }
- if (is_imm8(insn_off))
- EMIT2(add_1reg(0x40, dst_reg), insn_off);
- else
- EMIT1_off32(add_1reg(0x80, dst_reg), insn_off);
-
- EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code)));
+ emit_st(&prog, insn, dst_reg, outgoing_arg_base,
+ outgoing_rsp);
break;
/* STX: *(u8*)(dst_reg + off) = src_reg */
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (4 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 05/10] bpf, x86: refactor BPF_ST management in do_jit Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:18 ` sashiko-bot
2026-07-01 10:44 ` bot+bpf-ci
2026-07-01 10:02 ` [PATCH bpf-next v3 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
` (3 subsequent siblings)
9 siblings, 2 replies; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Insert KASAN shadow memory checks before memory load and store
operations in JIT-compiled BPF programs. This helps detect memory safety
bugs such as use-after-free and out-of-bounds accesses at runtime.
The main instructions being targeted are BPF_ST, BPF_STX and BPF_LDX,
but not all of them are being instrumented:
- if the load/store instruction is in fact accessing the program stack,
emit_kasan_check silently skips the instrumentation, as we already
have page guards to monitor stack accesses.
- if the load/store instruction is a BPF_PROBE_MEM or a BPF_PROBE_ATOMIC
instruction, we do not instrument it, as the passed address can fault
(hence the custom fault management with BPF_PROBE_XXX instructions),
and so the corresponding kasan check could fault as well.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- fix LLVM23 build failure
Changes in v2:
- support BPF_ATOMICS
- support BPF_ST
- make sure to systematically pass correct instruction to kasan check
---
arch/x86/net/bpf_jit_comp.c | 72 +++++++++++++++++++++++++++++++++++++--------
1 file changed, 60 insertions(+), 12 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index b70cecfec179..a383ffc8f289 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1576,17 +1576,31 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size,
return 0;
}
-static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg,
- u32 src_reg, s16 off, u8 bpf_size)
+static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog,
+ struct bpf_insn *insn, u8 *ip, u32 dst_reg,
+ u32 src_reg, bool accesses_stack_only)
{
+ u32 atomic_op = insn->imm;
+ int err;
+
switch (atomic_op) {
case BPF_LOAD_ACQ:
+ err = emit_kasan_check(env, pprog, src_reg, insn, ip, false,
+ accesses_stack_only);
+ if (err)
+ return err;
/* dst_reg = smp_load_acquire(src_reg + off16) */
- emit_ldx(pprog, bpf_size, dst_reg, src_reg, off);
+ emit_ldx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+ insn->off);
break;
case BPF_STORE_REL:
+ err = emit_kasan_check(env, pprog, dst_reg, insn, ip, true,
+ accesses_stack_only);
+ if (err)
+ return err;
/* smp_store_release(dst_reg + off16, src_reg) */
- emit_stx(pprog, bpf_size, dst_reg, src_reg, off);
+ emit_stx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg,
+ insn->off);
break;
default:
pr_err("bpf_jit: unknown atomic load/store opcode %02x\n",
@@ -1964,10 +1978,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
const s32 imm32 = insn->imm;
u32 dst_reg = insn->dst_reg;
u32 src_reg = insn->src_reg;
+ bool accesses_stack_only;
u8 b2 = 0, b3 = 0;
u8 *start_of_ldx;
s64 jmp_offset;
s32 insn_off;
+ int insn_idx;
u8 jmp_cond;
u8 *func;
int nops;
@@ -1984,6 +2000,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
EMIT_ENDBR();
ip = image + addrs[i - 1] + (prog - temp);
+ insn_idx = i - 1 + bpf_prog->aux->subprog_start;
+ accesses_stack_only =
+ env ? !env->insn_aux_data[insn_idx].non_stack_access :
+ false;
switch (insn->code) {
/* ALU */
@@ -2364,6 +2384,11 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
case BPF_ST | BPF_MEM | BPF_H:
case BPF_ST | BPF_MEM | BPF_W:
case BPF_ST | BPF_MEM | BPF_DW:
+ err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
+ true, accesses_stack_only);
+ if (err)
+ return err;
+
emit_st(&prog, insn, dst_reg, outgoing_arg_base,
outgoing_rsp);
break;
@@ -2383,6 +2408,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16;
dst_reg = BPF_REG_FP;
}
+ err = emit_kasan_check(env, &prog, dst_reg, insn, ip,
+ true, accesses_stack_only);
+ if (err)
+ return err;
emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off);
break;
@@ -2544,6 +2573,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
/* populate jmp_offset for JAE above to jump to start_of_ldx */
start_of_ldx = prog;
end_of_jmp[-1] = start_of_ldx - end_of_jmp;
+ } else {
+ err = emit_kasan_check(env, &prog, src_reg,
+ insn, ip, false,
+ accesses_stack_only);
+ if (err)
+ return err;
}
if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX ||
BPF_MODE(insn->code) == BPF_MEMSX)
@@ -2605,14 +2640,14 @@ 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:
+ 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;
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;
/*
* Can't be implemented with a single x86 insn.
@@ -2626,7 +2661,19 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
if (dst_reg == BPF_REG_0)
real_dst_reg = BPF_REG_AX;
+ ip += 3;
+ }
+ if (!bpf_atomic_is_load_store(insn)) {
+ err = emit_kasan_check(env, &prog, real_dst_reg,
+ insn, ip, false,
+ accesses_stack_only);
+ if (err)
+ return err;
branch_target = prog;
+ }
+ if (insn->imm == (BPF_AND | BPF_FETCH) ||
+ insn->imm == (BPF_OR | BPF_FETCH) ||
+ insn->imm == (BPF_XOR | BPF_FETCH)) {
/* Load old value */
emit_ldx(&prog, BPF_SIZE(insn->code),
BPF_REG_0, real_dst_reg, insn->off);
@@ -2658,15 +2705,16 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
}
if (bpf_atomic_is_load_store(insn))
- err = emit_atomic_ld_st(&prog, insn->imm, dst_reg, src_reg,
- insn->off, BPF_SIZE(insn->code));
+ err = emit_atomic_ld_st(env, &prog, insn, ip,
+ dst_reg, src_reg,
+ accesses_stack_only);
else
err = emit_atomic_rmw(&prog, insn->imm, dst_reg, src_reg,
insn->off, BPF_SIZE(insn->code));
if (err)
return err;
break;
-
+ }
case BPF_STX | BPF_PROBE_ATOMIC | BPF_B:
case BPF_STX | BPF_PROBE_ATOMIC | BPF_H:
if (!bpf_atomic_is_load_store(insn)) {
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:18 ` sashiko-bot
2026-07-01 10:44 ` bot+bpf-ci
1 sibling, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:18 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] emit_kasan_check fails to save and restore x86 caller-saved registers R10 (BPF_REG_AX) and R11 (AUX_REG), causing BPF register corruption.
--
commit f9acd1352526b0ce263a176070ea1146c5f57a4a
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf, x86: emit KASAN checks into x86 JITed programs
Insert KASAN shadow memory checks before memory load and store
operations in JIT-compiled BPF programs. This helps detect memory safety
bugs such as use-after-free and out-of-bounds accesses at runtime.
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index b70cecfec1793..a383ffc8f2899 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -2605,14 +2640,14 @@ 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:
> + 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;
> if (insn->imm == (BPF_AND | BPF_FETCH) ||
> insn->imm == (BPF_OR | BPF_FETCH) ||
> insn->imm == (BPF_XOR | BPF_FETCH)) {
>
> /*
> * Can't be implemented with a single x86 insn.
> @@ -2626,7 +2661,19 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
> if (dst_reg == BPF_REG_0)
> real_dst_reg = BPF_REG_AX;
>
> + ip += 3;
> + }
> + if (!bpf_atomic_is_load_store(insn)) {
> + err = emit_kasan_check(env, &prog, real_dst_reg,
> + insn, ip, false,
> + accesses_stack_only);
[Severity: Critical]
Does emit_kasan_check() correctly preserve x86 caller-saved registers like R10
(BPF_REG_AX) and R11 (AUX_REG)?
Looking at emit_kasan_check(), it saves r8 and r9 before the C function call
but seems to miss saving r10 and r11:
/* Save rdi */
EMIT1(0x57);
/* Save r8 */
EMIT2(0x41, 0x50);
/* Save r9 */
EMIT2(0x41, 0x51);
/* mov rdi, addr_reg */
Because the x86-64 System V ABI allows C functions like __asan_load* to
clobber R10 and R11, failing to save and restore them before the call can
corrupt BPF registers.
For example, in the atomic block above, BPF_REG_AX (R10) is used to temporarily
save BPF_REG_0 across the emit_kasan_check() call. If __asan_load* clobbers
BPF_REG_AX, the restored BPF_REG_0 will contain garbage, which could be used to
bypass verifier checks and lead to out-of-bounds kernel memory accesses.
> + if (err)
> + return err;
> branch_target = prog;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=6
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs
2026-07-01 10:02 ` [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
2026-07-01 10:18 ` sashiko-bot
@ 2026-07-01 10:44 ` bot+bpf-ci
1 sibling, 0 replies; 22+ messages in thread
From: bot+bpf-ci @ 2026-07-01 10:44 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
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: 2295 bytes --]
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index b70cecfec179..a383ffc8f289 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
[ ... ]
> @@ -2605,14 +2640,14 @@ 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:
> + 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;
> 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;
>
> /*
> * Can't be implemented with a single x86 insn.
> @@ -2626,7 +2661,19 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
> if (dst_reg == BPF_REG_0)
> real_dst_reg = BPF_REG_AX;
>
> + ip += 3;
> + }
> + if (!bpf_atomic_is_load_store(insn)) {
> + err = emit_kasan_check(env, &prog, real_dst_reg,
> + insn, ip, false,
> + accesses_stack_only);
> + if (err)
> + return err;
> branch_target = prog;
> + }
> + if (insn->imm == (BPF_AND | BPF_FETCH) ||
> + insn->imm == (BPF_OR | BPF_FETCH) ||
> + insn->imm == (BPF_XOR | BPF_FETCH)) {
> /* Load old value */
> emit_ldx(&prog, BPF_SIZE(insn->code),
> BPF_REG_0, real_dst_reg, insn->off);
This isn't a bug, but the FETCH-op check
insn->imm == (BPF_AND | BPF_FETCH) ||
insn->imm == (BPF_OR | BPF_FETCH) ||
insn->imm == (BPF_XOR | BPF_FETCH)
is now spelled out in two adjacent if blocks in this case, where it was
written once before the patch.
Could it be hoisted into a single local (e.g. bool is_fetch = ...) so the
two copies don't have to be kept in sync on later edits?
---
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/28510424881
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v3 07/10] bpf, x86: enable KASAN for JITed programs on x86
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (5 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 06/10] bpf, x86: emit KASAN checks into x86 JITed programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:15 ` sashiko-bot
2026-07-01 10:02 ` [PATCH bpf-next v3 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active Alexis Lothoré (eBPF Foundation)
` (2 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Mark x86 as supporting KASAN checks in JITed programs so that the
corresponding JIT compiler inserts checks on the translated
instructions.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
arch/x86/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index bdad90f210e4..705a9ab85f3a 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -233,6 +233,7 @@ config X86
select HAVE_SAMPLE_FTRACE_DIRECT if X86_64
select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64
select HAVE_EBPF_JIT
+ select HAVE_EBPF_JIT_KASAN if X86_64
select HAVE_EFFICIENT_UNALIGNED_ACCESS
select HAVE_EISA if X86_32
select HAVE_EXIT_THREAD
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 07/10] bpf, x86: enable KASAN for JITed programs on x86
2026-07-01 10:02 ` [PATCH bpf-next v3 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:15 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:15 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] emit_kasan_check() fails to preserve the caller-saved r10 register (BPF_REG_AX), causing BPF program state corruption and kernel crashes during BPF_ATOMIC_FETCH operations.
--
commit 9b578f89250bc8a04722f64b0dbcf3e863ff5692
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
bpf, x86: enable KASAN for JITed programs on x86
Mark x86 as supporting KASAN checks in JITed programs so that the
corresponding JIT compiler inserts checks on the translated
instructions.
> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
> index bdad90f210e4b..705a9ab85f3a9 100644
> --- a/arch/x86/Kconfig
> +++ b/arch/x86/Kconfig
> @@ -233,6 +233,7 @@ config X86
> select HAVE_SAMPLE_FTRACE_DIRECT if X86_64
> select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64
> select HAVE_EBPF_JIT
> + select HAVE_EBPF_JIT_KASAN if X86_64
[Severity: High]
This isn't a bug introduced by this patch, but enabling KASAN checks for
JITed BPF programs here exposes a pre-existing issue in the x86 JIT
compiler during atomic operations.
When handling BPF_ATOMIC_FETCH with BPF_REG_0 as a source or destination,
do_jit() saves BPF_REG_0 into BPF_REG_AX before emitting the 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 (!bpf_atomic_is_load_store(insn)) {
err = emit_kasan_check(env, &prog, real_dst_reg,
insn, ip, false,
accesses_stack_only);
...
}
In the x86 JIT, BPF_REG_AX maps to the r10 register. Looking at
emit_kasan_check(), it manually pushes caller-saved registers before calling
the KASAN C function:
arch/x86/net/bpf_jit_comp.c:emit_kasan_check() {
...
/* Save rdi */
EMIT1(0x57);
/* Save r8 */
EMIT2(0x41, 0x50);
/* Save r9 */
EMIT2(0x41, 0x51);
...
}
This sequence fails to save r10 and r11. According to the System V AMD64 ABI,
r10 is caller-saved, meaning the KASAN C function is free to clobber it.
When emit_kasan_check() returns, BPF_REG_AX (r10) might contain garbage.
Could this cause subsequent memory accesses using real_dst_reg to dereference
a garbage pointer if BPF_REG_0 was the destination?
Does this also corrupt the BPF program state when BPF_REG_0 is later restored
from BPF_REG_AX after the CMPXCHG loop?
> select HAVE_EFFICIENT_UNALIGNED_ACCESS
> select HAVE_EISA if X86_32
> select HAVE_EXIT_THREAD
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=7
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v3 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (6 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 07/10] bpf, x86: enable KASAN for JITed programs on x86 Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
9 siblings, 0 replies; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add a simple helper checking whether JIT compiler is able to insert
KASAN checks in programs. This will allow to conditionally run
selftests for KASAN checks in JITed programs.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v2:
- fix condition
---
tools/testing/selftests/bpf/unpriv_helpers.c | 5 +++++
tools/testing/selftests/bpf/unpriv_helpers.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index f997d7ec8fd0..11201b65a3d4 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -142,3 +142,8 @@ bool get_unpriv_disabled(void)
}
return mitigations_off;
}
+
+bool get_kasan_jit_enabled(void)
+{
+ return config_contains("CONFIG_BPF_JIT_KASAN=y") == 1;
+}
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h
index 151f67329665..bc5f4c953c9d 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.h
+++ b/tools/testing/selftests/bpf/unpriv_helpers.h
@@ -5,3 +5,4 @@
#define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled"
bool get_unpriv_disabled(void);
+bool get_kasan_jit_enabled(void);
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH bpf-next v3 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (7 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 08/10] selftests/bpf: add helper to check whether eBPF KASAN is active Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:02 ` [PATCH bpf-next v3 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
9 siblings, 0 replies; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Move set_bpf_jit_harden to testing helpers so that other selftests can
change the hardening configuration without re-implementing a helper.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- new patch
---
.../selftests/bpf/prog_tests/bpf_insn_array.c | 41 ++--------------------
tools/testing/selftests/bpf/testing_helpers.c | 32 +++++++++++++++++
tools/testing/selftests/bpf/testing_helpers.h | 1 +
3 files changed, 36 insertions(+), 38 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
index 0222a9a5d076..37c405fc2b0d 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@ -227,42 +227,6 @@ static void check_incorrect_index(void)
check_mid_insn_index();
}
-static int set_bpf_jit_harden(char *level)
-{
- char old_level;
- int err = -1;
- int fd = -1;
-
- fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
- if (fd < 0) {
- ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno);
- return -1;
- }
-
- err = read(fd, &old_level, 1);
- if (err != 1) {
- ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno);
- err = -1;
- goto end;
- }
-
- lseek(fd, 0, SEEK_SET);
-
- err = write(fd, level, 1);
- if (err != 1) {
- ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno);
- err = -1;
- goto end;
- }
-
- err = 0;
- *level = old_level;
-end:
- if (fd >= 0)
- close(fd);
- return err;
-}
-
static void check_blindness(void)
{
struct bpf_insn insns[] = {
@@ -272,7 +236,7 @@ static void check_blindness(void)
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
};
- int prog_fd = -1, map_fd;
+ int prog_fd = -1, map_fd, ret;
struct bpf_insn_array_value val = {};
char bpf_jit_harden = '@'; /* non-exizsting value */
int i;
@@ -291,7 +255,8 @@ static void check_blindness(void)
goto cleanup;
bpf_jit_harden = '2';
- if (set_bpf_jit_harden(&bpf_jit_harden)) {
+ ret = set_bpf_jit_harden(&bpf_jit_harden);
+ if (!ASSERT_OK(ret, "set bpf_jit_harden")) {
bpf_jit_harden = '@'; /* open, read or write failed => no write was done */
goto cleanup;
}
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index c970e7793dfc..737f668b35e2 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -519,6 +519,38 @@ bool is_jit_enabled(void)
return enabled;
}
+int set_bpf_jit_harden(char *level)
+{
+ char old_level;
+ int err = -1;
+ int fd = -1;
+
+ fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ return -1;
+
+ err = read(fd, &old_level, 1);
+ if (err != 1) {
+ err = -1;
+ goto end;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+
+ err = write(fd, level, 1);
+ if (err != 1) {
+ err = -1;
+ goto end;
+ }
+
+ err = 0;
+ *level = old_level;
+end:
+ if (fd >= 0)
+ close(fd);
+ return err;
+}
+
int stack_mprotect(void)
{
void *buf;
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index 2edc6fb7fc52..e00642afe86f 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -59,6 +59,7 @@ struct bpf_insn;
int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt);
int testing_prog_flags(void);
bool is_jit_enabled(void);
+int set_bpf_jit_harden(char *level);
int stack_mprotect(void);
#endif /* __TESTING_HELPERS_H */
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH bpf-next v3 10/10] selftests/bpf: add tests to validate KASAN on JIT programs
2026-07-01 10:02 [PATCH bpf-next v3 00/10] bpf: add support for KASAN checks in JITed programs Alexis Lothoré (eBPF Foundation)
` (8 preceding siblings ...)
2026-07-01 10:02 ` [PATCH bpf-next v3 09/10] selftests/bpf: move bpf_jit_harden helper into testing_helpers Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:02 ` Alexis Lothoré (eBPF Foundation)
2026-07-01 10:34 ` sashiko-bot
9 siblings, 1 reply; 22+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2026-07-01 10:02 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Shuah Khan, Ingo Molnar, Andrey Konovalov
Cc: ebpf, Bastien Curutchet, Thomas Petazzoni, bpf, linux-kernel,
linux-kselftest, Alexis Lothoré (eBPF Foundation)
Add a basic KASAN test runner that loads and test-run programs that can
trigger memory management bugs. The test captures kernel logs and ensure
that the expected KASAN splat is emitted by searching for the
corresponding first lines in the report, hence validated that the needed
instrumentation has been inserted by the JIT compiler before the
relevant memory accesses. To allow each test to trigger the expected
report, the kernel must run with the kasan_multi_shot configuration.
The runner covers different cases and settings: in the nominal case, it
validates kasan reports on basic instructions (on all supported accesses
sizes) but also when report _should not_ be emitted (eg: for accesses on
program stack). The runner also comes with a few specialized tests that
are then not executed for all sizes/locations:
- specific atomic ops
- test for instructions involving different verifier states, with some
states flagging memory as stack, and other states as non-stack memory
- tests that validate the stack marking shifting when a patch is emitted
by the verifier (zext/rnd_hi32, constant blindind)
A few of those tests depends on cpuv4 (load_acquire and store_release).
# ./test_progs -a kasan
#165/1 kasan/st_1_not_on_stack:OK
#165/2 kasan/st_1_on_stack:OK
#165/3 kasan/st_2_not_on_stack:OK
#165/4 kasan/st_2_on_stack:OK
#165/5 kasan/st_4_not_on_stack:OK
#165/6 kasan/st_4_on_stack:OK
#165/7 kasan/st_8_not_on_stack:OK
#165/8 kasan/st_8_on_stack:OK
#165/9 kasan/stx_1_not_on_stack:OK
#165/10 kasan/stx_1_on_stack:OK
#165/11 kasan/stx_2_not_on_stack:OK
#165/12 kasan/stx_2_on_stack:OK
#165/13 kasan/stx_4_not_on_stack:OK
#165/14 kasan/stx_4_on_stack:OK
#165/15 kasan/stx_8_not_on_stack:OK
#165/16 kasan/stx_8_on_stack:OK
#165/17 kasan/ldx_1_not_on_stack:OK
#165/18 kasan/ldx_1_on_stack:OK
#165/19 kasan/ldx_2_not_on_stack:OK
#165/20 kasan/ldx_2_on_stack:OK
#165/21 kasan/ldx_4_not_on_stack:OK
#165/22 kasan/ldx_4_on_stack:OK
#165/23 kasan/ldx_8_not_on_stack:OK
#165/24 kasan/ldx_8_on_stack:OK
#165/25 kasan/simple_atomic_4_not_on_stack:OK
#165/26 kasan/simple_atomic_4_on_stack:OK
#165/27 kasan/simple_atomic_8_not_on_stack:OK
#165/28 kasan/simple_atomic_8_on_stack:OK
#165/29 kasan/load_acquire_1_not_on_stack:SKIP
#165/30 kasan/load_acquire_1_on_stack:SKIP
#165/31 kasan/load_acquire_2_not_on_stack:SKIP
#165/32 kasan/load_acquire_2_on_stack:SKIP
#165/33 kasan/load_acquire_4_not_on_stack:SKIP
#165/34 kasan/load_acquire_4_on_stack:SKIP
#165/35 kasan/load_acquire_8_not_on_stack:SKIP
#165/36 kasan/load_acquire_8_on_stack:SKIP
#165/37 kasan/store_release_1_not_on_stack:SKIP
#165/38 kasan/store_release_1_on_stack:SKIP
#165/39 kasan/store_release_2_not_on_stack:SKIP
#165/40 kasan/store_release_2_on_stack:SKIP
#165/41 kasan/store_release_4_not_on_stack:SKIP
#165/42 kasan/store_release_4_on_stack:SKIP
#165/43 kasan/store_release_8_not_on_stack:SKIP
#165/44 kasan/store_release_8_on_stack:SKIP
#165/45 kasan/ldx_patched:OK
#165/46 kasan/ldx_patched_on_stack:OK
#165/47 kasan/verifier_paths_stack_and_non_stack:OK
#165/48 kasan/st_blinded:OK
#165 kasan:OK (SKIP: 16/48)
Summary: 1/32 PASSED, 16 SKIPPED, 0 FAILED
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in 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 | 437 +++++++++++++++++++++
tools/testing/selftests/bpf/progs/kasan.c | 394 +++++++++++++++++++
tools/testing/selftests/bpf/progs/kasan_harden.c | 41 ++
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 22 ++
4 files changed, 894 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..ff070c39eaac
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * Tests validating that KASAN reports are properly instrumented and
+ * generated on a wide variety of instructions. The running kernel needs
+ * kasan_multi_shot to run multiple kasan-generating subtests at once
+ */
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include <unpriv_helpers.h>
+#include "kasan.skel.h"
+#include "kasan_harden.skel.h"
+
+#define SUBTEST_NAME_MAX_LEN 128
+#define PROG_NAME_MAX_LEN 128
+
+#define MAX_LOG_SIZE (8 * 1024)
+#define READ_CHUNK_SIZE 256
+
+#define KASAN_PATTERN_SLAB_UAF "BUG: KASAN: slab-use-after-free " \
+ "in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s"
+#define KASAN_PATTERN_REPORT "%s of size %d at addr"
+
+static char klog_buffer[MAX_LOG_SIZE];
+static char record[MAX_LOG_SIZE];
+
+struct test_spec {
+ char *prog_type;
+ bool is_write;
+ bool only_32_or_64;
+ bool needs_load_acq_store_rel;
+ bool skip_multi_size_testing;
+ bool skip_on_stack_testing;
+ int run_size;
+ bool expect_no_report;
+ bool rnd_hi32;
+};
+
+struct kasan_write_val {
+ __u8 data_1;
+ __u16 data_2;
+ __u32 data_4;
+ __u64 data_8;
+};
+
+struct test_ctx {
+ __u8 prog_tag[BPF_TAG_SIZE];
+ struct bpf_object *obj;
+ int *access_size;
+ bool skip_load_acq_store_rel;
+ struct bpf_program *prog;
+ char prog_name[SUBTEST_NAME_MAX_LEN];
+ int klog_fd;
+};
+
+static int open_kernel_logs(void)
+{
+ int fd;
+
+ fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+ return fd;
+}
+
+static void skip_kernel_logs(int fd)
+{
+ lseek(fd, 0, SEEK_END);
+}
+
+static int read_kernel_logs(int fd, char *buf, size_t max_len)
+{
+ size_t total = 0;
+ ssize_t n;
+
+ buf[0] = '\0';
+ while (1) {
+ char *msg, *eol;
+ size_t len;
+
+ n = read(fd, record, sizeof(record) - 1);
+ if (n < 0) {
+ if (errno == EAGAIN)
+ break;
+ return n;
+ }
+ record[n] = '\0';
+
+ /*
+ * Each kmsg record starts with some metadata, separated
+ * from the actual content by a semi-colon
+ */
+ msg = strchr(record, ';');
+ if (!msg)
+ continue;
+ msg++;
+ eol = strchr(msg, '\n');
+ if (eol)
+ *eol = '\0';
+
+ len = strlen(msg);
+ if (total + len + 2 > max_len)
+ break;
+ memcpy(buf + total, msg, len);
+ total += len;
+ buf[total++] = '\n';
+ buf[total] = '\0';
+ }
+
+ return total;
+}
+
+static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
+ bool is_write, int size)
+{
+ char *access_desc_start, *access_desc_end, *tmp;
+ char access_log[READ_CHUNK_SIZE];
+ char *kasan_report_start;
+ int nsize;
+
+ snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_SLAB_UAF,
+ ctx->prog_tag[0], ctx->prog_tag[1], ctx->prog_tag[2],
+ ctx->prog_tag[3], ctx->prog_tag[4], ctx->prog_tag[5],
+ ctx->prog_tag[6], ctx->prog_tag[7], ctx->prog_name);
+ /*
+ * Searched kasan report is valid if
+ * - it contains the expected kasan pattern
+ * - the next line is the description of the faulty access
+ * - faulty access properties match the tested type and size
+ */
+ kasan_report_start = strstr(buf, access_log);
+
+ if (!kasan_report_start)
+ return 1;
+
+ /* Find next line */
+ access_desc_start = strchr(kasan_report_start, '\n');
+ if (!access_desc_start)
+ return 1;
+ access_desc_start++;
+
+ access_desc_end = strchr(access_desc_start, '\n');
+ if (!access_desc_end)
+ return 1;
+
+ nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT,
+ is_write ? "Write" : "Read", size);
+
+ tmp = memmem(access_desc_start, access_desc_end - access_desc_start,
+ access_log, nsize);
+
+ if (!tmp)
+ 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];
+ int ret, prog_fd;
+ __u32 info_len;
+
+ ctx->prog = bpf_object__find_program_by_name(ctx->obj,
+ ctx->prog_name);
+ if (!ASSERT_OK_PTR(ctx->prog, "find test prog"))
+ return;
+
+ info_len = sizeof(info);
+ memset(&info, 0, info_len);
+ prog_fd = bpf_program__fd(ctx->prog);
+ if (!ASSERT_OK_FD(prog_fd, "get prog fd"))
+ return;
+ ret = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len);
+ if (!ASSERT_OK(ret, "fetch loaded program info"))
+ return;
+ memcpy(ctx->prog_tag, info.tag, BPF_TAG_SIZE);
+
+ skip_kernel_logs(ctx->klog_fd);
+
+ topts.sz = sizeof(struct bpf_test_run_opts);
+ topts.data_size_in = ETH_HLEN;
+ topts.data_in = buf;
+ if (ctx->access_size)
+ *ctx->access_size = access_size;
+ ret = bpf_prog_test_run_opts(bpf_program__fd(ctx->prog),
+ &topts);
+ if (!ASSERT_OK(ret, "run prog"))
+ return;
+
+ ret = read_kernel_logs(ctx->klog_fd, klog_buffer, MAX_LOG_SIZE);
+ if (!ASSERT_GE(ret, 0, "read kernel logs"))
+ return;
+
+ ret = check_kasan_report_in_kernel_logs(klog_buffer, ctx,
+ test->is_write, access_size);
+ if (on_stack || test->expect_no_report)
+ ASSERT_NEQ(ret, 0, "no report should be generated");
+ else
+ ASSERT_OK(ret, "report should be generated");
+}
+
+static void run_subtest_with_size_and_location(struct test_ctx *ctx,
+ struct test_spec *test,
+ int access_size,
+ bool on_stack)
+{
+ char subtest_name[SUBTEST_NAME_MAX_LEN];
+
+ if (test->skip_multi_size_testing) {
+ snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s",
+ test->prog_type);
+ strncpy(ctx->prog_name, test->prog_type, PROG_NAME_MAX_LEN);
+ } else {
+ snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s_%d_%s",
+ test->prog_type, access_size,
+ on_stack ? "on_stack" : "not_on_stack");
+ snprintf(ctx->prog_name, PROG_NAME_MAX_LEN, "%s_%s",
+ test->prog_type,
+ on_stack ? "on_stack" : "not_on_stack");
+ }
+
+ if (!test__start_subtest(subtest_name))
+ return;
+
+ if (test->needs_load_acq_store_rel && ctx->skip_load_acq_store_rel) {
+ test__skip();
+ return;
+ }
+
+ exec_subtest(ctx, test, access_size, on_stack);
+}
+
+static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test,
+ int size)
+{
+ run_subtest_with_size_and_location(ctx, test, size, false);
+ if (!test->skip_on_stack_testing)
+ run_subtest_with_size_and_location(ctx, test, size, true);
+}
+
+static void run_subtest(struct test_ctx *ctx, struct test_spec *test)
+{
+ if (test->skip_multi_size_testing) {
+ run_subtest_with_size(ctx, test, test->run_size);
+ return;
+ }
+
+ if (!test->only_32_or_64) {
+ run_subtest_with_size(ctx, test, 1);
+ run_subtest_with_size(ctx, test, 2);
+ }
+ run_subtest_with_size(ctx, test, 4);
+ run_subtest_with_size(ctx, test, 8);
+}
+
+static void run_blinding_subtest(void)
+{
+ struct test_spec blinding_spec = {
+ .prog_type = "st_blinded",
+ .is_write = true,
+ };
+ char bpf_jit_harden = '2';
+ struct kasan_harden *skel;
+ struct test_ctx *ctx;
+
+ if (!test__start_subtest("st_blinded"))
+ return;
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (!ASSERT_OK_PTR(ctx, "alloc blinding ctx"))
+ return;
+ ctx->klog_fd = -1;
+
+ if (set_bpf_jit_harden(&bpf_jit_harden))
+ goto free_ctx;
+
+ skel = kasan_harden__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open and load blinded prog"))
+ goto restore;
+
+ ctx->klog_fd = open_kernel_logs();
+ if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+ goto destroy;
+
+ ctx->obj = skel->obj;
+ strncpy(ctx->prog_name, "st_blinded", PROG_NAME_MAX_LEN);
+
+ exec_subtest(ctx, &blinding_spec, 1, false);
+
+destroy:
+ close(ctx->klog_fd);
+ kasan_harden__destroy(skel);
+restore:
+ set_bpf_jit_harden(&bpf_jit_harden);
+free_ctx:
+ free(ctx);
+}
+
+static struct test_spec tests[] = {
+ {
+ .prog_type = "st",
+ .is_write = true
+ },
+ {
+ .prog_type = "stx",
+ .is_write = true
+ },
+ {
+ .prog_type = "ldx",
+ .is_write = false
+ },
+ {
+ .prog_type = "simple_atomic",
+ .is_write = false,
+ .only_32_or_64 = true
+ },
+ {
+ .prog_type = "load_acquire",
+ .is_write = false,
+ .needs_load_acq_store_rel = true
+ },
+ {
+ .prog_type = "store_release",
+ .is_write = true,
+ .needs_load_acq_store_rel = true
+ },
+ {
+ .prog_type = "ldx_patched",
+ .is_write = false,
+ .skip_multi_size_testing = true,
+ .skip_on_stack_testing = true,
+ .run_size = 4,
+ .rnd_hi32 = true
+ },
+ {
+ .prog_type = "ldx_patched_on_stack",
+ .is_write = false,
+ .skip_multi_size_testing = true,
+ .skip_on_stack_testing = true,
+ .run_size = 4,
+ .expect_no_report = true,
+ .rnd_hi32 = true
+ },
+ {
+ .prog_type = "verifier_paths_stack_and_non_stack",
+ .is_write = true,
+ .skip_multi_size_testing = true,
+ .skip_on_stack_testing = true,
+ .run_size = 1
+ }
+};
+
+void test_kasan(void)
+{
+ struct kasan_write_val val;
+ struct test_spec *test;
+ struct test_ctx *ctx;
+ struct kasan *skel;
+ __u32 key = 0;
+ int i, ret;
+
+ ctx = calloc(1, sizeof(struct test_ctx));
+ if (!ASSERT_OK_PTR(ctx, "alloc test ctx"))
+ return;
+
+ if (!is_jit_enabled() || !get_kasan_jit_enabled()) {
+ test__skip();
+ goto end;
+ }
+
+ skel = kasan__open();
+ if (!ASSERT_OK_PTR(skel, "open prog"))
+ goto end;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ struct bpf_program *prog;
+
+ if (!tests[i].rnd_hi32)
+ continue;
+
+ prog = bpf_object__find_program_by_name(skel->obj,
+ tests[i].prog_type);
+ if (!ASSERT_OK_PTR(prog, "find rnd_hi32 prog"))
+ goto destroy;
+ bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32);
+ }
+
+ if (!ASSERT_OK(kasan__load(skel), "load prog"))
+ goto destroy;
+
+ ctx->obj = skel->obj;
+ ctx->access_size = &skel->bss->access_size;
+ ctx->skip_load_acq_store_rel = skel->data->skip_load_acq_store_rel_tests;
+
+ ctx->klog_fd = open_kernel_logs();
+ if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs"))
+ goto destroy;
+
+ /* Fill map with recognizable values */
+ ret = bpf_map__lookup_elem(skel->maps.test_map, &key, sizeof(key),
+ &val, sizeof(val), 0);
+ if (!ASSERT_OK(ret, "get map"))
+ goto close;
+ val.data_1 = 0xAA;
+ val.data_2 = 0xBBBB;
+ val.data_4 = 0xCCCCCCCC;
+ val.data_8 = 0xDDDDDDDDDDDDDDDD;
+ ret = bpf_map__update_elem(skel->maps.test_map, &key, sizeof(key),
+ &val, sizeof(val), 0);
+ if (!ASSERT_OK(ret, "set map"))
+ goto close;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ test = &tests[i];
+ run_subtest(ctx, test);
+ }
+
+ /*
+ * Blinding subtest is handled differently as it needs the
+ * corresponding program to be loaded with bpf_jit_harden raised
+ */
+ run_blinding_subtest();
+
+close:
+ close(ctx->klog_fd);
+destroy:
+ kasan__destroy(skel);
+end:
+ free(ctx);
+}
diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c
new file mode 100644
index 000000000000..82ddce3059ae
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include <stdbool.h>
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+int access_size;
+
+struct kasan_write_val {
+ __u8 data_1;
+ __u16 data_2;
+ __u32 data_4;
+ __u64 data_8;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct kasan_write_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ val.data_1 = 0xAA;
+ break;
+ case 2:
+ val.data_2 = 0xAA;
+ break;
+ case 4:
+ val.data_4 = 0xAA;
+ break;
+ case 8:
+ val.data_8 = 0xAA;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int st_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ val->data_1 = 0xAA;
+ break;
+ case 2:
+ val->data_2 = 0xAA;
+ break;
+ case 4:
+ val->data_4 = 0xAA;
+ break;
+ case 8:
+ val->data_8 = 0xAA;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int stx_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ val.data_1 = access_size;
+ break;
+ case 2:
+ val.data_2 = access_size;
+ break;
+ case 4:
+ val.data_4 = access_size;
+ break;
+ case 8:
+ val.data_8 = access_size;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int stx_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ val->data_1 = access_size;
+ break;
+ case 2:
+ val->data_2 = access_size;
+ break;
+ case 4:
+ val->data_4 = access_size;
+ break;
+ case 8:
+ val->data_8 = access_size;
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ __sink(val.data_1);
+ break;
+ case 2:
+ __sink(val.data_2);
+ break;
+ case 4:
+ __sink(val.data_4);
+ break;
+ case 8:
+ __sink(val.data_8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ __sink(val->data_1);
+ break;
+ case 2:
+ __sink(val->data_2);
+ break;
+ case 4:
+ __sink(val->data_4);
+ break;
+ case 8:
+ __sink(val->data_8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ __sink(val->data_4);
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+
+ return 0;
+}
+
+SEC("tcx/ingress")
+int ldx_patched_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ __sink(val.data_4);
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+
+ return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 4:
+ __sync_fetch_and_add(&val.data_4, 4);
+ break;
+ case 8:
+ __sync_fetch_and_add(&val.data_8, 8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int simple_atomic_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 4:
+ __sync_fetch_and_add(&val->data_4, 4);
+ break;
+ case 8:
+ __sync_fetch_and_add(&val->data_8, 8);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL
+bool skip_load_acq_store_rel_tests SEC(".data") = 0;
+
+SEC("tcx/ingress")
+int load_acquire_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ __atomic_load_n(&val.data_1, __ATOMIC_ACQUIRE);
+ break;
+ case 2:
+ __atomic_load_n(&val.data_2, __ATOMIC_ACQUIRE);
+ break;
+ case 4:
+ __atomic_load_n(&val.data_4, __ATOMIC_ACQUIRE);
+ break;
+ case 8:
+ __atomic_load_n(&val.data_8, __ATOMIC_ACQUIRE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int load_acquire_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ __atomic_load_n(&val->data_1, __ATOMIC_ACQUIRE);
+ break;
+ case 2:
+ __atomic_load_n(&val->data_2, __ATOMIC_ACQUIRE);
+ break;
+ case 4:
+ __atomic_load_n(&val->data_4, __ATOMIC_ACQUIRE);
+ break;
+ case 8:
+ __atomic_load_n(&val->data_8, __ATOMIC_ACQUIRE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val val;
+
+ bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ __atomic_store_n(&val.data_1, 0xAA, __ATOMIC_RELEASE);
+ break;
+ case 2:
+ __atomic_store_n(&val.data_2, 0xBBBB, __ATOMIC_RELEASE);
+ break;
+ case 4:
+ __atomic_store_n(&val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+ break;
+ case 8:
+ __atomic_store_n(&val.data_8, 0xDDDDDDDDDDDDDDDD,
+ __ATOMIC_RELEASE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val));
+ return 0;
+}
+
+SEC("tcx/ingress")
+int store_release_not_on_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ switch (access_size) {
+ case 1:
+ __atomic_store_n(&val->data_1, 0xAA, __ATOMIC_RELEASE);
+ break;
+ case 2:
+ __atomic_store_n(&val->data_2, 0xBBBB, __ATOMIC_RELEASE);
+ break;
+ case 4:
+ __atomic_store_n(&val->data_4, 0xCCCCCCCC, __ATOMIC_RELEASE);
+ break;
+ case 8:
+ __atomic_store_n(&val->data_8, 0xDDDDDDDDDDDDDDDD,
+ __ATOMIC_RELEASE);
+ break;
+ }
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+ return 0;
+}
+#else
+bool skip_load_acq_store_rel_tests SEC(".data") = 1;
+#endif
+
+SEC("tcx/ingress")
+int verifier_paths_stack_and_non_stack(struct __sk_buff *skb)
+{
+ struct kasan_write_val stack_val = {};
+ struct kasan_write_val *val;
+ void *ptr;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ if (access_size)
+ ptr = val;
+ else
+ ptr = &stack_val;
+
+ bpf_kfunc_kasan_poison(val, sizeof(*val));
+ *(__u8 *)ptr = 0xAA;
+ bpf_kfunc_kasan_unpoison(val, sizeof(*val));
+ return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kasan_harden.c b/tools/testing/selftests/bpf/progs/kasan_harden.c
new file mode 100644
index 000000000000..808d7da64421
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kasan_harden.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym;
+extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym;
+
+struct kasan_write_val {
+ __u8 data_1;
+ __u16 data_2;
+ __u32 data_4;
+ __u64 data_8;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct kasan_write_val);
+} test_map SEC(".maps");
+
+SEC("tcx/ingress")
+int st_blinded(struct __sk_buff *skb)
+{
+ struct kasan_write_val *val;
+ __u32 key = 0;
+
+ val = bpf_map_lookup_elem(&test_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val));
+ val->data_1 = 0xAA;
+ bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val));
+
+ return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 30f1cd23093c..09a502a1742f 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -271,6 +271,26 @@ __bpf_kfunc void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member
*/
}
+#ifdef CONFIG_KASAN_GENERIC
+
+extern void kasan_poison(const void *addr, size_t size, u8 value, bool init);
+
+#define KASAN_SLAB_FREE 0xFB
+
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz)
+{
+ kasan_poison(mem, mem__sz, KASAN_SLAB_FREE, false);
+}
+
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz)
+{
+ kasan_poison(mem, mem__sz, 0x00, false);
+}
+#else
+__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) { }
+__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) { }
+#endif
+
__bpf_kfunc struct bpf_testmod_ctx *
bpf_testmod_ctx_create(int *err)
{
@@ -740,6 +760,8 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)
BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison)
+BTF_ID_FLAGS(func, bpf_kfunc_kasan_unpoison)
BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
BTF_ID_LIST(bpf_testmod_dtor_ids)
--
2.54.0
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH bpf-next v3 10/10] selftests/bpf: add tests to validate KASAN on JIT programs
2026-07-01 10:02 ` [PATCH bpf-next v3 10/10] selftests/bpf: add tests to validate KASAN on JIT programs Alexis Lothoré (eBPF Foundation)
@ 2026-07-01 10:34 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-01 10:34 UTC (permalink / raw)
Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Uninitialized stack memory is passed to the kernel via bpf_prog_test_run_opts, causing potential information leaks and triggering MSAN warnings.
- [Medium] The KASAN report parser is susceptible to spurious failures due to kernel log interleaving.
--
commit e974c567c6b3da1ae6e1c27c442840d9bd396091
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
selftests/bpf: add tests to validate KASAN on JIT programs
This patch adds a basic KASAN test runner that loads and test-runs BPF programs
designed to trigger memory management bugs. The test captures kernel logs and
ensures the expected KASAN splat is emitted by searching for the corresponding
first lines in the report.
> diff --git a/tools/testing/selftests/bpf/prog_tests/kasan.c b/tools/testing/selftests/bpf/prog_tests/kasan.c
> new file mode 100644
> index 0000000000000..ff070c39eaac1
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/kasan.c
[ ... ]
> +static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx,
> + bool is_write, int size)
> +{
> + char *access_desc_start, *access_desc_end, *tmp;
[ ... ]
> + /* Find next line */
> + access_desc_start = strchr(kasan_report_start, '\n');
> + if (!access_desc_start)
> + return 1;
> + access_desc_start++;
> +
> + access_desc_end = strchr(access_desc_start, '\n');
> + if (!access_desc_end)
> + return 1;
> +
> + nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT,
> + is_write ? "Write" : "Read", size);
> +
> + tmp = memmem(access_desc_start, access_desc_end - access_desc_start,
> + access_log, nsize);
[Severity: Medium]
Could this fail if other kernel components interleave log messages between the
KASAN header and the access description?
The parser assumes the exact next newline-delimited block contains the access
description. However, the report_lock used by KASAN internally only serializes
KASAN reports with each other, not with general printk output from other CPUs
or contexts. If another component prints a message precisely between the KASAN
header and the access description, the search bounds might be restricted to an
unrelated interleaved log line, causing memmem() to fail and making the test
flaky.
> +
> + if (!tmp)
> + 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];
[Severity: Medium]
Should buf be initialized before being passed to bpf_prog_test_run_opts()?
Uninitialized stack memory from the test executable is assigned to
topts.data_in and leaked into the kernel space via copy_from_user():
topts.data_in = buf;
ret = bpf_prog_test_run_opts(bpf_program__fd(ctx->prog), &topts);
This uninitialized access can trigger userspace dynamic analysis tools such as
MemorySanitizer (MSAN) when running the BPF selftests, causing spurious test
suite failures.
> + int ret, prog_fd;
> + __u32 info_len;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701-kasan-v3-0-bd09bb942d86@bootlin.com?part=10
^ permalink raw reply [flat|nested] 22+ messages in thread