* [PATCH bpf-next v3 1/5] selftests/bpf: Allow privileged preparation for capability tests
2026-09-16 19:27 [PATCH bpf-next v3 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-16 19:27 ` Kumar Kartikeya Dwivedi
2026-09-16 19:27 ` [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (3 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 19:27 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] 12+ messages in thread* [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 19:27 [PATCH bpf-next v3 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 19:27 ` [PATCH bpf-next v3 1/5] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
@ 2026-09-16 19:27 ` Kumar Kartikeya Dwivedi
2026-09-16 19:52 ` Amery Hung
` (2 more replies)
2026-09-16 19:28 ` [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
` (2 subsequent siblings)
4 siblings, 3 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 19:27 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.
Reuse check_raw_mode_ok() after generating the kfunc prototype to reject
multiple generic outputs before checking any call arguments. Include struct
pointers that are resolved to generic memory later. Record the actual output
slot during call checking, where the ABI slot and resolved memory type are
known.
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 | 88 +++++++++++++++++++++++++++---------
3 files changed, 90 insertions(+), 32 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..d7a40dc159ae 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) {
@@ -8157,9 +8158,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;
}
@@ -8885,6 +8889,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 +9190,16 @@ 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))
+ 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 +9245,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 +9563,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;
@@ -9844,6 +9884,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
{
+ bool seen = false;
int i;
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
@@ -9851,9 +9892,11 @@ 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;
+ if (!meta->btf)
+ meta->arg_raw_mem.regno = i + 1;
}
return true;
@@ -11572,16 +11615,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 +12473,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;
}
@@ -13063,6 +13099,10 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg
return -ENOTSUPP;
}
+ if (!check_raw_mode_ok(proto, meta)) {
+ verbose(env, "multiple __uninit buffers are not supported\n");
+ return -EINVAL;
+ }
return check_arg_prog_aux(env, proto) ? 0 : -EINVAL;
}
@@ -14140,6 +14180,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] 12+ messages in thread* Re: [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 19:27 ` [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-16 19:52 ` Amery Hung
2026-09-16 20:27 ` Amery Hung
2026-09-16 20:27 ` bot+bpf-ci
2026-09-17 19:29 ` Eduard Zingerman
2 siblings, 1 reply; 12+ messages in thread
From: Amery Hung @ 2026-09-16 19:52 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 +9190,16 @@ 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))
> + meta->arg_raw_mem.regno = slot + 1;
> +
Nit: Ideally check_raw_mode_ok should take slot instead of or with
regno and we don't need it here, but it is getting larger than what
the fix needs to be. I can do it as part of other func check
refactoring if you want to keep it as is.
Reviewed-by: Amery Hung <ameryhung@gmail.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 19:52 ` Amery Hung
@ 2026-09-16 20:27 ` Amery Hung
0 siblings, 0 replies; 12+ messages in thread
From: Amery Hung @ 2026-09-16 20:27 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Tejun Heo, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis, kkd,
kernel-team
On Wed, Sep 16, 2026 at 12:52 PM Amery Hung <ameryhung@gmail.com> wrote:
>
> > 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 +9190,16 @@ 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))
> > + meta->arg_raw_mem.regno = slot + 1;
> > +
>
> Nit: Ideally check_raw_mode_ok should take slot instead of or with
> regno and we don't need it here, but it is getting larger than what
> the fix needs to be. I can do it as part of other func check
> refactoring if you want to keep it as is.
>
> Reviewed-by: Amery Hung <ameryhung@gmail.com>
Actually after reading patch 4, I think we should either just drop
check_raw_mode_ok() all together or keep it and fully populate mask
there. This reduces the divergence in helper/kfunc handling.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 19:27 ` [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 19:52 ` Amery Hung
@ 2026-09-16 20:27 ` bot+bpf-ci
2026-09-17 19:29 ` Eduard Zingerman
2 siblings, 0 replies; 12+ messages in thread
From: bot+bpf-ci @ 2026-09-16 20:27 UTC (permalink / raw)
To: memxor, bpf
Cc: tj, ast, andrii, daniel, eddyz87, emil, ameryhung, kkd,
kernel-team, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6575 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.
> +
> +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::
>
> -An example is given below::
> + __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.
[ ... ]
This isn't a bug, but could the middle paragraphs be trimmed to the
decisions that are not obvious from the diff (for example why raw mode
must only be dropped for the tracked output), rather than listing each
hunk?
The first two paragraphs state the problem and the fix rationale well.
The middle paragraphs then walk the diff hunk by hunk, with several
sentences that restate a single line of code rather than explain a
decision: 'Use a common access-mode helper for fixed-size and sized
arguments' (the new func_arg_access_type()), 'Look up the output
register state only when there are recorded bytes to initialize' (the
'if (!meta->arg_raw_mem.size) return 0;' early exit), and 'Include
struct pointers that are resolved to generic memory later' (the
ARG_PTR_TO_BTF_ID term added to arg_type_is_raw_mem()).
A reader who has the diff learns little from those lines.
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index cf85141ea1674..3c1b06a3daf30 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;
> };
This isn't a bug, but now that this holds a one-based argument slot for
kfuncs, would naming it after the slot (e.g. arg_slot) read better next
to argno_from_arg()/release_regno, so the comment does not have to
carry two meanings?
The field keeps the name regno but, for kfuncs, now holds a one-based
argument slot rather than a register number, and the comment is what
carries that dual meaning. Every consumer converts it back:
mark_raw_stack() does 'u32 slot = meta->arg_raw_mem.regno - 1;' before
get_func_arg_reg(), and check_stack_range_initialized()/
check_mem_size_reg() compare it against abs(argno.argno) rather than
against a register number. The same file/function already uses distinct
slot terminology (argno_t, argno_from_arg(), argno_from_reg(),
reg_from_argno(), check_func_arg(env, arg, slot, prev_slot, ...)), and
release_regno next to it really is a register number, so a field named
regno holding a slot reads against the surrounding convention.
[ ... ]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6c6b8d8520cdf..d7a40dc159aea 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);
[ ... ]
> @@ -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;
This isn't a bug, but would a small arg_slot_from_argno() helper next
to the other argno_t accessors read better than open-coding
abs(argno.argno) in both check_stack_range_initialized() and
check_mem_size_reg()?
The same expression abs(<argno>.argno) is open-coded in two places
(check_stack_range_initialized() at line 6984 and check_mem_size_reg()
at line 7219). argno_t is a wrapper struct (typedef at verifier.c:270-
272) with three dedicated accessors right next to it - reg_from_argno()
(line 284), arg_from_argno() (line 293) and arg_idx_from_argno() (line
300). Grepping the file shows these two new lines are the only reads of
the raw .argno member anywhere in verifier.c outside those four
accessor functions; every other site goes through an accessor. Neither
existing accessor has the needed semantics (reg_from_argno() returns -1
for argument slots beyond MAX_BPF_FUNC_REG_ARGS, arg_from_argno()
returns -1 for register-encoded values), which is exactly why the raw
member is being read here.
[ ... ]
---
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/35141528669
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers
2026-09-16 19:27 ` [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 19:52 ` Amery Hung
2026-09-16 20:27 ` bot+bpf-ci
@ 2026-09-17 19:29 ` Eduard Zingerman
2 siblings, 0 replies; 12+ messages in thread
From: Eduard Zingerman @ 2026-09-17 19:29 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 Wed, 2026-09-16 at 21:27 +0200, Kumar Kartikeya Dwivedi wrote:
...
> @@ -8885,6 +8889,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 +9190,16 @@ 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))
> + meta->arg_raw_mem.regno = slot + 1;
> +
Let's avoid adding special cases for kfuncs as in two hunks above.
Please consider a rework as in [1], which makes processing of the
arg_raw_mem.regno and MEM_WRITE bit identical for kfuncs and helpers.
The details are in commit messages (partial slop).
Not identified incorrect handling of MEM_WRITE for helpers
(MEM_WRITE should imply MEM_READ).
[1] https://github.com/eddyz87/bpf/tree/kfunc-uninit-tweaks
...
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization
2026-09-16 19:27 [PATCH bpf-next v3 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
2026-09-16 19:27 ` [PATCH bpf-next v3 1/5] selftests/bpf: Allow privileged preparation for capability tests Kumar Kartikeya Dwivedi
2026-09-16 19:27 ` [PATCH bpf-next v3 2/5] bpf: Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
@ 2026-09-16 19:28 ` Kumar Kartikeya Dwivedi
2026-09-16 20:05 ` Amery Hung
2026-09-16 20:27 ` bot+bpf-ci
2026-09-16 19:28 ` [PATCH bpf-next v3 4/5] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
2026-09-16 19:28 ` [PATCH bpf-next v3 5/5] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
4 siblings, 2 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 19:28 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] 12+ messages in thread* Re: [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization
2026-09-16 19:28 ` [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
@ 2026-09-16 20:05 ` Amery Hung
2026-09-16 20:27 ` bot+bpf-ci
1 sibling, 0 replies; 12+ messages in thread
From: Amery Hung @ 2026-09-16 20:05 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
On Wed, Sep 16, 2026 at 12:28 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> 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>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization
2026-09-16 19:28 ` [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
2026-09-16 20:05 ` Amery Hung
@ 2026-09-16 20:27 ` bot+bpf-ci
1 sibling, 0 replies; 12+ messages in thread
From: bot+bpf-ci @ 2026-09-16 20:27 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: 3094 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
> index 7732df9bc8701..4a9affe81ee16 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"
This isn't a bug, but the include placement breaks the alphabetical
ordering that the rest of this list follows - "kfunc_uninit" sorts after
"kfunc_packet_access", so should this move one line down?
> @@ -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); }
Same ordering question here - should this also move down one line to
maintain alphabetical order?
[ ... ]
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> index fd2c0cdc91b18..dfffbdff06fa9 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;
> +}
This isn't a bug, but bpf_kfunc_test_uninit_alias() uses
get_unaligned()/put_unaligned() which are new to this file. They currently
resolve via a transitive include chain (linux/filter.h -> if_vlan.h ->
etherdevice.h -> unaligned.h). Should <linux/unaligned.h> be added to the
include block so the file doesn't depend on that chain?
---
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/35141528669
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH bpf-next v3 4/5] bpf: Support multiple __uninit kfunc output arguments
2026-09-16 19:27 [PATCH bpf-next v3 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (2 preceding siblings ...)
2026-09-16 19:28 ` [PATCH bpf-next v3 3/5] selftests/bpf: Cover generic __uninit output initialization Kumar Kartikeya Dwivedi
@ 2026-09-16 19:28 ` Kumar Kartikeya Dwivedi
2026-09-16 19:28 ` [PATCH bpf-next v3 5/5] selftests/bpf: Cover __uninit kfunc output argument slots Kumar Kartikeya Dwivedi
4 siblings, 0 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 19:28 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 in check_raw_mode_ok(). Remove the
kfunc output-count check, which no longer constrains the prototype, and
simplify the validator back to its helper-only role.
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 | 65 ++++++++++++++++++------------------
2 files changed, 38 insertions(+), 37 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 d7a40dc159ae..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",
@@ -8158,12 +8165,9 @@ 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) &&
+ base_type(type) == ARG_PTR_TO_MAP_VALUE) &&
type & MEM_UNINIT;
}
@@ -9038,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,
@@ -9191,7 +9195,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
bool known_memory;
if (meta->btf && (arg_type & MEM_UNINIT))
- meta->arg_raw_mem.regno = slot + 1;
+ 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.
@@ -9567,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;
@@ -9884,7 +9892,6 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
{
- bool seen = false;
int i;
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
@@ -9892,11 +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 (seen)
+ if (meta->arg_raw_mem.mask)
return false;
- seen = true;
- if (!meta->btf)
- meta->arg_raw_mem.regno = i + 1;
+ meta->arg_raw_mem.mask = BIT(i);
}
return true;
@@ -13099,10 +13104,6 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg
return -ENOTSUPP;
}
- if (!check_raw_mode_ok(proto, meta)) {
- verbose(env, "multiple __uninit buffers are not supported\n");
- return -EINVAL;
- }
return check_arg_prog_aux(env, proto) ? 0 : -EINVAL;
}
@@ -13899,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] 12+ messages in thread* [PATCH bpf-next v3 5/5] selftests/bpf: Cover __uninit kfunc output argument slots
2026-09-16 19:27 [PATCH bpf-next v3 0/5] Fix generic __uninit kfunc output buffers Kumar Kartikeya Dwivedi
` (3 preceding siblings ...)
2026-09-16 19:28 ` [PATCH bpf-next v3 4/5] bpf: Support multiple __uninit kfunc output arguments Kumar Kartikeya Dwivedi
@ 2026-09-16 19:28 ` Kumar Kartikeya Dwivedi
4 siblings, 0 replies; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-09-16 19:28 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 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..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 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] 12+ messages in thread