mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Tejun Heo <tj@kernel.org>
To: Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Andrii Nakryiko <andrii@kernel.org>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>
Cc: Martin KaFai Lau <martin.lau@linux.dev>,
	Song Liu <song@kernel.org>,
	Yonghong Song <yonghong.song@linux.dev>,
	Jiri Olsa <jolsa@kernel.org>,
	Emil Tsalapatis <emil@etsalapatis.com>,
	Ihor Solodrai <ihor.solodrai@linux.dev>,
	Amery Hung <ameryhung@gmail.com>,
	bpf@vger.kernel.org, sched-ext@lists.linux.dev,
	linux-kernel@vger.kernel.org
Subject: [PATCH bpf v2] bpf: Keep generic __uninit kfunc arguments live
Date: Mon, 14 Sep 2026 14:27:21 -1000	[thread overview]
Message-ID: <b6782cd33f9ccc6265b0b2140cf1f68d@kernel.org> (raw)

Stack liveness models a kfunc's __uninit pointer argument as a pure write:
whatever the buffer holds before the call is dead, and 2cb27158adb3 ("bpf:
poison dead stack slots") poisons those slots at every checkpoint the path
visits before the call. Argument validation disagrees. Only dynptr arguments
honor __uninit. A generic fixed-size buffer goes through check_mem_reg(),
whose read pass requires every byte readable. The two rules meet at the call
and the poisoned buffer is rejected with "slot poisoned by dead code
elimination", even when the program initialized it.

Every kfunc with a generic __uninit output argument is affected,
bpf_ksock_create() through err__uninit and sched_ext's scx_bpf_cid_topo()
through its output struct. The rejection needs the buffer's slots to be
allocated and dead at a checkpoint, for example on the second iteration of a
loop around the call. Before e566701b9b0c ("bpf: Check fixed-size mem args
of helpers and kfuncs the same way") kfunc arguments could read poisoned
slots, so the call passed, but the slots stayed poisoned and the program's
next read of the buffer was rejected instead.

Restrict the liveness write-only exception to dynptr arguments, where
validation accepts uninitialized output. A generic __uninit buffer stays
live and is never poisoned, and validation keeps requiring it to be
initialized. Add a selftest with a scalar output buffer and a checkpoint
between its initialization and the call.

v2: Rebased on bpf/master.

Fixes: 2cb27158adb3 ("bpf: poison dead stack slots")
Cc: stable@vger.kernel.org # v7.1+
Signed-off-by: Tejun Heo <tj@kernel.org>
---
 kernel/bpf/verifier.c                                      |    3 
 tools/testing/selftests/bpf/prog_tests/verifier.c          |    2 
 tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c  |   45 +++++++++++++
 tools/testing/selftests/bpf/test_kmods/bpf_testmod.c       |    9 ++
 tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h |    1 
 5 files changed, 59 insertions(+), 1 deletion(-)

--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -13588,7 +13588,8 @@ out:
 	/* 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]))
+	/* only dynptr validation accepts an uninitialized __uninit argument */
+	if (is_kfunc_arg_dynptr(btf, &args[arg]) && is_kfunc_arg_uninit(btf, &args[arg]))
 		return -size;
 	return size;
 }
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -55,6 +55,7 @@
 #include "verifier_jeq_infer_not_null.skel.h"
 #include "verifier_jit_convergence.skel.h"
 #include "verifier_kfunc_perfmon.skel.h"
+#include "verifier_kfunc_uninit.skel.h"
 #include "verifier_ld_ind.skel.h"
 #include "verifier_ldsx.skel.h"
 #include "verifier_leak_ptr.skel.h"
@@ -220,6 +221,7 @@ void test_verifier_iterating_callbacks(v
 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_perfmon(void)        { RUN(verifier_kfunc_perfmon); }
+void test_verifier_kfunc_uninit(void)         { RUN_TESTS(verifier_kfunc_uninit); }
 void test_verifier_load_acquire(void)         { RUN(verifier_load_acquire); }
 void test_verifier_ld_ind(void)               { RUN(verifier_ld_ind); }
 void test_verifier_ldsx(void)                  { RUN(verifier_ldsx); }
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_uninit.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Tejun Heo <tj@kernel.org> */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* keep the BTF FUNC record for the inline assembly reference */
+void __kfunc_btf_root(void)
+{
+	bpf_kfunc_call_test_uninit(0);
+}
+
+SEC("tc")
+__success __retval(42)
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void uninit_scalar_struct(void)
+{
+	/* dead before the call, so poisoned at the checkpoint */
+	asm volatile (
+	"*(u64 *)(r10 - 16) = 0;"
+	"*(u64 *)(r10 - 8) = 0;"
+	"goto +0;"
+	"r1 = r10;"
+	"r1 += -16;"
+	"call %[bpf_kfunc_call_test_uninit];"
+	"r1 = *(u32 *)(r10 - 16);"
+	"if r1 != 1 goto 1f;"
+	"r1 = *(u32 *)(r10 - 12);"
+	"if r1 != 2 goto 1f;"
+	"r1 = *(u32 *)(r10 - 8);"
+	"if r1 != 3 goto 1f;"
+	"r1 = *(u32 *)(r10 - 4);"
+	"if r1 != 4 goto 1f;"
+	"r0 = 42;"
+	"exit;"
+"1:"
+	"r0 = 0;"
+	"exit;"
+	:: __imm(bpf_kfunc_call_test_uninit)
+	: __clobber_all);
+}
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1102,6 +1102,14 @@ __bpf_kfunc void bpf_kfunc_call_test_pas
 {
 }
 
+__bpf_kfunc void bpf_kfunc_call_test_uninit(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_call_test_fail1(struct prog_test_fail1 *p)
 {
 }
@@ -1508,6 +1516,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_int_me
 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_call_test_uninit)
 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)
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -161,6 +161,7 @@ __u64 bpf_kfunc_call_stack_arg_big(__u64
 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_call_test_uninit(struct prog_test_pass1 *out__uninit) __ksym;
 void bpf_kfunc_call_test_mem_len_fail2(__u64 *mem, int len) __ksym;
 
 void bpf_kfunc_call_test_destructive(void) __ksym;

             reply	other threads:[~2026-09-15  0:27 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-15  0:27 Tejun Heo [this message]
2026-09-15  1:15 ` bot+bpf-ci

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b6782cd33f9ccc6265b0b2140cf1f68d@kernel.org \
    --to=tj@kernel.org \
    --cc=ameryhung@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=ihor.solodrai@linux.dev \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=sched-ext@lists.linux.dev \
    --cc=song@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®