All of lore.kernel.org
 help / color / mirror / Atom feed
From: Daniel Borkmann <daniel@iogearbox.net>
To: eddyz87@gmail.com
Cc: memxor@gmail.com, bpf@vger.kernel.org
Subject: [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load
Date: Thu, 13 Aug 2026 22:40:32 +0200	[thread overview]
Message-ID: <20260813204032.644949-4-daniel@iogearbox.net> (raw)
In-Reply-To: <20260813204032.644949-1-daniel@iogearbox.net>

Cover the ways in which the type recorded for a shared load used to lose
the BPF_PROBE_MEM rewrite. All four reach the same load with a PTR_TO_MEM
on one verification path and take a NULL deref on the other:

  - mixed_mem_untrusted_btf_id_type: pairs with PTR_TO_BTF_ID |
    PTR_UNTRUSTED from bpf_rdonly_cast() which is merged into
    PTR_TO_MEM | PTR_UNTRUSTED
  - mixed_mem_btf_id_type: pairs with bare PTR_TO_BTF_ID from
    a pointer walk which gets merged into a bare PTR_TO_MEM
  - mixed_rdonly_mem_btf_id_type: same, but with a PTR_TO_MEM |
    MEM_RDONLY from bpf_dynptr_slice() which is merged into
    PTR_TO_MEM | MEM_RDONLY
  - mixed_mem_mem_type: pairs PTR_TO_MEM | MEM_RINGBUF with the
    PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED of bpf_rdonly_cast()
    which was not merged at all

bpf_convert_ctx_accesses() rewrites none of these, so the NULL deref
on the second path panicked rather than returning 0. Assert that this
is not the case with the fix anymore.

  # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t mem_rdonly_untrusted
  [...]
  #238/1   mem_rdonly_untrusted/btf_id_to_ptr_mem:OK
  #238/2   mem_rdonly_untrusted/ldx_is_ok_bad_addr:OK
  #238/3   mem_rdonly_untrusted/ldx_is_ok_good_addr:OK
  #238/4   mem_rdonly_untrusted/offset_not_tracked:OK
  #238/5   mem_rdonly_untrusted/stx_not_ok:OK
  #238/6   mem_rdonly_untrusted/atomic_not_ok:OK
  #238/7   mem_rdonly_untrusted/atomic_rmw_not_ok:OK
  #238/8   mem_rdonly_untrusted/kfunc_param_not_ok:OK
  #238/9   mem_rdonly_untrusted/mixed_mem_type:OK
  #238/10  mem_rdonly_untrusted/mixed_mem_untrusted_btf_id_type:OK
  #238/11  mem_rdonly_untrusted/mixed_mem_btf_id_type:OK
  #238/12  mem_rdonly_untrusted/mixed_rdonly_mem_btf_id_type:OK
  #238/13  mem_rdonly_untrusted/mixed_mem_mem_type:OK
  #238/14  mem_rdonly_untrusted/diff_size_access:OK
  #238/15  mem_rdonly_untrusted/misaligned_access:OK
  #238/16  mem_rdonly_untrusted/null_check:OK
  #238/17  mem_rdonly_untrusted/ldx_is_ok_commuted_addr:OK
  #238/18  mem_rdonly_untrusted/helper_param_not_ok:OK
  #238     mem_rdonly_untrusted:OK
  Summary: 1/18 PASSED, 0 SKIPPED, 0/0 FAILED

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
 .../bpf/progs/mem_rdonly_untrusted.c          | 183 ++++++++++++++++++
 1 file changed, 183 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
index b91271d4caa4..127b745bb0b7 100644
--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
@@ -3,6 +3,7 @@
 #include <vmlinux.h>
 #include <bpf/bpf_core_read.h>
 #include "bpf_misc.h"
+#include "bpf_kfuncs.h"
 #include "../test_kmods/bpf_testmod_kfunc.h"
 
 SEC("tp_btf/sys_enter")
@@ -164,6 +165,188 @@ int mixed_mem_type(void *ctx)
 	return *p;
 }
 
+struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 4096);
+} ringbuf SEC(".maps");
+
+int zero;
+
+static __noinline u64 *get_mem_or_untrusted_addr(u64 *mem)
+{
+	/*
+	 * Try to avoid compiler hoisting load to if branches by using
+	 * __noinline func.
+	 */
+	if (zero)
+		return mem;
+	else
+		return bpf_rdonly_cast(0, bpf_core_type_id_kernel(struct sock));
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("= *(u64 *)(r{{[0-9]}} +0){{.*}}=untrusted_ptr_sock")
+__msg("= *(u64 *)(r{{[0-9]}} +0){{.*}}=ringbuf_mem")
+__retval(0)
+int mixed_mem_untrusted_btf_id_type(void *ctx)
+{
+	u64 *p, v;
+
+	p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
+	if (!p)
+		return 1;
+	*p = 42;
+	/*
+	 * The load below is reached with PTR_TO_MEM | MEM_RINGBUF on one
+	 * path and with PTR_TO_BTF_ID | PTR_UNTRUSTED on the other. The
+	 * merged type has to keep the BPF_PROBE_MEM rewrite, otherwise
+	 * the NULL deref taken at runtime panics the kernel instead of
+	 * returning 0.
+	 */
+	v = *get_mem_or_untrusted_addr(p);
+	bpf_ringbuf_discard(p, 0);
+	return v;
+}
+
+static __noinline u32 *get_mem_or_btf_id_addr(u32 *mem)
+{
+	struct task_struct *task;
+
+	/*
+	 * Try to avoid compiler hoisting load to if branches by using
+	 * __noinline func.
+	 */
+	if (zero)
+		return mem;
+
+	task = bpf_get_current_task_btf();
+	/*
+	 * A plain BTF pointer walk yields a bare PTR_TO_BTF_ID, and
+	 * task->nameidata is NULL unless the task currently is in the
+	 * middle of a path lookup.
+	 */
+	return (u32 *)&task->nameidata->flags;
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("= *(u32 *)(r{{[0-9]}} +0){{.*}}=ptr_nameidata")
+__msg("= *(u32 *)(r{{[0-9]}} +0){{.*}}=ringbuf_mem")
+__retval(0)
+int mixed_mem_btf_id_type(void *ctx)
+{
+	u32 *p, v;
+
+	p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
+	if (!p)
+		return 1;
+	*p = 42;
+	/*
+	 * Same as above, except that the other path yields a bare
+	 * PTR_TO_BTF_ID. Merging it with PTR_TO_MEM used to drop the
+	 * BPF_PROBE_MEM rewrite the bare PTR_TO_BTF_ID would have
+	 * gotten on its own.
+	 */
+	v = *get_mem_or_btf_id_addr(p);
+	bpf_ringbuf_discard(p, 0);
+	return v;
+}
+
+char dynptr_data[8];
+
+static __noinline u32 *get_rdonly_mem_or_btf_id_addr(u32 *mem)
+{
+	struct task_struct *task;
+
+	/*
+	 * Try to avoid compiler hoisting load to if branches by using
+	 * __noinline func.
+	 */
+	if (zero)
+		return mem;
+
+	task = bpf_get_current_task_btf();
+	return (u32 *)&task->nameidata->flags;
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ptr_nameidata")
+__msg("r8 = *(u32 *)(r7 +0){{.*}}R7=rdonly_mem")
+__retval(0)
+int mixed_rdonly_mem_btf_id_type(void *ctx)
+{
+	struct bpf_dynptr dptr;
+	char buf[sizeof(u32)];
+	u32 *p;
+	u64 v;
+
+	if (bpf_dynptr_from_mem(dynptr_data, sizeof(dynptr_data), 0, &dptr))
+		return 1;
+	p = bpf_dynptr_slice(&dptr, 0, buf, sizeof(buf));
+	if (!p)
+		return 1;
+	/*
+	 * Same as above, except that the PTR_TO_MEM side already carries
+	 * MEM_RDONLY. Merging it with a bare PTR_TO_BTF_ID used to yield
+	 * PTR_TO_MEM | MEM_RDONLY, which is not rewritten either since
+	 * only its PTR_UNTRUSTED variant is.
+	 */
+	p = get_rdonly_mem_or_btf_id_addr(p);
+	/* asm block to have reliable match target for __msg. */
+	asm volatile (
+	"r7 = %[p];"
+	"r8 = *(u32 *)(r7 + 0);"
+	"%[v] = r8;"
+	: [v]"=r"(v)
+	: [p]"r"(p)
+	: "r7", "r8");
+	return v;
+}
+
+static __noinline u64 *get_mem_or_rdonly_untrusted_mem_addr(u64 *mem)
+{
+	u64 *p = bpf_rdonly_cast(0, 0);
+
+	/*
+	 * Hoist the cast above the branch so that the PTR_TO_MEM |
+	 * MEM_RINGBUF path is verified first and thus gets its type
+	 * recorded first.
+	 */
+	if (zero == 0)
+		return p;
+	return mem;
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("= *(u64 *)(r{{[0-9]}} +0){{.*}}=ringbuf_mem")
+__msg("= *(u64 *)(r{{[0-9]}} +0){{.*}}=rdonly_untrusted_mem")
+__retval(0)
+int mixed_mem_mem_type(void *ctx)
+{
+	u64 *p, v;
+
+	p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
+	if (!p)
+		return 1;
+	*p = 42;
+	/*
+	 * Both paths are PTR_TO_MEM based, so they do not trip the type
+	 * mismatch check and used to skip the merge altogether, leaving
+	 * the insn with the PTR_TO_MEM | MEM_RINGBUF recorded first and
+	 * hence without the BPF_PROBE_MEM rewrite the other path needs.
+	 */
+	v = *get_mem_or_rdonly_untrusted_mem_addr(p);
+	bpf_ringbuf_discard(p, 0);
+	return v;
+}
+
 __attribute__((__aligned__(8)))
 u8 global[] = {
 	0x11, 0x22, 0x33, 0x44,
-- 
2.43.0


  parent reply	other threads:[~2026-08-13 20:40 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-13 20:40 [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types Daniel Borkmann
2026-08-13 20:40 ` [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM Daniel Borkmann
2026-08-13 21:44   ` bot+bpf-ci
2026-08-13 21:52     ` Daniel Borkmann
2026-08-14  0:14   ` Eduard Zingerman
2026-08-14  8:10     ` Daniel Borkmann
2026-08-14  1:08   ` sashiko-bot
2026-08-13 20:40 ` [PATCH bpf-next 3/4] bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation Daniel Borkmann
2026-08-13 21:44   ` bot+bpf-ci
2026-08-14  0:10   ` Eduard Zingerman
2026-08-13 20:40 ` Daniel Borkmann [this message]
2026-08-13 21:44   ` [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load bot+bpf-ci
2026-08-13 21:44 ` [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types bot+bpf-ci
2026-08-14  0:08 ` Eduard Zingerman

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=20260813204032.644949-4-daniel@iogearbox.net \
    --to=daniel@iogearbox.net \
    --cc=bpf@vger.kernel.org \
    --cc=eddyz87@gmail.com \
    --cc=memxor@gmail.com \
    /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 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.