* [PATCH bpf-next v4 1/8] selftests/bpf: Allow privileged preparation for capability tests
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-18 5:28 ` Kumar Kartikeya Dwivedi
2026-09-18 5:28 ` [PATCH bpf-next v4 2/8] bpf: Record raw memory arguments during argument checking Kumar Kartikeya Dwivedi
` (6 subsequent siblings)
7 siblings, 0 replies; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:28 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, Amery Hung, kkd,
kernel-team
The annotation-driven loader drops capabilities before libbpf prepares an
object. Resolving bpf_testmod kfuncs requires CAP_SYS_ADMIN to enumerate and
open module BTF, so tests without that capability fail before reaching the
verifier.
Add an opt-in __prepare_priv annotation. Call bpf_object__prepare() with the
fixture's initial capabilities, then apply __caps_unpriv before loading the
programs. This uses libbpf's explicit prepare/load boundary. In particular,
CAP_SYS_ADMIN must be dropped along with CAP_PERFMON to test uninitialized
stack checks, since CAP_SYS_ADMIN satisfies the verifier's CAP_PERFMON check.
Preparation also creates maps and loads BTF. Keep it opt-in so existing tests
continue checking those operations with reduced capabilities. The existing
pre-execution callback runs after program loading and is too late for this.
Allow tests retaining CAP_BPF to run when the unprivileged-BPF sysctl is set.
Check CPU mitigations separately: disabled or undetectable mitigations must
still skip these tests, because CAP_BPF does not restore speculative
execution checks.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
tools/testing/selftests/bpf/progs/bpf_misc.h | 9 +++-
tools/testing/selftests/bpf/test_loader.c | 48 ++++++++++++++------
tools/testing/selftests/bpf/unpriv_helpers.c | 16 +++++--
tools/testing/selftests/bpf/unpriv_helpers.h | 2 +
4 files changed, 55 insertions(+), 20 deletions(-)
diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index eb88d9ce6c34..2ced1d751ace 100644
--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
+++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
@@ -9,7 +9,8 @@
#define QUOTE(str) #str
#define EXPAND_QUOTE(str) QUOTE(str)
-/* This set of attributes controls behavior of the
+/*
+ * This set of attributes controls behavior of the
* test_loader.c:test_loader__run_subtests().
*
* The test_loader sequentially loads each program in a skeleton.
@@ -131,6 +132,11 @@
* Several __arch_* annotations could be specified at once.
* When test case is not run on current arch it is marked as skipped.
* __caps_unpriv Specify the capabilities that should be set when running the test.
+ * __prepare_priv In unprivileged mode, prepare the object with the fixture's
+ * initial capabilities before dropping them for program loading.
+ * Preparation includes map creation and BTF/kfunc resolution;
+ * these operations are not tested at the reduced capabilities.
+ * Program loading uses the normal __caps_unpriv selection.
*
* __linear_size Specify the size of the linear area of non-linear skbs, or
* 0 for linear skbs.
@@ -166,6 +172,7 @@
#define __arch_s390x __arch("s390x")
#define __arch_loongarch __arch("LOONGARCH")
#define __caps_unpriv(caps) __test_tag("test_caps_unpriv=" EXPAND_QUOTE(caps))
+#define __prepare_priv __test_tag("test_prepare_priv")
#define __load_if_JITed() __test_tag("load_mode=jited")
#define __load_if_no_JITed() __test_tag("load_mode=no_jited")
#define __stderr(msg) __test_tag("test_expect_stderr=" msg)
diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
index 28724de06322..a6e3fcc1079c 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -33,6 +33,7 @@ static inline const char *str_has_pfx(const char *str, const char *pfx)
#endif
static int sysctl_unpriv_disabled = -1;
+static int unpriv_mitigations_disabled = -1;
enum mode {
PRIV = 1,
@@ -71,6 +72,7 @@ struct test_spec {
int load_mask;
int linear_sz;
const char *skip_reason;
+ bool prepare_priv;
bool auxiliary;
bool valid;
};
@@ -606,6 +608,8 @@ static int parse_test_spec(struct test_loader *tester,
if (err)
goto cleanup;
spec->mode_mask |= UNPRIV;
+ } else if (strcmp(s, "test_prepare_priv") == 0) {
+ spec->prepare_priv = true;
} else if ((val = str_has_pfx(s, "load_mode="))) {
if (strcmp(val, "jited") == 0) {
load_mask = JITED;
@@ -1015,10 +1019,10 @@ struct cap_state {
bool initialized;
};
-static int drop_capabilities(struct cap_state *caps)
+static int drop_capabilities(struct cap_state *caps, __u64 keep_caps)
{
const __u64 caps_to_drop = (1ULL << CAP_SYS_ADMIN | 1ULL << CAP_NET_ADMIN |
- 1ULL << CAP_PERFMON | 1ULL << CAP_BPF);
+ 1ULL << CAP_PERFMON | 1ULL << CAP_BPF) & ~keep_caps;
int err;
err = cap_disable_effective(caps_to_drop, &caps->old_caps);
@@ -1028,6 +1032,13 @@ static int drop_capabilities(struct cap_state *caps)
}
caps->initialized = true;
+ if (keep_caps) {
+ err = cap_enable_effective(keep_caps, NULL);
+ if (err) {
+ PRINT_FAIL("failed to set capabilities: %i, %s\n", err, strerror(-err));
+ return err;
+ }
+ }
return 0;
}
@@ -1048,8 +1059,12 @@ static int restore_capabilities(struct cap_state *caps)
static bool can_execute_unpriv(struct test_loader *tester, struct test_spec *spec)
{
if (sysctl_unpriv_disabled < 0)
- sysctl_unpriv_disabled = get_unpriv_disabled() ? 1 : 0;
- if (sysctl_unpriv_disabled)
+ sysctl_unpriv_disabled = get_unpriv_sysctl_disabled();
+ if (sysctl_unpriv_disabled && !(spec->unpriv.caps & (1ULL << CAP_BPF)))
+ return false;
+ if (unpriv_mitigations_disabled < 0)
+ unpriv_mitigations_disabled = get_unpriv_mitigations_disabled();
+ if (unpriv_mitigations_disabled)
return false;
if ((spec->prog_flags & BPF_F_ANY_ALIGNMENT) && !EFFICIENT_UNALIGNED_ACCESS)
return false;
@@ -1351,17 +1366,8 @@ void run_subtest(struct test_loader *tester,
test__end_subtest();
return;
}
- if (drop_capabilities(&caps)) {
- test__end_subtest();
- return;
- }
- if (subspec->caps) {
- err = cap_enable_effective(subspec->caps, NULL);
- if (err) {
- PRINT_FAIL("failed to set capabilities: %i, %s\n", err, strerror(-err));
- goto subtest_cleanup;
- }
- }
+ if (!spec->prepare_priv && drop_capabilities(&caps, subspec->caps))
+ goto subtest_cleanup;
}
/* Implicitly reset to NULL if next test case doesn't specify.
@@ -1414,6 +1420,18 @@ void run_subtest(struct test_loader *tester,
bpf_object__for_each_map(map, tobj)
bpf_map__set_autocreate(map, !unpriv || is_unpriv_capable_map(map));
+ if (unpriv && spec->prepare_priv) {
+ /*
+ * Module BTF lookup needs CAP_SYS_ADMIN. Allow tests to prepare
+ * their objects first, then verify programs with the requested caps.
+ */
+ err = bpf_object__prepare(tobj);
+ if (!ASSERT_OK(err, "obj_prepare"))
+ goto tobj_cleanup;
+ if (drop_capabilities(&caps, subspec->caps))
+ goto tobj_cleanup;
+ }
+
err = bpf_object__load(tobj);
if (subspec->expect_failure) {
if (!ASSERT_ERR(err, "unexpected_load_success")) {
diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c
index 2c8c5edb8751..c8dd5d848584 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.c
+++ b/tools/testing/selftests/bpf/unpriv_helpers.c
@@ -111,9 +111,8 @@ static int get_mitigations_off(void)
return !enabled_in_config;
}
-bool get_unpriv_disabled(void)
+bool get_unpriv_sysctl_disabled(void)
{
- int mitigations_off;
bool disabled;
char buf[2];
FILE *fd;
@@ -127,8 +126,12 @@ bool get_unpriv_disabled(void)
disabled = true;
}
- if (disabled)
- return true;
+ return disabled;
+}
+
+bool get_unpriv_mitigations_disabled(void)
+{
+ int mitigations_off;
/*
* Some unpriv tests rely on spectre mitigations being on.
@@ -144,6 +147,11 @@ bool get_unpriv_disabled(void)
return mitigations_off;
}
+bool get_unpriv_disabled(void)
+{
+ return get_unpriv_sysctl_disabled() || get_unpriv_mitigations_disabled();
+}
+
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 a7ceb51577cd..c24d53e14f3a 100644
--- a/tools/testing/selftests/bpf/unpriv_helpers.h
+++ b/tools/testing/selftests/bpf/unpriv_helpers.h
@@ -5,5 +5,7 @@
#define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled"
bool get_unpriv_disabled(void);
+bool get_unpriv_sysctl_disabled(void);
+bool get_unpriv_mitigations_disabled(void);
bool get_kasan_jit_enabled(void);
bool get_kasan_multi_shot_enabled(void);
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH bpf-next v4 2/8] bpf: Record raw memory arguments during argument checking
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-18 5:28 ` [PATCH bpf-next v4 1/8] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
@ 2026-09-18 5:28 ` Kumar Kartikeya Dwivedi
2026-09-18 6:35 ` bot+bpf-ci
2026-09-18 5:28 ` [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments Kumar Kartikeya Dwivedi
` (5 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:28 UTC (permalink / raw)
To: bpf
Cc: Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, Tejun Heo, Amery Hung, kkd,
kernel-team
From: Eduard Zingerman <eddyz87@gmail.com>
Record meta->arg_raw_mem.regno in check_func_arg() instead of
check_raw_mode_ok(). This information would be used by the patch
fixing MEM_UNINIT handling for kfuncs further in the series.
For bpf_map_peek_elem() on a bloom filter, resolve_map_arg_type()
does:
case BPF_MAP_TYPE_BLOOM_FILTER:
if (is_helper_call(meta, BPF_FUNC_map_peek_elem))
*arg_type = ARG_PTR_TO_MAP_VALUE;
That replaces the entire type, removing both MEM_UNINIT and MEM_WRITE.
Recording the resolved type makes the later raw-mode clearing
unnecessary.
In the check_mem_size_reg() only clear raw mode for a variable size
when it belongs to the recorded output. An unrelated memory argument
must not discard the output's identity after its initialization size
has been recorded.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6c6b8d8520cd..1d5e4ee64fc0 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -7215,7 +7215,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
* raw mode so that the program is required to initialize all
* the memory that the helper could just partially fill up.
*/
- if (!tnum_is_const(size_reg->var_off))
+ if (!tnum_is_const(size_reg->var_off) &&
+ meta->arg_raw_mem.regno == reg_from_argno(mem_argno))
meta->arg_raw_mem.regno = 0;
if (reg_smin(size_reg) < 0) {
@@ -8929,6 +8930,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
if (err)
return err;
+ if (!meta->btf && (arg_type & MEM_UNINIT) &&
+ (base_type(arg_type) == ARG_PTR_TO_MEM ||
+ base_type(arg_type) == ARG_PTR_TO_MAP_VALUE))
+ meta->arg_raw_mem.regno = slot + 1;
+
if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
err = mark_arg_precision(env, argno);
if (err)
@@ -9018,14 +9024,6 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
return -EFAULT;
}
- /*
- * Disable raw mode for bpf_map_peek_elem() on a bloom filter. The helper reads
- * the value buffer as an input rather than filling it.
- */
- if (is_helper_call(meta, BPF_FUNC_map_peek_elem) &&
- meta->map.ptr->map_type == BPF_MAP_TYPE_BLOOM_FILTER)
- meta->arg_raw_mem.regno = 0;
-
err = check_helper_mem_access(env, reg, argno, meta->map.ptr->value_size,
arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
false, meta, NULL);
@@ -9842,8 +9840,9 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
return -EINVAL;
}
-static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
+static bool check_raw_mode_ok(const struct bpf_func_proto *fn)
{
+ bool seen = false;
int i;
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
@@ -9851,9 +9850,9 @@ static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_a
break;
if (!arg_type_is_raw_mem(fn->arg_type[i]))
continue;
- if (meta->arg_raw_mem.regno)
+ if (seen)
return false;
- meta->arg_raw_mem.regno = i + 1;
+ seen = true;
}
return true;
@@ -9985,7 +9984,7 @@ static int check_func_proto(struct bpf_verifier_env *env, const struct bpf_func_
struct bpf_call_arg_meta *meta)
{
return check_arg_prog_aux(env, fn) &&
- check_raw_mode_ok(fn, meta) &&
+ check_raw_mode_ok(fn) &&
check_arg_pair_ok(fn) &&
check_mem_arg_rw_flag_ok(fn) &&
check_proto_release_reg(fn, meta) &&
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 2/8] bpf: Record raw memory arguments during argument checking
2026-09-18 5:28 ` [PATCH bpf-next v4 2/8] bpf: Record raw memory arguments during argument checking Kumar Kartikeya Dwivedi
@ 2026-09-18 6:35 ` bot+bpf-ci
2026-09-18 16:24 ` Eduard Zingerman
0 siblings, 1 reply; 18+ messages in thread
From: bot+bpf-ci @ 2026-09-18 6:35 UTC (permalink / raw)
To: memxor, bpf
Cc: eddyz87, ast, andrii, daniel, emil, tj, ameryhung, kkd,
kernel-team, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2771 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6c6b8d8520cdf..1d5e4ee64fc00 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -7215,7 +7215,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
> * raw mode so that the program is required to initialize all
> * the memory that the helper could just partially fill up.
> */
> - if (!tnum_is_const(size_reg->var_off))
> + if (!tnum_is_const(size_reg->var_off) &&
> + meta->arg_raw_mem.regno == reg_from_argno(mem_argno))
> meta->arg_raw_mem.regno = 0;
This isn't a bug, but would it help to say which helpers this actually
changes (bpf_strtol/bpf_strtoul/bpf_kallsyms_lookup_name, where the
unrelated variable-length input is checked before the output is recorded)
and that the effect is to stop withdrawing raw mode from their output
buffer?
The commit message states that an unrelated memory argument must not
discard the output's identity after its initialization size has been
recorded. Scanning the existing helper protos, the three that have both
an unrelated variable-size pair and a MEM_UNINIT output all place the
variable-size input before the output:
bpf_strtol_proto / bpf_strtoul_proto (kernel/bpf/helpers.c)
bpf_kallsyms_lookup_name_proto (kernel/bpf/syscall.c)
.arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
.arg2_type = ARG_MEM_SIZE[_OR_ZERO],
.arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | ...
So the observable effect today is the opposite ordering: a variable
buf_len / name_sz no longer withdraws raw mode from the arg4 output
buffer.
[ ... ]
> @@ -8929,6 +8930,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
> if (err)
> return err;
>
> + if (!meta->btf && (arg_type & MEM_UNINIT) &&
> + (base_type(arg_type) == ARG_PTR_TO_MEM ||
> + base_type(arg_type) == ARG_PTR_TO_MAP_VALUE))
> + meta->arg_raw_mem.regno = slot + 1;
This isn't a bug, but could this reuse arg_type_is_raw_mem() (defined at
verifier.c:8155) so the recording site and check_raw_mode_ok() share one
definition of a raw memory argument, or are the two predicates meant to
diverge later in the series?
The new predicate is term-for-term the body of arg_type_is_raw_mem():
static bool arg_type_is_raw_mem(enum bpf_arg_type type)
{
return (base_type(type) == ARG_PTR_TO_MEM ||
base_type(type) == ARG_PTR_TO_MAP_VALUE) &&
type & MEM_UNINIT;
}
which is still used by check_raw_mode_ok().
[ ... ]
---
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/35311871659
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 2/8] bpf: Record raw memory arguments during argument checking
2026-09-18 6:35 ` bot+bpf-ci
@ 2026-09-18 16:24 ` Eduard Zingerman
0 siblings, 0 replies; 18+ messages in thread
From: Eduard Zingerman @ 2026-09-18 16:24 UTC (permalink / raw)
To: bot+bpf-ci, memxor, bpf
Cc: ast, andrii, daniel, emil, tj, ameryhung, kkd, kernel-team,
martin.lau, yonghong.song, mason, ihor.solodrai
On Fri, 2026-09-18 at 06:35 +0000, bot+bpf-ci@kernel.org wrote:
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 6c6b8d8520cdf..1d5e4ee64fc00 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
>
> [ ... ]
>
> > @@ -7215,7 +7215,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
> > * raw mode so that the program is required to initialize all
> > * the memory that the helper could just partially fill up.
> > */
> > - if (!tnum_is_const(size_reg->var_off))
> > + if (!tnum_is_const(size_reg->var_off) &&
> > + meta->arg_raw_mem.regno == reg_from_argno(mem_argno))
> > meta->arg_raw_mem.regno = 0;
>
> This isn't a bug, but would it help to say which helpers this actually
> changes (bpf_strtol/bpf_strtoul/bpf_kallsyms_lookup_name, where the
> unrelated variable-length input is checked before the output is recorded)
> and that the effect is to stop withdrawing raw mode from their output
> buffer?
>
> The commit message states that an unrelated memory argument must not
> discard the output's identity after its initialization size has been
> recorded. Scanning the existing helper protos, the three that have both
> an unrelated variable-size pair and a MEM_UNINIT output all place the
> variable-size input before the output:
>
> bpf_strtol_proto / bpf_strtoul_proto (kernel/bpf/helpers.c)
> bpf_kallsyms_lookup_name_proto (kernel/bpf/syscall.c)
> .arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
> .arg2_type = ARG_MEM_SIZE[_OR_ZERO],
> .arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | ...
>
> So the observable effect today is the opposite ordering: a variable
> buf_len / name_sz no longer withdraws raw mode from the arg4 output
> buffer.
Extending the commit message might make sense.
> [ ... ]
>
> > @@ -8929,6 +8930,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
> > if (err)
> > return err;
> >
> > + if (!meta->btf && (arg_type & MEM_UNINIT) &&
> > + (base_type(arg_type) == ARG_PTR_TO_MEM ||
> > + base_type(arg_type) == ARG_PTR_TO_MAP_VALUE))
> > + meta->arg_raw_mem.regno = slot + 1;
>
> This isn't a bug, but could this reuse arg_type_is_raw_mem() (defined at
> verifier.c:8155) so the recording site and check_raw_mode_ok() share one
> definition of a raw memory argument, or are the two predicates meant to
> diverge later in the series?
>
> The new predicate is term-for-term the body of arg_type_is_raw_mem():
>
> static bool arg_type_is_raw_mem(enum bpf_arg_type type)
> {
> return (base_type(type) == ARG_PTR_TO_MEM ||
> base_type(type) == ARG_PTR_TO_MAP_VALUE) &&
> type & MEM_UNINIT;
> }
>
> which is still used by check_raw_mode_ok().
Let's indeed reuse the helper.
...
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-18 5:28 ` [PATCH bpf-next v4 1/8] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
2026-09-18 5:28 ` [PATCH bpf-next v4 2/8] bpf: Record raw memory arguments during argument checking Kumar Kartikeya Dwivedi
@ 2026-09-18 5:28 ` Kumar Kartikeya Dwivedi
2026-09-18 5:50 ` sashiko-bot
2026-09-18 5:28 ` [PATCH bpf-next v4 4/8] selftests/bpf: Cover helper memory access permissions Kumar Kartikeya Dwivedi
` (4 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:28 UTC (permalink / raw)
To: bpf
Cc: Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, Tejun Heo, Amery Hung, kkd,
kernel-team
From: Eduard Zingerman <eddyz87@gmail.com>
MEM_WRITE without MEM_UNINIT denotes memory that may be read as well as
written. Helper argument checking currently requests only BPF_WRITE, which
checks stack initialization but omits read permission checks on map values.
For example, bpf_check_mtu() reads its mtu_len argument before overwriting it,
but the verifier permits that argument to point into a BPF_F_WRONLY_PROG map.
Derive generic memory access from argument flags: read-only for inputs,
write-only for MEM_WRITE | MEM_UNINIT, and read/write for MEM_WRITE alone.
Encode MEM_WRITE when classifying kfunc memory arguments and resolving
scalar-only struct pointers, replacing the kfunc-specific access override.
This rejects write-only map values passed to helper arguments that require
initialized writable memory. Generic kfunc __uninit output bookkeeping is
completed in a following patch.
Fixes: 8ea607330a39 ("bpf: Fix overloading of MEM_UNINIT's meaning")
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1d5e4ee64fc0..21eb806b1351 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -8886,6 +8886,15 @@ static int process_map_ptr_arg(struct bpf_verifier_env *env, struct bpf_reg_stat
return 0;
}
+static enum bpf_access_type func_arg_access_type(enum bpf_arg_type arg_type)
+{
+ if (!(arg_type & MEM_WRITE))
+ return BPF_READ;
+ if (arg_type & MEM_UNINIT)
+ return BPF_WRITE;
+ return BPF_READ | BPF_WRITE;
+}
+
static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 prev_slot,
struct bpf_call_arg_meta *meta,
int insn_idx)
@@ -9180,9 +9189,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
if (!(arg_type & MEM_FIXED_SIZE))
break;
- access_type = arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ;
- if (meta->btf)
- access_type = BPF_READ | BPF_WRITE;
+ access_type = func_arg_access_type(arg_type);
err = check_mem_reg(env, reg, argno, arg_size, access_type, meta, &known_memory);
if (err < 0) {
@@ -9228,9 +9235,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
if (meta->btf && bpf_register_is_null(buff_reg))
break;
- access_type = fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ;
- if (meta->btf)
- access_type = BPF_READ | BPF_WRITE;
+ access_type = func_arg_access_type(fn->arg_type[arg - 1]);
zero_size_allowed = meta->btf || base_type(arg_type) == ARG_MEM_SIZE_OR_ZERO;
@@ -12436,7 +12441,8 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env,
PTR_ERR(resolve_ret));
return -EINVAL;
}
- *arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | (*arg_type & PTR_MAYBE_NULL);
+ *arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE |
+ (*arg_type & PTR_MAYBE_NULL);
return 0;
}
@@ -12933,7 +12939,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
return -EINVAL;
}
- arg_type = ARG_PTR_TO_MEM;
+ arg_type = ARG_PTR_TO_MEM | MEM_WRITE;
} else if (btf_type_is_struct(ref_t))
/* A pointer to a struct without a size argument is classified as ARG_PTR_TO_BTF_ID */
arg_type = ARG_PTR_TO_BTF_ID;
@@ -12958,7 +12964,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
return -EINVAL;
}
proto->arg_size[arg] = type_size;
- arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
+ arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE;
}
if (is_kfunc_arg_uninit(meta->btf, &args[arg]))
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments
2026-09-18 5:28 ` [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments Kumar Kartikeya Dwivedi
@ 2026-09-18 5:50 ` sashiko-bot
2026-09-18 17:46 ` Eduard Zingerman
2026-09-18 18:01 ` Amery Hung
0 siblings, 2 replies; 18+ messages in thread
From: sashiko-bot @ 2026-09-18 5:50 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] bpf: MEM_WRITE without MEM_UNINIT enforces BPF_READ breaking output helpers
--
commit aebadfb01a3b32540321fe2b60bcb6fb1c8673be
Author: Eduard Zingerman <eddyz87@gmail.com>
bpf: Check read access for initialized writable memory arguments
This commit updates the BPF verifier to derive memory access types based on
argument flags, correctly enforcing read access for writable memory arguments
that are already initialized. The change addresses an issue where helper
argument checking previously omitted read permission checks on map values. It
adds a generic func_arg_access_type() helper to replace the kfunc-specific
access overrides.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 1d5e4ee64fc00..21eb806b1351c 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -8886,6 +8886,15 @@ static int process_map_ptr_arg(struct bpf_verifier_env *env, struct bpf_reg_stat
> return 0;
> }
>
> +static enum bpf_access_type func_arg_access_type(enum bpf_arg_type arg_type)
> +{
> + if (!(arg_type & MEM_WRITE))
> + return BPF_READ;
> + if (arg_type & MEM_UNINIT)
> + return BPF_WRITE;
> + return BPF_READ | BPF_WRITE;
> +}
[Severity: Critical]
Does this regression break pure output helpers that lack the MEM_UNINIT flag?
For example, bpf_snprintf() is defined as an output helper:
kernel/bpf/helpers.c:
const struct bpf_func_proto bpf_snprintf_proto = {
...
.arg1_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
...
};
Because bpf_snprintf() does not guarantee full buffer initialization, it
cannot be safely marked with MEM_UNINIT. However, with this change, the lack
of MEM_UNINIT means func_arg_access_type() will return BPF_READ | BPF_WRITE
instead of just BPF_WRITE.
Will this inadvertently force check_helper_mem_access() to reject valid
write-only maps (BPF_F_WRONLY_PROG) when passed to such output helpers, causing
valid BPF programs to fail verification?
Additionally, for unprivileged programs lacking CAP_PERFMON, won't this
erroneously enforce stack buffer pre-initialization by enforcing a read check
requiring stack initialization in check_stack_range_initialized()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260918052906.12226-1-memxor@gmail.com?part=3
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments
2026-09-18 5:50 ` sashiko-bot
@ 2026-09-18 17:46 ` Eduard Zingerman
2026-09-18 18:01 ` Amery Hung
1 sibling, 0 replies; 18+ messages in thread
From: Eduard Zingerman @ 2026-09-18 17:46 UTC (permalink / raw)
To: sashiko-reviews, Kumar Kartikeya Dwivedi; +Cc: bpf
On Fri, 2026-09-18 at 05:50 +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Critical] bpf: MEM_WRITE without MEM_UNINIT enforces BPF_READ breaking output helpers
> --
>
> commit aebadfb01a3b32540321fe2b60bcb6fb1c8673be
> Author: Eduard Zingerman <eddyz87@gmail.com>
>
> bpf: Check read access for initialized writable memory arguments
>
> This commit updates the BPF verifier to derive memory access types based on
> argument flags, correctly enforcing read access for writable memory arguments
> that are already initialized. The change addresses an issue where helper
> argument checking previously omitted read permission checks on map values. It
> adds a generic func_arg_access_type() helper to replace the kfunc-specific
> access overrides.
>
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 1d5e4ee64fc00..21eb806b1351c 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -8886,6 +8886,15 @@ static int process_map_ptr_arg(struct bpf_verifier_env *env, struct bpf_reg_stat
> > return 0;
> > }
> >
> > +static enum bpf_access_type func_arg_access_type(enum bpf_arg_type arg_type)
> > +{
> > + if (!(arg_type & MEM_WRITE))
> > + return BPF_READ;
> > + if (arg_type & MEM_UNINIT)
> > + return BPF_WRITE;
> > + return BPF_READ | BPF_WRITE;
> > +}
>
> [Severity: Critical]
> Does this regression break pure output helpers that lack the MEM_UNINIT flag?
>
> For example, bpf_snprintf() is defined as an output helper:
>
> kernel/bpf/helpers.c:
> const struct bpf_func_proto bpf_snprintf_proto = {
> ...
> .arg1_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
> ...
> };
>
> Because bpf_snprintf() does not guarantee full buffer initialization, it
> cannot be safely marked with MEM_UNINIT. However, with this change, the lack
> of MEM_UNINIT means func_arg_access_type() will return BPF_READ | BPF_WRITE
> instead of just BPF_WRITE.
>
> Will this inadvertently force check_helper_mem_access() to reject valid
> write-only maps (BPF_F_WRONLY_PROG) when passed to such output helpers, causing
> valid BPF programs to fail verification?
I think it should be safe to relax MEM_UNINIT interpretation for priv
and not to require full buffer initialization. That would be only a
documentation change, as far as I understand. Then it would be fine to
put MEM_UNINIT for snprintf. For unpriv and MEM_UNINIT buffer we can
scratch the buffer as a combination of BPF_MISC (for previously filled parts) /
BPF_INVALID (for previously invalid parts) instead of requiring full initialization.
Kartikeya, could you please take a look if this requires any code changes?
> Additionally, for unprivileged programs lacking CAP_PERFMON, won't this
> erroneously enforce stack buffer pre-initialization by enforcing a read check
> requiring stack initialization in check_stack_range_initialized()?
But that's not an error. If the helper reads the memory, there is no
MEM_UNINIT annotation and we pass a pointer to uninitiated memory
buffer to the program, then such a program should be rejected.
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments
2026-09-18 5:50 ` sashiko-bot
2026-09-18 17:46 ` Eduard Zingerman
@ 2026-09-18 18:01 ` Amery Hung
1 sibling, 0 replies; 18+ messages in thread
From: Amery Hung @ 2026-09-18 18:01 UTC (permalink / raw)
To: sashiko-reviews; +Cc: Kumar Kartikeya Dwivedi, bpf
On Thu, Sep 17, 2026 at 10:50 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Critical] bpf: MEM_WRITE without MEM_UNINIT enforces BPF_READ breaking output helpers
> --
>
> commit aebadfb01a3b32540321fe2b60bcb6fb1c8673be
> Author: Eduard Zingerman <eddyz87@gmail.com>
>
> bpf: Check read access for initialized writable memory arguments
>
> This commit updates the BPF verifier to derive memory access types based on
> argument flags, correctly enforcing read access for writable memory arguments
> that are already initialized. The change addresses an issue where helper
> argument checking previously omitted read permission checks on map values. It
> adds a generic func_arg_access_type() helper to replace the kfunc-specific
> access overrides.
>
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 1d5e4ee64fc00..21eb806b1351c 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -8886,6 +8886,15 @@ static int process_map_ptr_arg(struct bpf_verifier_env *env, struct bpf_reg_stat
> > return 0;
> > }
> >
> > +static enum bpf_access_type func_arg_access_type(enum bpf_arg_type arg_type)
> > +{
> > + if (!(arg_type & MEM_WRITE))
> > + return BPF_READ;
> > + if (arg_type & MEM_UNINIT)
> > + return BPF_WRITE;
> > + return BPF_READ | BPF_WRITE;
> > +}
>
> [Severity: Critical]
> Does this regression break pure output helpers that lack the MEM_UNINIT flag?
This is a valid concern.
I still think we should assign BPF_READ | BPF_WRITE to kfunc arguments
when generating the prototype, and only BPF_WRITE for __uninit
arguments. This would also avoid the need for func_arg_access_type().
>
> For example, bpf_snprintf() is defined as an output helper:
>
> kernel/bpf/helpers.c:
> const struct bpf_func_proto bpf_snprintf_proto = {
> ...
> .arg1_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
> ...
> };
>
> Because bpf_snprintf() does not guarantee full buffer initialization, it
> cannot be safely marked with MEM_UNINIT. However, with this change, the lack
> of MEM_UNINIT means func_arg_access_type() will return BPF_READ | BPF_WRITE
> instead of just BPF_WRITE.
>
> Will this inadvertently force check_helper_mem_access() to reject valid
> write-only maps (BPF_F_WRONLY_PROG) when passed to such output helpers, causing
> valid BPF programs to fail verification?
>
> Additionally, for unprivileged programs lacking CAP_PERFMON, won't this
> erroneously enforce stack buffer pre-initialization by enforcing a read check
> requiring stack initialization in check_stack_range_initialized()?
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260918052906.12226-1-memxor@gmail.com?part=3
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v4 4/8] selftests/bpf: Cover helper memory access permissions
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (2 preceding siblings ...)
2026-09-18 5:28 ` [PATCH bpf-next v4 3/8] bpf: Check read access for initialized writable memory arguments Kumar Kartikeya Dwivedi
@ 2026-09-18 5:28 ` Kumar Kartikeya Dwivedi
2026-09-18 6:17 ` bot+bpf-ci
2026-09-18 5:28 ` [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (3 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:28 UTC (permalink / raw)
To: bpf
Cc: Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, Tejun Heo, Amery Hung, kkd,
kernel-team
From: Eduard Zingerman <eddyz87@gmail.com>
Check that fixed-size and sized helper in/out buffers require both read
and write permission. Cover bpf_check_mtu() and bpf_fib_lookup(), including
read/write buffers as positive controls.
Keep rejecting read-only buffers for bpf_check_mtu(), and verify that a
write-only map remains a valid destination for bpf_get_current_comm(),
whose output is annotated MEM_UNINIT.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../progs/verifier_helper_access_var_len.c | 41 ++++++++++
.../selftests/bpf/progs/verifier_mtu.c | 75 +++++++++++++++++++
2 files changed, 116 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
index d1452ef6f2f9..6d2c6aec129f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
@@ -822,4 +822,45 @@ __naked void bytes_no_leak_init_memory(void)
: __clobber_all);
}
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __uint(map_flags, BPF_F_WRONLY_PROG);
+ __type(key, __u32);
+ __type(value, struct bpf_fib_lookup);
+} map_fib_wo SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct bpf_fib_lookup);
+} map_fib_rw SEC(".maps");
+
+SEC("tc")
+__failure __msg("read from map forbidden")
+int writeonly_sized_input(struct __sk_buff *ctx)
+{
+ struct bpf_fib_lookup *params;
+ __u32 key = 0;
+
+ params = bpf_map_lookup_elem(&map_fib_wo, &key);
+ if (params)
+ bpf_fib_lookup(ctx, params, sizeof(*params), 0);
+ return 0;
+}
+
+SEC("tc")
+__success
+int readwrite_sized_input(struct __sk_buff *ctx)
+{
+ struct bpf_fib_lookup *params;
+ __u32 key = 0;
+
+ params = bpf_map_lookup_elem(&map_fib_rw, &key);
+ if (params)
+ bpf_fib_lookup(ctx, params, sizeof(*params), 0);
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_mtu.c b/tools/testing/selftests/bpf/progs/verifier_mtu.c
index 256956ea1ac5..af89ce3d30f7 100644
--- a/tools/testing/selftests/bpf/progs/verifier_mtu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_mtu.c
@@ -17,4 +17,79 @@ int tc_uninit_mtu(struct __sk_buff *ctx)
return TCX_PASS;
}
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __uint(map_flags, BPF_F_WRONLY_PROG);
+ __type(key, __u32);
+ __type(value, __u32);
+} map_mtu_wo SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u32);
+} map_mtu_rw SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __uint(map_flags, BPF_F_RDONLY_PROG);
+ __type(key, __u32);
+ __type(value, __u32);
+} map_mtu_ro SEC(".maps");
+
+SEC("tc/ingress")
+__failure __msg("read from map forbidden")
+int tc_writeonly_mtu(struct __sk_buff *ctx)
+{
+ __u32 key = 0;
+ __u32 *mtu;
+
+ mtu = bpf_map_lookup_elem(&map_mtu_wo, &key);
+ if (mtu)
+ bpf_check_mtu(ctx, 0, mtu, 0, 0);
+ return TCX_PASS;
+}
+
+SEC("tc/ingress")
+__success
+int tc_readwrite_mtu(struct __sk_buff *ctx)
+{
+ __u32 key = 0;
+ __u32 *mtu;
+
+ mtu = bpf_map_lookup_elem(&map_mtu_rw, &key);
+ if (mtu)
+ bpf_check_mtu(ctx, 0, mtu, 0, 0);
+ return TCX_PASS;
+}
+
+SEC("tc/ingress")
+__failure __msg("write into map forbidden")
+int tc_readonly_mtu(struct __sk_buff *ctx)
+{
+ __u32 key = 0;
+ __u32 *mtu;
+
+ mtu = bpf_map_lookup_elem(&map_mtu_ro, &key);
+ if (mtu)
+ bpf_check_mtu(ctx, 0, mtu, 0, 0);
+ return TCX_PASS;
+}
+
+SEC("tc/ingress")
+__success
+int tc_writeonly_output(struct __sk_buff *ctx)
+{
+ __u32 key = 0;
+ __u32 *out;
+
+ out = bpf_map_lookup_elem(&map_mtu_wo, &key);
+ if (out)
+ bpf_get_current_comm(out, sizeof(*out));
+ return TCX_PASS;
+}
+
char LICENSE[] SEC("license") = "GPL";
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 4/8] selftests/bpf: Cover helper memory access permissions
2026-09-18 5:28 ` [PATCH bpf-next v4 4/8] selftests/bpf: Cover helper memory access permissions Kumar Kartikeya Dwivedi
@ 2026-09-18 6:17 ` bot+bpf-ci
0 siblings, 0 replies; 18+ messages in thread
From: bot+bpf-ci @ 2026-09-18 6:17 UTC (permalink / raw)
To: memxor, bpf
Cc: eddyz87, ast, andrii, daniel, emil, tj, ameryhung, kkd,
kernel-team, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2461 bytes --]
> Check that fixed-size and sized helper in/out buffers require both read
> and write permission. Cover bpf_check_mtu() and bpf_fib_lookup(), including
> read/write buffers as positive controls.
>
> Keep rejecting read-only buffers for bpf_check_mtu(), and verify that a
> write-only map remains a valid destination for bpf_get_current_comm(),
> whose output is annotated MEM_UNINIT.
Does the first sentence accurately describe the test coverage added?
For the fixed-size buffer case using bpf_check_mtu() (arg3 =
ARG_PTR_TO_FIXED_SIZE_MEM | MEM_WRITE | MEM_ALIGNED), verifier_mtu.c adds
all three test variants:
- map_mtu_wo (write-only map) -> expects "read from map forbidden"
- map_mtu_ro (read-only map) -> expects "write into map forbidden"
- map_mtu_rw (read-write map) -> __success
This covers both the read permission requirement and the write permission
requirement for fixed-size buffers.
For the sized buffer case using bpf_fib_lookup() (arg2 = ARG_PTR_TO_MEM |
MEM_WRITE with arg3 = ARG_MEM_SIZE), verifier_helper_access_var_len.c only
adds:
- map_fib_wo (write-only map with BPF_F_WRONLY_PROG) -> expects "read from
map forbidden"
- map_fib_rw (plain array) -> __success
There is no map_fib_ro test with a read-only map (BPF_F_RDONLY_PROG)
expecting "write into map forbidden" to mirror tc_readonly_mtu, so the
write-permission half of the claim is not exercised on the sized path even
though check_mem_size_reg() runs the same BPF_READ-then-BPF_WRITE pair as
check_mem_reg():
if (access_type & BPF_READ)
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
BPF_READ, zero_size_allowed, meta, NULL);
if (!err && access_type & BPF_WRITE)
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
BPF_WRITE, zero_size_allowed, meta, NULL);
Could you either add a BPF_F_RDONLY_PROG fib map test expecting "write into
map forbidden" (mirroring tc_readonly_mtu), or narrow the first sentence to
say the read-permission requirement is what is newly covered for the sized
case (the later paragraph already scopes read-only rejection to
bpf_check_mtu() only)?
---
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/35311871659
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (3 preceding siblings ...)
2026-09-18 5:28 ` [PATCH bpf-next v4 4/8] selftests/bpf: Cover helper memory access permissions Kumar Kartikeya Dwivedi
@ 2026-09-18 5:28 ` Kumar Kartikeya Dwivedi
2026-09-18 6:35 ` bot+bpf-ci
2026-09-18 17:50 ` Eduard Zingerman
2026-09-18 5:29 ` [PATCH bpf-next v4 6/8] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
` (2 subsequent siblings)
7 siblings, 2 replies; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:28 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, Amery Hung, kkd, kernel-team
Stack liveness treats __uninit kfunc arguments as writes. Even with
write-only access checks, ordinary clobber handling leaves poisoned stack
bytes poisoned after the call, so the output cannot be read.
Reuse the helper output descriptor for generic kfunc buffers. Record the
output after resolving its memory type, and initialize its stack bytes
only after all arguments have been checked. This prevents an output from
making an aliased, uninitialized input appear valid.
Track the output by its ABI argument slot, since a kfunc pointer may be
passed on the stack or follow a multi-slot by-value argument. Keep the
single-output restriction in prototype validation; multiple-output
tracking is a separate extension.
Document that output buffers must be fully initialized on every return
path, including error returns and padding.
Fixes: 2cb27158adb3 ("bpf: poison dead stack slots")
Reported-by: Tejun Heo <tj@kernel.org>
Link: https://lore.kernel.org/bpf/86d966ec88bbf27d21b2bb4e18c8aa00@kernel.org
Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
Documentation/bpf/kfuncs.rst | 27 ++++++++++----
include/linux/bpf_verifier.h | 7 ++--
kernel/bpf/verifier.c | 71 +++++++++++++++++++++++++++---------
3 files changed, 78 insertions(+), 27 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 6c2c048dccef..71fb0ca72d69 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -164,19 +164,32 @@ suffix should be used.
2.3.3 __uninit Annotation
-------------------------
-This annotation is used to indicate that the argument will be treated as
-uninitialized.
+Use ``__uninit`` on a pointer parameter for an output that the kfunc
+initializes without reading its incoming contents.
-An example is given below::
+For generic memory buffers, the kfunc must initialize every byte in the
+declared range on every return path, including error returns and struct
+padding. The range is determined by the pointed-to type or the associated
+``__sz`` or ``__szk`` size argument.
+
+The annotation does not change the accepted pointer types. A stack-backed
+struct passed as a generic memory buffer must still be scalar-only.
+
+A stack buffer with a verifier-known constant offset and size may be
+uninitialized before the call and is considered initialized afterwards.
+For variable offsets or sizes, callers with neither ``CAP_PERFMON`` nor
+``CAP_SYS_ADMIN`` must initialize the potentially accessed stack range before
+the call.
+
+For dynptr parameters, ``__uninit`` indicates that the kfunc constructs a
+dynptr in the supplied storage. For example::
- __bpf_kfunc int bpf_dynptr_from_skb(..., struct bpf_dynptr_kern *ptr__uninit)
+ __bpf_kfunc int bpf_dynptr_from_skb(..., struct bpf_dynptr *ptr__uninit)
{
...
}
-Here, the dynptr will be treated as an uninitialized dynptr. Without this
-annotation, the verifier will reject the program if the dynptr passed in is
-not initialized.
+Without this annotation, a dynptr argument must already be initialized.
2.3.4 __nullable Annotation
---------------------------
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index cf85141ea167..6ff1c227d298 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1548,10 +1548,11 @@ struct ref_obj_desc {
/*
* A memory argument a call fills in. The verifier allows the stack to be uninitialized if
- * the range is a known constant. Stack slots are marked as STACK_MISC by check_mem_access().
+ * the range is a known constant. Stack slots are marked as STACK_MISC by check_mem_access()
+ * after all arguments have been checked.
*/
struct arg_raw_mem_desc {
- u8 regno;
+ u8 argno; /* One-based ABI argument slot; zero means no output. */
int size;
};
@@ -1579,6 +1580,7 @@ struct bpf_call_arg_meta {
struct bpf_dynptr_desc dynptr;
struct ref_obj_desc ref_obj;
struct ret_mem_desc ret_mem;
+ struct arg_raw_mem_desc arg_raw_mem;
/* Only set by kfunc */
bool r0_rdonly;
@@ -1617,7 +1619,6 @@ struct bpf_call_arg_meta {
s64 const_map_key;
struct btf *ret_btf;
struct btf_field *kptr_field;
- struct arg_raw_mem_desc arg_raw_mem;
};
int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 21eb806b1351..d327b5356d53 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -302,6 +302,12 @@ static int arg_idx_from_argno(argno_t a)
return arg_from_argno(a) - 1;
}
+/* Normalize helper register numbers and kfunc argument numbers to ABI slots. */
+static u32 arg_slot_from_argno(argno_t a)
+{
+ return abs(a.argno) - 1;
+}
+
static const char *btf_type_name(const struct btf *btf, u32 id)
{
return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
@@ -6981,7 +6987,7 @@ static int check_stack_range_initialized(
*/
bool allow_poison = access_size < 0 || clobber;
/* The call will initialize the memory; uninitialized stack allowed */
- bool raw_mode = meta && meta->arg_raw_mem.regno == reg_from_argno(argno);
+ bool raw_mode = meta && meta->arg_raw_mem.argno == arg_slot_from_argno(argno) + 1;
access_size = abs(access_size);
@@ -7216,8 +7222,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
* the memory that the helper could just partially fill up.
*/
if (!tnum_is_const(size_reg->var_off) &&
- meta->arg_raw_mem.regno == reg_from_argno(mem_argno))
- meta->arg_raw_mem.regno = 0;
+ meta->arg_raw_mem.argno == arg_slot_from_argno(mem_argno) + 1)
+ meta->arg_raw_mem.argno = 0;
if (reg_smin(size_reg) < 0) {
verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
@@ -8158,9 +8164,12 @@ static bool arg_type_is_raw_mem(enum bpf_arg_type type)
* A map value output buffer (e.g. bpf_map_pop_elem) is also a raw
* (uninitialized) memory argument, and like ARG_PTR_TO_MEM it may be
* passed as a PTR_TO_STACK that reaches check_stack_range_initialized().
+ * A kfunc's struct pointer remains ARG_PTR_TO_BTF_ID until call argument
+ * checking resolves it to generic memory, so include it in proto validation.
*/
return (base_type(type) == ARG_PTR_TO_MEM ||
- base_type(type) == ARG_PTR_TO_MAP_VALUE) &&
+ base_type(type) == ARG_PTR_TO_MAP_VALUE ||
+ base_type(type) == ARG_PTR_TO_BTF_ID) &&
type & MEM_UNINIT;
}
@@ -8939,10 +8948,10 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
if (err)
return err;
- if (!meta->btf && (arg_type & MEM_UNINIT) &&
+ if ((arg_type & MEM_UNINIT) &&
(base_type(arg_type) == ARG_PTR_TO_MEM ||
base_type(arg_type) == ARG_PTR_TO_MAP_VALUE))
- meta->arg_raw_mem.regno = slot + 1;
+ meta->arg_raw_mem.argno = slot + 1;
if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
err = mark_arg_precision(env, argno);
@@ -9553,6 +9562,33 @@ static int check_func_args(struct bpf_verifier_env *env, struct bpf_call_arg_met
return 0;
}
+static int mark_raw_stack(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
+ int insn_idx)
+{
+ struct bpf_func_state *caller = cur_func(env);
+ struct bpf_reg_state *reg;
+ u32 slot = meta->arg_raw_mem.argno - 1;
+ int i, err;
+
+ if (!meta->arg_raw_mem.size)
+ return 0;
+ reg = get_func_arg_reg(caller, cur_regs(env), slot);
+
+ /*
+ * Validate every argument before initializing outputs: an input argument
+ * may alias an output buffer. Use the normal stack-write checks to discard
+ * stale spills and preserve the rules for special stack objects.
+ */
+ for (i = 0; i < meta->arg_raw_mem.size; i++) {
+ err = check_mem_access(env, insn_idx, reg, argno_from_arg(slot + 1), i, BPF_B,
+ BPF_WRITE, -1, false, false);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static bool may_update_sockmap(struct bpf_verifier_env *env, int func_id)
{
enum bpf_attach_type eatype = env->prog->expected_attach_type;
@@ -11576,16 +11612,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
regs = cur_regs(env);
- /* Mark slots with STACK_MISC in case of raw mode, stack offset
- * is inferred from register state.
- */
- for (i = 0; i < meta.arg_raw_mem.size; i++) {
- err = check_mem_access(env, insn_idx, regs + meta.arg_raw_mem.regno,
- argno_from_reg(meta.arg_raw_mem.regno), i, BPF_B,
- BPF_WRITE, -1, false, false);
- if (err)
- return err;
- }
+ err = mark_raw_stack(env, &meta, insn_idx);
+ if (err)
+ return err;
if (meta.release_regno) {
struct bpf_reg_state *reg = ®s[meta.release_regno];
@@ -12442,7 +12471,7 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env,
return -EINVAL;
}
*arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE |
- (*arg_type & PTR_MAYBE_NULL);
+ (*arg_type & (PTR_MAYBE_NULL | MEM_UNINIT));
return 0;
}
@@ -13068,6 +13097,10 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg
return -ENOTSUPP;
}
+ if (!check_raw_mode_ok(proto)) {
+ verbose(env, "multiple __uninit buffers are not supported\n");
+ return -EINVAL;
+ }
return check_arg_prog_aux(env, proto) ? 0 : -EINVAL;
}
@@ -14145,6 +14178,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err < 0)
return err;
+ err = mark_raw_stack(env, &meta, insn_idx);
+ if (err)
+ return err;
+
if ((is_bpf_obj_drop_kfunc(meta.func_id) ||
is_bpf_percpu_obj_drop_kfunc(meta.func_id)) && (is_tracing_prog_type(prog_type) ||
/* is_tracing_prog_type() for now doesn't cover non-iterator tracing progs. */
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers
2026-09-18 5:28 ` [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-18 6:35 ` bot+bpf-ci
2026-09-18 17:50 ` Eduard Zingerman
1 sibling, 0 replies; 18+ messages in thread
From: bot+bpf-ci @ 2026-09-18 6:35 UTC (permalink / raw)
To: memxor, bpf
Cc: tj, eddyz87, ast, andrii, daniel, emil, ameryhung, kkd,
kernel-team, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3750 bytes --]
> diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
> index 6c2c048dccef6..71fb0ca72d698 100644
> --- a/Documentation/bpf/kfuncs.rst
> +++ b/Documentation/bpf/kfuncs.rst
> @@ -164,19 +164,32 @@ suffix should be used.
> 2.3.3 __uninit Annotation
> -------------------------
>
> -This annotation is used to indicate that the argument will be treated as
> -uninitialized.
> +Use ``__uninit`` on a pointer parameter for an output that the kfunc
> +initializes without reading its incoming contents.
> +
> +For generic memory buffers, the kfunc must initialize every byte in the
> +declared range on every return path, including error returns and struct
> +padding. The range is determined by the pointed-to type or the associated
> +``__sz`` or ``__szk`` size argument.
> +
> +The annotation does not change the accepted pointer types. A stack-backed
> +struct passed as a generic memory buffer must still be scalar-only.
>
> -An example is given below::
> +A stack buffer with a verifier-known constant offset and size may be
> +uninitialized before the call and is considered initialized afterwards.
> +For variable offsets or sizes, callers with neither ``CAP_PERFMON`` nor
> +``CAP_SYS_ADMIN`` must initialize the potentially accessed stack range before
> +the call.
Does this capability-conditioned requirement match what the verifier
actually enforces?
Looking at check_stack_range_initialized() for the variable offset case,
unprivileged callers are rejected outright:
kernel/bpf/verifier.c:check_stack_range_initialized() {
if (!env->bypass_spec_v1) {
...
verbose(env, "%s variable offset stack access prohibited for !root, var_off=%s\n",
reg_arg_name(env, argno), tn_buf);
return -EACCES;
}
so pre-initializing the range does not make an unprivileged call verify.
Privileged callers that pass bypass_spec_v1 then hit:
kernel/bpf/verifier.c:check_stack_range_initialized() {
...
raw_mode = false;
which means exactly the callers the documentation exempts are the ones
required to have the range initialized. The requirement appears inverted
for variable offsets.
For the variable size case, check_mem_size_reg() clears the output
descriptor unconditionally with no capability test:
kernel/bpf/verifier.c:check_mem_size_reg() {
if (!tnum_is_const(size_reg->var_off) &&
meta->arg_raw_mem.argno == arg_slot_from_argno(mem_argno) + 1)
meta->arg_raw_mem.argno = 0;
With argno cleared, raw_mode becomes false for every caller, so every
caller must have the whole reg_umax(size) range initialized beforehand,
not just unprivileged callers.
The gate is also env->bypass_spec_v1 rather than a pure capability check.
bpf_bypass_spec_v1() returns true for bpf_jit_bypass_spec_v1(),
cpu_mitigations_off(), or bpf_token_capable(token, CAP_PERFMON), so the
behavior also flips with mitigations=off or a JIT that bypasses spec-v1.
The commit message only mentions documenting that outputs must be fully
initialized on return paths and does not claim anything about capabilities,
so the capability sentence appears to be an unbacked addition. The
pre-existing comment above check_mem_size_reg()'s clearing (line 7218-7222)
has similar inaccuracy and may be the source.
Could the documentation drop the capability qualifier? A variable size
always requires initialization, and a variable pointer offset additionally
requires CAP_PERFMON/CAP_SYS_ADMIN merely to be accepted at all.
---
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/35311871659
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers
2026-09-18 5:28 ` [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-18 6:35 ` bot+bpf-ci
@ 2026-09-18 17:50 ` Eduard Zingerman
1 sibling, 0 replies; 18+ messages in thread
From: Eduard Zingerman @ 2026-09-18 17:50 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Emil Tsalapatis, Amery Hung, kkd, kernel-team
On Fri, 2026-09-18 at 07:28 +0200, Kumar Kartikeya Dwivedi wrote:
> Stack liveness treats __uninit kfunc arguments as writes. Even with
> write-only access checks, ordinary clobber handling leaves poisoned stack
> bytes poisoned after the call, so the output cannot be read.
>
> Reuse the helper output descriptor for generic kfunc buffers. Record the
> output after resolving its memory type, and initialize its stack bytes
> only after all arguments have been checked. This prevents an output from
> making an aliased, uninitialized input appear valid.
>
> Track the output by its ABI argument slot, since a kfunc pointer may be
> passed on the stack or follow a multi-slot by-value argument. Keep the
> single-output restriction in prototype validation; multiple-output
> tracking is a separate extension.
>
> Document that output buffers must be fully initialized on every return
> path, including error returns and padding.
>
> Fixes: 2cb27158adb3 ("bpf: poison dead stack slots")
> Reported-by: Tejun Heo <tj@kernel.org>
> Link: https://lore.kernel.org/bpf/86d966ec88bbf27d21b2bb4e18c8aa00@kernel.org
> Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v4 6/8] selftests/bpf: Cover generic __uninit output initialization
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (4 preceding siblings ...)
2026-09-18 5:28 ` [PATCH bpf-next v4 5/8] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-18 5:29 ` Kumar Kartikeya Dwivedi
2026-09-18 5:29 ` [PATCH bpf-next v4 7/8] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
2026-09-18 5:29 ` [PATCH bpf-next v4 8/8] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
7 siblings, 0 replies; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:29 UTC (permalink / raw)
To: bpf
Cc: Amery Hung, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
Exercise the struct and sized-buffer cases where stack liveness poisons an
output before a kfunc call. Check that the verifier accepts these outputs
and that the kfunc initializes the memory read after the call.
Verify that an uninitialized input aliasing an output is still rejected
without CAP_PERFMON or CAP_SYS_ADMIN. Include an initialized alias as a
positive control, using an int-width store so its value is independent of
endianness.
Use __prepare_priv to resolve the test module's BTF before dropping to
CAP_BPF and CAP_NET_ADMIN for program loading. Keep multiple-output and
argument-slot coverage separate from these immediate regression tests.
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../bpf/progs/verifier_kfunc_uninit.c | 100 ++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 25 +++++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 3 +
4 files changed, 130 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 7732df9bc870..d1e50a952a13 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -56,6 +56,7 @@
#include "verifier_jeq_infer_not_null.skel.h"
#include "verifier_jit_convergence.skel.h"
#include "verifier_kfunc_packet_access.skel.h"
+#include "verifier_kfunc_uninit.skel.h"
#include "verifier_ld_ind.skel.h"
#include "verifier_ldsx.skel.h"
#include "verifier_leak_ptr.skel.h"
@@ -222,6 +223,7 @@ void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks
void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null); }
void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); }
void test_verifier_kfunc_packet_access(void) { RUN_TESTS(verifier_kfunc_packet_access); }
+void test_verifier_kfunc_uninit(void) { RUN_TESTS(verifier_kfunc_uninit); }
void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); }
void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); }
void test_verifier_ldsx(void) { RUN(verifier_ldsx); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c
new file mode 100644
index 000000000000..f7818303e703
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+/* Keep the kfunc BTF records used by the inline assembly. */
+void __kfunc_btf_root(void)
+{
+ asm volatile ("" :
+ : "r"(&bpf_kfunc_test_uninit_struct),
+ "r"(&bpf_kfunc_test_uninit_mem),
+ "r"(&bpf_kfunc_test_uninit_alias));
+}
+
+SEC("tc")
+__success __retval(10)
+__flag(BPF_F_TEST_STATE_FREQ)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void struct_poisoned_at_checkpoint(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 16) = 0;"
+ "*(u64 *)(r10 - 8) = 0;"
+ "goto +0;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "call %[bpf_kfunc_test_uninit_struct];"
+ "r0 = *(u32 *)(r10 - 16);"
+ "r1 = *(u32 *)(r10 - 12);"
+ "r0 += r1;"
+ "r1 = *(u32 *)(r10 - 8);"
+ "r0 += r1;"
+ "r1 = *(u32 *)(r10 - 4);"
+ "r0 += r1;"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_struct) : __clobber_all);
+}
+
+SEC("tc")
+__success __retval(0x2a2a2a2a)
+__flag(BPF_F_TEST_STATE_FREQ)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void sized_buffer_poisoned_at_checkpoint(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "goto +0;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = 8;"
+ "call %[bpf_kfunc_test_uninit_mem];"
+ "r0 = *(u32 *)(r10 - 8);"
+ "r1 = *(u32 *)(r10 - 4);"
+ "if r0 == r1 goto +1;"
+ "r0 = 0;"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_mem) : __clobber_all);
+}
+
+SEC("tc")
+__success __retval(7)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void initialized_input_alias(void)
+{
+ asm volatile (
+ "*(u32 *)(r10 - 8) = 7;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = r1;"
+ "call %[bpf_kfunc_test_uninit_alias];"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_alias) : __clobber_all);
+}
+
+SEC("tc")
+__success
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__failure_unpriv __msg_unpriv("invalid read from stack")
+__naked void uninitialized_input_alias(void)
+{
+ asm volatile (
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = r1;"
+ "call %[bpf_kfunc_test_uninit_alias];"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_alias) : __clobber_all);
+}
+
+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 fd2c0cdc91b1..542edeb28b27 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -17,6 +17,7 @@
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/un.h>
+#include <linux/unaligned.h>
#include <linux/filter.h>
#include <linux/rcupdate_trace.h>
#include <net/sock.h>
@@ -1316,6 +1317,27 @@ __bpf_kfunc void bpf_kfunc_call_test_pass2(struct prog_test_pass2 *p)
{
}
+__bpf_kfunc void bpf_kfunc_test_uninit_struct(struct prog_test_pass1 *out__uninit)
+{
+ out__uninit->x0 = 1;
+ out__uninit->x1 = 2;
+ out__uninit->x2 = 3;
+ out__uninit->x3 = 4;
+}
+
+__bpf_kfunc void bpf_kfunc_test_uninit_mem(void *out__uninit, u32 out__sz)
+{
+ memset(out__uninit, 0x2a, out__sz);
+}
+
+__bpf_kfunc int bpf_kfunc_test_uninit_alias(int *out__uninit, const int *in)
+{
+ int value = get_unaligned(in);
+
+ put_unaligned(42, out__uninit);
+ return value;
+}
+
__bpf_kfunc void bpf_kfunc_call_test_fail1(struct prog_test_fail1 *p)
{
}
@@ -1747,6 +1769,9 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_int_mem_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass_ctx)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass1)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass2)
+BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_struct)
+BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_mem)
+BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_alias)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail1)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail2)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail3)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index d3696d5254c9..91b64e783123 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -289,6 +289,9 @@ __u64 bpf_kfunc_call_stack_arg_big(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
void bpf_kfunc_call_test_pass_ctx(struct __sk_buff *skb) __ksym;
void bpf_kfunc_call_test_pass1(struct prog_test_pass1 *p) __ksym;
void bpf_kfunc_call_test_pass2(struct prog_test_pass2 *p) __ksym;
+void bpf_kfunc_test_uninit_struct(struct prog_test_pass1 *out__uninit) __ksym;
+void bpf_kfunc_test_uninit_mem(void *out__uninit, __u32 out__sz) __ksym;
+int bpf_kfunc_test_uninit_alias(int *out__uninit, const int *in) __ksym;
void bpf_kfunc_call_test_mem_len_fail2(__u64 *mem, int len) __ksym;
void bpf_kfunc_call_test_destructive(void) __ksym;
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH bpf-next v4 7/8] bpf: Support multiple __uninit kfunc output arguments
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (5 preceding siblings ...)
2026-09-18 5:29 ` [PATCH bpf-next v4 6/8] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
@ 2026-09-18 5:29 ` Kumar Kartikeya Dwivedi
2026-09-18 5:29 ` [PATCH bpf-next v4 8/8] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
7 siblings, 0 replies; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:29 UTC (permalink / raw)
To: bpf
Cc: Amery Hung, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
A single output descriptor cannot retain the initialization ranges of two
__uninit memory arguments. Track raw-mode eligibility and the definite
output size separately for each ABI slot, so a variable-size output leaves
independent constant-size outputs intact.
Use the shared argument-checking path to record outputs for both helpers
and kfuncs. With per-slot tracking, neither needs the single-output
prototype restriction. Initialize every recorded output after checking all
arguments, skipping empty slots before looking up their register state.
Use the resolved BTF parameter when looking up __uninit for stack liveness.
A preceding by-value parameter can consume multiple ABI slots, so its slot
number cannot index the BTF parameter array.
Suggested-by: Amery Hung <ameryhung@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
include/linux/bpf_verifier.h | 10 ++---
kernel/bpf/verifier.c | 79 +++++++++++-------------------------
2 files changed, 28 insertions(+), 61 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 6ff1c227d298..9ddbb20ec1e9 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1547,13 +1547,13 @@ struct ref_obj_desc {
};
/*
- * A memory argument a call fills in. The verifier allows the stack to be uninitialized if
- * the range is a known constant. Stack slots are marked as STACK_MISC by check_mem_access()
- * after all arguments have been checked.
+ * Memory arguments a call fills in, indexed by argument slot. The verifier allows the
+ * stack to be uninitialized if the range is a known constant. Stack slots are marked as
+ * STACK_MISC by check_mem_access() after all arguments have been checked.
*/
struct arg_raw_mem_desc {
- u8 argno; /* One-based ABI argument slot; zero means no output. */
- int size;
+ u16 mask;
+ int size[MAX_BPF_FUNC_ARGS];
};
/* Size of PTR_TO_MEM returned, taken from a constant allocation-size argument */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index d327b5356d53..4ba7929f6d7e 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6987,7 +6987,9 @@ static int check_stack_range_initialized(
*/
bool allow_poison = access_size < 0 || clobber;
/* The call will initialize the memory; uninitialized stack allowed */
- bool raw_mode = meta && meta->arg_raw_mem.argno == arg_slot_from_argno(argno) + 1;
+ u32 arg_slot = arg_slot_from_argno(argno);
+ bool raw_mode = meta && arg_slot < MAX_BPF_FUNC_ARGS &&
+ (meta->arg_raw_mem.mask & BIT(arg_slot));
access_size = abs(access_size);
@@ -7029,7 +7031,7 @@ static int check_stack_range_initialized(
}
if (raw_mode) {
- meta->arg_raw_mem.size = access_size;
+ meta->arg_raw_mem.size[arg_slot] = access_size;
return 0;
}
@@ -7221,9 +7223,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
* raw mode so that the program is required to initialize all
* the memory that the helper could just partially fill up.
*/
- if (!tnum_is_const(size_reg->var_off) &&
- meta->arg_raw_mem.argno == arg_slot_from_argno(mem_argno) + 1)
- meta->arg_raw_mem.argno = 0;
+ if (!tnum_is_const(size_reg->var_off))
+ meta->arg_raw_mem.mask &= ~BIT(arg_slot_from_argno(mem_argno));
if (reg_smin(size_reg) < 0) {
verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
@@ -8158,21 +8159,6 @@ static bool arg_type_is_mem_size(enum bpf_arg_type type)
return type == ARG_MEM_SIZE || type == ARG_MEM_SIZE_OR_ZERO;
}
-static bool arg_type_is_raw_mem(enum bpf_arg_type type)
-{
- /*
- * A map value output buffer (e.g. bpf_map_pop_elem) is also a raw
- * (uninitialized) memory argument, and like ARG_PTR_TO_MEM it may be
- * passed as a PTR_TO_STACK that reaches check_stack_range_initialized().
- * A kfunc's struct pointer remains ARG_PTR_TO_BTF_ID until call argument
- * checking resolves it to generic memory, so include it in proto validation.
- */
- return (base_type(type) == ARG_PTR_TO_MEM ||
- base_type(type) == ARG_PTR_TO_MAP_VALUE ||
- base_type(type) == ARG_PTR_TO_BTF_ID) &&
- type & MEM_UNINIT;
-}
-
static bool arg_type_is_release(enum bpf_arg_type type)
{
return type & OBJ_RELEASE;
@@ -8951,7 +8937,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
if ((arg_type & MEM_UNINIT) &&
(base_type(arg_type) == ARG_PTR_TO_MEM ||
base_type(arg_type) == ARG_PTR_TO_MAP_VALUE))
- meta->arg_raw_mem.argno = slot + 1;
+ meta->arg_raw_mem.mask |= BIT(slot);
if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
err = mark_arg_precision(env, argno);
@@ -9566,24 +9552,28 @@ static int mark_raw_stack(struct bpf_verifier_env *env, struct bpf_call_arg_meta
int insn_idx)
{
struct bpf_func_state *caller = cur_func(env);
- struct bpf_reg_state *reg;
- u32 slot = meta->arg_raw_mem.argno - 1;
+ u32 slot;
int i, err;
- if (!meta->arg_raw_mem.size)
- return 0;
- reg = get_func_arg_reg(caller, cur_regs(env), slot);
-
/*
* Validate every argument before initializing outputs: an input argument
* may alias an output buffer. Use the normal stack-write checks to discard
* stale spills and preserve the rules for special stack objects.
*/
- for (i = 0; i < meta->arg_raw_mem.size; i++) {
- err = check_mem_access(env, insn_idx, reg, argno_from_arg(slot + 1), i, BPF_B,
- BPF_WRITE, -1, false, false);
- if (err)
- return err;
+ for (slot = 0; slot < MAX_BPF_FUNC_ARGS; slot++) {
+ struct bpf_reg_state *reg;
+ argno_t argno = argno_from_arg(slot + 1);
+
+ if (!meta->arg_raw_mem.size[slot])
+ continue;
+ reg = get_func_arg_reg(caller, cur_regs(env), slot);
+
+ for (i = 0; i < meta->arg_raw_mem.size[slot]; i++) {
+ err = check_mem_access(env, insn_idx, reg, argno, i, BPF_B,
+ BPF_WRITE, -1, false, false);
+ if (err)
+ return err;
+ }
}
return 0;
@@ -9881,24 +9871,6 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
return -EINVAL;
}
-static bool check_raw_mode_ok(const struct bpf_func_proto *fn)
-{
- bool seen = false;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
- if (fn->arg_type[i] == ARG_UNUSED)
- break;
- if (!arg_type_is_raw_mem(fn->arg_type[i]))
- continue;
- if (seen)
- return false;
- seen = true;
- }
-
- return true;
-}
-
static bool check_args_pair_invalid(const struct bpf_func_proto *fn, int arg)
{
bool is_fixed = fn->arg_type[arg] & MEM_FIXED_SIZE;
@@ -10025,7 +9997,6 @@ static int check_func_proto(struct bpf_verifier_env *env, const struct bpf_func_
struct bpf_call_arg_meta *meta)
{
return check_arg_prog_aux(env, fn) &&
- check_raw_mode_ok(fn) &&
check_arg_pair_ok(fn) &&
check_mem_arg_rw_flag_ok(fn) &&
check_proto_release_reg(fn, meta) &&
@@ -13097,10 +13068,6 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg
return -ENOTSUPP;
}
- if (!check_raw_mode_ok(proto)) {
- verbose(env, "multiple __uninit buffers are not supported\n");
- return -EINVAL;
- }
return check_arg_prog_aux(env, proto) ? 0 : -EINVAL;
}
@@ -13897,7 +13864,7 @@ s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *
/* KF_ITER_NEW kfuncs initialize the iterator state at arg 0 */
if (arg == 0 && meta.kfunc_flags & KF_ITER_NEW)
return -size;
- if (is_kfunc_arg_uninit(btf, &args[arg]))
+ if (is_kfunc_arg_uninit(btf, &args[i]))
return -size;
return size;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH bpf-next v4 8/8] selftests/bpf: Cover __uninit kfunc output argument slots
2026-09-18 5:28 [PATCH bpf-next v4 0/8] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (6 preceding siblings ...)
2026-09-18 5:29 ` [PATCH bpf-next v4 7/8] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
@ 2026-09-18 5:29 ` Kumar Kartikeya Dwivedi
2026-09-18 6:35 ` bot+bpf-ci
7 siblings, 1 reply; 18+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-18 5:29 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, Amery Hung, kkd,
kernel-team
Keep coverage for per-slot output tracking separate from the immediate
single-output regression tests. Check that both constant-size outputs are
initialized, and that a variable-size output does not disable initialization
of an independent constant-size output.
Also exercise an output following a by-value parameter that occupies two
argument slots, and an output pointer passed on the stack. Run each case
with normal capabilities and with CAP_BPF and CAP_NET_ADMIN only. Leave the
stack-passed output uninitialized so its reduced-capability case fails if
the verifier treats it as an ordinary input buffer.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../bpf/progs/verifier_kfunc_uninit_multi.c | 113 ++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 20 ++++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 4 +
4 files changed, 139 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index d1e50a952a13..24a69807c74c 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -57,6 +57,7 @@
#include "verifier_jit_convergence.skel.h"
#include "verifier_kfunc_packet_access.skel.h"
#include "verifier_kfunc_uninit.skel.h"
+#include "verifier_kfunc_uninit_multi.skel.h"
#include "verifier_ld_ind.skel.h"
#include "verifier_ldsx.skel.h"
#include "verifier_leak_ptr.skel.h"
@@ -224,6 +225,7 @@ void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null)
void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); }
void test_verifier_kfunc_packet_access(void) { RUN_TESTS(verifier_kfunc_packet_access); }
void test_verifier_kfunc_uninit(void) { RUN_TESTS(verifier_kfunc_uninit); }
+void test_verifier_kfunc_uninit_multi(void) { RUN_TESTS(verifier_kfunc_uninit_multi); }
void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); }
void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); }
void test_verifier_ldsx(void) { RUN(verifier_ldsx); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
new file mode 100644
index 000000000000..18ced0d0a3b2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+/* Keep the kfunc BTF records used by the inline assembly. */
+void __kfunc_btf_root(void)
+{
+ asm volatile ("" :
+ : "r"(&bpf_kfunc_test_uninit_multi),
+ "r"(&bpf_kfunc_test_uninit_pair),
+ "r"(&bpf_kfunc_test_uninit_stack));
+}
+
+SEC("tc")
+__success __retval(42)
+__flag(BPF_F_TEST_STATE_FREQ)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void multiple_outputs(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 16) = 0;"
+ "*(u64 *)(r10 - 8) = 0;"
+ "goto +0;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r3 = 8;"
+ "call %[bpf_kfunc_test_uninit_multi];"
+ "r0 = *(u32 *)(r10 - 16);"
+ "r1 = *(u32 *)(r10 - 8);"
+ "if r1 != 0x2a2a2a2a goto 1f;"
+ "r1 = *(u32 *)(r10 - 4);"
+ "if r1 == 0x2a2a2a2a goto 2f;"
+ "1:;"
+ "r0 = 0;"
+ "2:;"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_multi) : __clobber_all);
+}
+
+SEC("tc")
+__success __retval(42)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void variable_size_preserves_other_output(void)
+{
+ asm volatile (
+ "r3 = *(u32 *)(r1 + 0);"
+ "r3 &= 7;"
+ "*(u64 *)(r10 - 8) = 0;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "call %[bpf_kfunc_test_uninit_multi];"
+ "r0 = *(u32 *)(r10 - 16);"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_multi) : __clobber_all);
+}
+
+SEC("tc")
+__arch_x86_64 __arch_arm64
+__success __retval(42)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void output_after_by_value_argument(void)
+{
+ asm volatile (
+ "r1 = 20;"
+ "r2 = 22;"
+ "r3 = r10;"
+ "r3 += -8;"
+ "call %[bpf_kfunc_test_uninit_pair];"
+ "r0 = *(u32 *)(r10 - 8);"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_pair) : __clobber_all);
+}
+
+#if defined(__BPF_FEATURE_STACK_ARGUMENT)
+SEC("tc")
+__arch_x86_64 __arch_arm64 __arch_riscv64
+__success __retval(15)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void output_passed_on_stack(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "r6 = r10;"
+ "r6 += -8;"
+ "*(u64 *)(r11 - 8) = r6;"
+ "call %[bpf_kfunc_test_uninit_stack];"
+ "r0 = *(u32 *)(r10 - 8);"
+ "exit;"
+ : : __imm(bpf_kfunc_test_uninit_stack) : __clobber_all);
+}
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 542edeb28b27..211886a8ee87 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1338,6 +1338,23 @@ __bpf_kfunc int bpf_kfunc_test_uninit_alias(int *out__uninit, const int *in)
return value;
}
+__bpf_kfunc void bpf_kfunc_test_uninit_multi(int *a__uninit, void *b__uninit, u32 b__sz)
+{
+ put_unaligned(42, a__uninit);
+ memset(b__uninit, 0x2a, b__sz);
+}
+
+__bpf_kfunc void bpf_kfunc_test_uninit_pair(struct prog_test_pair_arg p, int *out__uninit)
+{
+ put_unaligned((int)(p.lo + p.hi), out__uninit);
+}
+
+__bpf_kfunc void bpf_kfunc_test_uninit_stack(u64 a, u64 b, u64 c, u64 d, u64 e,
+ int *out__uninit)
+{
+ put_unaligned((int)(a + b + c + d + e), out__uninit);
+}
+
__bpf_kfunc void bpf_kfunc_call_test_fail1(struct prog_test_fail1 *p)
{
}
@@ -1772,6 +1789,9 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_pass2)
BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_struct)
BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_mem)
BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_alias)
+BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_multi)
+BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_pair)
+BTF_ID_FLAGS(func, bpf_kfunc_test_uninit_stack)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail1)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail2)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_fail3)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index 91b64e783123..524f2cb9bdf4 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -292,6 +292,10 @@ void bpf_kfunc_call_test_pass2(struct prog_test_pass2 *p) __ksym;
void bpf_kfunc_test_uninit_struct(struct prog_test_pass1 *out__uninit) __ksym;
void bpf_kfunc_test_uninit_mem(void *out__uninit, __u32 out__sz) __ksym;
int bpf_kfunc_test_uninit_alias(int *out__uninit, const int *in) __ksym;
+void bpf_kfunc_test_uninit_multi(int *a__uninit, void *b__uninit, __u32 b__sz) __ksym;
+void bpf_kfunc_test_uninit_pair(struct prog_test_pair_arg p, int *out__uninit) __ksym;
+void bpf_kfunc_test_uninit_stack(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
+ int *out__uninit) __ksym;
void bpf_kfunc_call_test_mem_len_fail2(__u64 *mem, int len) __ksym;
void bpf_kfunc_call_test_destructive(void) __ksym;
--
2.53.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v4 8/8] selftests/bpf: Cover __uninit kfunc output argument slots
2026-09-18 5:29 ` [PATCH bpf-next v4 8/8] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
@ 2026-09-18 6:35 ` bot+bpf-ci
0 siblings, 0 replies; 18+ messages in thread
From: bot+bpf-ci @ 2026-09-18 6:35 UTC (permalink / raw)
To: memxor, bpf
Cc: ast, andrii, daniel, eddyz87, emil, tj, ameryhung, kkd,
kernel-team, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4595 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
> index d1e50a952a13a..24a69807c74cb 100644
> --- a/tools/testing/selftests/bpf/prog_tests/verifier.c
> +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
> @@ -57,6 +57,7 @@
> #include "verifier_jit_convergence.skel.h"
> #include "verifier_kfunc_packet_access.skel.h"
> #include "verifier_kfunc_uninit.skel.h"
> +#include "verifier_kfunc_uninit_multi.skel.h"
> #include "verifier_ld_ind.skel.h"
> #include "verifier_ldsx.skel.h"
> #include "verifier_leak_ptr.skel.h"
> @@ -224,6 +225,7 @@ void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null)
> void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); }
> void test_verifier_kfunc_packet_access(void) { RUN_TESTS(verifier_kfunc_packet_access); }
> void test_verifier_kfunc_uninit(void) { RUN_TESTS(verifier_kfunc_uninit); }
> +void test_verifier_kfunc_uninit_multi(void) { RUN_TESTS(verifier_kfunc_uninit_multi); }
> void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); }
> void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); }
> void test_verifier_ldsx(void) { RUN(verifier_ldsx); }
> diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
> new file mode 100644
> index 0000000000000..18ced0d0a3b2c
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
> @@ -0,0 +1,113 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
> +
> +#include <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_misc.h"
> +#include "../test_kmods/bpf_testmod_kfunc.h"
> +
> +/* Keep the kfunc BTF records used by the inline assembly. */
> +void __kfunc_btf_root(void)
> +{
> + asm volatile ("" :
> + : "r"(&bpf_kfunc_test_uninit_multi),
> + "r"(&bpf_kfunc_test_uninit_pair),
> + "r"(&bpf_kfunc_test_uninit_stack));
> +}
> +
> +SEC("tc")
> +__success __retval(42)
> +__flag(BPF_F_TEST_STATE_FREQ)
> +__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
> +__prepare_priv
> +__success_unpriv
> +__naked void multiple_outputs(void)
> +{
A subsystem pattern flags this as potentially concerning: the new prog
file progs/verifier_kfunc_uninit_multi.c (plus a new #include and a new
test_verifier_kfunc_uninit_multi() entry in prog_tests/verifier.c) is
added for the per-argument-slot variant of __uninit output tracking.
Looking at progs/verifier_kfunc_uninit.c in the same directory - added
two commits earlier in the same series (40e810febe863) - it already
covers __uninit kfunc output initialization and uses exactly the same
scaffolding: identical SPDX/copyright header, identical include set, an
identically shaped __kfunc_btf_root() stub, and the same
__flag(BPF_F_TEST_STATE_FREQ) + 'goto +0' poisoning idiom with the same
__caps_unpriv(CAP_BPF | CAP_NET_ADMIN) __prepare_priv __success_unpriv
tag block.
The four new cases (multiple_outputs, variable_size_preserves_other_output,
output_after_by_value_argument, output_passed_on_stack) could be appended
to verifier_kfunc_uninit.c as additional cases, avoiding the duplicated
boilerplate and the extra skeleton/registration.
The commit message states an explicit rationale: "Keep coverage for
per-slot output tracking separate from the immediate single-output
regression tests." Splitting also keeps the arch-gated cases
(__arch_x86_64/__arch_arm64 and the __BPF_FEATURE_STACK_ARGUMENT case)
out of the arch-neutral file, and the BPF selftest Makefile globs
progs/*.c so no build plumbing is needed.
Worth asking: is the split into a second file wanted, or should these be
additional cases in verifier_kfunc_uninit.c?
> + asm volatile (
> + "*(u64 *)(r10 - 16) = 0;"
> + "*(u64 *)(r10 - 8) = 0;"
> + "goto +0;"
> + "r1 = r10;"
> + "r1 += -16;"
> + "r2 = r10;"
> + "r2 += -8;"
> + "r3 = 8;"
> + "call %[bpf_kfunc_test_uninit_multi];"
> + "r0 = *(u32 *)(r10 - 16);"
> + "r1 = *(u32 *)(r10 - 8);"
> + "if r1 != 0x2a2a2a2a goto 1f;"
> + "r1 = *(u32 *)(r10 - 4);"
> + "if r1 == 0x2a2a2a2a goto 2f;"
> + "1:;"
> + "r0 = 0;"
> + "2:;"
> + "exit;"
> + : : __imm(bpf_kfunc_test_uninit_multi) : __clobber_all);
> +}
[ ... ]
---
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/35311871659
^ permalink raw reply [flat|nested] 18+ messages in thread