* [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers
@ 2026-09-16 16:08 Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 1/5] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
` (4 more replies)
0 siblings, 5 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 16:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, Amery Hung, kkd,
kernel-team
Generic __uninit kfunc arguments are output buffers. Stack liveness treats
them as writes, but argument checking still requires readable contents and
does not record definite initialization after the call. Check these
arguments as write-only and record their initialization after validating all
inputs, including inputs that alias an output.
Keep the single-output fix and its immediate regression tests separate from
the extension for multiple outputs. The first three patches provide the
capability-test prerequisite, the backportable kernel fix, and its tests.
The kernel fix itself has no dependency on the test fixture. The final two
patches add per-argument output tracking and its focused tests.
The opt-in __prepare_priv annotation uses libbpf's prepare/load boundary to
resolve module BTF before dropping CAP_SYS_ADMIN and CAP_PERFMON. Program
loading then runs with the capabilities selected by __caps_unpriv. Ordinary
unprivileged tests keep their existing preparation path, and disabled or
undetectable CPU mitigations still cause the relevant tests to be skipped.
Changelog:
----------
v1 -> v2
v1: https://lore.kernel.org/bpf/20260915141004.1196460-1-memxor@gmail.com
* Separate the single-output fix and tests from multiple-output support
and its tests; reduce coverage to focused cases. (Eduard)
* Skip inactive output slots before looking up argument register state.
(Sashiko, Amery)
* Separate sysctl restrictions from mitigation-related test skips.
(BPF CI)
* Use an int-width initialization store in the alias test for big-endian
targets. (BPF CI)
* Centralize conversion from argument numbers to slots. (Eduard)
* Share clear access-mode selection between fixed-size and sized arguments.
(Amery)
* Clarify the opt-in prepare/load capability boundary and retain the
reduced-capability alias rejection test. (Eduard)
Kumar Kartikeya Dwivedi (5):
selftests/bpf: Allow privileged preparation for capability tests
bpf: Fix generic __uninit kfunc output buffers
selftests/bpf: Cover generic __uninit output initialization
bpf: Support multiple __uninit kfunc output arguments
selftests/bpf: Cover __uninit kfunc output argument slots
Documentation/bpf/kfuncs.rst | 27 ++--
include/linux/bpf_verifier.h | 11 +-
kernel/bpf/verifier.c | 93 ++++++++++----
.../selftests/bpf/prog_tests/verifier.c | 4 +
tools/testing/selftests/bpf/progs/bpf_misc.h | 9 +-
.../bpf/progs/verifier_kfunc_uninit.c | 100 +++++++++++++++
.../bpf/progs/verifier_kfunc_uninit_multi.c | 116 ++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 44 +++++++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 7 ++
tools/testing/selftests/bpf/test_loader.c | 48 +++++---
tools/testing/selftests/bpf/unpriv_helpers.c | 16 ++-
tools/testing/selftests/bpf/unpriv_helpers.h | 2 +
12 files changed, 421 insertions(+), 56 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c
create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
base-commit: 5ef40d69b38a93bc9951dadb1a15c85c597e1a40
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 1/5] selftests/bpf: Allow privileged preparation for capability tests
2026-09-16 16:08 [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-16 16:08 ` Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 16:08 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] 9+ messages in thread
* [PATCH bpf-next v2 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 16:08 [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 1/5] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
@ 2026-09-16 16:08 ` Kumar Kartikeya Dwivedi
2026-09-16 16:38 ` Amery Hung
2026-09-16 16:08 ` [PATCH bpf-next v2 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 16:08 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Amery Hung, kkd, kernel-team
Stack liveness treats __uninit kfunc arguments as writes, but generic memory
argument validation still checks them as both reads and writes. A checkpoint
can consequently poison an output buffer before the call and cause its read
check to reject the program. Allowing that read would not suffice: ordinary
clobber checks leave poisoned bytes poisoned after the call.
Check generic __uninit memory arguments as write-only and reuse the helper
output descriptor to record definite initialization of a single output.
Apply the writes after validating all arguments so an output does not make
an aliased, uninitialized input appear valid. Look up the output register
state only when there are recorded bytes to initialize.
Preserve MEM_UNINIT when a scalar-only struct pointer is resolved to a
fixed-size memory argument. Use a common access-mode helper for fixed-size
and sized arguments. Disable raw mode for a variable-size argument only
when it is the tracked output.
Document that generic output buffers must be fully initialized on every
return path, including error returns and padding. Multiple-output tracking
is left to a separate change.
Fixes: 2cb27158adb3 ("bpf: poison dead stack slots")
Reported-by: Tejun Heo <tj@kernel.org>
Link: https://lore.kernel.org/bpf/86d966ec88bbf27d21b2bb4e18c8aa00@kernel.org
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
Documentation/bpf/kfuncs.rst | 27 +++++++++----
include/linux/bpf_verifier.h | 7 ++--
kernel/bpf/verifier.c | 77 +++++++++++++++++++++++++++---------
3 files changed, 82 insertions(+), 29 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..3c1b06a3daf3 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 regno; /* Register number, or one-based kfunc argument slot. */
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 6c6b8d8520cd..0433a5183682 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6981,7 +6981,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.regno == abs(argno.argno);
access_size = abs(access_size);
@@ -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 == abs(mem_argno.argno))
meta->arg_raw_mem.regno = 0;
if (reg_smin(size_reg) < 0) {
@@ -8885,6 +8886,16 @@ 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,
+ const struct bpf_call_arg_meta *meta)
+{
+ if (arg_type & MEM_UNINIT)
+ return BPF_WRITE;
+ if (meta->btf)
+ return BPF_READ | BPF_WRITE;
+ return arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ;
+}
+
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)
@@ -9176,15 +9187,21 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
enum bpf_access_type access_type;
bool known_memory;
+ if (meta->btf && (arg_type & MEM_UNINIT)) {
+ if (meta->arg_raw_mem.regno) {
+ verbose(env, "multiple __uninit buffers are not supported\n");
+ return -EINVAL;
+ }
+ meta->arg_raw_mem.regno = slot + 1;
+ }
+
/* The access to this pointer is only checked when we hit the
* next is_mem_size argument below.
*/
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, meta);
err = check_mem_reg(env, reg, argno, arg_size, access_type, meta, &known_memory);
if (err < 0) {
@@ -9230,9 +9247,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], meta);
zero_size_allowed = meta->btf || base_type(arg_type) == ARG_MEM_SIZE_OR_ZERO;
@@ -9550,6 +9565,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.regno - 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;
@@ -11572,16 +11614,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];
@@ -12437,7 +12472,7 @@ 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 | (*arg_type & (PTR_MAYBE_NULL | MEM_UNINIT));
return 0;
}
@@ -14140,6 +14175,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] 9+ messages in thread
* [PATCH bpf-next v2 3/5] selftests/bpf: Cover generic __uninit output initialization
2026-09-16 16:08 [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 1/5] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-16 16:08 ` Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 4/5] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 5/5] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
4 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 16:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, Amery Hung, 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.
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 | 24 +++++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 3 +
4 files changed, 129 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..4a9affe81ee1 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -55,6 +55,7 @@
#include "verifier_iterating_callbacks.skel.h"
#include "verifier_jeq_infer_not_null.skel.h"
#include "verifier_jit_convergence.skel.h"
+#include "verifier_kfunc_uninit.skel.h"
#include "verifier_kfunc_packet_access.skel.h"
#include "verifier_ld_ind.skel.h"
#include "verifier_ldsx.skel.h"
@@ -221,6 +222,7 @@ void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); }
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_uninit(void) { RUN_TESTS(verifier_kfunc_uninit); }
void test_verifier_kfunc_packet_access(void) { RUN_TESTS(verifier_kfunc_packet_access); }
void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); }
void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); }
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..dfffbdff06fa 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1316,6 +1316,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 +1768,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] 9+ messages in thread
* [PATCH bpf-next v2 4/5] bpf: Support multiple __uninit kfunc output arguments
2026-09-16 16:08 [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (2 preceding siblings ...)
2026-09-16 16:08 ` [PATCH bpf-next v2 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
@ 2026-09-16 16:08 ` Kumar Kartikeya Dwivedi
2026-09-16 17:39 ` bot+bpf-ci
2026-09-16 16:08 ` [PATCH bpf-next v2 5/5] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
4 siblings, 1 reply; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 16:08 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, Amery Hung, 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 argument slot, so a variable-size output
also leaves independent constant-size outputs intact.
Normalize helper register numbers and kfunc argument numbers through one
slot conversion helper. After checking every argument, initialize each
recorded output. Skip empty entries before looking up register state: an
unused slot need not have an allocated stack-argument record. Helpers keep
their existing single-output restriction.
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. Keep this argument-slot handling
with the extension rather than the minimal single-output regression fix.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
include/linux/bpf_verifier.h | 10 +++---
kernel/bpf/verifier.c | 60 ++++++++++++++++++++----------------
2 files changed, 38 insertions(+), 32 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 3c1b06a3daf3..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 regno; /* Register number, or one-based kfunc argument slot. */
- 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 0433a5183682..644ada706c62 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,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.regno == abs(argno.argno);
+ 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);
@@ -7023,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;
}
@@ -7215,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.regno == abs(mem_argno.argno))
- meta->arg_raw_mem.regno = 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",
@@ -9035,7 +9042,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
*/
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;
+ meta->arg_raw_mem.mask &= ~BIT(slot);
err = check_helper_mem_access(env, reg, argno, meta->map.ptr->value_size,
arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
@@ -9187,13 +9194,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
enum bpf_access_type access_type;
bool known_memory;
- if (meta->btf && (arg_type & MEM_UNINIT)) {
- if (meta->arg_raw_mem.regno) {
- verbose(env, "multiple __uninit buffers are not supported\n");
- return -EINVAL;
- }
- meta->arg_raw_mem.regno = slot + 1;
- }
+ if (meta->btf && (arg_type & MEM_UNINIT))
+ meta->arg_raw_mem.mask |= BIT(slot);
/* The access to this pointer is only checked when we hit the
* next is_mem_size argument below.
@@ -9569,24 +9571,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.regno - 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;
@@ -9893,9 +9899,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 (meta->arg_raw_mem.mask)
return false;
- meta->arg_raw_mem.regno = i + 1;
+ meta->arg_raw_mem.mask = BIT(i);
}
return true;
@@ -13894,7 +13900,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] 9+ messages in thread
* [PATCH bpf-next v2 5/5] selftests/bpf: Cover __uninit kfunc output argument slots
2026-09-16 16:08 [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (3 preceding siblings ...)
2026-09-16 16:08 ` [PATCH bpf-next v2 4/5] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
@ 2026-09-16 16:08 ` Kumar Kartikeya Dwivedi
2026-09-16 16:28 ` sashiko-bot
4 siblings, 1 reply; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 16:08 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.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../bpf/progs/verifier_kfunc_uninit_multi.c | 116 ++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 20 +++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 4 +
4 files changed, 142 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 4a9affe81ee1..f3c1428ac155 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_uninit.skel.h"
+#include "verifier_kfunc_uninit_multi.skel.h"
#include "verifier_kfunc_packet_access.skel.h"
#include "verifier_ld_ind.skel.h"
#include "verifier_ldsx.skel.h"
@@ -223,6 +224,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_uninit(void) { RUN_TESTS(verifier_kfunc_uninit); }
+void test_verifier_kfunc_uninit_multi(void) { RUN_TESTS(verifier_kfunc_uninit_multi); }
void test_verifier_kfunc_packet_access(void) { RUN_TESTS(verifier_kfunc_packet_access); }
void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); }
void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); }
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..b944346b35e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
@@ -0,0 +1,116 @@
+// 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)
+__flag(BPF_F_TEST_STATE_FREQ)
+__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
+__prepare_priv
+__success_unpriv
+__naked void output_passed_on_stack(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "goto +0;"
+ "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 dfffbdff06fa..2fd5b6719308 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1337,6 +1337,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)
{
}
@@ -1771,6 +1788,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] 9+ messages in thread
* Re: [PATCH bpf-next v2 5/5] selftests/bpf: Cover __uninit kfunc output argument slots
2026-09-16 16:08 ` [PATCH bpf-next v2 5/5] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
@ 2026-09-16 16:28 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-16 16:28 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] bpf: ineffective test output_passed_on_stack for __uninit tracking
--
commit 70a455597d07423eca32cc332d11faa394e65772
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>
selftests/bpf: Cover __uninit kfunc output argument slots
This commit adds regression tests for the verifier's tracking of __uninit
kfunc output argument slots. It covers cases involving multiple outputs,
outputs following by-value parameters, and output pointers passed on the
stack.
> 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..b944346b35e34
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit_multi.c
[ ... ]
> +#if defined(__BPF_FEATURE_STACK_ARGUMENT)
> +SEC("tc")
> +__arch_x86_64 __arch_arm64 __arch_riscv64
> +__success __retval(15)
> +__flag(BPF_F_TEST_STATE_FREQ)
> +__caps_unpriv(CAP_BPF | CAP_NET_ADMIN)
> +__prepare_priv
> +__success_unpriv
> +__naked void output_passed_on_stack(void)
> +{
> + asm volatile (
> + "*(u64 *)(r10 - 8) = 0;"
> + "goto +0;"
[Severity: Medium]
Does pre-initializing the output stack slot here prevent the test from
actually catching __uninit tracking regressions?
If the verifier fails to recognize the __uninit annotation, it treats the
argument as a normal pointer. Because the slot is initialized and kept alive
across the goto +0 checkpoint, the memory is tracked as STACK_ZERO. This
satisfies the BPF_READ requirement in unprivileged mode, allowing the
BPF_WRITE check to clobber the slot to STACK_MISC and verify successfully.
Should this test omit the initialization (similar to how it is done in
output_after_by_value_argument) so that a broken verifier evaluates a
BPF_READ on STACK_INVALID, forcing an expected verification failure in
unprivileged mode?
> + "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
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260916160821.3157543-1-memxor@gmail.com?part=5
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 16:08 ` [PATCH bpf-next v2 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-16 16:38 ` Amery Hung
0 siblings, 0 replies; 9+ messages in thread
From: Amery Hung @ 2026-09-16 16:38 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Tejun Heo, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis, kkd,
kernel-team
> 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)
> @@ -9176,15 +9187,21 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
> enum bpf_access_type access_type;
> bool known_memory;
>
> + if (meta->btf && (arg_type & MEM_UNINIT)) {
> + if (meta->arg_raw_mem.regno) {
> + verbose(env, "multiple __uninit buffers are not supported\n");
> + return -EINVAL;
> + }
> + meta->arg_raw_mem.regno = slot + 1;
> + }
> +
Lets resue helper proto validation for arg_raw_mem,
check_raw_mode_ok(). For kfunc, the validation should happen after we
finish generating the proto, i.e., at the end of
gen_kfunc_arg_proto(). There is already one example:
check_arg_prog_aux().
> /* The access to this pointer is only checked when we hit the
> * next is_mem_size argument below.
> */
> if (!(arg_type & MEM_FIXED_SIZE))
> break;
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 4/5] bpf: Support multiple __uninit kfunc output arguments
2026-09-16 16:08 ` [PATCH bpf-next v2 4/5] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
@ 2026-09-16 17:39 ` bot+bpf-ci
0 siblings, 0 replies; 9+ messages in thread
From: bot+bpf-ci @ 2026-09-16 17:39 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: 1422 bytes --]
> 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 argument slot, so a variable-size output
> also leaves independent constant-size outputs intact.
>
> Normalize helper register numbers and kfunc argument numbers through one
> slot conversion helper. After checking every argument, initialize each
> recorded output. Skip empty entries before looking up register state: an
> unused slot need not have an allocated stack-argument record. Helpers keep
> their existing single-output restriction.
>
> 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. Keep this argument-slot handling
> with the extension rather than the minimal single-output regression fix.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This looks like a bug fix for multiple __uninit kfunc output arguments.
Should this include:
Fixes: ae6abae582b7 ("bpf: Recognize by-value struct and __int128 kfunc arguments")
---
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/35122594381
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-16 17:39 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16 16:08 [PATCH bpf-next v2 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 1/5] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 16:38 ` Amery Hung
2026-09-16 16:08 ` [PATCH bpf-next v2 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
2026-09-16 16:08 ` [PATCH bpf-next v2 4/5] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
2026-09-16 17:39 ` bot+bpf-ci
2026-09-16 16:08 ` [PATCH bpf-next v2 5/5] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
2026-09-16 16:28 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.