Linux Kernel Selftest development
 help / color / mirror / Atom feed
* [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers
@ 2026-06-15  8:31 Nuoqi Gui
  2026-06-15  8:31 ` [PATCH bpf 1/2] " Nuoqi Gui
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Nuoqi Gui @ 2026-06-15  8:31 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Martin KaFai Lau, Mykyta Yatsenko, Shuah Khan, bpf,
	linux-kselftest, linux-kernel, Nuoqi Gui

The dynptr probe-read kfuncs are registered as common kfuncs. That lets a
program with the generic kfunc privilege gate call
bpf_probe_read_kernel_dynptr(), while the legacy bpf_probe_read_kernel()
helper is exposed only with CAP_PERFMON and is blocked by kernel-read
lockdown.

Gate the four nofault probe-read dynptr kfuncs with CAP_PERFMON, and apply
the existing LOCKDOWN_BPF_READ_KERNEL policy to the kernel-read dynptr
variants. Add focused selftests that expect CAP_BPF without CAP_PERFMON
to be insufficient for bpf_probe_read_user_dynptr(),
bpf_probe_read_kernel_dynptr(), bpf_probe_read_user_str_dynptr(), and
bpf_probe_read_kernel_str_dynptr().

Bounded impact:
- Requires privileged BPF program loading with CAP_BPF.
- No unprivileged trigger, arbitrary disclosure primitive, memory
  corruption, privilege escalation, CVE, embargo, or security escalation is
  claimed.

Validation:
- The focused dynptr selftest loader exercises the CAP_BPF-only program load
  boundary for bpf_probe_read_user_dynptr(),
  bpf_probe_read_kernel_dynptr(), bpf_probe_read_user_str_dynptr(), and
  bpf_probe_read_kernel_str_dynptr().
- Unpatched bpf at 8496d9020ff37 ("Merge branch 'arena_direct_access'"):
  FAIL as expected because all four programs still load with CAP_BPF and
  without CAP_PERFMON.
- Patched with this series: PASS because all four same programs are rejected
  with -EACCES when loaded with CAP_BPF and without CAP_PERFMON.

Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")

Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
---
Nuoqi Gui (2):
      bpf: Gate dynptr probe-read kfuncs like helpers
      selftests/bpf: Cover dynptr probe-read kfunc capability gate

 kernel/bpf/helpers.c                               | 32 +++++++++
 tools/testing/selftests/bpf/prog_tests/dynptr.c    | 66 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/dynptr_success.c | 75 ++++++++++++++++++++++
 3 files changed, 173 insertions(+)
---
base-commit: 8496d9020ff37a33c2a7b2fc84350fd03ffbde78
change-id: 20260615-f01-07-dynptr-probe-read-cap-4dee7066bf5f

Best regards,
--  
Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH bpf 1/2] bpf: Gate dynptr probe-read kfuncs like helpers
  2026-06-15  8:31 [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Nuoqi Gui
@ 2026-06-15  8:31 ` Nuoqi Gui
  2026-06-15  8:31 ` [PATCH bpf 2/2] selftests/bpf: Cover dynptr probe-read kfunc capability gate Nuoqi Gui
  2026-06-15 15:48 ` [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Alexei Starovoitov
  2 siblings, 0 replies; 4+ messages in thread
From: Nuoqi Gui @ 2026-06-15  8:31 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Martin KaFai Lau, Mykyta Yatsenko, Shuah Khan, bpf,
	linux-kselftest, linux-kernel, Nuoqi Gui

The bpf_probe_read_{user,kernel}{,_str}_dynptr() kfuncs provide
probe-read style nofault copies into dynptr-backed BPF memory. Their
legacy helper counterparts are exposed only to programs with CAP_PERFMON,
and the kernel-read helpers are additionally blocked by
LOCKDOWN_BPF_READ_KERNEL.

The dynptr kfuncs are registered in the common kfunc set, so the verifier
currently allows them with the generic kfunc privilege gate. Add a common
kfunc filter that mirrors the helper policy for these probe-read dynptr
kfuncs: require CAP_PERFMON for all four nofault probe-read variants and
apply the existing lockdown check to the kernel-read variants.

This preserves the existing dynptr copy implementation while aligning the
capability policy with the legacy probe-read helpers.

Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")
Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
---
 kernel/bpf/helpers.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 9ca195104667..fbef89196f85 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4961,9 +4961,41 @@ BTF_ID_FLAGS(func, bpf_dynptr_file_discard)
 BTF_ID_FLAGS(func, bpf_timer_cancel_async)
 BTF_KFUNCS_END(common_btf_ids)
 
+#ifdef CONFIG_BPF_EVENTS
+BTF_KFUNCS_START(probe_read_dynptr_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr)
+BTF_KFUNCS_END(probe_read_dynptr_kfunc_ids)
+
+BTF_KFUNCS_START(probe_read_kernel_dynptr_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr)
+BTF_KFUNCS_END(probe_read_kernel_dynptr_kfunc_ids)
+#endif
+
+static int common_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+#ifdef CONFIG_BPF_EVENTS
+	if (!btf_id_set8_contains(&probe_read_dynptr_kfunc_ids, kfunc_id))
+		return 0;
+
+	if (!bpf_token_capable(prog->aux->token, CAP_PERFMON))
+		return -EACCES;
+
+	if (btf_id_set8_contains(&probe_read_kernel_dynptr_kfunc_ids, kfunc_id) &&
+	    security_locked_down(LOCKDOWN_BPF_READ_KERNEL) < 0)
+		return -EACCES;
+#endif
+
+	return 0;
+}
+
 static const struct btf_kfunc_id_set common_kfunc_set = {
 	.owner = THIS_MODULE,
 	.set   = &common_btf_ids,
+	.filter = common_kfunc_filter,
 };
 
 static int __init kfunc_init(void)

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH bpf 2/2] selftests/bpf: Cover dynptr probe-read kfunc capability gate
  2026-06-15  8:31 [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Nuoqi Gui
  2026-06-15  8:31 ` [PATCH bpf 1/2] " Nuoqi Gui
@ 2026-06-15  8:31 ` Nuoqi Gui
  2026-06-15 15:48 ` [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Alexei Starovoitov
  2 siblings, 0 replies; 4+ messages in thread
From: Nuoqi Gui @ 2026-06-15  8:31 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Martin KaFai Lau, Mykyta Yatsenko, Shuah Khan, bpf,
	linux-kselftest, linux-kernel, Nuoqi Gui

Add focused dynptr selftest programs that load socket-filter programs using
bpf_dynptr_from_mem() followed by each nofault probe-read dynptr kfunc:
bpf_probe_read_user_dynptr(), bpf_probe_read_kernel_dynptr(),
bpf_probe_read_user_str_dynptr(), and bpf_probe_read_kernel_str_dynptr().
Declare the dynptr kfuncs used by this BPF object locally in
dynptr_success.c so the dynptr BPF object builds with explicit prototypes.

The harness first verifies that the program loads with the caller's normal
capabilities. It then drops CAP_PERFMON and CAP_SYS_ADMIN while keeping
CAP_BPF effective and expects each same program load to fail with -EACCES.

This covers the policy boundary that these dynptr probe-read kfuncs should
not be callable with CAP_BPF alone.

Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
---
 tools/testing/selftests/bpf/prog_tests/dynptr.c    | 66 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/dynptr_success.c | 75 ++++++++++++++++++++++
 2 files changed, 141 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c
index 5fda11590708..5235b47e3342 100644
--- a/tools/testing/selftests/bpf/prog_tests/dynptr.c
+++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c
@@ -3,6 +3,7 @@
 
 #include <test_progs.h>
 #include <network_helpers.h>
+#include "cap_helpers.h"
 #include "dynptr_fail.skel.h"
 #include "dynptr_success.skel.h"
 
@@ -53,8 +54,66 @@ static struct {
 	{"test_copy_from_user_task_str_dynptr", SETUP_SYSCALL_SLEEP},
 };
 
+static const char * const probe_read_cap_tests[] = {
+	"test_probe_read_user_dynptr_cap",
+	"test_probe_read_kernel_dynptr_cap",
+	"test_probe_read_user_str_dynptr_cap",
+	"test_probe_read_kernel_str_dynptr_cap",
+};
+
 #define PAGE_SIZE_64K 65536
 
+static int load_probe_read_cap_prog(const char *prog_name)
+{
+	struct bpf_program *prog, *tmp;
+	struct dynptr_success *skel;
+	int err;
+
+	skel = dynptr_success__open();
+	if (!ASSERT_OK_PTR(skel, "dynptr_success__open"))
+		return -EINVAL;
+
+	bpf_object__for_each_program(tmp, skel->obj)
+		bpf_program__set_autoload(tmp, false);
+
+	prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+	if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name")) {
+		err = -ENOENT;
+		goto cleanup;
+	}
+
+	bpf_program__set_autoload(prog, true);
+	err = dynptr_success__load(skel);
+
+cleanup:
+	dynptr_success__destroy(skel);
+	return err;
+}
+
+static void verify_probe_read_cap(const char *prog_name)
+{
+	__u64 old_caps = 0;
+	int err;
+
+	err = load_probe_read_cap_prog(prog_name);
+	if (!ASSERT_OK(err, "full_caps_load"))
+		return;
+
+	err = cap_enable_effective(1ULL << CAP_BPF, &old_caps);
+	if (!ASSERT_OK(err, "enable_cap_bpf"))
+		return;
+
+	err = cap_disable_effective(1ULL << CAP_SYS_ADMIN | 1ULL << CAP_PERFMON, NULL);
+	if (!ASSERT_OK(err, "disable_cap_sys_admin_perfmon"))
+		goto restore_cap;
+
+	err = load_probe_read_cap_prog(prog_name);
+	ASSERT_EQ(err, -EACCES, "cap_bpf_without_perfmon_load");
+
+restore_cap:
+	cap_enable_effective(old_caps, NULL);
+}
+
 static void verify_success(const char *prog_name, enum test_setup_type setup_type)
 {
 	char user_data[384] = {[0 ... 382] = 'a', '\0'};
@@ -185,6 +244,13 @@ void test_dynptr(void)
 {
 	int i;
 
+	for (i = 0; i < ARRAY_SIZE(probe_read_cap_tests); i++) {
+		if (!test__start_subtest(probe_read_cap_tests[i]))
+			continue;
+
+		verify_probe_read_cap(probe_read_cap_tests[i]);
+	}
+
 	for (i = 0; i < ARRAY_SIZE(success_tests); i++) {
 		if (!test__start_subtest(success_tests[i].prog_name))
 			continue;
diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c
index e0745b6e467e..5b4e22198598 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_success.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -34,6 +34,13 @@ struct {
 	__type(value, __u32);
 } array_map SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, __u8[32]);
+} probe_read_map SEC(".maps");
+
 SEC("?tp/syscalls/sys_enter_nanosleep")
 int test_read_write(void *ctx)
 {
@@ -1092,6 +1099,74 @@ int test_probe_read_kernel_str_dynptr(struct xdp_md *xdp)
 	return XDP_PASS;
 }
 
+SEC("?socket")
+int test_probe_read_user_dynptr_cap(struct __sk_buff *skb)
+{
+	struct bpf_dynptr ptr;
+	__u32 key = 0;
+	void *value;
+
+	value = bpf_map_lookup_elem(&probe_read_map, &key);
+	if (!value)
+		return 0;
+
+	if (bpf_dynptr_from_mem(value, 32, 0, &ptr))
+		return 0;
+
+	return bpf_probe_read_user_dynptr(&ptr, 0, 8, value);
+}
+
+SEC("?socket")
+int test_probe_read_kernel_dynptr_cap(struct __sk_buff *skb)
+{
+	struct bpf_dynptr ptr;
+	__u32 key = 0;
+	void *value;
+
+	value = bpf_map_lookup_elem(&probe_read_map, &key);
+	if (!value)
+		return 0;
+
+	if (bpf_dynptr_from_mem(value, 32, 0, &ptr))
+		return 0;
+
+	return bpf_probe_read_kernel_dynptr(&ptr, 0, 8, value);
+}
+
+SEC("?socket")
+int test_probe_read_user_str_dynptr_cap(struct __sk_buff *skb)
+{
+	struct bpf_dynptr ptr;
+	__u32 key = 0;
+	void *value;
+
+	value = bpf_map_lookup_elem(&probe_read_map, &key);
+	if (!value)
+		return 0;
+
+	if (bpf_dynptr_from_mem(value, 32, 0, &ptr))
+		return 0;
+
+	return bpf_probe_read_user_str_dynptr(&ptr, 0, 8, value);
+}
+
+SEC("?socket")
+int test_probe_read_kernel_str_dynptr_cap(struct __sk_buff *skb)
+{
+	struct bpf_dynptr ptr;
+	__u32 key = 0;
+	void *value;
+
+	value = bpf_map_lookup_elem(&probe_read_map, &key);
+	if (!value)
+		return 0;
+
+	if (bpf_dynptr_from_mem(value, 32, 0, &ptr))
+		return 0;
+
+	return bpf_probe_read_kernel_str_dynptr(&ptr, 0, 8, value);
+}
+
 SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
 int test_copy_from_user_dynptr(void *ctx)
 {

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers
  2026-06-15  8:31 [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Nuoqi Gui
  2026-06-15  8:31 ` [PATCH bpf 1/2] " Nuoqi Gui
  2026-06-15  8:31 ` [PATCH bpf 2/2] selftests/bpf: Cover dynptr probe-read kfunc capability gate Nuoqi Gui
@ 2026-06-15 15:48 ` Alexei Starovoitov
  2 siblings, 0 replies; 4+ messages in thread
From: Alexei Starovoitov @ 2026-06-15 15:48 UTC (permalink / raw)
  To: Nuoqi Gui, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Martin KaFai Lau, Mykyta Yatsenko, Shuah Khan, bpf,
	linux-kselftest, linux-kernel

On Mon Jun 15, 2026 at 1:31 AM PDT, Nuoqi Gui wrote:
> The dynptr probe-read kfuncs are registered as common kfuncs. That lets a
> program with the generic kfunc privilege gate call
> bpf_probe_read_kernel_dynptr(), while the legacy bpf_probe_read_kernel()
> helper is exposed only with CAP_PERFMON and is blocked by kernel-read
> lockdown.

It's fine as-is. Won't fix.

pw-bot: cr

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-15 15:48 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-15  8:31 [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Nuoqi Gui
2026-06-15  8:31 ` [PATCH bpf 1/2] " Nuoqi Gui
2026-06-15  8:31 ` [PATCH bpf 2/2] selftests/bpf: Cover dynptr probe-read kfunc capability gate Nuoqi Gui
2026-06-15 15:48 ` [PATCH bpf 0/2] bpf: Gate dynptr probe-read kfuncs like helpers Alexei Starovoitov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox