BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types
@ 2026-08-13 20:40 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
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-13 20:40 UTC (permalink / raw)
  To: eddyz87; +Cc: memxor, bpf

When the same BPF_LDX instruction is reached through paths that yield
different pointer types, save_aux_ptr_type() merges them into a single
type which is later used by bpf_convert_ctx_accesses() to decide whether
the load has to be rewritten into a BPF_PROBE_MEM one.

Before commit f2362a57aeff ("bpf: allow void* cast using bpf_rdonly_cast()")
the merge only accepted two PTR_TO_BTF_ID pointers and unconditionally
fell back to PTR_TO_BTF_ID | PTR_UNTRUSTED, so the merged type was always
one that gets the BPF_PROBE_MEM rewrite. However, the mentioned commit
widened the merge to also cover a PTR_TO_MEM base and replaced the
fallback by a union of the PTR_UNTRUSTED and MEM_RDONLY flags.

The union can produce types which bpf_convert_ctx_accesses() does not
rewrite, and the load then stays a plain one without an exception table
entry, e.g.:

  - PTR_TO_MEM merged with PTR_TO_BTF_ID | PTR_UNTRUSTED
   => PTR_TO_MEM | PTR_UNTRUSTED but only the MEM_RDONLY variant is valid
  - PTR_TO_MEM merged with a plain PTR_TO_BTF_ID
   => PTR_TO_MEM dropping the rewrite the latter type would have gotten
  - PTR_TO_MEM | MEM_RDONLY merged with a plain PTR_TO_BTF_ID
   => PTR_TO_MEM | MEM_RDONLY which is not rewritten either since only
    its PTR_UNTRUSTED variant is

In all three cases a program can take the unsafe path at runtime with a
NULL or otherwise bad pointer and panic the kernel on the faulting load.
Fix it by normalizing the merged type: if either side needs the rewrite,
pick the one rewritten form the merged base type has.

Reuse the may_fault_on_deref() helper in is_load_acq_unsafe() as well to
avoid open coding, and trim the overly verbose comment which is more of
implementation detail of bpf_convert_ctx_accesses() anyway.

Fixes: f2362a57aeff ("bpf: allow void* cast using bpf_rdonly_cast()")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
 kernel/bpf/verifier.c | 61 +++++++++++++++++++++++++------------------
 1 file changed, 35 insertions(+), 26 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 61ef43325c6f..0d3b76d7820e 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4819,6 +4819,18 @@ static bool is_arena_reg(struct bpf_verifier_env *env, int regno)
 	return reg->type == PTR_TO_ARENA;
 }
 
+static bool may_fault_on_deref(enum bpf_reg_type type)
+{
+	/*
+	 * The pointer types which must not be dereferenced without fault
+	 * protection, that is, the ones bpf_convert_ctx_accesses() has to
+	 * turn a BPF_LDX into a BPF_PROBE_MEM one for. Slightly wider than
+	 * the list matched there, which relies on an untrusted PTR_TO_MEM
+	 * always carrying MEM_RDONLY as well.
+	 */
+	return type == PTR_TO_BTF_ID || (type_flag(type) & PTR_UNTRUSTED);
+}
+
 static bool is_load_acq_unsafe(struct bpf_verifier_env *env, int regno,
 			       struct bpf_insn *insn)
 {
@@ -4828,19 +4840,11 @@ static bool is_load_acq_unsafe(struct bpf_verifier_env *env, int regno,
 	 * A BPF_LOAD_ACQ is not rewritten to a BPF_PROBE_MEM load by the
 	 * verifier, unlike a regular BPF_LDX. The JIT would emit a plain load
 	 * with no exception table entry, so a fault (e.g. NULL deref) crashes
-	 * the kernel instead of being handled.
-	 *
-	 * Reject the source pointer types that a BPF_LDX would have had that
-	 * fault protection applied to, i.e. the ones bpf_convert_ctx_accesses()
-	 * turns into BPF_PROBE_MEM: a bare PTR_TO_BTF_ID and any PTR_UNTRUSTED
-	 * pointer (untrusted btf ids, untrusted MEM_ALLOC, rdonly untrusted
-	 * memory). A PTR_TRUSTED pointer is not among them, is not converted,
-	 * and stays allowed. Same for the other flagged PTR_TO_BTF_ID variants
-	 * (MEM_ALLOC, MEM_RCU, ...), hence the exact match on the base type.
+	 * the kernel instead of being handled. Reject the source pointer types
+	 * that would have needed that protection, the remaining ones stay
+	 * allowed.
 	 */
-	return insn->imm == BPF_LOAD_ACQ &&
-	       (reg->type == PTR_TO_BTF_ID ||
-		(type_flag(reg->type) & PTR_UNTRUSTED));
+	return insn->imm == BPF_LOAD_ACQ && may_fault_on_deref(reg->type);
 }
 
 /* Return false if @regno contains a pointer whose type isn't supported for
@@ -17021,11 +17025,24 @@ static bool is_ptr_to_mem(enum bpf_reg_type type)
 	return base_type(type) == PTR_TO_MEM;
 }
 
+static enum bpf_reg_type merge_ptr_types(enum bpf_reg_type type_a,
+					 enum bpf_reg_type type_b)
+{
+	bool to_mem = is_ptr_to_mem(type_a) || is_ptr_to_mem(type_b);
+	enum bpf_reg_type type_merged = to_mem ? PTR_TO_MEM : PTR_TO_BTF_ID;
+
+	if (may_fault_on_deref(type_a) || may_fault_on_deref(type_b))
+		type_merged |= to_mem ? MEM_RDONLY | PTR_UNTRUSTED :
+					PTR_UNTRUSTED;
+	else
+		type_merged |= ((type_a | type_b) & MEM_RDONLY);
+	return type_merged;
+}
+
 static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
 			     bool allow_trust_mismatch)
 {
 	enum bpf_reg_type *prev_type = &env->insn_aux_data[env->insn_idx].ptr_type;
-	enum bpf_reg_type merged_type;
 
 	if (*prev_type == NOT_INIT) {
 		/* Saw a valid insn
@@ -17046,20 +17063,12 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
 		    is_ptr_to_mem_or_btf_id(*prev_type)) {
 			/*
 			 * Have to support a use case when one path through
-			 * the program yields TRUSTED pointer while another
-			 * is UNTRUSTED. Fallback to UNTRUSTED to generate
-			 * BPF_PROBE_MEM/BPF_PROBE_MEMSX.
-			 * Same behavior of MEM_RDONLY flag.
+			 * the program yields a TRUSTED pointer while another
+			 * is UNTRUSTED. Merge them into a type which keeps
+			 * the BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when
+			 * either side needs it.
 			 */
-			if (is_ptr_to_mem(type) || is_ptr_to_mem(*prev_type))
-				merged_type = PTR_TO_MEM;
-			else
-				merged_type = PTR_TO_BTF_ID;
-			if ((type & PTR_UNTRUSTED) || (*prev_type & PTR_UNTRUSTED))
-				merged_type |= PTR_UNTRUSTED;
-			if ((type & MEM_RDONLY) || (*prev_type & MEM_RDONLY))
-				merged_type |= MEM_RDONLY;
-			*prev_type = merged_type;
+			*prev_type = merge_ptr_types(type, *prev_type);
 		} else {
 			verbose(env, "same insn cannot be used with different pointers\n");
 			return -EINVAL;
-- 
2.43.0


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

* [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM
  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 ` Daniel Borkmann
  2026-08-13 21:44   ` bot+bpf-ci
                     ` (2 more replies)
  2026-08-13 20:40 ` [PATCH bpf-next 3/4] bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation Daniel Borkmann
                   ` (3 subsequent siblings)
  4 siblings, 3 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-13 20:40 UTC (permalink / raw)
  To: eddyz87; +Cc: memxor, bpf

save_aux_ptr_type() only reaches the merge when reg_type_mismatch() says
the two types are incompatible, and that in turn requires at least one of
them to have a base type reg_type_mismatch_ok() rejects. PTR_TO_MEM is
not among those, so for two PTR_TO_MEM based types the merge never runs
and the recorded type stays the one of whichever path was verified first.

That is ok as long as all PTR_TO_MEM variants can be dereferenced with
a plain load, which stopped being true with commit f2362a57aeff ("bpf:
allow void* cast using bpf_rdonly_cast()") adding PTR_TO_MEM | MEM_RDONLY
| PTR_UNTRUSTED. If the other path saved e.g. a PTR_TO_MEM | MEM_RINGBUF
first, then bpf_convert_ctx_accesses() does not rewrite the load into a
BPF_PROBE_MEM one, and the untrusted path faults on a plain load.

Fix by merging the two whenever they differ and both are of PTR_TO_MEM or
PTR_TO_BTF_ID base instead of keying it off reg_type_mismatch(), so that
merge_ptr_types() gets to normalize the result in this case as well. The
rejection of genuinely incompatible types is left untouched.

Fixes: f2362a57aeff ("bpf: allow void* cast using bpf_rdonly_cast()")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
 kernel/bpf/verifier.c | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 0d3b76d7820e..8ef9418733ca 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -17050,6 +17050,17 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
 		 * save type to validate intersecting paths
 		 */
 		*prev_type = type;
+	} else if (*prev_type != type && allow_trust_mismatch &&
+		   is_ptr_to_mem_or_btf_id(type) &&
+		   is_ptr_to_mem_or_btf_id(*prev_type)) {
+		/*
+		 * Have to support a use case when one path through the
+		 * program yields a TRUSTED pointer while another is
+		 * UNTRUSTED. Merge them into a type which keeps the
+		 * BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when either
+		 * side needs it.
+		 */
+		*prev_type = merge_ptr_types(type, *prev_type);
 	} else if (reg_type_mismatch(type, *prev_type)) {
 		/* Abuser program is trying to use the same insn
 		 * dst_reg = *(u32*) (src_reg + off)
@@ -17058,21 +17069,8 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
 		 * src_reg == stack|map in some other branch.
 		 * Reject it.
 		 */
-		if (allow_trust_mismatch &&
-		    is_ptr_to_mem_or_btf_id(type) &&
-		    is_ptr_to_mem_or_btf_id(*prev_type)) {
-			/*
-			 * Have to support a use case when one path through
-			 * the program yields a TRUSTED pointer while another
-			 * is UNTRUSTED. Merge them into a type which keeps
-			 * the BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when
-			 * either side needs it.
-			 */
-			*prev_type = merge_ptr_types(type, *prev_type);
-		} else {
-			verbose(env, "same insn cannot be used with different pointers\n");
-			return -EINVAL;
-		}
+		verbose(env, "same insn cannot be used with different pointers\n");
+		return -EINVAL;
 	}
 
 	return 0;
-- 
2.43.0


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

* [PATCH bpf-next 3/4] bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation
  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 20:40 ` Daniel Borkmann
  2026-08-13 21:44   ` bot+bpf-ci
  2026-08-14  0:10   ` Eduard Zingerman
  2026-08-13 20:40 ` [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load Daniel Borkmann
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-13 20:40 UTC (permalink / raw)
  To: eddyz87; +Cc: memxor, bpf

invalidate_rcu_protected_refs() turns MEM_RCU pointers into PTR_UNTRUSTED
ones once the RCU read-side critical section ends.

For a PTR_TO_BTF_ID base that is fine, but for a PTR_TO_MEM base the
result has to carry MEM_RDONLY as well, since the rest of the verifier
relies on the two coming as a pair (e.g. bpf_convert_ctx_accesses()).

A writable untrusted PTR_TO_MEM would otherwise end up as a plain load
or store to memory without fault protection. No in-tree kfunc produces
the combination today, thus this is mainly hardening.

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

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 8ef9418733ca..0c0dd53118d5 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9054,6 +9054,9 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
 		if (reg->type & MEM_RCU) {
 			reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
 			reg->type |= PTR_UNTRUSTED;
+			/* An untrusted PTR_TO_MEM has to be MEM_RDONLY. */
+			if (base_type(reg->type) == PTR_TO_MEM)
+				reg->type |= MEM_RDONLY;
 		}
 	}));
 }
-- 
2.43.0


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

* [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load
  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 20:40 ` [PATCH bpf-next 3/4] bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation Daniel Borkmann
@ 2026-08-13 20:40 ` Daniel Borkmann
  2026-08-13 21:44   ` 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
  4 siblings, 1 reply; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-13 20:40 UTC (permalink / raw)
  To: eddyz87; +Cc: memxor, bpf

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


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

* Re: [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types
  2026-08-13 20:40 [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types Daniel Borkmann
                   ` (2 preceding siblings ...)
  2026-08-13 20:40 ` [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load Daniel Borkmann
@ 2026-08-13 21:44 ` bot+bpf-ci
  2026-08-14  0:08 ` Eduard Zingerman
  4 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-13 21:44 UTC (permalink / raw)
  To: daniel, eddyz87
  Cc: memxor, bpf, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2639 bytes --]


commit e49db69f3fc735b4b80a6b4b8a50b41285d42c00
Author: Daniel Borkmann <daniel@iogearbox.net>

bpf: Keep fault protection when merging pointer types

When the same BPF_LDX instruction is reached through paths that yield
different pointer types, save_aux_ptr_type() merges them into a single
type which is later used by bpf_convert_ctx_accesses() to decide whether
the load has to be rewritten into a BPF_PROBE_MEM one.

Before commit f2362a57aeff ("bpf: allow void* cast using bpf_rdonly_cast()")
the merge only accepted two PTR_TO_BTF_ID pointers and unconditionally
fell back to PTR_TO_BTF_ID | PTR_UNTRUSTED, so the merged type was always
one that gets the BPF_PROBE_MEM rewrite. However, the mentioned commit
widened the merge to also cover a PTR_TO_MEM base and replaced the
fallback by a union of the PTR_UNTRUSTED and MEM_RDONLY flags.

The union can produce types which bpf_convert_ctx_accesses() does not
rewrite, and the load then stays a plain one without an exception table
entry, e.g.:

  - PTR_TO_MEM merged with PTR_TO_BTF_ID | PTR_UNTRUSTED
   => PTR_TO_MEM | PTR_UNTRUSTED but only the MEM_RDONLY variant is valid
  - PTR_TO_MEM merged with a plain PTR_TO_BTF_ID
   => PTR_TO_MEM dropping the rewrite the latter type would have gotten
  - PTR_TO_MEM | MEM_RDONLY merged with a plain PTR_TO_BTF_ID
   => PTR_TO_MEM | MEM_RDONLY which is not rewritten either since only
    its PTR_UNTRUSTED variant is

In all three cases a program can take the unsafe path at runtime with a
NULL or otherwise bad pointer and panic the kernel on the faulting load.
Fix it by normalizing the merged type: if either side needs the rewrite,
pick the one rewritten form the merged base type has.

Reuse the may_fault_on_deref() helper in is_load_acq_unsafe() as well to
avoid open coding, and trim the overly verbose comment which is more of
implementation detail of bpf_convert_ctx_accesses() anyway.

This isn't a bug, but there are a few readability issues in the changelog.
The sentence "pick the one rewritten form the merged base type has" is
hard to parse - could it be reworded to something like "pick the rewritten
form that exists for the merged base type"? Also, "which is more of
implementation detail of" is missing an article. Finally, the continuation
lines in the ASCII list use 3-space indentation under 2-space bullets, so
the '=>' lines don't align with the bullet text above them.


---
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/31743446421

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

* Re: [PATCH bpf-next 3/4] bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation
  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
  1 sibling, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-13 21:44 UTC (permalink / raw)
  To: daniel, eddyz87
  Cc: memxor, bpf, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1113 bytes --]

>     bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation
>
>     invalidate_rcu_protected_refs() turns MEM_RCU pointers into PTR_UNTRUSTED
>     ones once the RCU read-side critical section ends.
>
>     For a PTR_TO_BTF_ID base that is fine, but for a PTR_TO_MEM base the
>     result has to carry MEM_RDONLY as well, since the rest of the verifier
>     relies on the two coming as a pair (e.g. bpf_convert_ctx_accesses()).
>
>     A writable untrusted PTR_TO_MEM would otherwise end up as a plain load
>     or store to memory without fault protection. No in-tree kfunc produces
>     the combination today, thus this is mainly hardening.
>
>     Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>

This looks like a bug fix that closes a security hole.  Should this include:

  Fixes: 5861d1e8dbc4 ("bpf: Allow bpf_spin_{lock,unlock} in sleepable progs")


---
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/31743446421

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

* Re: [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM
  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  1:08   ` sashiko-bot
  2 siblings, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-13 21:44 UTC (permalink / raw)
  To: daniel, eddyz87
  Cc: memxor, bpf, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2678 bytes --]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 1f2ed6164305e..5daca4ef27278 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -17096,6 +17096,17 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
>  		 * save type to validate intersecting paths
>  		 */
>  		*prev_type = type;
> +	} else if (*prev_type != type && allow_trust_mismatch &&
> +		   is_ptr_to_mem_or_btf_id(type) &&
> +		   is_ptr_to_mem_or_btf_id(*prev_type)) {

Does this condition still leave a gap when the other side is one of
the remaining reg_type_mismatch_ok() base types?

Looking at reg_type_mismatch_ok() in verifier.c, it returns false only for
PTR_TO_CTX, PTR_TO_SOCKET, PTR_TO_SOCK_COMMON, PTR_TO_TCP_SOCK, PTR_TO_XDP_SOCK,
PTR_TO_BTF_ID and PTR_TO_ARENA. Everything else (PTR_TO_MAP_VALUE, PTR_TO_STACK,
PTR_TO_PACKET, PTR_TO_BUF, PTR_TO_TP_BUFFER, PTR_TO_MAP_KEY, PTR_TO_FLOW_KEYS)
is "mismatch ok".

For such a type paired with PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED at the
same BPF_LDX:

  is_ptr_to_mem_or_btf_id(*prev_type) would be false, so this new branch
  would be skipped

  reg_type_mismatch() would be false (both sides are mismatch_ok), so
  nothing is recorded either

  *prev_type keeps the type of whichever path was verified first

If that first path was PTR_TO_MAP_VALUE, bpf_convert_ctx_accesses() in
kernel/bpf/fixups.c falls into the default case - no BPF_PROBE_MEM rewrite
and no exception table entry - and the untrusted path then takes a plain
faulting load.

The commit message says this is being fixed, but doesn't this exact panic
stay reachable? The existing selftest ldx_is_ok_bad_addr() in
tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c already loads from
bpf_rdonly_cast(0, 0) and relies on the PROBE_MEM rewrite; the map-value
half is just a post-NULL-check bpf_map_lookup_elem() result.

Should the merge also fire when only one side is PTR_TO_MEM or PTR_TO_BTF_ID
and that side may_fault_on_deref(), or should such a mix be rejected
outright?

> +		/*
> +		 * Have to support a use case when one path through the
> +		 * program yields a TRUSTED pointer while another is
> +		 * UNTRUSTED. Merge them into a type which keeps the
> +		 * BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when either
> +		 * side needs it.
> +		 */
> +		*prev_type = merge_ptr_types(type, *prev_type);
>  	} else if (reg_type_mismatch(type, *prev_type)) {

[ ... ]


---
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/31743446421

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

* Re: [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load
  2026-08-13 20:40 ` [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load Daniel Borkmann
@ 2026-08-13 21:44   ` bot+bpf-ci
  0 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-08-13 21:44 UTC (permalink / raw)
  To: daniel, eddyz87
  Cc: memxor, bpf, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1732 bytes --]

> diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
> index b91271d4caa48..127b745bb0b7d 100644
> --- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
> +++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c

[ ... ]

> +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;
> +}

[ ... ]

> +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;
> +}

This isn't a bug, but get_rdonly_mem_or_btf_id_addr() looks identical to
get_mem_or_btf_id_addr() above (same signature, same branch structure, same
BTF pointer walk). Since libbpf appends a referenced static subprog to every
main program that calls it anyway, could mixed_rdonly_mem_btf_id_type just
call the existing helper?


---
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/31743446421

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

* Re: [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM
  2026-08-13 21:44   ` bot+bpf-ci
@ 2026-08-13 21:52     ` Daniel Borkmann
  0 siblings, 0 replies; 13+ messages in thread
From: Daniel Borkmann @ 2026-08-13 21:52 UTC (permalink / raw)
  To: bot+bpf-ci, eddyz87
  Cc: memxor, bpf, ast, andrii, martin.lau, yonghong.song, clm,
	ihor.solodrai

On 8/13/26 11:44 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 1f2ed6164305e..5daca4ef27278 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -17096,6 +17096,17 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
>>   		 * save type to validate intersecting paths
>>   		 */
>>   		*prev_type = type;
>> +	} else if (*prev_type != type && allow_trust_mismatch &&
>> +		   is_ptr_to_mem_or_btf_id(type) &&
>> +		   is_ptr_to_mem_or_btf_id(*prev_type)) {
> 
> Does this condition still leave a gap when the other side is one of
> the remaining reg_type_mismatch_ok() base types?
> 
> Looking at reg_type_mismatch_ok() in verifier.c, it returns false only for
> PTR_TO_CTX, PTR_TO_SOCKET, PTR_TO_SOCK_COMMON, PTR_TO_TCP_SOCK, PTR_TO_XDP_SOCK,
> PTR_TO_BTF_ID and PTR_TO_ARENA. Everything else (PTR_TO_MAP_VALUE, PTR_TO_STACK,
> PTR_TO_PACKET, PTR_TO_BUF, PTR_TO_TP_BUFFER, PTR_TO_MAP_KEY, PTR_TO_FLOW_KEYS)
> is "mismatch ok".
> 
> For such a type paired with PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED at the
> same BPF_LDX:
> 
>    is_ptr_to_mem_or_btf_id(*prev_type) would be false, so this new branch
>    would be skipped
> 
>    reg_type_mismatch() would be false (both sides are mismatch_ok), so
>    nothing is recorded either
> 
>    *prev_type keeps the type of whichever path was verified first
> 
> If that first path was PTR_TO_MAP_VALUE, bpf_convert_ctx_accesses() in
> kernel/bpf/fixups.c falls into the default case - no BPF_PROBE_MEM rewrite
> and no exception table entry - and the untrusted path then takes a plain
> faulting load.
> 
> The commit message says this is being fixed, but doesn't this exact panic
> stay reachable? The existing selftest ldx_is_ok_bad_addr() in
> tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c already loads from
> bpf_rdonly_cast(0, 0) and relies on the PROBE_MEM rewrite; the map-value
> half is just a post-NULL-check bpf_map_lookup_elem() result.
> 
> Should the merge also fire when only one side is PTR_TO_MEM or PTR_TO_BTF_ID
> and that side may_fault_on_deref(), or should such a mix be rejected
> outright?

I'll check this one tomorrow in more detail. Fwiw, the other bot+bpf-ci
reviews are non-issues.

>> +		/*
>> +		 * Have to support a use case when one path through the
>> +		 * program yields a TRUSTED pointer while another is
>> +		 * UNTRUSTED. Merge them into a type which keeps the
>> +		 * BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when either
>> +		 * side needs it.
>> +		 */
>> +		*prev_type = merge_ptr_types(type, *prev_type);
>>   	} else if (reg_type_mismatch(type, *prev_type)) {
> 
> [ ... ]

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

* Re: [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types
  2026-08-13 20:40 [PATCH bpf-next 1/4] bpf: Keep fault protection when merging pointer types Daniel Borkmann
                   ` (3 preceding siblings ...)
  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
  4 siblings, 0 replies; 13+ messages in thread
From: Eduard Zingerman @ 2026-08-14  0:08 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: memxor, bpf

On Thu, 2026-08-13 at 22:40 +0200, Daniel Borkmann wrote:

...

> +static bool may_fault_on_deref(enum bpf_reg_type type)
> +{
> +	/*
> +	 * The pointer types which must not be dereferenced without fault
> +	 * protection, that is, the ones bpf_convert_ctx_accesses() has to
> +	 * turn a BPF_LDX into a BPF_PROBE_MEM one for. Slightly wider than
> +	 * the list matched there, which relies on an untrusted PTR_TO_MEM
> +	 * always carrying MEM_RDONLY as well.
> +	 */
> +	return type == PTR_TO_BTF_ID || (type_flag(type) & PTR_UNTRUSTED);
> +}
> +
>  static bool is_load_acq_unsafe(struct bpf_verifier_env *env, int regno,
>  			       struct bpf_insn *insn)
>  {

...

> @@ -17021,11 +17025,24 @@ static bool is_ptr_to_mem(enum bpf_reg_type type)
>  	return base_type(type) == PTR_TO_MEM;
>  }
>  
> +static enum bpf_reg_type merge_ptr_types(enum bpf_reg_type type_a,
> +					 enum bpf_reg_type type_b)
> +{
> +	bool to_mem = is_ptr_to_mem(type_a) || is_ptr_to_mem(type_b);
> +	enum bpf_reg_type type_merged = to_mem ? PTR_TO_MEM : PTR_TO_BTF_ID;
> +
> +	if (may_fault_on_deref(type_a) || may_fault_on_deref(type_b))
> +		type_merged |= to_mem ? MEM_RDONLY | PTR_UNTRUSTED :
> +					PTR_UNTRUSTED;
> +	else
> +		type_merged |= ((type_a | type_b) & MEM_RDONLY);
> +	return type_merged;
> +}
> +
>  static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
>  			     bool allow_trust_mismatch)
>  {
>  	enum bpf_reg_type *prev_type = &env->insn_aux_data[env->insn_idx].ptr_type;
> -	enum bpf_reg_type merged_type;
>  
>  	if (*prev_type == NOT_INIT) {
>  		/* Saw a valid insn
> @@ -17046,20 +17063,12 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
>  		    is_ptr_to_mem_or_btf_id(*prev_type)) {
>  			/*
>  			 * Have to support a use case when one path through
> -			 * the program yields TRUSTED pointer while another
> -			 * is UNTRUSTED. Fallback to UNTRUSTED to generate
> -			 * BPF_PROBE_MEM/BPF_PROBE_MEMSX.
> -			 * Same behavior of MEM_RDONLY flag.
> +			 * the program yields a TRUSTED pointer while another
> +			 * is UNTRUSTED. Merge them into a type which keeps
> +			 * the BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when
> +			 * either side needs it.
>  			 */
> -			if (is_ptr_to_mem(type) || is_ptr_to_mem(*prev_type))
> -				merged_type = PTR_TO_MEM;
> -			else
> -				merged_type = PTR_TO_BTF_ID;
> -			if ((type & PTR_UNTRUSTED) || (*prev_type & PTR_UNTRUSTED))
> -				merged_type |= PTR_UNTRUSTED;
> -			if ((type & MEM_RDONLY) || (*prev_type & MEM_RDONLY))
> -				merged_type |= MEM_RDONLY;
> -			*prev_type = merged_type;
> +			*prev_type = merge_ptr_types(type, *prev_type);
>  		} else {
>  			verbose(env, "same insn cannot be used with different pointers\n");
>  			return -EINVAL;

Given that we don't document the list of all valid flag and type
combinations, I think that extending bpf_convert_ctx_accesses() to
match base type and checking UNTRUSTED flag on it is more future proof
solution.

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

* Re: [PATCH bpf-next 3/4] bpf: Keep untrusted PTR_TO_MEM read-only on RCU invalidation
  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
  1 sibling, 0 replies; 13+ messages in thread
From: Eduard Zingerman @ 2026-08-14  0:10 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: memxor, bpf

On Thu, 2026-08-13 at 22:40 +0200, Daniel Borkmann wrote:
> invalidate_rcu_protected_refs() turns MEM_RCU pointers into PTR_UNTRUSTED
> ones once the RCU read-side critical section ends.
> 
> For a PTR_TO_BTF_ID base that is fine, but for a PTR_TO_MEM base the
> result has to carry MEM_RDONLY as well, since the rest of the verifier
> relies on the two coming as a pair (e.g. bpf_convert_ctx_accesses()).
> 
> A writable untrusted PTR_TO_MEM would otherwise end up as a plain load
> or store to memory without fault protection. No in-tree kfunc produces
> the combination today, thus this is mainly hardening.
> 
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
>  kernel/bpf/verifier.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 8ef9418733ca..0c0dd53118d5 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -9054,6 +9054,9 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
>  		if (reg->type & MEM_RCU) {
>  			reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
>  			reg->type |= PTR_UNTRUSTED;
> +			/* An untrusted PTR_TO_MEM has to be MEM_RDONLY. */
> +			if (base_type(reg->type) == PTR_TO_MEM)
> +				reg->type |= MEM_RDONLY;
>  		}
>  	}));
>  }

And said extension to bpf_convert_ctx_accesses() would cover this case automatically.

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

* Re: [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM
  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-14  0:14   ` Eduard Zingerman
  2026-08-14  1:08   ` sashiko-bot
  2 siblings, 0 replies; 13+ messages in thread
From: Eduard Zingerman @ 2026-08-14  0:14 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: memxor, bpf

On Thu, 2026-08-13 at 22:40 +0200, Daniel Borkmann wrote:
> save_aux_ptr_type() only reaches the merge when reg_type_mismatch() says
> the two types are incompatible, and that in turn requires at least one of
> them to have a base type reg_type_mismatch_ok() rejects. PTR_TO_MEM is
> not among those, so for two PTR_TO_MEM based types the merge never runs
> and the recorded type stays the one of whichever path was verified first.
> 
> That is ok as long as all PTR_TO_MEM variants can be dereferenced with
> a plain load, which stopped being true with commit f2362a57aeff ("bpf:
> allow void* cast using bpf_rdonly_cast()") adding PTR_TO_MEM | MEM_RDONLY
> > PTR_UNTRUSTED. If the other path saved e.g. a PTR_TO_MEM | MEM_RINGBUF
> first, then bpf_convert_ctx_accesses() does not rewrite the load into a
> BPF_PROBE_MEM one, and the untrusted path faults on a plain load.
> 
> Fix by merging the two whenever they differ and both are of PTR_TO_MEM or
> PTR_TO_BTF_ID base instead of keying it off reg_type_mismatch(), so that
> merge_ptr_types() gets to normalize the result in this case as well. The
> rejection of genuinely incompatible types is left untouched.
> 
> Fixes: f2362a57aeff ("bpf: allow void* cast using bpf_rdonly_cast()")
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---

Would the following work?

  diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
  index 1e537bc25ef5..a02914af0f5b 100644
  --- a/kernel/bpf/verifier.c
  +++ b/kernel/bpf/verifier.c
  @@ -17022,6 +17022,8 @@ static bool reg_type_mismatch_ok(enum bpf_reg_type type)
        case PTR_TO_BTF_ID:
        case PTR_TO_ARENA:
                return false;
  +     case PTR_TO_MEM:
  +             return !may_fault_on_deref(type);
        default:
                return true;

?

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

* Re: [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM
  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-14  0:14   ` Eduard Zingerman
@ 2026-08-14  1:08   ` sashiko-bot
  2 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-14  1:08 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The patch fixes a missing BPF_PROBE_MEM rewrite when an untrusted PTR_TO_MEM shares an instruction with a trusted PTR_TO_MEM, but leaves the exact same vulnerability open when mixed with PTR_TO_STACK, PTR_TO_MAP_VALUE, or other generic pointer types.
--

--- Patch [2]: [PATCH bpf-next 2/4] bpf: Merge pointer types also when both are PTR_TO_MEM ---
commit 7f1a4eb01f4f65011b9dba6050e4321da3dbce61
Author: Daniel Borkmann <daniel@iogearbox.net>

bpf: Merge pointer types also when both are PTR_TO_MEM

This commit fixes a verifier issue where merging a trusted PTR_TO_MEM and an
untrusted PTR_TO_MEM failed to trigger the BPF_PROBE_MEM rewrite. It updates
save_aux_ptr_type() to correctly merge these types when they intersect.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -17096,6 +17096,17 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
>  		 * save type to validate intersecting paths
>  		 */
>  		*prev_type = type;
> +	} else if (*prev_type != type && allow_trust_mismatch &&
> +		   is_ptr_to_mem_or_btf_id(type) &&
> +		   is_ptr_to_mem_or_btf_id(*prev_type)) {
> +		/*
> +		 * Have to support a use case when one path through the
> +		 * program yields a TRUSTED pointer while another is
> +		 * UNTRUSTED. Merge them into a type which keeps the
> +		 * BPF_PROBE_MEM/BPF_PROBE_MEMSX rewrite when either
> +		 * side needs it.
> +		 */
> +		*prev_type = merge_ptr_types(type, *prev_type);
>  	} else if (reg_type_mismatch(type, *prev_type)) {

[Severity: High]
This is a pre-existing issue, but does this update still leave a vulnerability
open if an untrusted PTR_TO_MEM intersects with PTR_TO_STACK or
PTR_TO_MAP_VALUE?

If a BPF program has a conditional branch where a memory load is reached by
either a PTR_TO_STACK or an untrusted PTR_TO_MEM | PTR_UNTRUSTED (e.g. from
bpf_rdonly_cast()), the verifier might explore the PTR_TO_STACK path first.

When it evaluates the untrusted memory path in save_aux_ptr_type(), it will
skip the new type-merging logic because PTR_TO_STACK fails the
is_ptr_to_mem_or_btf_id() check.

It then falls through to reg_type_mismatch(), which returns false because both
types satisfy reg_type_mismatch_ok(). The verifier permits the shared
instruction but leaves ptr_type as PTR_TO_STACK.

During JIT compilation, fixup_bpf_calls() would then observe PTR_TO_STACK and
skip the BPF_PROBE_MEM rewrite. At runtime, when the untrusted branch executes,
could this result in an unhandled page fault and kernel panic if it performs
a plain memory load on an unverified address?

>  		/* Abuser program is trying to use the same insn
>  		 * dst_reg = *(u32*) (src_reg + off)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260813204032.644949-1-daniel@iogearbox.net?part=2

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

end of thread, other threads:[~2026-08-14  1:08 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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  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 ` [PATCH bpf-next 4/4] selftests/bpf: Add tests for pointer type merge at a shared load Daniel Borkmann
2026-08-13 21:44   ` 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

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