public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability.
@ 2025-06-10 14:43 Puranjay Mohan
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types Puranjay Mohan
                   ` (7 more replies)
  0 siblings, 8 replies; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:43 UTC (permalink / raw)
  To: Greg KH
  Cc: Puranjay Mohan, Alexei Starovoitov, Andrii Nakryiko,
	Daniel Borkmann, laura, stable, Hao Luo, Puranjay Mohan

Hi Greg,

Please cherry-pick this patch series into 5.10.y stable. It
includes a feature that fixes CVE-2022-0500 which allows a user with
cap_bpf privileges to get root privileges. The patch that fixes
the bug is

 patch 6/8: bpf: Make per_cpu_ptr return rdonly PTR_TO_MEM

The rest are the depedences required by the fix patch.

This patchset has been merged in mainline v5.17 and backported to v5.16[1]
and v5.15[2]

Tested by compile, build and run through the bpf selftest test_progs.

Before:

./test_progs -t ksyms_btf/write_check
test_ksyms_btf:PASS:btf_exists 0 nsec
test_write_check:FAIL:skel_open unexpected load of a prog writing to ksym memory
#44/3 write_check:FAIL
#44 ksyms_btf:FAIL
Summary: 0/0 PASSED, 0 SKIPPED, 2 FAILED

After:

./test_progs -t ksyms_btf/write_check
#44/3 write_check:OK
#44 ksyms_btf:OK
Summary: 1/1 PASSED, 0 SKIPPED, 0 FAILED

[1] https://lore.kernel.org/all/Yg6cixLJFoxDmp+I@kroah.com/
[2] https://lore.kernel.org/all/Ymupcl2JshcWjmMD@kroah.com/

Hao Luo (8):
  bpf: Introduce composable reg, ret and arg types.
  bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL
  bpf: Replace RET_XXX_OR_NULL with RET_XXX | PTR_MAYBE_NULL
  bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX | PTR_MAYBE_NULL
  bpf: Introduce MEM_RDONLY flag
  bpf: Make per_cpu_ptr return rdonly PTR_TO_MEM.
  bpf: Add MEM_RDONLY for helper args that are pointers to rdonly mem.
  bpf/selftests: Test PTR_TO_RDONLY_MEM

 include/linux/bpf.h                           |  98 +++-
 include/linux/bpf_verifier.h                  |  18 +
 kernel/bpf/btf.c                              |   8 +-
 kernel/bpf/cgroup.c                           |   2 +-
 kernel/bpf/helpers.c                          |  10 +-
 kernel/bpf/map_iter.c                         |   4 +-
 kernel/bpf/ringbuf.c                          |   2 +-
 kernel/bpf/verifier.c                         | 477 +++++++++---------
 kernel/trace/bpf_trace.c                      |  22 +-
 net/core/bpf_sk_storage.c                     |   2 +-
 net/core/filter.c                             |  62 +--
 net/core/sock_map.c                           |   2 +-
 .../selftests/bpf/prog_tests/ksyms_btf.c      |  14 +
 .../bpf/progs/test_ksyms_btf_write_check.c    |  29 ++
 14 files changed, 441 insertions(+), 309 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_ksyms_btf_write_check.c

-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types.
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
@ 2025-06-10 14:43 ` Puranjay Mohan
  2025-06-11 13:16   ` Sasha Levin
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 2/8] bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL Puranjay Mohan
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:43 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit d639b9d13a39cf15639cbe6e8b2c43eb60148a73 upstream.

There are some common properties shared between bpf reg, ret and arg
values. For instance, a value may be a NULL pointer, or a pointer to
a read-only memory. Previously, to express these properties, enumeration
was used. For example, in order to test whether a reg value can be NULL,
reg_type_may_be_null() simply enumerates all types that are possibly
NULL. The problem of this approach is that it's not scalable and causes
a lot of duplication. These properties can be combined, for example, a
type could be either MAYBE_NULL or RDONLY, or both.

This patch series rewrites the layout of reg_type, arg_type and
ret_type, so that common properties can be extracted and represented as
composable flag. For example, one can write

 ARG_PTR_TO_MEM | PTR_MAYBE_NULL

which is equivalent to the previous

 ARG_PTR_TO_MEM_OR_NULL

The type ARG_PTR_TO_MEM are called "base type" in this patch. Base
types can be extended with flags. A flag occupies the higher bits while
base types sits in the lower bits.

This patch in particular sets up a set of macro for this purpose. The
following patches will rewrite arg_types, ret_types and reg_types
respectively.

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-2-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 include/linux/bpf.h          | 43 ++++++++++++++++++++++++++++++++++++
 include/linux/bpf_verifier.h | 14 ++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 340f4fef5b5a..1abc115c82de 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -263,6 +263,29 @@ bool bpf_map_meta_equal(const struct bpf_map *meta0,
 
 extern const struct bpf_map_ops bpf_map_offload_ops;
 
+/* bpf_type_flag contains a set of flags that are applicable to the values of
+ * arg_type, ret_type and reg_type. For example, a pointer value may be null,
+ * or a memory is read-only. We classify types into two categories: base types
+ * and extended types. Extended types are base types combined with a type flag.
+ *
+ * Currently there are no more than 32 base types in arg_type, ret_type and
+ * reg_types.
+ */
+#define BPF_BASE_TYPE_BITS	8
+
+enum bpf_type_flag {
+	/* PTR may be NULL. */
+	PTR_MAYBE_NULL		= BIT(0 + BPF_BASE_TYPE_BITS),
+
+	__BPF_TYPE_LAST_FLAG	= PTR_MAYBE_NULL,
+};
+
+/* Max number of base types. */
+#define BPF_BASE_TYPE_LIMIT	(1UL << BPF_BASE_TYPE_BITS)
+
+/* Max number of all types. */
+#define BPF_TYPE_LIMIT		(__BPF_TYPE_LAST_FLAG | (__BPF_TYPE_LAST_FLAG - 1))
+
 /* function argument constraints */
 enum bpf_arg_type {
 	ARG_DONTCARE = 0,	/* unused argument in helper function */
@@ -305,7 +328,13 @@ enum bpf_arg_type {
 	ARG_PTR_TO_BTF_ID_SOCK_COMMON,	/* pointer to in-kernel sock_common or bpf-mirrored bpf_sock */
 	ARG_PTR_TO_PERCPU_BTF_ID,	/* pointer to in-kernel percpu type */
 	__BPF_ARG_TYPE_MAX,
+
+	/* This must be the last entry. Its purpose is to ensure the enum is
+	 * wide enough to hold the higher bits reserved for bpf_type_flag.
+	 */
+	__BPF_ARG_TYPE_LIMIT	= BPF_TYPE_LIMIT,
 };
+static_assert(__BPF_ARG_TYPE_MAX <= BPF_BASE_TYPE_LIMIT);
 
 /* type of values returned from helper functions */
 enum bpf_return_type {
@@ -320,7 +349,14 @@ enum bpf_return_type {
 	RET_PTR_TO_BTF_ID_OR_NULL,	/* returns a pointer to a btf_id or NULL */
 	RET_PTR_TO_MEM_OR_BTF_ID_OR_NULL, /* returns a pointer to a valid memory or a btf_id or NULL */
 	RET_PTR_TO_MEM_OR_BTF_ID,	/* returns a pointer to a valid memory or a btf_id */
+	__BPF_RET_TYPE_MAX,
+
+	/* This must be the last entry. Its purpose is to ensure the enum is
+	 * wide enough to hold the higher bits reserved for bpf_type_flag.
+	 */
+	__BPF_RET_TYPE_LIMIT	= BPF_TYPE_LIMIT,
 };
+static_assert(__BPF_RET_TYPE_MAX <= BPF_BASE_TYPE_LIMIT);
 
 /* eBPF function prototype used by verifier to allow BPF_CALLs from eBPF programs
  * to in-kernel helper functions and for adjusting imm32 field in BPF_CALL
@@ -419,7 +455,14 @@ enum bpf_reg_type {
 	PTR_TO_RDWR_BUF,	 /* reg points to a read/write buffer */
 	PTR_TO_RDWR_BUF_OR_NULL, /* reg points to a read/write buffer or NULL */
 	PTR_TO_PERCPU_BTF_ID,	 /* reg points to a percpu kernel variable */
+	__BPF_REG_TYPE_MAX,
+
+	/* This must be the last entry. Its purpose is to ensure the enum is
+	 * wide enough to hold the higher bits reserved for bpf_type_flag.
+	 */
+	__BPF_REG_TYPE_LIMIT	= BPF_TYPE_LIMIT,
 };
+static_assert(__BPF_REG_TYPE_MAX <= BPF_BASE_TYPE_LIMIT);
 
 /* The information passed from prog-specific *_is_valid_access
  * back to the verifier.
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 4d37c69e76b1..71192aa285df 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -509,4 +509,18 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
 			    u32 btf_id,
 			    struct bpf_attach_target_info *tgt_info);
 
+#define BPF_BASE_TYPE_MASK	GENMASK(BPF_BASE_TYPE_BITS - 1, 0)
+
+/* extract base type from bpf_{arg, return, reg}_type. */
+static inline u32 base_type(u32 type)
+{
+	return type & BPF_BASE_TYPE_MASK;
+}
+
+/* extract flags from an extended type. See bpf_type_flag in bpf.h. */
+static inline u32 type_flag(u32 type)
+{
+	return type & ~BPF_BASE_TYPE_MASK;
+}
+
 #endif /* _LINUX_BPF_VERIFIER_H */
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 2/8] bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types Puranjay Mohan
@ 2025-06-10 14:43 ` Puranjay Mohan
  2025-06-11 13:15   ` Sasha Levin
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 3/8] bpf: Replace RET_XXX_OR_NULL with RET_XXX " Puranjay Mohan
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:43 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit 48946bd6a5d695c50b34546864b79c1f910a33c1 upstream.

We have introduced a new type to make bpf_arg composable, by
reserving high bits of bpf_arg to represent flags of a type.

One of the flags is PTR_MAYBE_NULL which indicates a pointer
may be NULL. When applying this flag to an arg_type, it means
the arg can take NULL pointer. This patch switches the
qualified arg_types to use this flag. The arg_types changed
in this patch include:

1. ARG_PTR_TO_MAP_VALUE_OR_NULL
2. ARG_PTR_TO_MEM_OR_NULL
3. ARG_PTR_TO_CTX_OR_NULL
4. ARG_PTR_TO_SOCKET_OR_NULL
5. ARG_PTR_TO_ALLOC_MEM_OR_NULL
6. ARG_PTR_TO_STACK_OR_NULL

This patch does not eliminate the use of these arg_types, instead
it makes them an alias to the 'ARG_XXX | PTR_MAYBE_NULL'.

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-3-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 include/linux/bpf.h   | 12 +++++++-----
 kernel/bpf/verifier.c | 36 +++++++++++++-----------------------
 2 files changed, 20 insertions(+), 28 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 1abc115c82de..55d6bf94e6eb 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -297,13 +297,11 @@ enum bpf_arg_type {
 	ARG_PTR_TO_MAP_KEY,	/* pointer to stack used as map key */
 	ARG_PTR_TO_MAP_VALUE,	/* pointer to stack used as map value */
 	ARG_PTR_TO_UNINIT_MAP_VALUE,	/* pointer to valid memory used to store a map value */
-	ARG_PTR_TO_MAP_VALUE_OR_NULL,	/* pointer to stack used as map value or NULL */
 
 	/* the following constraints used to prototype bpf_memcmp() and other
 	 * functions that access data on eBPF program stack
 	 */
 	ARG_PTR_TO_MEM,		/* pointer to valid memory (stack, packet, map value) */
-	ARG_PTR_TO_MEM_OR_NULL, /* pointer to valid memory or NULL */
 	ARG_PTR_TO_UNINIT_MEM,	/* pointer to memory does not need to be initialized,
 				 * helper function must fill all bytes or clear
 				 * them in error case.
@@ -313,22 +311,26 @@ enum bpf_arg_type {
 	ARG_CONST_SIZE_OR_ZERO,	/* number of bytes accessed from memory or 0 */
 
 	ARG_PTR_TO_CTX,		/* pointer to context */
-	ARG_PTR_TO_CTX_OR_NULL,	/* pointer to context or NULL */
 	ARG_ANYTHING,		/* any (initialized) argument is ok */
 	ARG_PTR_TO_SPIN_LOCK,	/* pointer to bpf_spin_lock */
 	ARG_PTR_TO_SOCK_COMMON,	/* pointer to sock_common */
 	ARG_PTR_TO_INT,		/* pointer to int */
 	ARG_PTR_TO_LONG,	/* pointer to long */
 	ARG_PTR_TO_SOCKET,	/* pointer to bpf_sock (fullsock) */
-	ARG_PTR_TO_SOCKET_OR_NULL,	/* pointer to bpf_sock (fullsock) or NULL */
 	ARG_PTR_TO_BTF_ID,	/* pointer to in-kernel struct */
 	ARG_PTR_TO_ALLOC_MEM,	/* pointer to dynamically allocated memory */
-	ARG_PTR_TO_ALLOC_MEM_OR_NULL,	/* pointer to dynamically allocated memory or NULL */
 	ARG_CONST_ALLOC_SIZE_OR_ZERO,	/* number of allocated bytes requested */
 	ARG_PTR_TO_BTF_ID_SOCK_COMMON,	/* pointer to in-kernel sock_common or bpf-mirrored bpf_sock */
 	ARG_PTR_TO_PERCPU_BTF_ID,	/* pointer to in-kernel percpu type */
 	__BPF_ARG_TYPE_MAX,
 
+	/* Extended arg_types. */
+	ARG_PTR_TO_MAP_VALUE_OR_NULL	= PTR_MAYBE_NULL | ARG_PTR_TO_MAP_VALUE,
+	ARG_PTR_TO_MEM_OR_NULL		= PTR_MAYBE_NULL | ARG_PTR_TO_MEM,
+	ARG_PTR_TO_CTX_OR_NULL		= PTR_MAYBE_NULL | ARG_PTR_TO_CTX,
+	ARG_PTR_TO_SOCKET_OR_NULL	= PTR_MAYBE_NULL | ARG_PTR_TO_SOCKET,
+	ARG_PTR_TO_ALLOC_MEM_OR_NULL	= PTR_MAYBE_NULL | ARG_PTR_TO_ALLOC_MEM,
+
 	/* This must be the last entry. Its purpose is to ensure the enum is
 	 * wide enough to hold the higher bits reserved for bpf_type_flag.
 	 */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e6d50e371a2b..fdcb42fe4303 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -437,13 +437,9 @@ static bool arg_type_may_be_refcounted(enum bpf_arg_type type)
 	return type == ARG_PTR_TO_SOCK_COMMON;
 }
 
-static bool arg_type_may_be_null(enum bpf_arg_type type)
+static bool type_may_be_null(u32 type)
 {
-	return type == ARG_PTR_TO_MAP_VALUE_OR_NULL ||
-	       type == ARG_PTR_TO_MEM_OR_NULL ||
-	       type == ARG_PTR_TO_CTX_OR_NULL ||
-	       type == ARG_PTR_TO_SOCKET_OR_NULL ||
-	       type == ARG_PTR_TO_ALLOC_MEM_OR_NULL;
+	return type & PTR_MAYBE_NULL;
 }
 
 /* Determine whether the function releases some resources allocated by another
@@ -4486,9 +4482,8 @@ static int process_spin_lock(struct bpf_verifier_env *env, int regno,
 
 static bool arg_type_is_mem_ptr(enum bpf_arg_type type)
 {
-	return type == ARG_PTR_TO_MEM ||
-	       type == ARG_PTR_TO_MEM_OR_NULL ||
-	       type == ARG_PTR_TO_UNINIT_MEM;
+	return base_type(type) == ARG_PTR_TO_MEM ||
+	       base_type(type) == ARG_PTR_TO_UNINIT_MEM;
 }
 
 static bool arg_type_is_mem_size(enum bpf_arg_type type)
@@ -4615,26 +4610,21 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
 	[ARG_PTR_TO_MAP_KEY]		= &map_key_value_types,
 	[ARG_PTR_TO_MAP_VALUE]		= &map_key_value_types,
 	[ARG_PTR_TO_UNINIT_MAP_VALUE]	= &map_key_value_types,
-	[ARG_PTR_TO_MAP_VALUE_OR_NULL]	= &map_key_value_types,
 	[ARG_CONST_SIZE]		= &scalar_types,
 	[ARG_CONST_SIZE_OR_ZERO]	= &scalar_types,
 	[ARG_CONST_ALLOC_SIZE_OR_ZERO]	= &scalar_types,
 	[ARG_CONST_MAP_PTR]		= &const_map_ptr_types,
 	[ARG_PTR_TO_CTX]		= &context_types,
-	[ARG_PTR_TO_CTX_OR_NULL]	= &context_types,
 	[ARG_PTR_TO_SOCK_COMMON]	= &sock_types,
 #ifdef CONFIG_NET
 	[ARG_PTR_TO_BTF_ID_SOCK_COMMON]	= &btf_id_sock_common_types,
 #endif
 	[ARG_PTR_TO_SOCKET]		= &fullsock_types,
-	[ARG_PTR_TO_SOCKET_OR_NULL]	= &fullsock_types,
 	[ARG_PTR_TO_BTF_ID]		= &btf_ptr_types,
 	[ARG_PTR_TO_SPIN_LOCK]		= &spin_lock_types,
 	[ARG_PTR_TO_MEM]		= &mem_types,
-	[ARG_PTR_TO_MEM_OR_NULL]	= &mem_types,
 	[ARG_PTR_TO_UNINIT_MEM]		= &mem_types,
 	[ARG_PTR_TO_ALLOC_MEM]		= &alloc_mem_types,
-	[ARG_PTR_TO_ALLOC_MEM_OR_NULL]	= &alloc_mem_types,
 	[ARG_PTR_TO_INT]		= &int_ptr_types,
 	[ARG_PTR_TO_LONG]		= &int_ptr_types,
 	[ARG_PTR_TO_PERCPU_BTF_ID]	= &percpu_btf_ptr_types,
@@ -4649,7 +4639,7 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
 	const struct bpf_reg_types *compatible;
 	int i, j;
 
-	compatible = compatible_reg_types[arg_type];
+	compatible = compatible_reg_types[base_type(arg_type)];
 	if (!compatible) {
 		verbose(env, "verifier internal error: unsupported arg type %d\n", arg_type);
 		return -EFAULT;
@@ -4730,15 +4720,14 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		return -EACCES;
 	}
 
-	if (arg_type == ARG_PTR_TO_MAP_VALUE ||
-	    arg_type == ARG_PTR_TO_UNINIT_MAP_VALUE ||
-	    arg_type == ARG_PTR_TO_MAP_VALUE_OR_NULL) {
+	if (base_type(arg_type) == ARG_PTR_TO_MAP_VALUE ||
+	    base_type(arg_type) == ARG_PTR_TO_UNINIT_MAP_VALUE) {
 		err = resolve_map_arg_type(env, meta, &arg_type);
 		if (err)
 			return err;
 	}
 
-	if (register_is_null(reg) && arg_type_may_be_null(arg_type))
+	if (register_is_null(reg) && type_may_be_null(arg_type))
 		/* A NULL register has a SCALAR_VALUE type, so skip
 		 * type checking.
 		 */
@@ -4785,10 +4774,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		err = check_helper_mem_access(env, regno,
 					      meta->map_ptr->key_size, false,
 					      NULL);
-	} else if (arg_type == ARG_PTR_TO_MAP_VALUE ||
-		   (arg_type == ARG_PTR_TO_MAP_VALUE_OR_NULL &&
-		    !register_is_null(reg)) ||
-		   arg_type == ARG_PTR_TO_UNINIT_MAP_VALUE) {
+	} else if (base_type(arg_type) == ARG_PTR_TO_MAP_VALUE ||
+		   base_type(arg_type) == ARG_PTR_TO_UNINIT_MAP_VALUE) {
+		if (type_may_be_null(arg_type) && register_is_null(reg))
+			return 0;
+
 		/* bpf_map_xxx(..., map_ptr, ..., value) call:
 		 * check [value, value + map->value_size) validity
 		 */
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 3/8] bpf: Replace RET_XXX_OR_NULL with RET_XXX | PTR_MAYBE_NULL
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types Puranjay Mohan
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 2/8] bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL Puranjay Mohan
@ 2025-06-10 14:43 ` Puranjay Mohan
  2025-06-11 13:24   ` Sasha Levin
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 4/8] bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX " Puranjay Mohan
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:43 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit 3c4807322660d4290ac9062c034aed6b87243861 upstream.

We have introduced a new type to make bpf_ret composable, by
reserving high bits to represent flags.

One of the flag is PTR_MAYBE_NULL, which indicates a pointer
may be NULL. When applying this flag to ret_types, it means
the returned value could be a NULL pointer. This patch
switches the qualified arg_types to use this flag.
The ret_types changed in this patch include:

1. RET_PTR_TO_MAP_VALUE_OR_NULL
2. RET_PTR_TO_SOCKET_OR_NULL
3. RET_PTR_TO_TCP_SOCK_OR_NULL
4. RET_PTR_TO_SOCK_COMMON_OR_NULL
5. RET_PTR_TO_ALLOC_MEM_OR_NULL
6. RET_PTR_TO_MEM_OR_BTF_ID_OR_NULL
7. RET_PTR_TO_BTF_ID_OR_NULL

This patch doesn't eliminate the use of these names, instead
it makes them aliases to 'RET_PTR_TO_XXX | PTR_MAYBE_NULL'.

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-4-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 include/linux/bpf.h   | 20 +++++++++++-------
 kernel/bpf/helpers.c  |  2 +-
 kernel/bpf/verifier.c | 49 +++++++++++++++++++++++--------------------
 3 files changed, 40 insertions(+), 31 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 55d6bf94e6eb..ce0ff9e78263 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -343,16 +343,22 @@ enum bpf_return_type {
 	RET_INTEGER,			/* function returns integer */
 	RET_VOID,			/* function doesn't return anything */
 	RET_PTR_TO_MAP_VALUE,		/* returns a pointer to map elem value */
-	RET_PTR_TO_MAP_VALUE_OR_NULL,	/* returns a pointer to map elem value or NULL */
-	RET_PTR_TO_SOCKET_OR_NULL,	/* returns a pointer to a socket or NULL */
-	RET_PTR_TO_TCP_SOCK_OR_NULL,	/* returns a pointer to a tcp_sock or NULL */
-	RET_PTR_TO_SOCK_COMMON_OR_NULL,	/* returns a pointer to a sock_common or NULL */
-	RET_PTR_TO_ALLOC_MEM_OR_NULL,	/* returns a pointer to dynamically allocated memory or NULL */
-	RET_PTR_TO_BTF_ID_OR_NULL,	/* returns a pointer to a btf_id or NULL */
-	RET_PTR_TO_MEM_OR_BTF_ID_OR_NULL, /* returns a pointer to a valid memory or a btf_id or NULL */
+	RET_PTR_TO_SOCKET,		/* returns a pointer to a socket */
+	RET_PTR_TO_TCP_SOCK,		/* returns a pointer to a tcp_sock */
+	RET_PTR_TO_SOCK_COMMON,		/* returns a pointer to a sock_common */
+	RET_PTR_TO_ALLOC_MEM,		/* returns a pointer to dynamically allocated memory */
 	RET_PTR_TO_MEM_OR_BTF_ID,	/* returns a pointer to a valid memory or a btf_id */
+	RET_PTR_TO_BTF_ID,		/* returns a pointer to a btf_id */
 	__BPF_RET_TYPE_MAX,
 
+	/* Extended ret_types. */
+	RET_PTR_TO_MAP_VALUE_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_MAP_VALUE,
+	RET_PTR_TO_SOCKET_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_SOCKET,
+	RET_PTR_TO_TCP_SOCK_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_TCP_SOCK,
+	RET_PTR_TO_SOCK_COMMON_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_SOCK_COMMON,
+	RET_PTR_TO_ALLOC_MEM_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_ALLOC_MEM,
+	RET_PTR_TO_BTF_ID_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_BTF_ID,
+
 	/* This must be the last entry. Its purpose is to ensure the enum is
 	 * wide enough to hold the higher bits reserved for bpf_type_flag.
 	 */
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 238a51daefa4..60ae9fcd9473 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -653,7 +653,7 @@ BPF_CALL_2(bpf_per_cpu_ptr, const void *, ptr, u32, cpu)
 const struct bpf_func_proto bpf_per_cpu_ptr_proto = {
 	.func		= bpf_per_cpu_ptr,
 	.gpl_only	= false,
-	.ret_type	= RET_PTR_TO_MEM_OR_BTF_ID_OR_NULL,
+	.ret_type	= RET_PTR_TO_MEM_OR_BTF_ID | PTR_MAYBE_NULL,
 	.arg1_type	= ARG_PTR_TO_PERCPU_BTF_ID,
 	.arg2_type	= ARG_ANYTHING,
 };
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fdcb42fe4303..2df320c6e641 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5555,6 +5555,7 @@ static int check_reference_leak(struct bpf_verifier_env *env)
 static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn_idx)
 {
 	const struct bpf_func_proto *fn = NULL;
+	enum bpf_return_type ret_type;
 	struct bpf_reg_state *regs;
 	struct bpf_call_arg_meta meta;
 	bool changes_data;
@@ -5666,13 +5667,13 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 	regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
 
 	/* update return register (already marked as written above) */
-	if (fn->ret_type == RET_INTEGER) {
+	ret_type = fn->ret_type;
+	if (ret_type == RET_INTEGER) {
 		/* sets type to SCALAR_VALUE */
 		mark_reg_unknown(env, regs, BPF_REG_0);
-	} else if (fn->ret_type == RET_VOID) {
+	} else if (ret_type == RET_VOID) {
 		regs[BPF_REG_0].type = NOT_INIT;
-	} else if (fn->ret_type == RET_PTR_TO_MAP_VALUE_OR_NULL ||
-		   fn->ret_type == RET_PTR_TO_MAP_VALUE) {
+	} else if (base_type(ret_type) == RET_PTR_TO_MAP_VALUE) {
 		/* There is no offset yet applied, variable or fixed */
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		/* remember map_ptr, so that check_map_access()
@@ -5685,28 +5686,27 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 			return -EINVAL;
 		}
 		regs[BPF_REG_0].map_ptr = meta.map_ptr;
-		if (fn->ret_type == RET_PTR_TO_MAP_VALUE) {
+		if (type_may_be_null(ret_type)) {
+			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE_OR_NULL;
+		} else {
 			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE;
 			if (map_value_has_spin_lock(meta.map_ptr))
 				regs[BPF_REG_0].id = ++env->id_gen;
-		} else {
-			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE_OR_NULL;
 		}
-	} else if (fn->ret_type == RET_PTR_TO_SOCKET_OR_NULL) {
+	} else if (base_type(ret_type) == RET_PTR_TO_SOCKET) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		regs[BPF_REG_0].type = PTR_TO_SOCKET_OR_NULL;
-	} else if (fn->ret_type == RET_PTR_TO_SOCK_COMMON_OR_NULL) {
+	} else if (base_type(ret_type) == RET_PTR_TO_SOCK_COMMON) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		regs[BPF_REG_0].type = PTR_TO_SOCK_COMMON_OR_NULL;
-	} else if (fn->ret_type == RET_PTR_TO_TCP_SOCK_OR_NULL) {
+	} else if (base_type(ret_type) == RET_PTR_TO_TCP_SOCK) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		regs[BPF_REG_0].type = PTR_TO_TCP_SOCK_OR_NULL;
-	} else if (fn->ret_type == RET_PTR_TO_ALLOC_MEM_OR_NULL) {
+	} else if (base_type(ret_type) == RET_PTR_TO_ALLOC_MEM) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		regs[BPF_REG_0].type = PTR_TO_MEM_OR_NULL;
 		regs[BPF_REG_0].mem_size = meta.mem_size;
-	} else if (fn->ret_type == RET_PTR_TO_MEM_OR_BTF_ID_OR_NULL ||
-		   fn->ret_type == RET_PTR_TO_MEM_OR_BTF_ID) {
+	} else if (base_type(ret_type) == RET_PTR_TO_MEM_OR_BTF_ID) {
 		const struct btf_type *t;
 
 		mark_reg_known_zero(env, regs, BPF_REG_0);
@@ -5725,30 +5725,33 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 				return -EINVAL;
 			}
 			regs[BPF_REG_0].type =
-				fn->ret_type == RET_PTR_TO_MEM_OR_BTF_ID ?
-				PTR_TO_MEM : PTR_TO_MEM_OR_NULL;
+				(ret_type & PTR_MAYBE_NULL) ?
+				PTR_TO_MEM_OR_NULL : PTR_TO_MEM;
 			regs[BPF_REG_0].mem_size = tsize;
 		} else {
 			regs[BPF_REG_0].type =
-				fn->ret_type == RET_PTR_TO_MEM_OR_BTF_ID ?
-				PTR_TO_BTF_ID : PTR_TO_BTF_ID_OR_NULL;
+				(ret_type & PTR_MAYBE_NULL) ?
+				PTR_TO_BTF_ID_OR_NULL : PTR_TO_BTF_ID;
 			regs[BPF_REG_0].btf_id = meta.ret_btf_id;
 		}
-	} else if (fn->ret_type == RET_PTR_TO_BTF_ID_OR_NULL) {
+	} else if (base_type(ret_type) == RET_PTR_TO_BTF_ID) {
 		int ret_btf_id;
 
 		mark_reg_known_zero(env, regs, BPF_REG_0);
-		regs[BPF_REG_0].type = PTR_TO_BTF_ID_OR_NULL;
+		regs[BPF_REG_0].type = (ret_type & PTR_MAYBE_NULL) ?
+					PTR_TO_BTF_ID_OR_NULL :
+					PTR_TO_BTF_ID;
 		ret_btf_id = *fn->ret_btf_id;
 		if (ret_btf_id == 0) {
-			verbose(env, "invalid return type %d of func %s#%d\n",
-				fn->ret_type, func_id_name(func_id), func_id);
+			verbose(env, "invalid return type %u of func %s#%d\n",
+				base_type(ret_type), func_id_name(func_id),
+				func_id);
 			return -EINVAL;
 		}
 		regs[BPF_REG_0].btf_id = ret_btf_id;
 	} else {
-		verbose(env, "unknown return type %d of func %s#%d\n",
-			fn->ret_type, func_id_name(func_id), func_id);
+		verbose(env, "unknown return type %u of func %s#%d\n",
+			base_type(ret_type), func_id_name(func_id), func_id);
 		return -EINVAL;
 	}
 
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 4/8] bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX | PTR_MAYBE_NULL
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
                   ` (2 preceding siblings ...)
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 3/8] bpf: Replace RET_XXX_OR_NULL with RET_XXX " Puranjay Mohan
@ 2025-06-10 14:43 ` Puranjay Mohan
  2025-06-11 13:16   ` Sasha Levin
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 5/8] bpf: Introduce MEM_RDONLY flag Puranjay Mohan
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:43 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit c25b2ae136039ffa820c26138ed4a5e5f3ab3841 upstream.

We have introduced a new type to make bpf_reg composable, by
allocating bits in the type to represent flags.

One of the flags is PTR_MAYBE_NULL which indicates a pointer
may be NULL. This patch switches the qualified reg_types to
use this flag. The reg_types changed in this patch include:

1. PTR_TO_MAP_VALUE_OR_NULL
2. PTR_TO_SOCKET_OR_NULL
3. PTR_TO_SOCK_COMMON_OR_NULL
4. PTR_TO_TCP_SOCK_OR_NULL
5. PTR_TO_BTF_ID_OR_NULL
6. PTR_TO_MEM_OR_NULL
7. PTR_TO_RDONLY_BUF_OR_NULL
8. PTR_TO_RDWR_BUF_OR_NULL

[puranjay: backport notes
 There was a reg_type_may_be_null() in adjust_ptr_min_max_vals() in
 5.10.x, but didn't exist in the upstream commit. This backport
 converted that reg_type_may_be_null() to type_may_be_null() as well.]

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/r/20211217003152.48334-5-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 include/linux/bpf.h          |  15 +-
 include/linux/bpf_verifier.h |   4 +
 kernel/bpf/btf.c             |   7 +-
 kernel/bpf/map_iter.c        |   4 +-
 kernel/bpf/verifier.c        | 294 ++++++++++++++++-------------------
 net/core/bpf_sk_storage.c    |   2 +-
 net/core/sock_map.c          |   2 +-
 7 files changed, 152 insertions(+), 176 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index ce0ff9e78263..d26457379e45 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -426,18 +426,14 @@ enum bpf_reg_type {
 	PTR_TO_CTX,		 /* reg points to bpf_context */
 	CONST_PTR_TO_MAP,	 /* reg points to struct bpf_map */
 	PTR_TO_MAP_VALUE,	 /* reg points to map element value */
-	PTR_TO_MAP_VALUE_OR_NULL,/* points to map elem value or NULL */
 	PTR_TO_STACK,		 /* reg == frame_pointer + offset */
 	PTR_TO_PACKET_META,	 /* skb->data - meta_len */
 	PTR_TO_PACKET,		 /* reg points to skb->data */
 	PTR_TO_PACKET_END,	 /* skb->data + headlen */
 	PTR_TO_FLOW_KEYS,	 /* reg points to bpf_flow_keys */
 	PTR_TO_SOCKET,		 /* reg points to struct bpf_sock */
-	PTR_TO_SOCKET_OR_NULL,	 /* reg points to struct bpf_sock or NULL */
 	PTR_TO_SOCK_COMMON,	 /* reg points to sock_common */
-	PTR_TO_SOCK_COMMON_OR_NULL, /* reg points to sock_common or NULL */
 	PTR_TO_TCP_SOCK,	 /* reg points to struct tcp_sock */
-	PTR_TO_TCP_SOCK_OR_NULL, /* reg points to struct tcp_sock or NULL */
 	PTR_TO_TP_BUFFER,	 /* reg points to a writable raw tp's buffer */
 	PTR_TO_XDP_SOCK,	 /* reg points to struct xdp_sock */
 	/* PTR_TO_BTF_ID points to a kernel struct that does not need
@@ -455,16 +451,19 @@ enum bpf_reg_type {
 	 * been checked for null. Used primarily to inform the verifier
 	 * an explicit null check is required for this struct.
 	 */
-	PTR_TO_BTF_ID_OR_NULL,
 	PTR_TO_MEM,		 /* reg points to valid memory region */
-	PTR_TO_MEM_OR_NULL,	 /* reg points to valid memory region or NULL */
 	PTR_TO_RDONLY_BUF,	 /* reg points to a readonly buffer */
-	PTR_TO_RDONLY_BUF_OR_NULL, /* reg points to a readonly buffer or NULL */
 	PTR_TO_RDWR_BUF,	 /* reg points to a read/write buffer */
-	PTR_TO_RDWR_BUF_OR_NULL, /* reg points to a read/write buffer or NULL */
 	PTR_TO_PERCPU_BTF_ID,	 /* reg points to a percpu kernel variable */
 	__BPF_REG_TYPE_MAX,
 
+	/* Extended reg_types. */
+	PTR_TO_MAP_VALUE_OR_NULL	= PTR_MAYBE_NULL | PTR_TO_MAP_VALUE,
+	PTR_TO_SOCKET_OR_NULL		= PTR_MAYBE_NULL | PTR_TO_SOCKET,
+	PTR_TO_SOCK_COMMON_OR_NULL	= PTR_MAYBE_NULL | PTR_TO_SOCK_COMMON,
+	PTR_TO_TCP_SOCK_OR_NULL		= PTR_MAYBE_NULL | PTR_TO_TCP_SOCK,
+	PTR_TO_BTF_ID_OR_NULL		= PTR_MAYBE_NULL | PTR_TO_BTF_ID,
+
 	/* This must be the last entry. Its purpose is to ensure the enum is
 	 * wide enough to hold the higher bits reserved for bpf_type_flag.
 	 */
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 71192aa285df..d47f127fcf6e 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -17,6 +17,8 @@
  * that converting umax_value to int cannot overflow.
  */
 #define BPF_MAX_VAR_SIZ	(1 << 29)
+/* size of type_str_buf in bpf_verifier. */
+#define TYPE_STR_BUF_LEN 64
 
 /* Liveness marks, used for registers and spilled-regs (in stack slots).
  * Read marks propagate upwards until they find a write mark; they record that
@@ -462,6 +464,8 @@ struct bpf_verifier_env {
 	u32 peak_states;
 	/* longest register parentage chain walked for liveness marking */
 	u32 longest_mark_read_walk;
+	/* buffer used in reg_type_str() to generate reg_type string */
+	char type_str_buf[TYPE_STR_BUF_LEN];
 };
 
 __printf(2, 0) void bpf_verifier_vlog(struct bpf_verifier_log *log,
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index cfbb038d150c..a49eca0ae72a 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -4535,10 +4535,13 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
 	/* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */
 	for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
 		const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
+		u32 type, flag;
 
+		type = base_type(ctx_arg_info->reg_type);
+		flag = type_flag(ctx_arg_info->reg_type);
 		if (ctx_arg_info->offset == off &&
-		    (ctx_arg_info->reg_type == PTR_TO_RDONLY_BUF_OR_NULL ||
-		     ctx_arg_info->reg_type == PTR_TO_RDWR_BUF_OR_NULL)) {
+		    (type == PTR_TO_RDWR_BUF || type == PTR_TO_RDONLY_BUF) &&
+		    (flag & PTR_MAYBE_NULL)) {
 			info->reg_type = ctx_arg_info->reg_type;
 			return true;
 		}
diff --git a/kernel/bpf/map_iter.c b/kernel/bpf/map_iter.c
index 6a9542af4212..631f0e44b7a9 100644
--- a/kernel/bpf/map_iter.c
+++ b/kernel/bpf/map_iter.c
@@ -174,9 +174,9 @@ static const struct bpf_iter_reg bpf_map_elem_reg_info = {
 	.ctx_arg_info_size	= 2,
 	.ctx_arg_info		= {
 		{ offsetof(struct bpf_iter__bpf_map_elem, key),
-		  PTR_TO_RDONLY_BUF_OR_NULL },
+		  PTR_TO_RDONLY_BUF | PTR_MAYBE_NULL },
 		{ offsetof(struct bpf_iter__bpf_map_elem, value),
-		  PTR_TO_RDWR_BUF_OR_NULL },
+		  PTR_TO_RDWR_BUF | PTR_MAYBE_NULL },
 	},
 };
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2df320c6e641..f45db8be23a3 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -404,18 +404,6 @@ static bool reg_type_not_null(enum bpf_reg_type type)
 		type == PTR_TO_SOCK_COMMON;
 }
 
-static bool reg_type_may_be_null(enum bpf_reg_type type)
-{
-	return type == PTR_TO_MAP_VALUE_OR_NULL ||
-	       type == PTR_TO_SOCKET_OR_NULL ||
-	       type == PTR_TO_SOCK_COMMON_OR_NULL ||
-	       type == PTR_TO_TCP_SOCK_OR_NULL ||
-	       type == PTR_TO_BTF_ID_OR_NULL ||
-	       type == PTR_TO_MEM_OR_NULL ||
-	       type == PTR_TO_RDONLY_BUF_OR_NULL ||
-	       type == PTR_TO_RDWR_BUF_OR_NULL;
-}
-
 static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)
 {
 	return reg->type == PTR_TO_MAP_VALUE &&
@@ -424,12 +412,9 @@ static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)
 
 static bool reg_type_may_be_refcounted_or_null(enum bpf_reg_type type)
 {
-	return type == PTR_TO_SOCKET ||
-		type == PTR_TO_SOCKET_OR_NULL ||
-		type == PTR_TO_TCP_SOCK ||
-		type == PTR_TO_TCP_SOCK_OR_NULL ||
-		type == PTR_TO_MEM ||
-		type == PTR_TO_MEM_OR_NULL;
+	return base_type(type) == PTR_TO_SOCKET ||
+		base_type(type) == PTR_TO_TCP_SOCK ||
+		base_type(type) == PTR_TO_MEM;
 }
 
 static bool arg_type_may_be_refcounted(enum bpf_arg_type type)
@@ -492,37 +477,50 @@ static bool is_ptr_cast_function(enum bpf_func_id func_id)
 		func_id == BPF_FUNC_skc_to_tcp_request_sock;
 }
 
-/* string representation of 'enum bpf_reg_type' */
-static const char * const reg_type_str[] = {
-	[NOT_INIT]		= "?",
-	[SCALAR_VALUE]		= "inv",
-	[PTR_TO_CTX]		= "ctx",
-	[CONST_PTR_TO_MAP]	= "map_ptr",
-	[PTR_TO_MAP_VALUE]	= "map_value",
-	[PTR_TO_MAP_VALUE_OR_NULL] = "map_value_or_null",
-	[PTR_TO_STACK]		= "fp",
-	[PTR_TO_PACKET]		= "pkt",
-	[PTR_TO_PACKET_META]	= "pkt_meta",
-	[PTR_TO_PACKET_END]	= "pkt_end",
-	[PTR_TO_FLOW_KEYS]	= "flow_keys",
-	[PTR_TO_SOCKET]		= "sock",
-	[PTR_TO_SOCKET_OR_NULL] = "sock_or_null",
-	[PTR_TO_SOCK_COMMON]	= "sock_common",
-	[PTR_TO_SOCK_COMMON_OR_NULL] = "sock_common_or_null",
-	[PTR_TO_TCP_SOCK]	= "tcp_sock",
-	[PTR_TO_TCP_SOCK_OR_NULL] = "tcp_sock_or_null",
-	[PTR_TO_TP_BUFFER]	= "tp_buffer",
-	[PTR_TO_XDP_SOCK]	= "xdp_sock",
-	[PTR_TO_BTF_ID]		= "ptr_",
-	[PTR_TO_BTF_ID_OR_NULL]	= "ptr_or_null_",
-	[PTR_TO_PERCPU_BTF_ID]	= "percpu_ptr_",
-	[PTR_TO_MEM]		= "mem",
-	[PTR_TO_MEM_OR_NULL]	= "mem_or_null",
-	[PTR_TO_RDONLY_BUF]	= "rdonly_buf",
-	[PTR_TO_RDONLY_BUF_OR_NULL] = "rdonly_buf_or_null",
-	[PTR_TO_RDWR_BUF]	= "rdwr_buf",
-	[PTR_TO_RDWR_BUF_OR_NULL] = "rdwr_buf_or_null",
-};
+/* string representation of 'enum bpf_reg_type'
+ *
+ * Note that reg_type_str() can not appear more than once in a single verbose()
+ * statement.
+ */
+static const char *reg_type_str(struct bpf_verifier_env *env,
+				enum bpf_reg_type type)
+{
+	char postfix[16] = {0};
+	static const char * const str[] = {
+		[NOT_INIT]		= "?",
+		[SCALAR_VALUE]		= "inv",
+		[PTR_TO_CTX]		= "ctx",
+		[CONST_PTR_TO_MAP]	= "map_ptr",
+		[PTR_TO_MAP_VALUE]	= "map_value",
+		[PTR_TO_STACK]		= "fp",
+		[PTR_TO_PACKET]		= "pkt",
+		[PTR_TO_PACKET_META]	= "pkt_meta",
+		[PTR_TO_PACKET_END]	= "pkt_end",
+		[PTR_TO_FLOW_KEYS]	= "flow_keys",
+		[PTR_TO_SOCKET]		= "sock",
+		[PTR_TO_SOCK_COMMON]	= "sock_common",
+		[PTR_TO_TCP_SOCK]	= "tcp_sock",
+		[PTR_TO_TP_BUFFER]	= "tp_buffer",
+		[PTR_TO_XDP_SOCK]	= "xdp_sock",
+		[PTR_TO_BTF_ID]		= "ptr_",
+		[PTR_TO_PERCPU_BTF_ID]	= "percpu_ptr_",
+		[PTR_TO_MEM]		= "mem",
+		[PTR_TO_RDONLY_BUF]	= "rdonly_buf",
+		[PTR_TO_RDWR_BUF]	= "rdwr_buf",
+	};
+
+	if (type & PTR_MAYBE_NULL) {
+		if (base_type(type) == PTR_TO_BTF_ID ||
+		    base_type(type) == PTR_TO_PERCPU_BTF_ID)
+			strncpy(postfix, "or_null_", 16);
+		else
+			strncpy(postfix, "_or_null", 16);
+	}
+
+	snprintf(env->type_str_buf, TYPE_STR_BUF_LEN, "%s%s",
+		 str[base_type(type)], postfix);
+	return env->type_str_buf;
+}
 
 static char slot_type_char[] = {
 	[STACK_INVALID]	= '?',
@@ -588,7 +586,7 @@ static void print_verifier_state(struct bpf_verifier_env *env,
 			continue;
 		verbose(env, " R%d", i);
 		print_liveness(env, reg->live);
-		verbose(env, "=%s", reg_type_str[t]);
+		verbose(env, "=%s", reg_type_str(env, t));
 		if (t == SCALAR_VALUE && reg->precise)
 			verbose(env, "P");
 		if ((t == SCALAR_VALUE || t == PTR_TO_STACK) &&
@@ -596,9 +594,8 @@ static void print_verifier_state(struct bpf_verifier_env *env,
 			/* reg->off should be 0 for SCALAR_VALUE */
 			verbose(env, "%lld", reg->var_off.value + reg->off);
 		} else {
-			if (t == PTR_TO_BTF_ID ||
-			    t == PTR_TO_BTF_ID_OR_NULL ||
-			    t == PTR_TO_PERCPU_BTF_ID)
+			if (base_type(t) == PTR_TO_BTF_ID ||
+			    base_type(t) == PTR_TO_PERCPU_BTF_ID)
 				verbose(env, "%s", kernel_type_name(reg->btf_id));
 			verbose(env, "(id=%d", reg->id);
 			if (reg_type_may_be_refcounted_or_null(t))
@@ -607,9 +604,8 @@ static void print_verifier_state(struct bpf_verifier_env *env,
 				verbose(env, ",off=%d", reg->off);
 			if (type_is_pkt_pointer(t))
 				verbose(env, ",r=%d", reg->range);
-			else if (t == CONST_PTR_TO_MAP ||
-				 t == PTR_TO_MAP_VALUE ||
-				 t == PTR_TO_MAP_VALUE_OR_NULL)
+			else if (base_type(t) == CONST_PTR_TO_MAP ||
+				 base_type(t) == PTR_TO_MAP_VALUE)
 				verbose(env, ",ks=%d,vs=%d",
 					reg->map_ptr->key_size,
 					reg->map_ptr->value_size);
@@ -679,7 +675,7 @@ static void print_verifier_state(struct bpf_verifier_env *env,
 		if (is_spilled_reg(&state->stack[i])) {
 			reg = &state->stack[i].spilled_ptr;
 			t = reg->type;
-			verbose(env, "=%s", reg_type_str[t]);
+			verbose(env, "=%s", reg_type_str(env, t));
 			if (t == SCALAR_VALUE && reg->precise)
 				verbose(env, "P");
 			if (t == SCALAR_VALUE && tnum_is_const(reg->var_off))
@@ -1577,7 +1573,7 @@ static int mark_reg_read(struct bpf_verifier_env *env,
 			break;
 		if (parent->live & REG_LIVE_DONE) {
 			verbose(env, "verifier BUG type %s var_off %lld off %d\n",
-				reg_type_str[parent->type],
+				reg_type_str(env, parent->type),
 				parent->var_off.value, parent->off);
 			return -EFAULT;
 		}
@@ -2366,9 +2362,8 @@ static int mark_chain_precision_stack_frame(struct bpf_verifier_env *env, int fr
 
 static bool is_spillable_regtype(enum bpf_reg_type type)
 {
-	switch (type) {
+	switch (base_type(type)) {
 	case PTR_TO_MAP_VALUE:
-	case PTR_TO_MAP_VALUE_OR_NULL:
 	case PTR_TO_STACK:
 	case PTR_TO_CTX:
 	case PTR_TO_PACKET:
@@ -2377,21 +2372,14 @@ static bool is_spillable_regtype(enum bpf_reg_type type)
 	case PTR_TO_FLOW_KEYS:
 	case CONST_PTR_TO_MAP:
 	case PTR_TO_SOCKET:
-	case PTR_TO_SOCKET_OR_NULL:
 	case PTR_TO_SOCK_COMMON:
-	case PTR_TO_SOCK_COMMON_OR_NULL:
 	case PTR_TO_TCP_SOCK:
-	case PTR_TO_TCP_SOCK_OR_NULL:
 	case PTR_TO_XDP_SOCK:
 	case PTR_TO_BTF_ID:
-	case PTR_TO_BTF_ID_OR_NULL:
 	case PTR_TO_RDONLY_BUF:
-	case PTR_TO_RDONLY_BUF_OR_NULL:
 	case PTR_TO_RDWR_BUF:
-	case PTR_TO_RDWR_BUF_OR_NULL:
 	case PTR_TO_PERCPU_BTF_ID:
 	case PTR_TO_MEM:
-	case PTR_TO_MEM_OR_NULL:
 		return true;
 	default:
 		return false;
@@ -3252,7 +3240,7 @@ static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off,
 		 */
 		*reg_type = info.reg_type;
 
-		if (*reg_type == PTR_TO_BTF_ID || *reg_type == PTR_TO_BTF_ID_OR_NULL)
+		if (base_type(*reg_type) == PTR_TO_BTF_ID)
 			*btf_id = info.btf_id;
 		else
 			env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
@@ -3318,7 +3306,7 @@ static int check_sock_access(struct bpf_verifier_env *env, int insn_idx,
 	}
 
 	verbose(env, "R%d invalid %s access off=%d size=%d\n",
-		regno, reg_type_str[reg->type], off, size);
+		regno, reg_type_str(env, reg->type), off, size);
 
 	return -EACCES;
 }
@@ -4057,7 +4045,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 			} else {
 				mark_reg_known_zero(env, regs,
 						    value_regno);
-				if (reg_type_may_be_null(reg_type))
+				if (type_may_be_null(reg_type))
 					regs[value_regno].id = ++env->id_gen;
 				/* A load of ctx field could have different
 				 * actual load size with the one encoded in the
@@ -4065,8 +4053,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 				 * a sub-register.
 				 */
 				regs[value_regno].subreg_def = DEF_NOT_SUBREG;
-				if (reg_type == PTR_TO_BTF_ID ||
-				    reg_type == PTR_TO_BTF_ID_OR_NULL)
+				if (base_type(reg_type) == PTR_TO_BTF_ID)
 					regs[value_regno].btf_id = btf_id;
 			}
 			regs[value_regno].type = reg_type;
@@ -4117,7 +4104,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 	} else if (type_is_sk_pointer(reg->type)) {
 		if (t == BPF_WRITE) {
 			verbose(env, "R%d cannot write into %s\n",
-				regno, reg_type_str[reg->type]);
+				regno, reg_type_str(env, reg->type));
 			return -EACCES;
 		}
 		err = check_sock_access(env, insn_idx, regno, off, size, t);
@@ -4136,7 +4123,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 	} else if (reg->type == PTR_TO_RDONLY_BUF) {
 		if (t == BPF_WRITE) {
 			verbose(env, "R%d cannot write into %s\n",
-				regno, reg_type_str[reg->type]);
+				regno, reg_type_str(env, reg->type));
 			return -EACCES;
 		}
 		err = check_buffer_access(env, reg, regno, off, size, false,
@@ -4152,7 +4139,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 			mark_reg_unknown(env, regs, value_regno);
 	} else {
 		verbose(env, "R%d invalid mem access '%s'\n", regno,
-			reg_type_str[reg->type]);
+			reg_type_str(env, reg->type));
 		return -EACCES;
 	}
 
@@ -4195,7 +4182,7 @@ static int check_xadd(struct bpf_verifier_env *env, int insn_idx, struct bpf_ins
 	    is_sk_reg(env, insn->dst_reg)) {
 		verbose(env, "BPF_XADD stores into R%d %s is not allowed\n",
 			insn->dst_reg,
-			reg_type_str[reg_state(env, insn->dst_reg)->type]);
+			reg_type_str(env, reg_state(env, insn->dst_reg)->type));
 		return -EACCES;
 	}
 
@@ -4392,9 +4379,9 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
 		    register_is_null(reg))
 			return 0;
 
-		verbose(env, "R%d type=%s expected=%s\n", regno,
-			reg_type_str[reg->type],
-			reg_type_str[PTR_TO_STACK]);
+		verbose(env, "R%d type=%s ", regno,
+			reg_type_str(env, reg->type));
+		verbose(env, "expected=%s\n", reg_type_str(env, PTR_TO_STACK));
 		return -EACCES;
 	}
 }
@@ -4654,10 +4641,10 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
 			goto found;
 	}
 
-	verbose(env, "R%d type=%s expected=", regno, reg_type_str[type]);
+	verbose(env, "R%d type=%s expected=", regno, reg_type_str(env, type));
 	for (j = 0; j + 1 < i; j++)
-		verbose(env, "%s, ", reg_type_str[compatible->types[j]]);
-	verbose(env, "%s\n", reg_type_str[compatible->types[j]]);
+		verbose(env, "%s, ", reg_type_str(env, compatible->types[j]));
+	verbose(env, "%s\n", reg_type_str(env, compatible->types[j]));
 	return -EACCES;
 
 found:
@@ -5556,6 +5543,7 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 {
 	const struct bpf_func_proto *fn = NULL;
 	enum bpf_return_type ret_type;
+	enum bpf_type_flag ret_flag;
 	struct bpf_reg_state *regs;
 	struct bpf_call_arg_meta meta;
 	bool changes_data;
@@ -5668,6 +5656,7 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 
 	/* update return register (already marked as written above) */
 	ret_type = fn->ret_type;
+	ret_flag = type_flag(fn->ret_type);
 	if (ret_type == RET_INTEGER) {
 		/* sets type to SCALAR_VALUE */
 		mark_reg_unknown(env, regs, BPF_REG_0);
@@ -5686,25 +5675,23 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 			return -EINVAL;
 		}
 		regs[BPF_REG_0].map_ptr = meta.map_ptr;
-		if (type_may_be_null(ret_type)) {
-			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE_OR_NULL;
-		} else {
-			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE;
-			if (map_value_has_spin_lock(meta.map_ptr))
-				regs[BPF_REG_0].id = ++env->id_gen;
+		regs[BPF_REG_0].type = PTR_TO_MAP_VALUE | ret_flag;
+		if (!type_may_be_null(ret_type) &&
+		    map_value_has_spin_lock(meta.map_ptr)) {
+			regs[BPF_REG_0].id = ++env->id_gen;
 		}
 	} else if (base_type(ret_type) == RET_PTR_TO_SOCKET) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
-		regs[BPF_REG_0].type = PTR_TO_SOCKET_OR_NULL;
+		regs[BPF_REG_0].type = PTR_TO_SOCKET | ret_flag;
 	} else if (base_type(ret_type) == RET_PTR_TO_SOCK_COMMON) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
-		regs[BPF_REG_0].type = PTR_TO_SOCK_COMMON_OR_NULL;
+		regs[BPF_REG_0].type = PTR_TO_SOCK_COMMON | ret_flag;
 	} else if (base_type(ret_type) == RET_PTR_TO_TCP_SOCK) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
-		regs[BPF_REG_0].type = PTR_TO_TCP_SOCK_OR_NULL;
+		regs[BPF_REG_0].type = PTR_TO_TCP_SOCK | ret_flag;
 	} else if (base_type(ret_type) == RET_PTR_TO_ALLOC_MEM) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
-		regs[BPF_REG_0].type = PTR_TO_MEM_OR_NULL;
+		regs[BPF_REG_0].type = PTR_TO_MEM | ret_flag;
 		regs[BPF_REG_0].mem_size = meta.mem_size;
 	} else if (base_type(ret_type) == RET_PTR_TO_MEM_OR_BTF_ID) {
 		const struct btf_type *t;
@@ -5724,23 +5711,17 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 					tname, PTR_ERR(ret));
 				return -EINVAL;
 			}
-			regs[BPF_REG_0].type =
-				(ret_type & PTR_MAYBE_NULL) ?
-				PTR_TO_MEM_OR_NULL : PTR_TO_MEM;
+			regs[BPF_REG_0].type = PTR_TO_MEM | ret_flag;
 			regs[BPF_REG_0].mem_size = tsize;
 		} else {
-			regs[BPF_REG_0].type =
-				(ret_type & PTR_MAYBE_NULL) ?
-				PTR_TO_BTF_ID_OR_NULL : PTR_TO_BTF_ID;
+			regs[BPF_REG_0].type = PTR_TO_BTF_ID | ret_flag;
 			regs[BPF_REG_0].btf_id = meta.ret_btf_id;
 		}
 	} else if (base_type(ret_type) == RET_PTR_TO_BTF_ID) {
 		int ret_btf_id;
 
 		mark_reg_known_zero(env, regs, BPF_REG_0);
-		regs[BPF_REG_0].type = (ret_type & PTR_MAYBE_NULL) ?
-					PTR_TO_BTF_ID_OR_NULL :
-					PTR_TO_BTF_ID;
+		regs[BPF_REG_0].type = PTR_TO_BTF_ID | ret_flag;
 		ret_btf_id = *fn->ret_btf_id;
 		if (ret_btf_id == 0) {
 			verbose(env, "invalid return type %u of func %s#%d\n",
@@ -5755,7 +5736,7 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 		return -EINVAL;
 	}
 
-	if (reg_type_may_be_null(regs[BPF_REG_0].type))
+	if (type_may_be_null(regs[BPF_REG_0].type))
 		regs[BPF_REG_0].id = ++env->id_gen;
 
 	if (is_ptr_cast_function(func_id)) {
@@ -5856,25 +5837,25 @@ static bool check_reg_sane_offset(struct bpf_verifier_env *env,
 
 	if (known && (val >= BPF_MAX_VAR_OFF || val <= -BPF_MAX_VAR_OFF)) {
 		verbose(env, "math between %s pointer and %lld is not allowed\n",
-			reg_type_str[type], val);
+			reg_type_str(env, type), val);
 		return false;
 	}
 
 	if (reg->off >= BPF_MAX_VAR_OFF || reg->off <= -BPF_MAX_VAR_OFF) {
 		verbose(env, "%s pointer offset %d is not allowed\n",
-			reg_type_str[type], reg->off);
+			reg_type_str(env, type), reg->off);
 		return false;
 	}
 
 	if (smin == S64_MIN) {
 		verbose(env, "math between %s pointer and register with unbounded min value is not allowed\n",
-			reg_type_str[type]);
+			reg_type_str(env, type));
 		return false;
 	}
 
 	if (smin >= BPF_MAX_VAR_OFF || smin <= -BPF_MAX_VAR_OFF) {
 		verbose(env, "value %lld makes %s pointer be out of bounds\n",
-			smin, reg_type_str[type]);
+			smin, reg_type_str(env, type));
 		return false;
 	}
 
@@ -6251,11 +6232,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 		return -EACCES;
 	}
 
-	switch (ptr_reg->type) {
-	case PTR_TO_MAP_VALUE_OR_NULL:
+	if (ptr_reg->type & PTR_MAYBE_NULL) {
 		verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
-			dst, reg_type_str[ptr_reg->type]);
+			dst, reg_type_str(env, ptr_reg->type));
 		return -EACCES;
+	}
+
+	switch (base_type(ptr_reg->type)) {
 	case CONST_PTR_TO_MAP:
 		/* smin_val represents the known value */
 		if (known && smin_val == 0 && opcode == BPF_ADD)
@@ -6268,10 +6251,10 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 	case PTR_TO_XDP_SOCK:
 reject:
 		verbose(env, "R%d pointer arithmetic on %s prohibited\n",
-			dst, reg_type_str[ptr_reg->type]);
+			dst, reg_type_str(env, ptr_reg->type));
 		return -EACCES;
 	default:
-		if (reg_type_may_be_null(ptr_reg->type))
+		if (type_may_be_null(ptr_reg->type))
 			goto reject;
 		break;
 	}
@@ -7964,7 +7947,7 @@ static void mark_ptr_or_null_reg(struct bpf_func_state *state,
 				 struct bpf_reg_state *reg, u32 id,
 				 bool is_null)
 {
-	if (reg_type_may_be_null(reg->type) && reg->id == id &&
+	if (type_may_be_null(reg->type) && reg->id == id &&
 	    !WARN_ON_ONCE(!reg->id)) {
 		if (WARN_ON_ONCE(reg->smin_value || reg->smax_value ||
 				 !tnum_equals_const(reg->var_off, 0) ||
@@ -7978,7 +7961,17 @@ static void mark_ptr_or_null_reg(struct bpf_func_state *state,
 		}
 		if (is_null) {
 			reg->type = SCALAR_VALUE;
-		} else if (reg->type == PTR_TO_MAP_VALUE_OR_NULL) {
+			/* We don't need id and ref_obj_id from this point
+			 * onwards anymore, thus we should better reset it,
+			 * so that state pruning has chances to take effect.
+			 */
+			reg->id = 0;
+			reg->ref_obj_id = 0;
+
+			return;
+		}
+
+		if (base_type(reg->type) == PTR_TO_MAP_VALUE) {
 			const struct bpf_map *map = reg->map_ptr;
 
 			if (map->inner_map_meta) {
@@ -7992,29 +7985,11 @@ static void mark_ptr_or_null_reg(struct bpf_func_state *state,
 			} else {
 				reg->type = PTR_TO_MAP_VALUE;
 			}
-		} else if (reg->type == PTR_TO_SOCKET_OR_NULL) {
-			reg->type = PTR_TO_SOCKET;
-		} else if (reg->type == PTR_TO_SOCK_COMMON_OR_NULL) {
-			reg->type = PTR_TO_SOCK_COMMON;
-		} else if (reg->type == PTR_TO_TCP_SOCK_OR_NULL) {
-			reg->type = PTR_TO_TCP_SOCK;
-		} else if (reg->type == PTR_TO_BTF_ID_OR_NULL) {
-			reg->type = PTR_TO_BTF_ID;
-		} else if (reg->type == PTR_TO_MEM_OR_NULL) {
-			reg->type = PTR_TO_MEM;
-		} else if (reg->type == PTR_TO_RDONLY_BUF_OR_NULL) {
-			reg->type = PTR_TO_RDONLY_BUF;
-		} else if (reg->type == PTR_TO_RDWR_BUF_OR_NULL) {
-			reg->type = PTR_TO_RDWR_BUF;
+		} else {
+			reg->type &= ~PTR_MAYBE_NULL;
 		}
-		if (is_null) {
-			/* We don't need id and ref_obj_id from this point
-			 * onwards anymore, thus we should better reset it,
-			 * so that state pruning has chances to take effect.
-			 */
-			reg->id = 0;
-			reg->ref_obj_id = 0;
-		} else if (!reg_may_point_to_spin_lock(reg)) {
+
+		if (!reg_may_point_to_spin_lock(reg)) {
 			/* For not-NULL ptr, reg->ref_obj_id will be reset
 			 * in release_reference().
 			 *
@@ -8341,7 +8316,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 	 */
 	if (!is_jmp32 && BPF_SRC(insn->code) == BPF_K &&
 	    insn->imm == 0 && (opcode == BPF_JEQ || opcode == BPF_JNE) &&
-	    reg_type_may_be_null(dst_reg->type)) {
+	    type_may_be_null(dst_reg->type)) {
 		/* Mark all identical registers in each branch as either
 		 * safe or unknown depending R == 0 or R != 0 conditional.
 		 */
@@ -8570,7 +8545,7 @@ static int check_return_code(struct bpf_verifier_env *env)
 	if (is_subprog) {
 		if (reg->type != SCALAR_VALUE) {
 			verbose(env, "At subprogram exit the register R0 is not a scalar value (%s)\n",
-				reg_type_str[reg->type]);
+				reg_type_str(env, reg->type));
 			return -EINVAL;
 		}
 		return 0;
@@ -8631,7 +8606,7 @@ static int check_return_code(struct bpf_verifier_env *env)
 
 	if (reg->type != SCALAR_VALUE) {
 		verbose(env, "At program exit the register R0 is not a known value (%s)\n",
-			reg_type_str[reg->type]);
+			reg_type_str(env, reg->type));
 		return -EINVAL;
 	}
 
@@ -9379,7 +9354,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
 		return true;
 	if (rcur->type == NOT_INIT)
 		return false;
-	switch (rold->type) {
+	switch (base_type(rold->type)) {
 	case SCALAR_VALUE:
 		if (env->explore_alu_limits)
 			return false;
@@ -9400,6 +9375,22 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
 			return false;
 		}
 	case PTR_TO_MAP_VALUE:
+		/* a PTR_TO_MAP_VALUE could be safe to use as a
+		 * PTR_TO_MAP_VALUE_OR_NULL into the same map.
+		 * However, if the old PTR_TO_MAP_VALUE_OR_NULL then got NULL-
+		 * checked, doing so could have affected others with the same
+		 * id, and we can't check for that because we lost the id when
+		 * we converted to a PTR_TO_MAP_VALUE.
+		 */
+		if (type_may_be_null(rold->type)) {
+			if (!type_may_be_null(rcur->type))
+				return false;
+			if (memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)))
+				return false;
+			/* Check our ids match any regs they're supposed to */
+			return check_ids(rold->id, rcur->id, idmap);
+		}
+
 		/* If the new min/max/var_off satisfy the old ones and
 		 * everything else matches, we are OK.
 		 * 'id' is not compared, since it's only used for maps with
@@ -9411,20 +9402,6 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
 		return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
 		       range_within(rold, rcur) &&
 		       tnum_in(rold->var_off, rcur->var_off);
-	case PTR_TO_MAP_VALUE_OR_NULL:
-		/* a PTR_TO_MAP_VALUE could be safe to use as a
-		 * PTR_TO_MAP_VALUE_OR_NULL into the same map.
-		 * However, if the old PTR_TO_MAP_VALUE_OR_NULL then got NULL-
-		 * checked, doing so could have affected others with the same
-		 * id, and we can't check for that because we lost the id when
-		 * we converted to a PTR_TO_MAP_VALUE.
-		 */
-		if (rcur->type != PTR_TO_MAP_VALUE_OR_NULL)
-			return false;
-		if (memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)))
-			return false;
-		/* Check our ids match any regs they're supposed to */
-		return check_ids(rold->id, rcur->id, idmap);
 	case PTR_TO_PACKET_META:
 	case PTR_TO_PACKET:
 		if (rcur->type != rold->type)
@@ -9453,11 +9430,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
 	case PTR_TO_PACKET_END:
 	case PTR_TO_FLOW_KEYS:
 	case PTR_TO_SOCKET:
-	case PTR_TO_SOCKET_OR_NULL:
 	case PTR_TO_SOCK_COMMON:
-	case PTR_TO_SOCK_COMMON_OR_NULL:
 	case PTR_TO_TCP_SOCK:
-	case PTR_TO_TCP_SOCK_OR_NULL:
 	case PTR_TO_XDP_SOCK:
 		/* Only valid matches are exact, which memcmp() above
 		 * would have accepted
@@ -9979,17 +9953,13 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
 /* Return true if it's OK to have the same insn return a different type. */
 static bool reg_type_mismatch_ok(enum bpf_reg_type type)
 {
-	switch (type) {
+	switch (base_type(type)) {
 	case PTR_TO_CTX:
 	case PTR_TO_SOCKET:
-	case PTR_TO_SOCKET_OR_NULL:
 	case PTR_TO_SOCK_COMMON:
-	case PTR_TO_SOCK_COMMON_OR_NULL:
 	case PTR_TO_TCP_SOCK:
-	case PTR_TO_TCP_SOCK_OR_NULL:
 	case PTR_TO_XDP_SOCK:
 	case PTR_TO_BTF_ID:
-	case PTR_TO_BTF_ID_OR_NULL:
 		return false;
 	default:
 		return true;
@@ -10207,7 +10177,7 @@ static int do_check(struct bpf_verifier_env *env)
 			if (is_ctx_reg(env, insn->dst_reg)) {
 				verbose(env, "BPF_ST stores into R%d %s is not allowed\n",
 					insn->dst_reg,
-					reg_type_str[reg_state(env, insn->dst_reg)->type]);
+					reg_type_str(env, reg_state(env, insn->dst_reg)->type));
 				return -EACCES;
 			}
 
diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c
index a811fe0f0f6f..ba4e1df72ce5 100644
--- a/net/core/bpf_sk_storage.c
+++ b/net/core/bpf_sk_storage.c
@@ -867,7 +867,7 @@ static struct bpf_iter_reg bpf_sk_storage_map_reg_info = {
 		{ offsetof(struct bpf_iter__bpf_sk_storage_map, sk),
 		  PTR_TO_BTF_ID_OR_NULL },
 		{ offsetof(struct bpf_iter__bpf_sk_storage_map, value),
-		  PTR_TO_RDWR_BUF_OR_NULL },
+		  PTR_TO_RDWR_BUF | PTR_MAYBE_NULL },
 	},
 	.seq_info		= &iter_seq_info,
 };
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index d334a2ccd523..d1a20f11c335 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -1655,7 +1655,7 @@ static struct bpf_iter_reg sock_map_iter_reg = {
 	.ctx_arg_info_size	= 2,
 	.ctx_arg_info		= {
 		{ offsetof(struct bpf_iter__sockmap, key),
-		  PTR_TO_RDONLY_BUF_OR_NULL },
+		  PTR_TO_RDONLY_BUF | PTR_MAYBE_NULL },
 		{ offsetof(struct bpf_iter__sockmap, sk),
 		  PTR_TO_BTF_ID_OR_NULL },
 	},
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 5/8] bpf: Introduce MEM_RDONLY flag
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
                   ` (3 preceding siblings ...)
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 4/8] bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX " Puranjay Mohan
@ 2025-06-10 14:44 ` Puranjay Mohan
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 6/8] bpf: Make per_cpu_ptr return rdonly PTR_TO_MEM Puranjay Mohan
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:44 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit 20b2aff4bc15bda809f994761d5719827d66c0b4 upstream.

This patch introduce a flag MEM_RDONLY to tag a reg value
pointing to read-only memory. It makes the following changes:

1. PTR_TO_RDWR_BUF -> PTR_TO_BUF
2. PTR_TO_RDONLY_BUF -> PTR_TO_BUF | MEM_RDONLY

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-6-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 include/linux/bpf.h       |  8 ++--
 kernel/bpf/btf.c          |  3 +-
 kernel/bpf/map_iter.c     |  4 +-
 kernel/bpf/verifier.c     | 84 +++++++++++++++++++++++----------------
 net/core/bpf_sk_storage.c |  2 +-
 net/core/sock_map.c       |  2 +-
 6 files changed, 60 insertions(+), 43 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index d26457379e45..ceee5a512d25 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -277,7 +277,10 @@ enum bpf_type_flag {
 	/* PTR may be NULL. */
 	PTR_MAYBE_NULL		= BIT(0 + BPF_BASE_TYPE_BITS),
 
-	__BPF_TYPE_LAST_FLAG	= PTR_MAYBE_NULL,
+	/* MEM is read-only. */
+	MEM_RDONLY		= BIT(1 + BPF_BASE_TYPE_BITS),
+
+	__BPF_TYPE_LAST_FLAG	= MEM_RDONLY,
 };
 
 /* Max number of base types. */
@@ -452,8 +455,7 @@ enum bpf_reg_type {
 	 * an explicit null check is required for this struct.
 	 */
 	PTR_TO_MEM,		 /* reg points to valid memory region */
-	PTR_TO_RDONLY_BUF,	 /* reg points to a readonly buffer */
-	PTR_TO_RDWR_BUF,	 /* reg points to a read/write buffer */
+	PTR_TO_BUF,		 /* reg points to a read/write buffer */
 	PTR_TO_PERCPU_BTF_ID,	 /* reg points to a percpu kernel variable */
 	__BPF_REG_TYPE_MAX,
 
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a49eca0ae72a..35c1b40dd622 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -4539,8 +4539,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
 
 		type = base_type(ctx_arg_info->reg_type);
 		flag = type_flag(ctx_arg_info->reg_type);
-		if (ctx_arg_info->offset == off &&
-		    (type == PTR_TO_RDWR_BUF || type == PTR_TO_RDONLY_BUF) &&
+		if (ctx_arg_info->offset == off && type == PTR_TO_BUF &&
 		    (flag & PTR_MAYBE_NULL)) {
 			info->reg_type = ctx_arg_info->reg_type;
 			return true;
diff --git a/kernel/bpf/map_iter.c b/kernel/bpf/map_iter.c
index 631f0e44b7a9..b0fa190b0979 100644
--- a/kernel/bpf/map_iter.c
+++ b/kernel/bpf/map_iter.c
@@ -174,9 +174,9 @@ static const struct bpf_iter_reg bpf_map_elem_reg_info = {
 	.ctx_arg_info_size	= 2,
 	.ctx_arg_info		= {
 		{ offsetof(struct bpf_iter__bpf_map_elem, key),
-		  PTR_TO_RDONLY_BUF | PTR_MAYBE_NULL },
+		  PTR_TO_BUF | PTR_MAYBE_NULL | MEM_RDONLY },
 		{ offsetof(struct bpf_iter__bpf_map_elem, value),
-		  PTR_TO_RDWR_BUF | PTR_MAYBE_NULL },
+		  PTR_TO_BUF | PTR_MAYBE_NULL },
 	},
 };
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index f45db8be23a3..bc36fea0dbe8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -417,6 +417,11 @@ static bool reg_type_may_be_refcounted_or_null(enum bpf_reg_type type)
 		base_type(type) == PTR_TO_MEM;
 }
 
+static bool type_is_rdonly_mem(u32 type)
+{
+	return type & MEM_RDONLY;
+}
+
 static bool arg_type_may_be_refcounted(enum bpf_arg_type type)
 {
 	return type == ARG_PTR_TO_SOCK_COMMON;
@@ -485,7 +490,7 @@ static bool is_ptr_cast_function(enum bpf_func_id func_id)
 static const char *reg_type_str(struct bpf_verifier_env *env,
 				enum bpf_reg_type type)
 {
-	char postfix[16] = {0};
+	char postfix[16] = {0}, prefix[16] = {0};
 	static const char * const str[] = {
 		[NOT_INIT]		= "?",
 		[SCALAR_VALUE]		= "inv",
@@ -505,8 +510,7 @@ static const char *reg_type_str(struct bpf_verifier_env *env,
 		[PTR_TO_BTF_ID]		= "ptr_",
 		[PTR_TO_PERCPU_BTF_ID]	= "percpu_ptr_",
 		[PTR_TO_MEM]		= "mem",
-		[PTR_TO_RDONLY_BUF]	= "rdonly_buf",
-		[PTR_TO_RDWR_BUF]	= "rdwr_buf",
+		[PTR_TO_BUF]		= "buf",
 	};
 
 	if (type & PTR_MAYBE_NULL) {
@@ -517,8 +521,11 @@ static const char *reg_type_str(struct bpf_verifier_env *env,
 			strncpy(postfix, "_or_null", 16);
 	}
 
-	snprintf(env->type_str_buf, TYPE_STR_BUF_LEN, "%s%s",
-		 str[base_type(type)], postfix);
+	if (type & MEM_RDONLY)
+		strncpy(prefix, "rdonly_", 16);
+
+	snprintf(env->type_str_buf, TYPE_STR_BUF_LEN, "%s%s%s",
+		 prefix, str[base_type(type)], postfix);
 	return env->type_str_buf;
 }
 
@@ -2376,8 +2383,7 @@ static bool is_spillable_regtype(enum bpf_reg_type type)
 	case PTR_TO_TCP_SOCK:
 	case PTR_TO_XDP_SOCK:
 	case PTR_TO_BTF_ID:
-	case PTR_TO_RDONLY_BUF:
-	case PTR_TO_RDWR_BUF:
+	case PTR_TO_BUF:
 	case PTR_TO_PERCPU_BTF_ID:
 	case PTR_TO_MEM:
 		return true;
@@ -4120,22 +4126,28 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 	} else if (reg->type == CONST_PTR_TO_MAP) {
 		err = check_ptr_to_map_access(env, regs, regno, off, size, t,
 					      value_regno);
-	} else if (reg->type == PTR_TO_RDONLY_BUF) {
-		if (t == BPF_WRITE) {
-			verbose(env, "R%d cannot write into %s\n",
-				regno, reg_type_str(env, reg->type));
-			return -EACCES;
+	} else if (base_type(reg->type) == PTR_TO_BUF) {
+		bool rdonly_mem = type_is_rdonly_mem(reg->type);
+		const char *buf_info;
+		u32 *max_access;
+
+		if (rdonly_mem) {
+			if (t == BPF_WRITE) {
+				verbose(env, "R%d cannot write into %s\n",
+					regno, reg_type_str(env, reg->type));
+				return -EACCES;
+			}
+			buf_info = "rdonly";
+			max_access = &env->prog->aux->max_rdonly_access;
+		} else {
+			buf_info = "rdwr";
+			max_access = &env->prog->aux->max_rdwr_access;
 		}
+
 		err = check_buffer_access(env, reg, regno, off, size, false,
-					  "rdonly",
-					  &env->prog->aux->max_rdonly_access);
-		if (!err && value_regno >= 0)
-			mark_reg_unknown(env, regs, value_regno);
-	} else if (reg->type == PTR_TO_RDWR_BUF) {
-		err = check_buffer_access(env, reg, regno, off, size, false,
-					  "rdwr",
-					  &env->prog->aux->max_rdwr_access);
-		if (!err && t == BPF_READ && value_regno >= 0)
+					  buf_info, max_access);
+
+		if (!err && value_regno >= 0 && (rdonly_mem || t == BPF_READ))
 			mark_reg_unknown(env, regs, value_regno);
 	} else {
 		verbose(env, "R%d invalid mem access '%s'\n", regno,
@@ -4339,8 +4351,10 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
 				   struct bpf_call_arg_meta *meta)
 {
 	struct bpf_reg_state *regs = cur_regs(env), *reg = &regs[regno];
+	const char *buf_info;
+	u32 *max_access;
 
-	switch (reg->type) {
+	switch (base_type(reg->type)) {
 	case PTR_TO_PACKET:
 	case PTR_TO_PACKET_META:
 		return check_packet_access(env, regno, reg->off, access_size,
@@ -4356,18 +4370,20 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
 		return check_mem_region_access(env, regno, reg->off,
 					       access_size, reg->mem_size,
 					       zero_size_allowed);
-	case PTR_TO_RDONLY_BUF:
-		if (meta && meta->raw_mode)
-			return -EACCES;
-		return check_buffer_access(env, reg, regno, reg->off,
-					   access_size, zero_size_allowed,
-					   "rdonly",
-					   &env->prog->aux->max_rdonly_access);
-	case PTR_TO_RDWR_BUF:
+	case PTR_TO_BUF:
+		if (type_is_rdonly_mem(reg->type)) {
+			if (meta && meta->raw_mode)
+				return -EACCES;
+
+			buf_info = "rdonly";
+			max_access = &env->prog->aux->max_rdonly_access;
+		} else {
+			buf_info = "rdwr";
+			max_access = &env->prog->aux->max_rdwr_access;
+		}
 		return check_buffer_access(env, reg, regno, reg->off,
 					   access_size, zero_size_allowed,
-					   "rdwr",
-					   &env->prog->aux->max_rdwr_access);
+					   buf_info, max_access);
 	case PTR_TO_STACK:
 		return check_stack_range_initialized(
 				env,
@@ -4570,8 +4586,8 @@ static const struct bpf_reg_types mem_types = {
 		PTR_TO_PACKET_META,
 		PTR_TO_MAP_VALUE,
 		PTR_TO_MEM,
-		PTR_TO_RDONLY_BUF,
-		PTR_TO_RDWR_BUF,
+		PTR_TO_BUF,
+		PTR_TO_BUF | MEM_RDONLY,
 	},
 };
 
diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c
index ba4e1df72ce5..3fad2f5b920e 100644
--- a/net/core/bpf_sk_storage.c
+++ b/net/core/bpf_sk_storage.c
@@ -867,7 +867,7 @@ static struct bpf_iter_reg bpf_sk_storage_map_reg_info = {
 		{ offsetof(struct bpf_iter__bpf_sk_storage_map, sk),
 		  PTR_TO_BTF_ID_OR_NULL },
 		{ offsetof(struct bpf_iter__bpf_sk_storage_map, value),
-		  PTR_TO_RDWR_BUF | PTR_MAYBE_NULL },
+		  PTR_TO_BUF | PTR_MAYBE_NULL },
 	},
 	.seq_info		= &iter_seq_info,
 };
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index d1a20f11c335..f8d0538e27f3 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -1655,7 +1655,7 @@ static struct bpf_iter_reg sock_map_iter_reg = {
 	.ctx_arg_info_size	= 2,
 	.ctx_arg_info		= {
 		{ offsetof(struct bpf_iter__sockmap, key),
-		  PTR_TO_RDONLY_BUF | PTR_MAYBE_NULL },
+		  PTR_TO_BUF | PTR_MAYBE_NULL | MEM_RDONLY },
 		{ offsetof(struct bpf_iter__sockmap, sk),
 		  PTR_TO_BTF_ID_OR_NULL },
 	},
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 6/8] bpf: Make per_cpu_ptr return rdonly PTR_TO_MEM.
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
                   ` (4 preceding siblings ...)
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 5/8] bpf: Introduce MEM_RDONLY flag Puranjay Mohan
@ 2025-06-10 14:44 ` Puranjay Mohan
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 7/8] bpf: Add MEM_RDONLY for helper args that are pointers to rdonly mem Puranjay Mohan
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 8/8] bpf/selftests: Test PTR_TO_RDONLY_MEM Puranjay Mohan
  7 siblings, 0 replies; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:44 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit 34d3a78c681e8e7844b43d1a2f4671a04249c821 upstream.

Tag the return type of {per, this}_cpu_ptr with RDONLY_MEM. The
returned value of this pair of helpers is kernel object, which
can not be updated by bpf programs. Previously these two helpers
return PTR_OT_MEM for kernel objects of scalar type, which allows
one to directly modify the memory. Now with RDONLY_MEM tagging,
the verifier will reject programs that write into RDONLY_MEM.

Fixes: 63d9b80dcf2c ("bpf: Introducte bpf_this_cpu_ptr()")
Fixes: eaa6bcb71ef6 ("bpf: Introduce bpf_per_cpu_ptr()")
Fixes: 4976b718c355 ("bpf: Introduce pseudo_btf_id")
Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-8-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 kernel/bpf/helpers.c  |  4 ++--
 kernel/bpf/verifier.c | 30 ++++++++++++++++++++++++++----
 2 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 60ae9fcd9473..3e5466693615 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -653,7 +653,7 @@ BPF_CALL_2(bpf_per_cpu_ptr, const void *, ptr, u32, cpu)
 const struct bpf_func_proto bpf_per_cpu_ptr_proto = {
 	.func		= bpf_per_cpu_ptr,
 	.gpl_only	= false,
-	.ret_type	= RET_PTR_TO_MEM_OR_BTF_ID | PTR_MAYBE_NULL,
+	.ret_type	= RET_PTR_TO_MEM_OR_BTF_ID | PTR_MAYBE_NULL | MEM_RDONLY,
 	.arg1_type	= ARG_PTR_TO_PERCPU_BTF_ID,
 	.arg2_type	= ARG_ANYTHING,
 };
@@ -666,7 +666,7 @@ BPF_CALL_1(bpf_this_cpu_ptr, const void *, percpu_ptr)
 const struct bpf_func_proto bpf_this_cpu_ptr_proto = {
 	.func		= bpf_this_cpu_ptr,
 	.gpl_only	= false,
-	.ret_type	= RET_PTR_TO_MEM_OR_BTF_ID,
+	.ret_type	= RET_PTR_TO_MEM_OR_BTF_ID | MEM_RDONLY,
 	.arg1_type	= ARG_PTR_TO_PERCPU_BTF_ID,
 };
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index bc36fea0dbe8..03e40ee02c0c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4014,15 +4014,30 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
 				mark_reg_unknown(env, regs, value_regno);
 			}
 		}
-	} else if (reg->type == PTR_TO_MEM) {
+	} else if (base_type(reg->type) == PTR_TO_MEM) {
+		bool rdonly_mem = type_is_rdonly_mem(reg->type);
+
+		if (type_may_be_null(reg->type)) {
+			verbose(env, "R%d invalid mem access '%s'\n", regno,
+				reg_type_str(env, reg->type));
+			return -EACCES;
+		}
+
+		if (t == BPF_WRITE && rdonly_mem) {
+			verbose(env, "R%d cannot write into %s\n",
+				regno, reg_type_str(env, reg->type));
+			return -EACCES;
+		}
+
 		if (t == BPF_WRITE && value_regno >= 0 &&
 		    is_pointer_value(env, value_regno)) {
 			verbose(env, "R%d leaks addr into mem\n", value_regno);
 			return -EACCES;
 		}
+
 		err = check_mem_region_access(env, regno, off, size,
 					      reg->mem_size, false);
-		if (!err && t == BPF_READ && value_regno >= 0)
+		if (!err && value_regno >= 0 && (t == BPF_READ || rdonly_mem))
 			mark_reg_unknown(env, regs, value_regno);
 	} else if (reg->type == PTR_TO_CTX) {
 		enum bpf_reg_type reg_type = SCALAR_VALUE;
@@ -5730,6 +5745,13 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
 			regs[BPF_REG_0].type = PTR_TO_MEM | ret_flag;
 			regs[BPF_REG_0].mem_size = tsize;
 		} else {
+			/* MEM_RDONLY may be carried from ret_flag, but it
+			 * doesn't apply on PTR_TO_BTF_ID. Fold it, otherwise
+			 * it will confuse the check of PTR_TO_BTF_ID in
+			 * check_mem_access().
+			 */
+			ret_flag &= ~MEM_RDONLY;
+
 			regs[BPF_REG_0].type = PTR_TO_BTF_ID | ret_flag;
 			regs[BPF_REG_0].btf_id = meta.ret_btf_id;
 		}
@@ -8387,7 +8409,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
 		mark_reg_known_zero(env, regs, insn->dst_reg);
 
 		dst_reg->type = aux->btf_var.reg_type;
-		switch (dst_reg->type) {
+		switch (base_type(dst_reg->type)) {
 		case PTR_TO_MEM:
 			dst_reg->mem_size = aux->btf_var.mem_size;
 			break;
@@ -10401,7 +10423,7 @@ static int check_pseudo_btf_id(struct bpf_verifier_env *env,
 				tname, PTR_ERR(ret));
 			return -EINVAL;
 		}
-		aux->btf_var.reg_type = PTR_TO_MEM;
+		aux->btf_var.reg_type = PTR_TO_MEM | MEM_RDONLY;
 		aux->btf_var.mem_size = tsize;
 	} else {
 		aux->btf_var.reg_type = PTR_TO_BTF_ID;
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 7/8] bpf: Add MEM_RDONLY for helper args that are pointers to rdonly mem.
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
                   ` (5 preceding siblings ...)
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 6/8] bpf: Make per_cpu_ptr return rdonly PTR_TO_MEM Puranjay Mohan
@ 2025-06-10 14:44 ` Puranjay Mohan
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 8/8] bpf/selftests: Test PTR_TO_RDONLY_MEM Puranjay Mohan
  7 siblings, 0 replies; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:44 UTC (permalink / raw)
  To: Greg KH; +Cc: Hao Luo, Alexei Starovoitov, Puranjay Mohan, stable

From: Hao Luo <haoluo@google.com>

commit 216e3cd2f28dbbf1fe86848e0e29e6693b9f0a20 upstream.

Some helper functions may modify its arguments, for example,
bpf_d_path, bpf_get_stack etc. Previously, their argument types
were marked as ARG_PTR_TO_MEM, which is compatible with read-only
mem types, such as PTR_TO_RDONLY_BUF. Therefore it's legitimate,
but technically incorrect, to modify a read-only memory by passing
it into one of such helper functions.

This patch tags the bpf_args compatible with immutable memory with
MEM_RDONLY flag. The arguments that don't have this flag will be
only compatible with mutable memory types, preventing the helper
from modifying a read-only memory. The bpf_args that have
MEM_RDONLY are compatible with both mutable memory and immutable
memory.

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-9-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 include/linux/bpf.h      |  4 ++-
 kernel/bpf/cgroup.c      |  2 +-
 kernel/bpf/helpers.c     |  6 ++--
 kernel/bpf/ringbuf.c     |  2 +-
 kernel/bpf/verifier.c    | 20 +++++++++++--
 kernel/trace/bpf_trace.c | 22 +++++++-------
 net/core/filter.c        | 62 ++++++++++++++++++++--------------------
 7 files changed, 67 insertions(+), 51 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index ceee5a512d25..8efa395a1bb2 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -277,7 +277,9 @@ enum bpf_type_flag {
 	/* PTR may be NULL. */
 	PTR_MAYBE_NULL		= BIT(0 + BPF_BASE_TYPE_BITS),
 
-	/* MEM is read-only. */
+	/* MEM is read-only. When applied on bpf_arg, it indicates the arg is
+	 * compatible with both mutable and immutable memory.
+	 */
 	MEM_RDONLY		= BIT(1 + BPF_BASE_TYPE_BITS),
 
 	__BPF_TYPE_LAST_FLAG	= MEM_RDONLY,
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index 85927c2aa343..54321df6cfac 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -1738,7 +1738,7 @@ static const struct bpf_func_proto bpf_sysctl_set_new_value_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 };
 
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 3e5466693615..d9d65bbaf210 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -516,7 +516,7 @@ const struct bpf_func_proto bpf_strtol_proto = {
 	.func		= bpf_strtol,
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
-	.arg1_type	= ARG_PTR_TO_MEM,
+	.arg1_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg2_type	= ARG_CONST_SIZE,
 	.arg3_type	= ARG_ANYTHING,
 	.arg4_type	= ARG_PTR_TO_LONG,
@@ -544,7 +544,7 @@ const struct bpf_func_proto bpf_strtoul_proto = {
 	.func		= bpf_strtoul,
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
-	.arg1_type	= ARG_PTR_TO_MEM,
+	.arg1_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg2_type	= ARG_CONST_SIZE,
 	.arg3_type	= ARG_ANYTHING,
 	.arg4_type	= ARG_PTR_TO_LONG,
@@ -616,7 +616,7 @@ const struct bpf_func_proto bpf_event_output_data_proto =  {
 	.arg1_type      = ARG_PTR_TO_CTX,
 	.arg2_type      = ARG_CONST_MAP_PTR,
 	.arg3_type      = ARG_ANYTHING,
-	.arg4_type      = ARG_PTR_TO_MEM,
+	.arg4_type      = ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type      = ARG_CONST_SIZE_OR_ZERO,
 };
 
diff --git a/kernel/bpf/ringbuf.c b/kernel/bpf/ringbuf.c
index eac0026e2fa6..149af5a50cdd 100644
--- a/kernel/bpf/ringbuf.c
+++ b/kernel/bpf/ringbuf.c
@@ -483,7 +483,7 @@ const struct bpf_func_proto bpf_ringbuf_output_proto = {
 	.func		= bpf_ringbuf_output,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_CONST_MAP_PTR,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE_OR_ZERO,
 	.arg4_type	= ARG_ANYTHING,
 };
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 03e40ee02c0c..d9417244d3b8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4602,7 +4602,6 @@ static const struct bpf_reg_types mem_types = {
 		PTR_TO_MAP_VALUE,
 		PTR_TO_MEM,
 		PTR_TO_BUF,
-		PTR_TO_BUF | MEM_RDONLY,
 	},
 };
 
@@ -4663,6 +4662,21 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
 		return -EFAULT;
 	}
 
+	/* ARG_PTR_TO_MEM + RDONLY is compatible with PTR_TO_MEM and PTR_TO_MEM + RDONLY,
+	 * but ARG_PTR_TO_MEM is compatible only with PTR_TO_MEM and NOT with PTR_TO_MEM + RDONLY
+	 *
+	 * Same for MAYBE_NULL:
+	 *
+	 * ARG_PTR_TO_MEM + MAYBE_NULL is compatible with PTR_TO_MEM and PTR_TO_MEM + MAYBE_NULL,
+	 * but ARG_PTR_TO_MEM is compatible only with PTR_TO_MEM but NOT with PTR_TO_MEM + MAYBE_NULL
+	 *
+	 * Therefore we fold these flags depending on the arg_type before comparison.
+	 */
+	if (arg_type & MEM_RDONLY)
+		type &= ~MEM_RDONLY;
+	if (arg_type & PTR_MAYBE_NULL)
+		type &= ~PTR_MAYBE_NULL;
+
 	for (i = 0; i < ARRAY_SIZE(compatible->types); i++) {
 		expected = compatible->types[i];
 		if (expected == NOT_INIT)
@@ -4672,14 +4686,14 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
 			goto found;
 	}
 
-	verbose(env, "R%d type=%s expected=", regno, reg_type_str(env, type));
+	verbose(env, "R%d type=%s expected=", regno, reg_type_str(env, reg->type));
 	for (j = 0; j + 1 < i; j++)
 		verbose(env, "%s, ", reg_type_str(env, compatible->types[j]));
 	verbose(env, "%s\n", reg_type_str(env, compatible->types[j]));
 	return -EACCES;
 
 found:
-	if (type == PTR_TO_BTF_ID) {
+	if (reg->type == PTR_TO_BTF_ID) {
 		if (!arg_btf_id) {
 			if (!compatible->btf_id) {
 				verbose(env, "verifier internal error: missing arg compatible BTF ID\n");
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 6957381b139c..fc80e7a1db2a 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -342,7 +342,7 @@ static const struct bpf_func_proto bpf_probe_write_user_proto = {
 	.gpl_only	= true,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_ANYTHING,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 };
 
@@ -545,7 +545,7 @@ static const struct bpf_func_proto bpf_trace_printk_proto = {
 	.func		= bpf_trace_printk,
 	.gpl_only	= true,
 	.ret_type	= RET_INTEGER,
-	.arg1_type	= ARG_PTR_TO_MEM,
+	.arg1_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg2_type	= ARG_CONST_SIZE,
 };
 
@@ -754,9 +754,9 @@ static const struct bpf_func_proto bpf_seq_printf_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_BTF_ID,
 	.arg1_btf_id	= &btf_seq_file_ids[0],
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
-	.arg4_type      = ARG_PTR_TO_MEM_OR_NULL,
+	.arg4_type      = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
 	.arg5_type      = ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -771,7 +771,7 @@ static const struct bpf_func_proto bpf_seq_write_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_BTF_ID,
 	.arg1_btf_id	= &btf_seq_file_ids[0],
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -795,7 +795,7 @@ static const struct bpf_func_proto bpf_seq_printf_btf_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_BTF_ID,
 	.arg1_btf_id	= &btf_seq_file_ids[0],
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE_OR_ZERO,
 	.arg4_type	= ARG_ANYTHING,
 };
@@ -956,7 +956,7 @@ static const struct bpf_func_proto bpf_perf_event_output_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -1247,7 +1247,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_MEM,
 	.arg2_type	= ARG_CONST_SIZE,
-	.arg3_type	= ARG_PTR_TO_MEM,
+	.arg3_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE,
 	.arg5_type	= ARG_ANYTHING,
 };
@@ -1422,7 +1422,7 @@ static const struct bpf_func_proto bpf_perf_event_output_proto_tp = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -1640,7 +1640,7 @@ static const struct bpf_func_proto bpf_perf_event_output_proto_raw_tp = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -1694,7 +1694,7 @@ static const struct bpf_func_proto bpf_get_stack_proto_raw_tp = {
 	.gpl_only	= true,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE_OR_ZERO,
 	.arg4_type	= ARG_ANYTHING,
 };
diff --git a/net/core/filter.c b/net/core/filter.c
index b262cad02bad..68d2c3ae9878 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -1724,7 +1724,7 @@ static const struct bpf_func_proto bpf_skb_store_bytes_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
-	.arg3_type	= ARG_PTR_TO_MEM,
+	.arg3_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE,
 	.arg5_type	= ARG_ANYTHING,
 };
@@ -2032,9 +2032,9 @@ static const struct bpf_func_proto bpf_csum_diff_proto = {
 	.gpl_only	= false,
 	.pkt_access	= true,
 	.ret_type	= RET_INTEGER,
-	.arg1_type	= ARG_PTR_TO_MEM_OR_NULL,
+	.arg1_type	= ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
 	.arg2_type	= ARG_CONST_SIZE_OR_ZERO,
-	.arg3_type	= ARG_PTR_TO_MEM_OR_NULL,
+	.arg3_type	= ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE_OR_ZERO,
 	.arg5_type	= ARG_ANYTHING,
 };
@@ -2581,7 +2581,7 @@ static const struct bpf_func_proto bpf_redirect_neigh_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_ANYTHING,
-	.arg2_type      = ARG_PTR_TO_MEM_OR_NULL,
+	.arg2_type      = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
 	.arg3_type      = ARG_CONST_SIZE_OR_ZERO,
 	.arg4_type	= ARG_ANYTHING,
 };
@@ -4253,7 +4253,7 @@ static const struct bpf_func_proto bpf_skb_event_output_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -4267,7 +4267,7 @@ const struct bpf_func_proto bpf_skb_output_proto = {
 	.arg1_btf_id	= &bpf_skb_output_btf_ids[0],
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -4450,7 +4450,7 @@ static const struct bpf_func_proto bpf_skb_set_tunnel_key_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 };
@@ -4476,7 +4476,7 @@ static const struct bpf_func_proto bpf_skb_set_tunnel_opt_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 };
 
@@ -4646,7 +4646,7 @@ static const struct bpf_func_proto bpf_xdp_event_output_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -4660,7 +4660,7 @@ const struct bpf_func_proto bpf_xdp_output_proto = {
 	.arg1_btf_id	= &bpf_xdp_output_btf_ids[0],
 	.arg2_type	= ARG_CONST_MAP_PTR,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE_OR_ZERO,
 };
 
@@ -5078,7 +5078,7 @@ static const struct bpf_func_proto bpf_sock_addr_setsockopt_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE,
 };
 
@@ -5112,7 +5112,7 @@ static const struct bpf_func_proto bpf_sock_ops_setsockopt_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
 	.arg3_type	= ARG_ANYTHING,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE,
 };
 
@@ -5287,7 +5287,7 @@ static const struct bpf_func_proto bpf_bind_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 };
 
@@ -5748,7 +5748,7 @@ static const struct bpf_func_proto bpf_lwt_in_push_encap_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
-	.arg3_type	= ARG_PTR_TO_MEM,
+	.arg3_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE
 };
 
@@ -5758,7 +5758,7 @@ static const struct bpf_func_proto bpf_lwt_xmit_push_encap_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
-	.arg3_type	= ARG_PTR_TO_MEM,
+	.arg3_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE
 };
 
@@ -5801,7 +5801,7 @@ static const struct bpf_func_proto bpf_lwt_seg6_store_bytes_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
-	.arg3_type	= ARG_PTR_TO_MEM,
+	.arg3_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE
 };
 
@@ -5889,7 +5889,7 @@ static const struct bpf_func_proto bpf_lwt_seg6_action_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
 	.arg2_type	= ARG_ANYTHING,
-	.arg3_type	= ARG_PTR_TO_MEM,
+	.arg3_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg4_type	= ARG_CONST_SIZE
 };
 
@@ -6136,7 +6136,7 @@ static const struct bpf_func_proto bpf_skc_lookup_tcp_proto = {
 	.pkt_access	= true,
 	.ret_type	= RET_PTR_TO_SOCK_COMMON_OR_NULL,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 	.arg5_type	= ARG_ANYTHING,
@@ -6155,7 +6155,7 @@ static const struct bpf_func_proto bpf_sk_lookup_tcp_proto = {
 	.pkt_access	= true,
 	.ret_type	= RET_PTR_TO_SOCKET_OR_NULL,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 	.arg5_type	= ARG_ANYTHING,
@@ -6174,7 +6174,7 @@ static const struct bpf_func_proto bpf_sk_lookup_udp_proto = {
 	.pkt_access	= true,
 	.ret_type	= RET_PTR_TO_SOCKET_OR_NULL,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 	.arg5_type	= ARG_ANYTHING,
@@ -6211,7 +6211,7 @@ static const struct bpf_func_proto bpf_xdp_sk_lookup_udp_proto = {
 	.pkt_access     = true,
 	.ret_type       = RET_PTR_TO_SOCKET_OR_NULL,
 	.arg1_type      = ARG_PTR_TO_CTX,
-	.arg2_type      = ARG_PTR_TO_MEM,
+	.arg2_type      = ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type      = ARG_CONST_SIZE,
 	.arg4_type      = ARG_ANYTHING,
 	.arg5_type      = ARG_ANYTHING,
@@ -6234,7 +6234,7 @@ static const struct bpf_func_proto bpf_xdp_skc_lookup_tcp_proto = {
 	.pkt_access     = true,
 	.ret_type       = RET_PTR_TO_SOCK_COMMON_OR_NULL,
 	.arg1_type      = ARG_PTR_TO_CTX,
-	.arg2_type      = ARG_PTR_TO_MEM,
+	.arg2_type      = ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type      = ARG_CONST_SIZE,
 	.arg4_type      = ARG_ANYTHING,
 	.arg5_type      = ARG_ANYTHING,
@@ -6257,7 +6257,7 @@ static const struct bpf_func_proto bpf_xdp_sk_lookup_tcp_proto = {
 	.pkt_access     = true,
 	.ret_type       = RET_PTR_TO_SOCKET_OR_NULL,
 	.arg1_type      = ARG_PTR_TO_CTX,
-	.arg2_type      = ARG_PTR_TO_MEM,
+	.arg2_type      = ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type      = ARG_CONST_SIZE,
 	.arg4_type      = ARG_ANYTHING,
 	.arg5_type      = ARG_ANYTHING,
@@ -6276,7 +6276,7 @@ static const struct bpf_func_proto bpf_sock_addr_skc_lookup_tcp_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_PTR_TO_SOCK_COMMON_OR_NULL,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 	.arg5_type	= ARG_ANYTHING,
@@ -6295,7 +6295,7 @@ static const struct bpf_func_proto bpf_sock_addr_sk_lookup_tcp_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_PTR_TO_SOCKET_OR_NULL,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 	.arg5_type	= ARG_ANYTHING,
@@ -6314,7 +6314,7 @@ static const struct bpf_func_proto bpf_sock_addr_sk_lookup_udp_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_PTR_TO_SOCKET_OR_NULL,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 	.arg5_type	= ARG_ANYTHING,
@@ -6636,9 +6636,9 @@ static const struct bpf_func_proto bpf_tcp_check_syncookie_proto = {
 	.pkt_access	= true,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_BTF_ID_SOCK_COMMON,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE,
 };
 
@@ -6705,9 +6705,9 @@ static const struct bpf_func_proto bpf_tcp_gen_syncookie_proto = {
 	.pkt_access	= true,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_BTF_ID_SOCK_COMMON,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
-	.arg4_type	= ARG_PTR_TO_MEM,
+	.arg4_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg5_type	= ARG_CONST_SIZE,
 };
 
@@ -6938,7 +6938,7 @@ static const struct bpf_func_proto bpf_sock_ops_store_hdr_opt_proto = {
 	.gpl_only	= false,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_CTX,
-	.arg2_type	= ARG_PTR_TO_MEM,
+	.arg2_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
 	.arg3_type	= ARG_CONST_SIZE,
 	.arg4_type	= ARG_ANYTHING,
 };
-- 
2.47.1


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

* [PATCH stable linux-5.10.y v1 8/8] bpf/selftests: Test PTR_TO_RDONLY_MEM
  2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
                   ` (6 preceding siblings ...)
  2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 7/8] bpf: Add MEM_RDONLY for helper args that are pointers to rdonly mem Puranjay Mohan
@ 2025-06-10 14:44 ` Puranjay Mohan
  7 siblings, 0 replies; 13+ messages in thread
From: Puranjay Mohan @ 2025-06-10 14:44 UTC (permalink / raw)
  To: Greg KH
  Cc: Hao Luo, Alexei Starovoitov, Andrii Nakryiko, Puranjay Mohan,
	stable

From: Hao Luo <haoluo@google.com>

commit 9497c458c10b049438ef6e6ddda898edbc3ec6a8 upstream.

This test verifies that a ksym of non-struct can not be directly
updated.

Signed-off-by: Hao Luo <haoluo@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
[Changed ASSERT_ERR_PTR() to CHECK()]
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/bpf/20211217003152.48334-10-haoluo@google.com
Cc: stable@vger.kernel.org # 5.10.x
---
 .../selftests/bpf/prog_tests/ksyms_btf.c      | 14 +++++++++
 .../bpf/progs/test_ksyms_btf_write_check.c    | 29 +++++++++++++++++++
 2 files changed, 43 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/test_ksyms_btf_write_check.c

diff --git a/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c b/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c
index b58b775d19f3..97f38d4f6a26 100644
--- a/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/ksyms_btf.c
@@ -6,6 +6,7 @@
 #include <bpf/btf.h>
 #include "test_ksyms_btf.skel.h"
 #include "test_ksyms_btf_null_check.skel.h"
+#include "test_ksyms_btf_write_check.skel.h"
 
 static int duration;
 
@@ -81,6 +82,16 @@ static void test_null_check(void)
 	test_ksyms_btf_null_check__destroy(skel);
 }
 
+static void test_write_check(void)
+{
+	struct test_ksyms_btf_write_check *skel;
+
+	skel = test_ksyms_btf_write_check__open_and_load();
+	CHECK(skel, "skel_open", "unexpected load of a prog writing to ksym memory\n");
+
+	test_ksyms_btf_write_check__destroy(skel);
+}
+
 void test_ksyms_btf(void)
 {
 	int percpu_datasec;
@@ -106,4 +117,7 @@ void test_ksyms_btf(void)
 
 	if (test__start_subtest("null_check"))
 		test_null_check();
+
+	if (test__start_subtest("write_check"))
+		test_write_check();
 }
diff --git a/tools/testing/selftests/bpf/progs/test_ksyms_btf_write_check.c b/tools/testing/selftests/bpf/progs/test_ksyms_btf_write_check.c
new file mode 100644
index 000000000000..2180c41cd890
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_ksyms_btf_write_check.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2021 Google */
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+
+extern const int bpf_prog_active __ksym; /* int type global var. */
+
+SEC("raw_tp/sys_enter")
+int handler(const void *ctx)
+{
+	int *active;
+	__u32 cpu;
+
+	cpu = bpf_get_smp_processor_id();
+	active = (int *)bpf_per_cpu_ptr(&bpf_prog_active, cpu);
+	if (active) {
+		/* Kernel memory obtained from bpf_{per,this}_cpu_ptr
+		 * is read-only, should _not_ pass verification.
+		 */
+		/* WRITE_ONCE */
+		*(volatile int *)active = -1;
+	}
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.47.1


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

* Re: [PATCH stable linux-5.10.y v1 2/8] bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 2/8] bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL Puranjay Mohan
@ 2025-06-11 13:15   ` Sasha Levin
  0 siblings, 0 replies; 13+ messages in thread
From: Sasha Levin @ 2025-06-11 13:15 UTC (permalink / raw)
  To: stable; +Cc: Puranjay Mohan, Sasha Levin

[ Sasha's backport helper bot ]

Hi,

✅ All tests passed successfully. No issues detected.
No action required from the submitter.

The upstream commit SHA1 provided is correct: 48946bd6a5d695c50b34546864b79c1f910a33c1

WARNING: Author mismatch between patch and upstream commit:
Backport author: Puranjay Mohan<puranjay@kernel.org>
Commit author: Hao Luo<haoluo@google.com>

Status in newer kernel trees:
6.15.y | Present (exact SHA1)
6.14.y | Present (exact SHA1)
6.12.y | Present (exact SHA1)
6.6.y | Present (exact SHA1)
6.1.y | Present (exact SHA1)
5.15.y | Present (different SHA1: d58a396fa6c9)

Note: The patch differs from the upstream commit:
---
1:  48946bd6a5d69 ! 1:  4b4f340273f41 bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL
    @@ Metadata
      ## Commit message ##
         bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL
     
    +    commit 48946bd6a5d695c50b34546864b79c1f910a33c1 upstream.
    +
         We have introduced a new type to make bpf_arg composable, by
         reserving high bits of bpf_arg to represent flags of a type.
     
    @@ Commit message
     
         Signed-off-by: Hao Luo <haoluo@google.com>
         Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    +    Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
         Link: https://lore.kernel.org/bpf/20211217003152.48334-3-haoluo@google.com
    +    Cc: stable@vger.kernel.org # 5.10.x
     
      ## include/linux/bpf.h ##
     @@ include/linux/bpf.h: enum bpf_arg_type {
    @@ include/linux/bpf.h: enum bpf_arg_type {
      	ARG_CONST_ALLOC_SIZE_OR_ZERO,	/* number of allocated bytes requested */
      	ARG_PTR_TO_BTF_ID_SOCK_COMMON,	/* pointer to in-kernel sock_common or bpf-mirrored bpf_sock */
      	ARG_PTR_TO_PERCPU_BTF_ID,	/* pointer to in-kernel percpu type */
    - 	ARG_PTR_TO_FUNC,	/* pointer to a bpf program function */
    --	ARG_PTR_TO_STACK_OR_NULL,	/* pointer to stack or NULL */
    -+	ARG_PTR_TO_STACK,	/* pointer to stack */
    - 	ARG_PTR_TO_CONST_STR,	/* pointer to a null terminated read-only string */
    - 	ARG_PTR_TO_TIMER,	/* pointer to bpf_timer */
      	__BPF_ARG_TYPE_MAX,
      
     +	/* Extended arg_types. */
    @@ include/linux/bpf.h: enum bpf_arg_type {
     +	ARG_PTR_TO_CTX_OR_NULL		= PTR_MAYBE_NULL | ARG_PTR_TO_CTX,
     +	ARG_PTR_TO_SOCKET_OR_NULL	= PTR_MAYBE_NULL | ARG_PTR_TO_SOCKET,
     +	ARG_PTR_TO_ALLOC_MEM_OR_NULL	= PTR_MAYBE_NULL | ARG_PTR_TO_ALLOC_MEM,
    -+	ARG_PTR_TO_STACK_OR_NULL	= PTR_MAYBE_NULL | ARG_PTR_TO_STACK,
     +
      	/* This must be the last entry. Its purpose is to ensure the enum is
      	 * wide enough to hold the higher bits reserved for bpf_type_flag.
    @@ kernel/bpf/verifier.c: static bool arg_type_may_be_refcounted(enum bpf_arg_type
     -	       type == ARG_PTR_TO_MEM_OR_NULL ||
     -	       type == ARG_PTR_TO_CTX_OR_NULL ||
     -	       type == ARG_PTR_TO_SOCKET_OR_NULL ||
    --	       type == ARG_PTR_TO_ALLOC_MEM_OR_NULL ||
    --	       type == ARG_PTR_TO_STACK_OR_NULL;
    +-	       type == ARG_PTR_TO_ALLOC_MEM_OR_NULL;
     +	return type & PTR_MAYBE_NULL;
      }
      
      /* Determine whether the function releases some resources allocated by another
    -@@ kernel/bpf/verifier.c: static int process_timer_func(struct bpf_verifier_env *env, int regno,
    +@@ kernel/bpf/verifier.c: static int process_spin_lock(struct bpf_verifier_env *env, int regno,
      
      static bool arg_type_is_mem_ptr(enum bpf_arg_type type)
      {
    @@ kernel/bpf/verifier.c: static const struct bpf_reg_types *compatible_reg_types[_
      	[ARG_PTR_TO_INT]		= &int_ptr_types,
      	[ARG_PTR_TO_LONG]		= &int_ptr_types,
      	[ARG_PTR_TO_PERCPU_BTF_ID]	= &percpu_btf_ptr_types,
    - 	[ARG_PTR_TO_FUNC]		= &func_ptr_types,
    --	[ARG_PTR_TO_STACK_OR_NULL]	= &stack_ptr_types,
    -+	[ARG_PTR_TO_STACK]		= &stack_ptr_types,
    - 	[ARG_PTR_TO_CONST_STR]		= &const_str_ptr_types,
    - 	[ARG_PTR_TO_TIMER]		= &timer_types,
    - };
     @@ kernel/bpf/verifier.c: static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
      	const struct bpf_reg_types *compatible;
      	int i, j;
---

Results of testing on various branches:

| Branch                    | Patch Apply | Build Test |
|---------------------------|-------------|------------|
| stable/linux-5.15.y       |  Success    |  Success   |

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

* Re: [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types.
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types Puranjay Mohan
@ 2025-06-11 13:16   ` Sasha Levin
  0 siblings, 0 replies; 13+ messages in thread
From: Sasha Levin @ 2025-06-11 13:16 UTC (permalink / raw)
  To: stable; +Cc: Puranjay Mohan, Sasha Levin

[ Sasha's backport helper bot ]

Hi,

✅ All tests passed successfully. No issues detected.
No action required from the submitter.

The upstream commit SHA1 provided is correct: d639b9d13a39cf15639cbe6e8b2c43eb60148a73

WARNING: Author mismatch between patch and upstream commit:
Backport author: Puranjay Mohan<puranjay@kernel.org>
Commit author: Hao Luo<haoluo@google.com>

Status in newer kernel trees:
6.15.y | Present (exact SHA1)
6.14.y | Present (exact SHA1)
6.12.y | Present (exact SHA1)
6.6.y | Present (exact SHA1)
6.1.y | Present (exact SHA1)
5.15.y | Present (different SHA1: a76020980b9f)

Note: The patch differs from the upstream commit:
---
1:  d639b9d13a39c ! 1:  b9a913ca98c49 bpf: Introduce composable reg, ret and arg types.
    @@ Metadata
      ## Commit message ##
         bpf: Introduce composable reg, ret and arg types.
     
    +    commit d639b9d13a39cf15639cbe6e8b2c43eb60148a73 upstream.
    +
         There are some common properties shared between bpf reg, ret and arg
         values. For instance, a value may be a NULL pointer, or a pointer to
         a read-only memory. Previously, to express these properties, enumeration
    @@ Commit message
     
         Signed-off-by: Hao Luo <haoluo@google.com>
         Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    +    Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
         Link: https://lore.kernel.org/bpf/20211217003152.48334-2-haoluo@google.com
    +    Cc: stable@vger.kernel.org # 5.10.x
     
      ## include/linux/bpf.h ##
     @@ include/linux/bpf.h: bool bpf_map_meta_equal(const struct bpf_map *meta0,
    @@ include/linux/bpf.h: bool bpf_map_meta_equal(const struct bpf_map *meta0,
      enum bpf_arg_type {
      	ARG_DONTCARE = 0,	/* unused argument in helper function */
     @@ include/linux/bpf.h: enum bpf_arg_type {
    - 	ARG_PTR_TO_CONST_STR,	/* pointer to a null terminated read-only string */
    - 	ARG_PTR_TO_TIMER,	/* pointer to bpf_timer */
    + 	ARG_PTR_TO_BTF_ID_SOCK_COMMON,	/* pointer to in-kernel sock_common or bpf-mirrored bpf_sock */
    + 	ARG_PTR_TO_PERCPU_BTF_ID,	/* pointer to in-kernel percpu type */
      	__BPF_ARG_TYPE_MAX,
     +
     +	/* This must be the last entry. Its purpose is to ensure the enum is
    @@ include/linux/bpf.h: enum bpf_arg_type {
      /* type of values returned from helper functions */
      enum bpf_return_type {
     @@ include/linux/bpf.h: enum bpf_return_type {
    + 	RET_PTR_TO_BTF_ID_OR_NULL,	/* returns a pointer to a btf_id or NULL */
      	RET_PTR_TO_MEM_OR_BTF_ID_OR_NULL, /* returns a pointer to a valid memory or a btf_id or NULL */
      	RET_PTR_TO_MEM_OR_BTF_ID,	/* returns a pointer to a valid memory or a btf_id */
    - 	RET_PTR_TO_BTF_ID,		/* returns a pointer to a btf_id */
     +	__BPF_RET_TYPE_MAX,
     +
     +	/* This must be the last entry. Its purpose is to ensure the enum is
    @@ include/linux/bpf.h: enum bpf_return_type {
      /* eBPF function prototype used by verifier to allow BPF_CALLs from eBPF programs
       * to in-kernel helper functions and for adjusting imm32 field in BPF_CALL
     @@ include/linux/bpf.h: enum bpf_reg_type {
    - 	PTR_TO_FUNC,		 /* reg points to a bpf program function */
    - 	PTR_TO_MAP_KEY,		 /* reg points to a map element key */
    - 	__BPF_REG_TYPE_MAX,
    + 	PTR_TO_RDWR_BUF,	 /* reg points to a read/write buffer */
    + 	PTR_TO_RDWR_BUF_OR_NULL, /* reg points to a read/write buffer or NULL */
    + 	PTR_TO_PERCPU_BTF_ID,	 /* reg points to a percpu kernel variable */
    ++	__BPF_REG_TYPE_MAX,
     +
     +	/* This must be the last entry. Its purpose is to ensure the enum is
     +	 * wide enough to hold the higher bits reserved for bpf_type_flag.
    @@ include/linux/bpf.h: enum bpf_reg_type {
     
      ## include/linux/bpf_verifier.h ##
     @@ include/linux/bpf_verifier.h: int bpf_check_attach_target(struct bpf_verifier_log *log,
    + 			    u32 btf_id,
      			    struct bpf_attach_target_info *tgt_info);
    - void bpf_free_kfunc_btf_tab(struct bpf_kfunc_btf_tab *tab);
      
     +#define BPF_BASE_TYPE_MASK	GENMASK(BPF_BASE_TYPE_BITS - 1, 0)
     +
    @@ include/linux/bpf_verifier.h: int bpf_check_attach_target(struct bpf_verifier_lo
     +{
     +	return type & ~BPF_BASE_TYPE_MASK;
     +}
    - 
    ++
      #endif /* _LINUX_BPF_VERIFIER_H */
---

Results of testing on various branches:

| Branch                    | Patch Apply | Build Test |
|---------------------------|-------------|------------|
| stable/linux-5.10.y       |  Success    |  Success   |

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

* Re: [PATCH stable linux-5.10.y v1 4/8] bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX | PTR_MAYBE_NULL
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 4/8] bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX " Puranjay Mohan
@ 2025-06-11 13:16   ` Sasha Levin
  0 siblings, 0 replies; 13+ messages in thread
From: Sasha Levin @ 2025-06-11 13:16 UTC (permalink / raw)
  To: stable, puranjay; +Cc: Sasha Levin

[ Sasha's backport helper bot ]

Hi,

Summary of potential issues:
ℹ️ This is part 4/8 of a series
❌ Build failures detected
⚠️ Found follow-up fixes in mainline

The upstream commit SHA1 provided is correct: c25b2ae136039ffa820c26138ed4a5e5f3ab3841

WARNING: Author mismatch between patch and upstream commit:
Backport author: Puranjay Mohan<puranjay@kernel.org>
Commit author: Hao Luo<haoluo@google.com>

Status in newer kernel trees:
6.15.y | Present (exact SHA1)
6.14.y | Present (exact SHA1)
6.12.y | Present (exact SHA1)
6.6.y | Present (exact SHA1)
6.1.y | Present (exact SHA1)
5.15.y | Present (different SHA1: 8d38cde47a7e)

Found fixes commits:
45ce4b4f9009 bpf: Fix crash due to out of bounds access into reg2btf_ids.

Note: The patch differs from the upstream commit:
---
1:  c25b2ae136039 ! 1:  f9aec68f75333 bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX | PTR_MAYBE_NULL
    @@ Metadata
      ## Commit message ##
         bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX | PTR_MAYBE_NULL
     
    +    commit c25b2ae136039ffa820c26138ed4a5e5f3ab3841 upstream.
    +
         We have introduced a new type to make bpf_reg composable, by
         allocating bits in the type to represent flags.
     
    @@ Commit message
         7. PTR_TO_RDONLY_BUF_OR_NULL
         8. PTR_TO_RDWR_BUF_OR_NULL
     
    +    [puranjay: backport notes
    +     There was a reg_type_may_be_null() in adjust_ptr_min_max_vals() in
    +     5.10.x, but didn't exist in the upstream commit. This backport
    +     converted that reg_type_may_be_null() to type_may_be_null() as well.]
    +
         Signed-off-by: Hao Luo <haoluo@google.com>
         Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    +    Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
         Link: https://lore.kernel.org/r/20211217003152.48334-5-haoluo@google.com
    +    Cc: stable@vger.kernel.org # 5.10.x
     
      ## include/linux/bpf.h ##
     @@ include/linux/bpf.h: enum bpf_reg_type {
    @@ include/linux/bpf.h: enum bpf_reg_type {
      	CONST_PTR_TO_MAP,	 /* reg points to struct bpf_map */
      	PTR_TO_MAP_VALUE,	 /* reg points to map element value */
     -	PTR_TO_MAP_VALUE_OR_NULL,/* points to map elem value or NULL */
    -+	PTR_TO_MAP_KEY,		 /* reg points to a map element key */
      	PTR_TO_STACK,		 /* reg == frame_pointer + offset */
      	PTR_TO_PACKET_META,	 /* skb->data - meta_len */
      	PTR_TO_PACKET,		 /* reg points to skb->data */
    @@ include/linux/bpf.h: enum bpf_reg_type {
      	PTR_TO_RDWR_BUF,	 /* reg points to a read/write buffer */
     -	PTR_TO_RDWR_BUF_OR_NULL, /* reg points to a read/write buffer or NULL */
      	PTR_TO_PERCPU_BTF_ID,	 /* reg points to a percpu kernel variable */
    - 	PTR_TO_FUNC,		 /* reg points to a bpf program function */
    --	PTR_TO_MAP_KEY,		 /* reg points to a map element key */
      	__BPF_REG_TYPE_MAX,
      
     +	/* Extended reg_types. */
    @@ include/linux/bpf.h: enum bpf_reg_type {
     +	PTR_TO_SOCK_COMMON_OR_NULL	= PTR_MAYBE_NULL | PTR_TO_SOCK_COMMON,
     +	PTR_TO_TCP_SOCK_OR_NULL		= PTR_MAYBE_NULL | PTR_TO_TCP_SOCK,
     +	PTR_TO_BTF_ID_OR_NULL		= PTR_MAYBE_NULL | PTR_TO_BTF_ID,
    -+	PTR_TO_MEM_OR_NULL		= PTR_MAYBE_NULL | PTR_TO_MEM,
     +
      	/* This must be the last entry. Its purpose is to ensure the enum is
      	 * wide enough to hold the higher bits reserved for bpf_type_flag.
    @@ include/linux/bpf_verifier.h
      /* Liveness marks, used for registers and spilled-regs (in stack slots).
       * Read marks propagate upwards until they find a write mark; they record that
     @@ include/linux/bpf_verifier.h: struct bpf_verifier_env {
    - 	/* Same as scratched_regs but for stack slots */
    - 	u64 scratched_stack_slots;
    - 	u32 prev_log_len, prev_insn_print_len;
    + 	u32 peak_states;
    + 	/* longest register parentage chain walked for liveness marking */
    + 	u32 longest_mark_read_walk;
     +	/* buffer used in reg_type_str() to generate reg_type string */
     +	char type_str_buf[TYPE_STR_BUF_LEN];
      };
    @@ kernel/bpf/verifier.c: static bool reg_may_point_to_spin_lock(const struct bpf_r
      }
      
      static bool arg_type_may_be_refcounted(enum bpf_arg_type type)
    -@@ kernel/bpf/verifier.c: static bool is_cmpxchg_insn(const struct bpf_insn *insn)
    - 	       insn->imm == BPF_CMPXCHG;
    +@@ kernel/bpf/verifier.c: static bool is_ptr_cast_function(enum bpf_func_id func_id)
    + 		func_id == BPF_FUNC_skc_to_tcp_request_sock;
      }
      
     -/* string representation of 'enum bpf_reg_type' */
    @@ kernel/bpf/verifier.c: static bool is_cmpxchg_insn(const struct bpf_insn *insn)
     -	[PTR_TO_RDONLY_BUF_OR_NULL] = "rdonly_buf_or_null",
     -	[PTR_TO_RDWR_BUF]	= "rdwr_buf",
     -	[PTR_TO_RDWR_BUF_OR_NULL] = "rdwr_buf_or_null",
    --	[PTR_TO_FUNC]		= "func",
    --	[PTR_TO_MAP_KEY]	= "map_key",
     -};
     +/* string representation of 'enum bpf_reg_type'
     + *
    @@ kernel/bpf/verifier.c: static bool is_cmpxchg_insn(const struct bpf_insn *insn)
     +		[PTR_TO_MEM]		= "mem",
     +		[PTR_TO_RDONLY_BUF]	= "rdonly_buf",
     +		[PTR_TO_RDWR_BUF]	= "rdwr_buf",
    -+		[PTR_TO_FUNC]		= "func",
    -+		[PTR_TO_MAP_KEY]	= "map_key",
     +	};
     +
     +	if (type & PTR_MAYBE_NULL) {
    @@ kernel/bpf/verifier.c: static void print_verifier_state(struct bpf_verifier_env
     -			    t == PTR_TO_PERCPU_BTF_ID)
     +			if (base_type(t) == PTR_TO_BTF_ID ||
     +			    base_type(t) == PTR_TO_PERCPU_BTF_ID)
    - 				verbose(env, "%s", kernel_type_name(reg->btf, reg->btf_id));
    + 				verbose(env, "%s", kernel_type_name(reg->btf_id));
      			verbose(env, "(id=%d", reg->id);
      			if (reg_type_may_be_refcounted_or_null(t))
     @@ kernel/bpf/verifier.c: static void print_verifier_state(struct bpf_verifier_env *env,
    @@ kernel/bpf/verifier.c: static void print_verifier_state(struct bpf_verifier_env
      			if (type_is_pkt_pointer(t))
      				verbose(env, ",r=%d", reg->range);
     -			else if (t == CONST_PTR_TO_MAP ||
    --				 t == PTR_TO_MAP_KEY ||
     -				 t == PTR_TO_MAP_VALUE ||
     -				 t == PTR_TO_MAP_VALUE_OR_NULL)
     +			else if (base_type(t) == CONST_PTR_TO_MAP ||
    -+				 base_type(t) == PTR_TO_MAP_KEY ||
     +				 base_type(t) == PTR_TO_MAP_VALUE)
      				verbose(env, ",ks=%d,vs=%d",
      					reg->map_ptr->key_size,
    @@ kernel/bpf/verifier.c: static void print_verifier_state(struct bpf_verifier_env
      			if (t == SCALAR_VALUE && reg->precise)
      				verbose(env, "P");
      			if (t == SCALAR_VALUE && tnum_is_const(reg->var_off))
    -@@ kernel/bpf/verifier.c: static void mark_reg_known_zero(struct bpf_verifier_env *env,
    - 
    - static void mark_ptr_not_null_reg(struct bpf_reg_state *reg)
    - {
    --	switch (reg->type) {
    --	case PTR_TO_MAP_VALUE_OR_NULL: {
    -+	if (base_type(reg->type) == PTR_TO_MAP_VALUE) {
    - 		const struct bpf_map *map = reg->map_ptr;
    - 
    - 		if (map->inner_map_meta) {
    -@@ kernel/bpf/verifier.c: static void mark_ptr_not_null_reg(struct bpf_reg_state *reg)
    - 		} else {
    - 			reg->type = PTR_TO_MAP_VALUE;
    - 		}
    --		break;
    --	}
    --	case PTR_TO_SOCKET_OR_NULL:
    --		reg->type = PTR_TO_SOCKET;
    --		break;
    --	case PTR_TO_SOCK_COMMON_OR_NULL:
    --		reg->type = PTR_TO_SOCK_COMMON;
    --		break;
    --	case PTR_TO_TCP_SOCK_OR_NULL:
    --		reg->type = PTR_TO_TCP_SOCK;
    --		break;
    --	case PTR_TO_BTF_ID_OR_NULL:
    --		reg->type = PTR_TO_BTF_ID;
    --		break;
    --	case PTR_TO_MEM_OR_NULL:
    --		reg->type = PTR_TO_MEM;
    --		break;
    --	case PTR_TO_RDONLY_BUF_OR_NULL:
    --		reg->type = PTR_TO_RDONLY_BUF;
    --		break;
    --	case PTR_TO_RDWR_BUF_OR_NULL:
    --		reg->type = PTR_TO_RDWR_BUF;
    --		break;
    --	default:
    --		WARN_ONCE(1, "unknown nullable register type");
    -+		return;
    - 	}
    -+
    -+	reg->type &= ~PTR_MAYBE_NULL;
    - }
    - 
    - static bool reg_is_pkt_pointer(const struct bpf_reg_state *reg)
     @@ kernel/bpf/verifier.c: static int mark_reg_read(struct bpf_verifier_env *env,
      			break;
      		if (parent->live & REG_LIVE_DONE) {
    @@ kernel/bpf/verifier.c: static int mark_reg_read(struct bpf_verifier_env *env,
      				parent->var_off.value, parent->off);
      			return -EFAULT;
      		}
    -@@ kernel/bpf/verifier.c: static int mark_chain_precision_stack(struct bpf_verifier_env *env, int spi)
    +@@ kernel/bpf/verifier.c: static int mark_chain_precision_stack_frame(struct bpf_verifier_env *env, int fr
      
      static bool is_spillable_regtype(enum bpf_reg_type type)
      {
    @@ kernel/bpf/verifier.c: static bool is_spillable_regtype(enum bpf_reg_type type)
      	case PTR_TO_PERCPU_BTF_ID:
      	case PTR_TO_MEM:
     -	case PTR_TO_MEM_OR_NULL:
    - 	case PTR_TO_FUNC:
    - 	case PTR_TO_MAP_KEY:
      		return true;
    + 	default:
    + 		return false;
     @@ kernel/bpf/verifier.c: static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off,
      		 */
      		*reg_type = info.reg_type;
      
    --		if (*reg_type == PTR_TO_BTF_ID || *reg_type == PTR_TO_BTF_ID_OR_NULL) {
    -+		if (base_type(*reg_type) == PTR_TO_BTF_ID) {
    - 			*btf = info.btf;
    +-		if (*reg_type == PTR_TO_BTF_ID || *reg_type == PTR_TO_BTF_ID_OR_NULL)
    ++		if (base_type(*reg_type) == PTR_TO_BTF_ID)
      			*btf_id = info.btf_id;
    - 		} else {
    + 		else
    + 			env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
     @@ kernel/bpf/verifier.c: static int check_sock_access(struct bpf_verifier_env *env, int insn_idx,
      	}
      
    @@ kernel/bpf/verifier.c: static int check_mem_access(struct bpf_verifier_env *env,
      				 */
      				regs[value_regno].subreg_def = DEF_NOT_SUBREG;
     -				if (reg_type == PTR_TO_BTF_ID ||
    --				    reg_type == PTR_TO_BTF_ID_OR_NULL) {
    -+				if (base_type(reg_type) == PTR_TO_BTF_ID) {
    - 					regs[value_regno].btf = btf;
    +-				    reg_type == PTR_TO_BTF_ID_OR_NULL)
    ++				if (base_type(reg_type) == PTR_TO_BTF_ID)
      					regs[value_regno].btf_id = btf_id;
    - 				}
    + 			}
    + 			regs[value_regno].type = reg_type;
     @@ kernel/bpf/verifier.c: static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
      	} else if (type_is_sk_pointer(reg->type)) {
      		if (t == BPF_WRITE) {
    @@ kernel/bpf/verifier.c: static int check_mem_access(struct bpf_verifier_env *env,
      		return -EACCES;
      	}
      
    -@@ kernel/bpf/verifier.c: static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_i
    +@@ kernel/bpf/verifier.c: static int check_xadd(struct bpf_verifier_env *env, int insn_idx, struct bpf_ins
      	    is_sk_reg(env, insn->dst_reg)) {
    - 		verbose(env, "BPF_ATOMIC stores into R%d %s is not allowed\n",
    + 		verbose(env, "BPF_XADD stores into R%d %s is not allowed\n",
      			insn->dst_reg,
     -			reg_type_str[reg_state(env, insn->dst_reg)->type]);
     +			reg_type_str(env, reg_state(env, insn->dst_reg)->type));
    @@ kernel/bpf/verifier.c: static int check_helper_mem_access(struct bpf_verifier_en
      		return -EACCES;
      	}
      }
    -@@ kernel/bpf/verifier.c: int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
    - 	if (register_is_null(reg))
    - 		return 0;
    - 
    --	if (reg_type_may_be_null(reg->type)) {
    -+	if (type_may_be_null(reg->type)) {
    - 		/* Assuming that the register contains a value check if the memory
    - 		 * access is safe. Temporarily save and restore the register's state as
    - 		 * the conversion shouldn't be visible to a caller.
     @@ kernel/bpf/verifier.c: static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
      			goto found;
      	}
    @@ kernel/bpf/verifier.c: static int check_reg_type(struct bpf_verifier_env *env, u
      	return -EACCES;
      
      found:
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
      {
      	const struct bpf_func_proto *fn = NULL;
      	enum bpf_return_type ret_type;
     +	enum bpf_type_flag ret_flag;
      	struct bpf_reg_state *regs;
      	struct bpf_call_arg_meta meta;
    - 	int insn_idx = *insn_idx_p;
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    + 	bool changes_data;
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
      
      	/* update return register (already marked as written above) */
      	ret_type = fn->ret_type;
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
      	if (ret_type == RET_INTEGER) {
      		/* sets type to SCALAR_VALUE */
      		mark_reg_unknown(env, regs, BPF_REG_0);
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
    + 			return -EINVAL;
      		}
      		regs[BPF_REG_0].map_ptr = meta.map_ptr;
    - 		regs[BPF_REG_0].map_uid = meta.map_uid;
     -		if (type_may_be_null(ret_type)) {
     -			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE_OR_NULL;
     -		} else {
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
      		regs[BPF_REG_0].mem_size = meta.mem_size;
      	} else if (base_type(ret_type) == RET_PTR_TO_MEM_OR_BTF_ID) {
      		const struct btf_type *t;
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
      					tname, PTR_ERR(ret));
      				return -EINVAL;
      			}
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
     -				(ret_type & PTR_MAYBE_NULL) ?
     -				PTR_TO_BTF_ID_OR_NULL : PTR_TO_BTF_ID;
     +			regs[BPF_REG_0].type = PTR_TO_BTF_ID | ret_flag;
    - 			regs[BPF_REG_0].btf = meta.ret_btf;
      			regs[BPF_REG_0].btf_id = meta.ret_btf_id;
      		}
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    + 	} else if (base_type(ret_type) == RET_PTR_TO_BTF_ID) {
      		int ret_btf_id;
      
      		mark_reg_known_zero(env, regs, BPF_REG_0);
     -		regs[BPF_REG_0].type = (ret_type & PTR_MAYBE_NULL) ?
    --						     PTR_TO_BTF_ID_OR_NULL :
    --						     PTR_TO_BTF_ID;
    +-					PTR_TO_BTF_ID_OR_NULL :
    +-					PTR_TO_BTF_ID;
     +		regs[BPF_REG_0].type = PTR_TO_BTF_ID | ret_flag;
      		ret_btf_id = *fn->ret_btf_id;
      		if (ret_btf_id == 0) {
      			verbose(env, "invalid return type %u of func %s#%d\n",
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
      		return -EINVAL;
      	}
      
    @@ kernel/bpf/verifier.c: static int adjust_ptr_min_max_vals(struct bpf_verifier_en
      		/* smin_val represents the known value */
      		if (known && smin_val == 0 && opcode == BPF_ADD)
     @@ kernel/bpf/verifier.c: static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
    - 		fallthrough;
    - 	case PTR_TO_PACKET_END:
    - 	case PTR_TO_SOCKET:
    --	case PTR_TO_SOCKET_OR_NULL:
    - 	case PTR_TO_SOCK_COMMON:
    --	case PTR_TO_SOCK_COMMON_OR_NULL:
    - 	case PTR_TO_TCP_SOCK:
    --	case PTR_TO_TCP_SOCK_OR_NULL:
      	case PTR_TO_XDP_SOCK:
    + reject:
      		verbose(env, "R%d pointer arithmetic on %s prohibited\n",
     -			dst, reg_type_str[ptr_reg->type]);
     +			dst, reg_type_str(env, ptr_reg->type));
      		return -EACCES;
      	default:
    +-		if (reg_type_may_be_null(ptr_reg->type))
    ++		if (type_may_be_null(ptr_reg->type))
    + 			goto reject;
      		break;
    + 	}
     @@ kernel/bpf/verifier.c: static void mark_ptr_or_null_reg(struct bpf_func_state *state,
      				 struct bpf_reg_state *reg, u32 id,
      				 bool is_null)
    @@ kernel/bpf/verifier.c: static void mark_ptr_or_null_reg(struct bpf_func_state *s
     -	if (reg_type_may_be_null(reg->type) && reg->id == id &&
     +	if (type_may_be_null(reg->type) && reg->id == id &&
      	    !WARN_ON_ONCE(!reg->id)) {
    - 		/* Old offset (both fixed and variable parts) should
    - 		 * have been known-zero, because we don't allow pointer
    + 		if (WARN_ON_ONCE(reg->smin_value || reg->smax_value ||
    + 				 !tnum_equals_const(reg->var_off, 0) ||
    +@@ kernel/bpf/verifier.c: static void mark_ptr_or_null_reg(struct bpf_func_state *state,
    + 		}
    + 		if (is_null) {
    + 			reg->type = SCALAR_VALUE;
    +-		} else if (reg->type == PTR_TO_MAP_VALUE_OR_NULL) {
    ++			/* We don't need id and ref_obj_id from this point
    ++			 * onwards anymore, thus we should better reset it,
    ++			 * so that state pruning has chances to take effect.
    ++			 */
    ++			reg->id = 0;
    ++			reg->ref_obj_id = 0;
    ++
    ++			return;
    ++		}
    ++
    ++		if (base_type(reg->type) == PTR_TO_MAP_VALUE) {
    + 			const struct bpf_map *map = reg->map_ptr;
    + 
    + 			if (map->inner_map_meta) {
    +@@ kernel/bpf/verifier.c: static void mark_ptr_or_null_reg(struct bpf_func_state *state,
    + 			} else {
    + 				reg->type = PTR_TO_MAP_VALUE;
    + 			}
    +-		} else if (reg->type == PTR_TO_SOCKET_OR_NULL) {
    +-			reg->type = PTR_TO_SOCKET;
    +-		} else if (reg->type == PTR_TO_SOCK_COMMON_OR_NULL) {
    +-			reg->type = PTR_TO_SOCK_COMMON;
    +-		} else if (reg->type == PTR_TO_TCP_SOCK_OR_NULL) {
    +-			reg->type = PTR_TO_TCP_SOCK;
    +-		} else if (reg->type == PTR_TO_BTF_ID_OR_NULL) {
    +-			reg->type = PTR_TO_BTF_ID;
    +-		} else if (reg->type == PTR_TO_MEM_OR_NULL) {
    +-			reg->type = PTR_TO_MEM;
    +-		} else if (reg->type == PTR_TO_RDONLY_BUF_OR_NULL) {
    +-			reg->type = PTR_TO_RDONLY_BUF;
    +-		} else if (reg->type == PTR_TO_RDWR_BUF_OR_NULL) {
    +-			reg->type = PTR_TO_RDWR_BUF;
    ++		} else {
    ++			reg->type &= ~PTR_MAYBE_NULL;
    + 		}
    +-		if (is_null) {
    +-			/* We don't need id and ref_obj_id from this point
    +-			 * onwards anymore, thus we should better reset it,
    +-			 * so that state pruning has chances to take effect.
    +-			 */
    +-			reg->id = 0;
    +-			reg->ref_obj_id = 0;
    +-		} else if (!reg_may_point_to_spin_lock(reg)) {
    ++
    ++		if (!reg_may_point_to_spin_lock(reg)) {
    + 			/* For not-NULL ptr, reg->ref_obj_id will be reset
    + 			 * in release_reference().
    + 			 *
     @@ kernel/bpf/verifier.c: static int check_cond_jmp_op(struct bpf_verifier_env *env,
      	 */
      	if (!is_jmp32 && BPF_SRC(insn->code) == BPF_K &&
    @@ kernel/bpf/verifier.c: static int check_cond_jmp_op(struct bpf_verifier_env *env
      		/* Mark all identical registers in each branch as either
      		 * safe or unknown depending R == 0 or R != 0 conditional.
      		 */
    -@@ kernel/bpf/verifier.c: static int check_return_code(struct bpf_verifier_env *env)
    - 		/* enforce return zero from async callbacks like timer */
    - 		if (reg->type != SCALAR_VALUE) {
    - 			verbose(env, "In async callback the register R0 is not a known value (%s)\n",
    --				reg_type_str[reg->type]);
    -+				reg_type_str(env, reg->type));
    - 			return -EINVAL;
    - 		}
    - 
     @@ kernel/bpf/verifier.c: static int check_return_code(struct bpf_verifier_env *env)
      	if (is_subprog) {
      		if (reg->type != SCALAR_VALUE) {
    @@ kernel/bpf/verifier.c: static bool regsafe(struct bpf_verifier_env *env, struct
      		if (env->explore_alu_limits)
      			return false;
     @@ kernel/bpf/verifier.c: static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
    + 			return false;
      		}
    - 	case PTR_TO_MAP_KEY:
      	case PTR_TO_MAP_VALUE:
     +		/* a PTR_TO_MAP_VALUE could be safe to use as a
     +		 * PTR_TO_MAP_VALUE_OR_NULL into the same map.
---

NOTE: These results are for this patch alone. Full series testing will be
performed when all parts are received.

Results of testing on various branches:

| Branch                    | Patch Apply | Build Test |
|---------------------------|-------------|------------|
| stable/linux-5.15.y       |  Success    |  Failed    |

Build Errors:
Build error:
    kernel/trace/trace_events_synth.c: In function 'synth_event_reg':
    kernel/trace/trace_events_synth.c:847:9: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
      847 |         int ret = trace_event_reg(call, type, data);
          |         ^~~
    In file included from ./include/linux/kernel.h:15,
                     from ./include/linux/list.h:9,
                     from ./include/linux/module.h:12,
                     from net/ipv4/inet_hashtables.c:12:
    net/ipv4/inet_hashtables.c: In function 'inet_ehash_locks_alloc':
    ./include/linux/minmax.h:20:35: warning: comparison of distinct pointer types lacks a cast [-Wcompare-distinct-pointer-types]
       20 |         (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
          |                                   ^~
    ./include/linux/minmax.h:26:18: note: in expansion of macro '__typecheck'
       26 |                 (__typecheck(x, y) && __no_side_effects(x, y))
          |                  ^~~~~~~~~~~
    ./include/linux/minmax.h:36:31: note: in expansion of macro '__safe_cmp'
       36 |         __builtin_choose_expr(__safe_cmp(x, y), \
          |                               ^~~~~~~~~~
    ./include/linux/minmax.h:52:25: note: in expansion of macro '__careful_cmp'
       52 | #define max(x, y)       __careful_cmp(x, y, >)
          |                         ^~~~~~~~~~~~~
    net/ipv4/inet_hashtables.c:946:19: note: in expansion of macro 'max'
      946 |         nblocks = max(nblocks, num_online_nodes() * PAGE_SIZE / locksz);
          |                   ^~~
    In file included from ./include/linux/kernel.h:15,
                     from ./include/linux/list.h:9,
                     from ./include/linux/kobject.h:19,
                     from ./include/linux/of.h:17,
                     from ./include/linux/clk-provider.h:9,
                     from drivers/clk/qcom/clk-rpmh.c:6:
    drivers/clk/qcom/clk-rpmh.c: In function 'clk_rpmh_bcm_send_cmd':
    ./include/linux/minmax.h:20:35: warning: comparison of distinct pointer types lacks a cast [-Wcompare-distinct-pointer-types]
       20 |         (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
          |                                   ^~
    ./include/linux/minmax.h:26:18: note: in expansion of macro '__typecheck'
       26 |                 (__typecheck(x, y) && __no_side_effects(x, y))
          |                  ^~~~~~~~~~~
    ./include/linux/minmax.h:36:31: note: in expansion of macro '__safe_cmp'
       36 |         __builtin_choose_expr(__safe_cmp(x, y), \
          |                               ^~~~~~~~~~
    ./include/linux/minmax.h:45:25: note: in expansion of macro '__careful_cmp'
       45 | #define min(x, y)       __careful_cmp(x, y, <)
          |                         ^~~~~~~~~~~~~
    drivers/clk/qcom/clk-rpmh.c:273:21: note: in expansion of macro 'min'
      273 |         cmd_state = min(cmd_state, BCM_TCS_CMD_VOTE_MASK);
          |                     ^~~
    drivers/firmware/efi/mokvar-table.c: In function 'efi_mokvar_table_init':
    drivers/firmware/efi/mokvar-table.c:107:23: warning: unused variable 'size' [-Wunused-variable]
      107 |         unsigned long size;
          |                       ^~~~
    In file included from <command-line>:
    drivers/net/ethernet/netronome/nfp/bpf/verifier.c: In function 'nfp_bpf_check_helper_call':
    ././include/linux/compiler_types.h:309:45: error: call to '__compiletime_assert_1821' declared with attribute error: BUILD_BUG_ON failed: NFP_BPF_SCALAR_VALUE != SCALAR_VALUE || NFP_BPF_MAP_VALUE != PTR_TO_MAP_VALUE || NFP_BPF_STACK != PTR_TO_STACK || NFP_BPF_PACKET_DATA != PTR_TO_PACKET
      309 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
          |                                             ^
    ././include/linux/compiler_types.h:290:25: note: in definition of macro '__compiletime_assert'
      290 |                         prefix ## suffix();                             \
          |                         ^~~~~~
    ././include/linux/compiler_types.h:309:9: note: in expansion of macro '_compiletime_assert'
      309 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
          |         ^~~~~~~~~~~~~~~~~~~
    ./include/linux/build_bug.h:39:37: note: in expansion of macro 'compiletime_assert'
       39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
          |                                     ^~~~~~~~~~~~~~~~~~
    ./include/linux/build_bug.h:50:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
       50 |         BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
          |         ^~~~~~~~~~~~~~~~
    drivers/net/ethernet/netronome/nfp/bpf/verifier.c:234:17: note: in expansion of macro 'BUILD_BUG_ON'
      234 |                 BUILD_BUG_ON(NFP_BPF_SCALAR_VALUE != SCALAR_VALUE ||
          |                 ^~~~~~~~~~~~
    make[5]: *** [scripts/Makefile.build:286: drivers/net/ethernet/netronome/nfp/bpf/verifier.o] Error 1
    make[5]: Target '__build' not remade because of errors.
    make[4]: *** [scripts/Makefile.build:503: drivers/net/ethernet/netronome/nfp] Error 2
    make[4]: Target '__build' not remade because of errors.
    make[3]: *** [scripts/Makefile.build:503: drivers/net/ethernet/netronome] Error 2
    make[3]: Target '__build' not remade because of errors.
    make[2]: *** [scripts/Makefile.build:503: drivers/net/ethernet] Error 2
    make[2]: Target '__build' not remade because of errors.
    make[1]: *** [scripts/Makefile.build:503: drivers/net] Error 2
    make[1]: Target '__build' not remade because of errors.
    make: *** [Makefile:1852: drivers] Error 2
    make: Target '__all' not remade because of errors.

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

* Re: [PATCH stable linux-5.10.y v1 3/8] bpf: Replace RET_XXX_OR_NULL with RET_XXX | PTR_MAYBE_NULL
  2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 3/8] bpf: Replace RET_XXX_OR_NULL with RET_XXX " Puranjay Mohan
@ 2025-06-11 13:24   ` Sasha Levin
  0 siblings, 0 replies; 13+ messages in thread
From: Sasha Levin @ 2025-06-11 13:24 UTC (permalink / raw)
  To: stable; +Cc: Puranjay Mohan, Sasha Levin

[ Sasha's backport helper bot ]

Hi,

✅ All tests passed successfully. No issues detected.
No action required from the submitter.

The upstream commit SHA1 provided is correct: 3c4807322660d4290ac9062c034aed6b87243861

WARNING: Author mismatch between patch and upstream commit:
Backport author: Puranjay Mohan<puranjay@kernel.org>
Commit author: Hao Luo<haoluo@google.com>

Status in newer kernel trees:
6.15.y | Present (exact SHA1)
6.14.y | Present (exact SHA1)
6.12.y | Present (exact SHA1)
6.6.y | Present (exact SHA1)
6.1.y | Present (exact SHA1)
5.15.y | Present (different SHA1: 3c141c82b958)

Note: The patch differs from the upstream commit:
---
1:  3c4807322660d ! 1:  687af1d867c44 bpf: Replace RET_XXX_OR_NULL with RET_XXX | PTR_MAYBE_NULL
    @@ Metadata
      ## Commit message ##
         bpf: Replace RET_XXX_OR_NULL with RET_XXX | PTR_MAYBE_NULL
     
    +    commit 3c4807322660d4290ac9062c034aed6b87243861 upstream.
    +
         We have introduced a new type to make bpf_ret composable, by
         reserving high bits to represent flags.
     
    @@ Commit message
     
         Signed-off-by: Hao Luo <haoluo@google.com>
         Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    +    Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
         Link: https://lore.kernel.org/bpf/20211217003152.48334-4-haoluo@google.com
    +    Cc: stable@vger.kernel.org # 5.10.x
     
      ## include/linux/bpf.h ##
     @@ include/linux/bpf.h: enum bpf_return_type {
    @@ include/linux/bpf.h: enum bpf_return_type {
     +	RET_PTR_TO_SOCK_COMMON,		/* returns a pointer to a sock_common */
     +	RET_PTR_TO_ALLOC_MEM,		/* returns a pointer to dynamically allocated memory */
      	RET_PTR_TO_MEM_OR_BTF_ID,	/* returns a pointer to a valid memory or a btf_id */
    - 	RET_PTR_TO_BTF_ID,		/* returns a pointer to a btf_id */
    ++	RET_PTR_TO_BTF_ID,		/* returns a pointer to a btf_id */
      	__BPF_RET_TYPE_MAX,
      
     +	/* Extended ret_types. */
    @@ kernel/bpf/helpers.c: BPF_CALL_2(bpf_per_cpu_ptr, const void *, ptr, u32, cpu)
      };
     
      ## kernel/bpf/verifier.c ##
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    - 			     int *insn_idx_p)
    +@@ kernel/bpf/verifier.c: static int check_reference_leak(struct bpf_verifier_env *env)
    + static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn_idx)
      {
      	const struct bpf_func_proto *fn = NULL;
     +	enum bpf_return_type ret_type;
      	struct bpf_reg_state *regs;
      	struct bpf_call_arg_meta meta;
    - 	int insn_idx = *insn_idx_p;
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    + 	bool changes_data;
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
      	regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
      
      	/* update return register (already marked as written above) */
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
      		/* There is no offset yet applied, variable or fixed */
      		mark_reg_known_zero(env, regs, BPF_REG_0);
      		/* remember map_ptr, so that check_map_access()
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
    + 			return -EINVAL;
      		}
      		regs[BPF_REG_0].map_ptr = meta.map_ptr;
    - 		regs[BPF_REG_0].map_uid = meta.map_uid;
     -		if (fn->ret_type == RET_PTR_TO_MAP_VALUE) {
     +		if (type_may_be_null(ret_type)) {
     +			regs[BPF_REG_0].type = PTR_TO_MAP_VALUE_OR_NULL;
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
      		const struct btf_type *t;
      
      		mark_reg_known_zero(env, regs, BPF_REG_0);
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    +@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
      				return -EINVAL;
      			}
      			regs[BPF_REG_0].type =
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
     -				PTR_TO_BTF_ID : PTR_TO_BTF_ID_OR_NULL;
     +				(ret_type & PTR_MAYBE_NULL) ?
     +				PTR_TO_BTF_ID_OR_NULL : PTR_TO_BTF_ID;
    - 			regs[BPF_REG_0].btf = meta.ret_btf;
      			regs[BPF_REG_0].btf_id = meta.ret_btf_id;
      		}
    --	} else if (fn->ret_type == RET_PTR_TO_BTF_ID_OR_NULL ||
    --		   fn->ret_type == RET_PTR_TO_BTF_ID) {
    +-	} else if (fn->ret_type == RET_PTR_TO_BTF_ID_OR_NULL) {
     +	} else if (base_type(ret_type) == RET_PTR_TO_BTF_ID) {
      		int ret_btf_id;
      
      		mark_reg_known_zero(env, regs, BPF_REG_0);
    --		regs[BPF_REG_0].type = fn->ret_type == RET_PTR_TO_BTF_ID ?
    --						     PTR_TO_BTF_ID :
    --						     PTR_TO_BTF_ID_OR_NULL;
    +-		regs[BPF_REG_0].type = PTR_TO_BTF_ID_OR_NULL;
     +		regs[BPF_REG_0].type = (ret_type & PTR_MAYBE_NULL) ?
    -+						     PTR_TO_BTF_ID_OR_NULL :
    -+						     PTR_TO_BTF_ID;
    ++					PTR_TO_BTF_ID_OR_NULL :
    ++					PTR_TO_BTF_ID;
      		ret_btf_id = *fn->ret_btf_id;
      		if (ret_btf_id == 0) {
     -			verbose(env, "invalid return type %d of func %s#%d\n",
    @@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env
     +				func_id);
      			return -EINVAL;
      		}
    - 		/* current BPF helper definitions are only coming from
    -@@ kernel/bpf/verifier.c: static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
    - 		regs[BPF_REG_0].btf = btf_vmlinux;
      		regs[BPF_REG_0].btf_id = ret_btf_id;
      	} else {
     -		verbose(env, "unknown return type %d of func %s#%d\n",
---

Results of testing on various branches:

| Branch                    | Patch Apply | Build Test |
|---------------------------|-------------|------------|
| stable/linux-5.15.y       |  Success    |  Success   |

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

end of thread, other threads:[~2025-06-11 13:24 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-10 14:43 [PATCH stable linux-5.10.y v1 0/8] Fix bpf mem read/write vulnerability Puranjay Mohan
2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 1/8] bpf: Introduce composable reg, ret and arg types Puranjay Mohan
2025-06-11 13:16   ` Sasha Levin
2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 2/8] bpf: Replace ARG_XXX_OR_NULL with ARG_XXX | PTR_MAYBE_NULL Puranjay Mohan
2025-06-11 13:15   ` Sasha Levin
2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 3/8] bpf: Replace RET_XXX_OR_NULL with RET_XXX " Puranjay Mohan
2025-06-11 13:24   ` Sasha Levin
2025-06-10 14:43 ` [PATCH stable linux-5.10.y v1 4/8] bpf: Replace PTR_TO_XXX_OR_NULL with PTR_TO_XXX " Puranjay Mohan
2025-06-11 13:16   ` Sasha Levin
2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 5/8] bpf: Introduce MEM_RDONLY flag Puranjay Mohan
2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 6/8] bpf: Make per_cpu_ptr return rdonly PTR_TO_MEM Puranjay Mohan
2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 7/8] bpf: Add MEM_RDONLY for helper args that are pointers to rdonly mem Puranjay Mohan
2025-06-10 14:44 ` [PATCH stable linux-5.10.y v1 8/8] bpf/selftests: Test PTR_TO_RDONLY_MEM Puranjay Mohan

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