bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM
@ 2025-07-04 23:03 Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 1/8] bpf: make makr_btf_ld_reg return error for unexpected reg types Eduard Zingerman
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman

This patch set introduces two usability enhancements leveraging
untrusted pointers to mem:
- When reading a pointer field from a PTR_TO_BTF_ID, the resulting
  value is now assumed to be PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED
  instead of SCALAR_VALUE, provided the pointer points to a primitive
  type.
- __arg_untrusted attribute for global function parameters,
  allowed for pointer arguments of both structural and primitive
  types:
  - For structural types, the attribute produces
    PTR_TO_BTF_ID|PTR_UNTRUSTED.
  - For primitive types, it yields
    PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED.

Here are examples enabled by the series:

  struct foo {
    int *arr;
  };
  ...
  p = bpf_core_cast(..., struct foo);
  bpf_for(i, 0, ...) {
    ... p->arr[i] ...       // load at any offset is allowed
  }

  int memcmp(void *a __arg_untrusted, void *b __arg_untrusted, size_t n) {
    bpf_for(i, 0, n)
      if (a[i] - b[i])      // load at any offset is allowed
        return ...;
    return 0;
  }

The patch-set was inspired by Anrii's series [1]. The goal of that
series was to define a generic global glob_match function, capable to
accept any pointer type:

  __weak int glob_match(const char *pat, const char *str);

  char filename_glob[32];

  void foo(...) {
    ...
    task = bpf_get_current_task_btf();
    filename = task->mm->exe_file->f_path.dentry->d_name.name;
    ... match_glob(filename_glob,  // pointer to map value
                   filename) ...   // scalar
  }

At the moment, there is no straightforward way to express such a
function. This patch-set makes it possible to define it as follows:

  __weak int glob_match(const char *pat __arg_untrusted,
                        const char *str __arg_untrusted);

[1] https://github.com/anakryiko/linux/tree/bpf-mem-cast

Changelog:
v1: https://lore.kernel.org/bpf/20250702224209.3300396-1-eddyz87@gmail.com/
v1 -> v2:
- Added safety check in btf_prepare_func_args() to ensure that only
  struct or primitive types could be combined with __arg_untrusted
  (Alexei).
- Removed unnecessary 'continue' btf_check_func_arg_match() (Alexei).
- Added test cases for __arg_untrusted pointers to enum and
  __arg_untrusted combined with non-kernel type (Kumar).
- Added acks from Kumar.

Eduard Zingerman (8):
  bpf: make makr_btf_ld_reg return error for unexpected reg types
  bpf: rdonly_untrusted_mem for btf id walk pointer leafs
  selftests/bpf: ptr_to_btf_id struct walk ending with primitive pointer
  bpf: attribute __arg_untrusted for global function parameters
  libbpf: __arg_untrusted in bpf_helpers.h
  selftests/bpf: test cases for __arg_untrusted
  bpf: support for void/primitive __arg_untrusted global func params
  selftests/bpf: tests for __arg_untrusted void * global func params

 include/linux/btf.h                           |   1 +
 kernel/bpf/btf.c                              |  57 +++++++-
 kernel/bpf/verifier.c                         |  77 +++++++---
 tools/lib/bpf/bpf_helpers.h                   |   1 +
 .../selftests/bpf/prog_tests/linked_list.c    |   2 +-
 .../bpf/progs/mem_rdonly_untrusted.c          |  31 ++++
 .../bpf/progs/verifier_global_ptr_args.c      | 134 ++++++++++++++++++
 7 files changed, 274 insertions(+), 29 deletions(-)

-- 
2.49.0


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

* [PATCH bpf-next v2 1/8] bpf: make makr_btf_ld_reg return error for unexpected reg types
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 2/8] bpf: rdonly_untrusted_mem for btf id walk pointer leafs Eduard Zingerman
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Kumar Kartikeya Dwivedi

Non-functional change:
mark_btf_ld_reg() expects 'reg_type' parameter to be either
SCALAR_VALUE or PTR_TO_BTF_ID. Next commit expands this set, so update
this function to fail if unexpected type is passed. Also update
callers to propagate the error.

Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 kernel/bpf/verifier.c | 59 ++++++++++++++++++++++++++++---------------
 1 file changed, 39 insertions(+), 20 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 0f6cc2275695..9e8328f40b88 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2796,22 +2796,28 @@ static void mark_reg_not_init(struct bpf_verifier_env *env,
 	__mark_reg_not_init(env, regs + regno);
 }
 
-static void mark_btf_ld_reg(struct bpf_verifier_env *env,
-			    struct bpf_reg_state *regs, u32 regno,
-			    enum bpf_reg_type reg_type,
-			    struct btf *btf, u32 btf_id,
-			    enum bpf_type_flag flag)
+static int mark_btf_ld_reg(struct bpf_verifier_env *env,
+			   struct bpf_reg_state *regs, u32 regno,
+			   enum bpf_reg_type reg_type,
+			   struct btf *btf, u32 btf_id,
+			   enum bpf_type_flag flag)
 {
-	if (reg_type == SCALAR_VALUE) {
+	switch (reg_type) {
+	case SCALAR_VALUE:
 		mark_reg_unknown(env, regs, regno);
-		return;
+		return 0;
+	case PTR_TO_BTF_ID:
+		mark_reg_known_zero(env, regs, regno);
+		regs[regno].type = PTR_TO_BTF_ID | flag;
+		regs[regno].btf = btf;
+		regs[regno].btf_id = btf_id;
+		if (type_may_be_null(flag))
+			regs[regno].id = ++env->id_gen;
+		return 0;
+	default:
+		verifier_bug(env, "unexpected reg_type %d in %s\n", reg_type, __func__);
+		return -EFAULT;
 	}
-	mark_reg_known_zero(env, regs, regno);
-	regs[regno].type = PTR_TO_BTF_ID | flag;
-	regs[regno].btf = btf;
-	regs[regno].btf_id = btf_id;
-	if (type_may_be_null(flag))
-		regs[regno].id = ++env->id_gen;
 }
 
 #define DEF_NOT_SUBREG	(0)
@@ -5965,6 +5971,7 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
 	struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
 	int class = BPF_CLASS(insn->code);
 	struct bpf_reg_state *val_reg;
+	int ret;
 
 	/* Things we already checked for in check_map_access and caller:
 	 *  - Reject cases where variable offset may touch kptr
@@ -5998,8 +6005,11 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
 		/* We can simply mark the value_regno receiving the pointer
 		 * value from map as PTR_TO_BTF_ID, with the correct type.
 		 */
-		mark_btf_ld_reg(env, cur_regs(env), value_regno, PTR_TO_BTF_ID, kptr_field->kptr.btf,
-				kptr_field->kptr.btf_id, btf_ld_kptr_type(env, kptr_field));
+		ret = mark_btf_ld_reg(env, cur_regs(env), value_regno, PTR_TO_BTF_ID,
+				      kptr_field->kptr.btf, kptr_field->kptr.btf_id,
+				      btf_ld_kptr_type(env, kptr_field));
+		if (ret < 0)
+			return ret;
 	} else if (class == BPF_STX) {
 		val_reg = reg_state(env, value_regno);
 		if (!register_is_null(val_reg) &&
@@ -7298,8 +7308,11 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
 		clear_trusted_flags(&flag);
 	}
 
-	if (atype == BPF_READ && value_regno >= 0)
-		mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
+	if (atype == BPF_READ && value_regno >= 0) {
+		ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
+		if (ret < 0)
+			return ret;
+	}
 
 	return 0;
 }
@@ -7353,13 +7366,19 @@ static int check_ptr_to_map_access(struct bpf_verifier_env *env,
 
 	/* Simulate access to a PTR_TO_BTF_ID */
 	memset(&map_reg, 0, sizeof(map_reg));
-	mark_btf_ld_reg(env, &map_reg, 0, PTR_TO_BTF_ID, btf_vmlinux, *map->ops->map_btf_id, 0);
+	ret = mark_btf_ld_reg(env, &map_reg, 0, PTR_TO_BTF_ID,
+			      btf_vmlinux, *map->ops->map_btf_id, 0);
+	if (ret < 0)
+		return ret;
 	ret = btf_struct_access(&env->log, &map_reg, off, size, atype, &btf_id, &flag, NULL);
 	if (ret < 0)
 		return ret;
 
-	if (value_regno >= 0)
-		mark_btf_ld_reg(env, regs, value_regno, ret, btf_vmlinux, btf_id, flag);
+	if (value_regno >= 0) {
+		ret = mark_btf_ld_reg(env, regs, value_regno, ret, btf_vmlinux, btf_id, flag);
+		if (ret < 0)
+			return ret;
+	}
 
 	return 0;
 }
-- 
2.49.0


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

* [PATCH bpf-next v2 2/8] bpf: rdonly_untrusted_mem for btf id walk pointer leafs
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 1/8] bpf: make makr_btf_ld_reg return error for unexpected reg types Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 3/8] selftests/bpf: ptr_to_btf_id struct walk ending with primitive pointer Eduard Zingerman
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Alexei Starovoitov, Kumar Kartikeya Dwivedi

When processing a load from a PTR_TO_BTF_ID, the verifier calculates
the type of the loaded structure field based on the load offset.
For example, given the following types:

  struct foo {
    struct foo *a;
    int *b;
  } *p;

The verifier would calculate the type of `p->a` as a pointer to
`struct foo`. However, the type of `p->b` is currently calculated as a
SCALAR_VALUE.

This commit updates the logic for processing PTR_TO_BTF_ID to instead
calculate the type of p->b as PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED.
This change allows further dereferencing of such pointers (using probe
memory instructions).

Suggested-by: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 kernel/bpf/btf.c                                     | 6 ++++++
 kernel/bpf/verifier.c                                | 5 +++++
 tools/testing/selftests/bpf/prog_tests/linked_list.c | 2 +-
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 05fd64a371af..b3c8a95d38fb 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6915,6 +6915,7 @@ enum bpf_struct_walk_result {
 	/* < 0 error */
 	WALK_SCALAR = 0,
 	WALK_PTR,
+	WALK_PTR_UNTRUSTED,
 	WALK_STRUCT,
 };
 
@@ -7156,6 +7157,8 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf,
 					*field_name = mname;
 				return WALK_PTR;
 			}
+
+			return WALK_PTR_UNTRUSTED;
 		}
 
 		/* Allow more flexible access within an int as long as
@@ -7228,6 +7231,9 @@ int btf_struct_access(struct bpf_verifier_log *log,
 			*next_btf_id = id;
 			*flag = tmp_flag;
 			return PTR_TO_BTF_ID;
+		case WALK_PTR_UNTRUSTED:
+			*flag = MEM_RDONLY | PTR_UNTRUSTED;
+			return PTR_TO_MEM;
 		case WALK_SCALAR:
 			return SCALAR_VALUE;
 		case WALK_STRUCT:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 9e8328f40b88..87ab00b40d9f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2814,6 +2814,11 @@ static int mark_btf_ld_reg(struct bpf_verifier_env *env,
 		if (type_may_be_null(flag))
 			regs[regno].id = ++env->id_gen;
 		return 0;
+	case PTR_TO_MEM:
+		mark_reg_known_zero(env, regs, regno);
+		regs[regno].type = PTR_TO_MEM | flag;
+		regs[regno].mem_size = 0;
+		return 0;
 	default:
 		verifier_bug(env, "unexpected reg_type %d in %s\n", reg_type, __func__);
 		return -EFAULT;
diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c
index 5266c7022863..14c5a7ef0e87 100644
--- a/tools/testing/selftests/bpf/prog_tests/linked_list.c
+++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c
@@ -72,7 +72,7 @@ static struct {
 	{ "new_null_ret", "R0 invalid mem access 'ptr_or_null_'" },
 	{ "obj_new_acq", "Unreleased reference id=" },
 	{ "use_after_drop", "invalid mem access 'scalar'" },
-	{ "ptr_walk_scalar", "type=scalar expected=percpu_ptr_" },
+	{ "ptr_walk_scalar", "type=rdonly_untrusted_mem expected=percpu_ptr_" },
 	{ "direct_read_lock", "direct access to bpf_spin_lock is disallowed" },
 	{ "direct_write_lock", "direct access to bpf_spin_lock is disallowed" },
 	{ "direct_read_head", "direct access to bpf_list_head is disallowed" },
-- 
2.49.0


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

* [PATCH bpf-next v2 3/8] selftests/bpf: ptr_to_btf_id struct walk ending with primitive pointer
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 1/8] bpf: make makr_btf_ld_reg return error for unexpected reg types Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 2/8] bpf: rdonly_untrusted_mem for btf id walk pointer leafs Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 4/8] bpf: attribute __arg_untrusted for global function parameters Eduard Zingerman
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Kumar Kartikeya Dwivedi

Validate that reading a PTR_TO_BTF_ID field produces a value of type
PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED, if field is a pointer to a
primitive type.

Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 .../bpf/progs/mem_rdonly_untrusted.c          | 31 +++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
index 8185130ede95..4f94c971ae86 100644
--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
@@ -5,6 +5,37 @@
 #include "bpf_misc.h"
 #include "../test_kmods/bpf_testmod_kfunc.h"
 
+SEC("tp_btf/sys_enter")
+__success
+__log_level(2)
+__msg("r8 = *(u64 *)(r7 +0)          ; R7_w=ptr_nameidata(off={{[0-9]+}}) R8_w=rdonly_untrusted_mem(sz=0)")
+__msg("r9 = *(u8 *)(r8 +0)           ; R8_w=rdonly_untrusted_mem(sz=0) R9_w=scalar")
+int btf_id_to_ptr_mem(void *ctx)
+{
+	struct task_struct *task;
+	struct nameidata *idata;
+	u64 ret, off;
+
+	task = bpf_get_current_task_btf();
+	idata = task->nameidata;
+	off = bpf_core_field_offset(struct nameidata, pathname);
+	/*
+	 * asm block to have reliable match target for __msg, equivalent of:
+	 *   ret = task->nameidata->pathname[0];
+	 */
+	asm volatile (
+	"r7 = %[idata];"
+	"r7 += %[off];"
+	"r8 = *(u64 *)(r7 + 0);"
+	"r9 = *(u8 *)(r8 + 0);"
+	"%[ret] = r9;"
+	: [ret]"=r"(ret)
+	: [idata]"r"(idata),
+	  [off]"r"(off)
+	: "r7", "r8", "r9");
+	return ret;
+}
+
 SEC("socket")
 __success
 __retval(0)
-- 
2.49.0


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

* [PATCH bpf-next v2 4/8] bpf: attribute __arg_untrusted for global function parameters
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
                   ` (2 preceding siblings ...)
  2025-07-04 23:03 ` [PATCH bpf-next v2 3/8] selftests/bpf: ptr_to_btf_id struct walk ending with primitive pointer Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 5/8] libbpf: __arg_untrusted in bpf_helpers.h Eduard Zingerman
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Alexei Starovoitov, Kumar Kartikeya Dwivedi

Add support for PTR_TO_BTF_ID | PTR_UNTRUSTED global function
parameters. Anything is allowed to pass to such parameters, as these
are read-only and probe read instructions would protect against
invalid memory access.

Suggested-by: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 kernel/bpf/btf.c      | 38 +++++++++++++++++++++++++++++++++-----
 kernel/bpf/verifier.c |  6 ++++++
 2 files changed, 39 insertions(+), 5 deletions(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index b3c8a95d38fb..e0414d9f5e29 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7646,11 +7646,12 @@ static int btf_get_ptr_to_btf_id(struct bpf_verifier_log *log, int arg_idx,
 }
 
 enum btf_arg_tag {
-	ARG_TAG_CTX	 = BIT_ULL(0),
-	ARG_TAG_NONNULL  = BIT_ULL(1),
-	ARG_TAG_TRUSTED  = BIT_ULL(2),
-	ARG_TAG_NULLABLE = BIT_ULL(3),
-	ARG_TAG_ARENA	 = BIT_ULL(4),
+	ARG_TAG_CTX	  = BIT_ULL(0),
+	ARG_TAG_NONNULL   = BIT_ULL(1),
+	ARG_TAG_TRUSTED   = BIT_ULL(2),
+	ARG_TAG_UNTRUSTED = BIT_ULL(3),
+	ARG_TAG_NULLABLE  = BIT_ULL(4),
+	ARG_TAG_ARENA	  = BIT_ULL(5),
 };
 
 /* Process BTF of a function to produce high-level expectation of function
@@ -7758,6 +7759,8 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 				tags |= ARG_TAG_CTX;
 			} else if (strcmp(tag, "trusted") == 0) {
 				tags |= ARG_TAG_TRUSTED;
+			} else if (strcmp(tag, "untrusted") == 0) {
+				tags |= ARG_TAG_UNTRUSTED;
 			} else if (strcmp(tag, "nonnull") == 0) {
 				tags |= ARG_TAG_NONNULL;
 			} else if (strcmp(tag, "nullable") == 0) {
@@ -7818,6 +7821,31 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 			sub->args[i].btf_id = kern_type_id;
 			continue;
 		}
+		if (tags & ARG_TAG_UNTRUSTED) {
+			struct btf *vmlinux_btf;
+			int kern_type_id;
+
+			if (tags & ~ARG_TAG_UNTRUSTED) {
+				bpf_log(log, "arg#%d untrusted cannot be combined with any other tags\n", i);
+				return -EINVAL;
+			}
+
+			kern_type_id = btf_get_ptr_to_btf_id(log, i, btf, t);
+			if (kern_type_id < 0)
+				return kern_type_id;
+
+			vmlinux_btf = bpf_get_btf_vmlinux();
+			ref_t = btf_type_by_id(vmlinux_btf, kern_type_id);
+			if (!btf_type_is_struct(ref_t)) {
+				tname = __btf_name_by_offset(vmlinux_btf, t->name_off);
+				bpf_log(log, "arg#%d has type %s '%s', but only struct types are allowed\n",
+					i, btf_type_str(ref_t), tname);
+				return -EINVAL;
+			}
+			sub->args[i].arg_type = ARG_PTR_TO_BTF_ID | PTR_UNTRUSTED;
+			sub->args[i].btf_id = kern_type_id;
+			continue;
+		}
 		if (tags & ARG_TAG_ARENA) {
 			if (tags & ~ARG_TAG_ARENA) {
 				bpf_log(log, "arg#%d arena cannot be combined with any other tags\n", i);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 87ab00b40d9f..7af902c3ecc3 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10437,6 +10437,12 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 				bpf_log(log, "R%d is not a scalar\n", regno);
 				return -EINVAL;
 			}
+		} else if (arg->arg_type & PTR_UNTRUSTED) {
+			/*
+			 * Anything is allowed for untrusted arguments, as these are
+			 * read-only and probe read instructions would protect against
+			 * invalid memory access.
+			 */
 		} else if (arg->arg_type == ARG_PTR_TO_CTX) {
 			ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
 			if (ret < 0)
-- 
2.49.0


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

* [PATCH bpf-next v2 5/8] libbpf: __arg_untrusted in bpf_helpers.h
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
                   ` (3 preceding siblings ...)
  2025-07-04 23:03 ` [PATCH bpf-next v2 4/8] bpf: attribute __arg_untrusted for global function parameters Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 6/8] selftests/bpf: test cases for __arg_untrusted Eduard Zingerman
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Kumar Kartikeya Dwivedi

Make btf_decl_tag("arg:untrusted") available for libbpf users via
macro. Makes the following usage possible:

  void foo(struct bar *p __arg_untrusted) { ... }
  void bar(struct foo *p __arg_trusted) {
    ...
    foo(p->buz->bar); // buz derefrence looses __trusted
    ...
  }

Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 tools/lib/bpf/bpf_helpers.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
index 76b127a9f24d..80c028540656 100644
--- a/tools/lib/bpf/bpf_helpers.h
+++ b/tools/lib/bpf/bpf_helpers.h
@@ -215,6 +215,7 @@ enum libbpf_tristate {
 #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
 #define __arg_nullable __attribute((btf_decl_tag("arg:nullable")))
 #define __arg_trusted __attribute((btf_decl_tag("arg:trusted")))
+#define __arg_untrusted __attribute((btf_decl_tag("arg:untrusted")))
 #define __arg_arena __attribute((btf_decl_tag("arg:arena")))
 
 #ifndef ___bpf_concat
-- 
2.49.0


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

* [PATCH bpf-next v2 6/8] selftests/bpf: test cases for __arg_untrusted
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
                   ` (4 preceding siblings ...)
  2025-07-04 23:03 ` [PATCH bpf-next v2 5/8] libbpf: __arg_untrusted in bpf_helpers.h Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 7/8] bpf: support for void/primitive __arg_untrusted global func params Eduard Zingerman
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Kumar Kartikeya Dwivedi

Check usage of __arg_untrusted parameters with PTR_TO_BTF_ID:
- combining __arg_untrusted with other tags is forbidden;
- non-kernel (program local) types for __arg_untrusted are forbidden;
- passing of {trusted, untrusted, map value, scalar value, values with
  variable offset} to untrusted is ok;
- passing of PTR_TO_BTF_ID with a different type to untrusted is ok;
- passing of untrusted to trusted is forbidden.

Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 .../bpf/progs/verifier_global_ptr_args.c      | 81 +++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
index 4ab0ef18d7eb..4bd436a35826 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
@@ -179,4 +179,85 @@ int BPF_PROG(trusted_acq_rel, struct task_struct *task, u64 clone_flags)
 	return subprog_trusted_acq_rel(task);
 }
 
+__weak int subprog_untrusted_bad_tags(struct task_struct *task __arg_untrusted __arg_nullable)
+{
+	return task->pid;
+}
+
+SEC("tp_btf/sys_enter")
+__failure
+__msg("arg#0 untrusted cannot be combined with any other tags")
+int untrusted_bad_tags(void *ctx)
+{
+	return subprog_untrusted_bad_tags(0);
+}
+
+struct local_type_wont_be_accepted {};
+
+__weak int subprog_untrusted_bad_type(struct local_type_wont_be_accepted *p __arg_untrusted)
+{
+	return 0;
+}
+
+SEC("tp_btf/sys_enter")
+__failure
+__msg("arg#0 reference type('STRUCT local_type_wont_be_accepted') has no matches")
+int untrusted_bad_type(void *ctx)
+{
+	return subprog_untrusted_bad_type(bpf_rdonly_cast(0, 0));
+}
+
+__weak int subprog_untrusted(const volatile struct task_struct *restrict task __arg_untrusted)
+{
+	return task->pid;
+}
+
+SEC("tp_btf/sys_enter")
+__success
+__log_level(2)
+__msg("r1 = {{.*}}; {{.*}}R1_w=trusted_ptr_task_struct()")
+__msg("Func#1 ('subprog_untrusted') is global and assumed valid.")
+__msg("Validating subprog_untrusted() func#1...")
+__msg(": R1=untrusted_ptr_task_struct")
+int trusted_to_untrusted(void *ctx)
+{
+	return subprog_untrusted(bpf_get_current_task_btf());
+}
+
+char mem[16];
+u32 off;
+
+SEC("tp_btf/sys_enter")
+__success
+int anything_to_untrusted(void *ctx)
+{
+	/* untrusted to untrusted */
+	subprog_untrusted(bpf_core_cast(0, struct task_struct));
+	/* wrong type to untrusted */
+	subprog_untrusted((void *)bpf_core_cast(0, struct bpf_verifier_env));
+	/* map value to untrusted */
+	subprog_untrusted((void *)mem);
+	/* scalar to untrusted */
+	subprog_untrusted(0);
+	/* variable offset to untrusted (map) */
+	subprog_untrusted((void *)mem + off);
+	/* variable offset to untrusted (trusted) */
+	subprog_untrusted((void *)bpf_get_current_task_btf() + off);
+	return 0;
+}
+
+__weak int subprog_untrusted2(struct task_struct *task __arg_untrusted)
+{
+	return subprog_trusted_task_nullable(task);
+}
+
+SEC("tp_btf/sys_enter")
+__failure
+__msg("R1 type=untrusted_ptr_ expected=ptr_, trusted_ptr_, rcu_ptr_")
+__msg("Caller passes invalid args into func#{{.*}} ('subprog_trusted_task_nullable')")
+int untrusted_to_trusted(void *ctx)
+{
+	return subprog_untrusted2(bpf_get_current_task_btf());
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.49.0


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

* [PATCH bpf-next v2 7/8] bpf: support for void/primitive __arg_untrusted global func params
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
                   ` (5 preceding siblings ...)
  2025-07-04 23:03 ` [PATCH bpf-next v2 6/8] selftests/bpf: test cases for __arg_untrusted Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-04 23:03 ` [PATCH bpf-next v2 8/8] selftests/bpf: tests for __arg_untrusted void * " Eduard Zingerman
  2025-07-07 15:30 ` [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM patchwork-bot+netdevbpf
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Alexei Starovoitov, Kumar Kartikeya Dwivedi

Allow specifying __arg_untrusted for void */char */int */long *
parameters. Treat such parameters as
PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED of size zero.
Intended usage is as follows:

  int memcmp(char *a __arg_untrusted, char *b __arg_untrusted, size_t n) {
    bpf_for(i, 0, n) {
      if (a[i] - b[i])      // load at any offset is allowed
        return a[i] - b[i];
    }
    return 0;
  }

Allocate register id for ARG_PTR_TO_MEM parameters only when
PTR_MAYBE_NULL is set. Register id for PTR_TO_MEM is used only to
propagate non-null status after conditionals.

Suggested-by: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 include/linux/btf.h   |  1 +
 kernel/bpf/btf.c      | 15 ++++++++++++++-
 kernel/bpf/verifier.c |  7 ++++---
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/include/linux/btf.h b/include/linux/btf.h
index a40beb9cf160..9eda6b113f9b 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -223,6 +223,7 @@ u32 btf_nr_types(const struct btf *btf);
 struct btf *btf_base_btf(const struct btf *btf);
 bool btf_type_is_i32(const struct btf_type *t);
 bool btf_type_is_i64(const struct btf_type *t);
+bool btf_type_is_primitive(const struct btf_type *t);
 bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s,
 			   const struct btf_member *m,
 			   u32 expected_offset, u32 expected_size);
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index e0414d9f5e29..2dd13eea7b0e 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -891,6 +891,12 @@ bool btf_type_is_i64(const struct btf_type *t)
 	return btf_type_is_int(t) && __btf_type_int_is_regular(t, 8);
 }
 
+bool btf_type_is_primitive(const struct btf_type *t)
+{
+	return (btf_type_is_int(t) && btf_type_int_is_regular(t)) ||
+	       btf_is_any_enum(t);
+}
+
 /*
  * Check that given struct member is a regular int with expected
  * offset and size.
@@ -7830,6 +7836,13 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 				return -EINVAL;
 			}
 
+			ref_t = btf_type_skip_modifiers(btf, t->type, NULL);
+			if (btf_type_is_void(ref_t) || btf_type_is_primitive(ref_t)) {
+				sub->args[i].arg_type = ARG_PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED;
+				sub->args[i].mem_size = 0;
+				continue;
+			}
+
 			kern_type_id = btf_get_ptr_to_btf_id(log, i, btf, t);
 			if (kern_type_id < 0)
 				return kern_type_id;
@@ -7838,7 +7851,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 			ref_t = btf_type_by_id(vmlinux_btf, kern_type_id);
 			if (!btf_type_is_struct(ref_t)) {
 				tname = __btf_name_by_offset(vmlinux_btf, t->name_off);
-				bpf_log(log, "arg#%d has type %s '%s', but only struct types are allowed\n",
+				bpf_log(log, "arg#%d has type %s '%s', but only struct or primitive types are allowed\n",
 					i, btf_type_str(ref_t), tname);
 				return -EINVAL;
 			}
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7af902c3ecc3..1e567fff6f23 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -23152,11 +23152,12 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 				__mark_dynptr_reg(reg, BPF_DYNPTR_TYPE_LOCAL, true, ++env->id_gen);
 			} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
 				reg->type = PTR_TO_MEM;
-				if (arg->arg_type & PTR_MAYBE_NULL)
-					reg->type |= PTR_MAYBE_NULL;
+				reg->type |= arg->arg_type &
+					     (PTR_MAYBE_NULL | PTR_UNTRUSTED | MEM_RDONLY);
 				mark_reg_known_zero(env, regs, i);
 				reg->mem_size = arg->mem_size;
-				reg->id = ++env->id_gen;
+				if (arg->arg_type & PTR_MAYBE_NULL)
+					reg->id = ++env->id_gen;
 			} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
 				reg->type = PTR_TO_BTF_ID;
 				if (arg->arg_type & PTR_MAYBE_NULL)
-- 
2.49.0


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

* [PATCH bpf-next v2 8/8] selftests/bpf: tests for __arg_untrusted void * global func params
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
                   ` (6 preceding siblings ...)
  2025-07-04 23:03 ` [PATCH bpf-next v2 7/8] bpf: support for void/primitive __arg_untrusted global func params Eduard Zingerman
@ 2025-07-04 23:03 ` Eduard Zingerman
  2025-07-07 15:30 ` [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM patchwork-bot+netdevbpf
  8 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2025-07-04 23:03 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
	Eduard Zingerman, Kumar Kartikeya Dwivedi

Check usage of __arg_untrusted parameters of primitive type:
- passing of {trusted, untrusted, map value, scalar value, values with
  variable offset} to untrusted `void *`, `char *` or enum is ok;
- varifier represents such parameters as rdonly_untrusted_mem(sz=0).

Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 .../bpf/progs/verifier_global_ptr_args.c      | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
index 4bd436a35826..b346f669d159 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
@@ -260,4 +260,57 @@ int untrusted_to_trusted(void *ctx)
 	return subprog_untrusted2(bpf_get_current_task_btf());
 }
 
+__weak int subprog_void_untrusted(void *p __arg_untrusted)
+{
+	return *(int *)p;
+}
+
+__weak int subprog_char_untrusted(char *p __arg_untrusted)
+{
+	return *(int *)p;
+}
+
+__weak int subprog_enum_untrusted(enum bpf_attach_type *p __arg_untrusted)
+{
+	return *(int *)p;
+}
+
+__weak int subprog_enum64_untrusted(enum scx_public_consts *p __arg_untrusted)
+{
+	return *(int *)p;
+}
+
+SEC("tp_btf/sys_enter")
+__success
+__log_level(2)
+__msg("r1 = {{.*}}; {{.*}}R1_w=trusted_ptr_task_struct()")
+__msg("Func#1 ('subprog_void_untrusted') is global and assumed valid.")
+__msg("Validating subprog_void_untrusted() func#1...")
+__msg(": R1=rdonly_untrusted_mem(sz=0)")
+int trusted_to_untrusted_mem(void *ctx)
+{
+	return subprog_void_untrusted(bpf_get_current_task_btf());
+}
+
+SEC("tp_btf/sys_enter")
+__success
+int anything_to_untrusted_mem(void *ctx)
+{
+	/* untrusted to untrusted mem */
+	subprog_void_untrusted(bpf_core_cast(0, struct task_struct));
+	/* map value to untrusted mem */
+	subprog_void_untrusted(mem);
+	/* scalar to untrusted mem */
+	subprog_void_untrusted(0);
+	/* variable offset to untrusted mem (map) */
+	subprog_void_untrusted((void *)mem + off);
+	/* variable offset to untrusted mem (trusted) */
+	subprog_void_untrusted(bpf_get_current_task_btf() + off);
+	/* variable offset to untrusted char/enum/enum64 (map) */
+	subprog_char_untrusted(mem + off);
+	subprog_enum_untrusted((void *)mem + off);
+	subprog_enum64_untrusted((void *)mem + off);
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.49.0


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

* Re: [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM
  2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
                   ` (7 preceding siblings ...)
  2025-07-04 23:03 ` [PATCH bpf-next v2 8/8] selftests/bpf: tests for __arg_untrusted void * " Eduard Zingerman
@ 2025-07-07 15:30 ` patchwork-bot+netdevbpf
  8 siblings, 0 replies; 10+ messages in thread
From: patchwork-bot+netdevbpf @ 2025-07-07 15:30 UTC (permalink / raw)
  To: Eduard Zingerman
  Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song

Hello:

This series was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Fri,  4 Jul 2025 16:03:46 -0700 you wrote:
> This patch set introduces two usability enhancements leveraging
> untrusted pointers to mem:
> - When reading a pointer field from a PTR_TO_BTF_ID, the resulting
>   value is now assumed to be PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED
>   instead of SCALAR_VALUE, provided the pointer points to a primitive
>   type.
> - __arg_untrusted attribute for global function parameters,
>   allowed for pointer arguments of both structural and primitive
>   types:
>   - For structural types, the attribute produces
>     PTR_TO_BTF_ID|PTR_UNTRUSTED.
>   - For primitive types, it yields
>     PTR_TO_MEM|MEM_RDONLY|PTR_UNTRUSTED.
> 
> [...]

Here is the summary with links:
  - [bpf-next,v2,1/8] bpf: make makr_btf_ld_reg return error for unexpected reg types
    https://git.kernel.org/bpf/bpf-next/c/b9d44bc9fd30
  - [bpf-next,v2,2/8] bpf: rdonly_untrusted_mem for btf id walk pointer leafs
    https://git.kernel.org/bpf/bpf-next/c/2d5c91e1cc14
  - [bpf-next,v2,3/8] selftests/bpf: ptr_to_btf_id struct walk ending with primitive pointer
    https://git.kernel.org/bpf/bpf-next/c/f1f5d6f25d09
  - [bpf-next,v2,4/8] bpf: attribute __arg_untrusted for global function parameters
    https://git.kernel.org/bpf/bpf-next/c/182f7df70419
  - [bpf-next,v2,5/8] libbpf: __arg_untrusted in bpf_helpers.h
    https://git.kernel.org/bpf/bpf-next/c/aaa0e57e6930
  - [bpf-next,v2,6/8] selftests/bpf: test cases for __arg_untrusted
    https://git.kernel.org/bpf/bpf-next/c/54ac2c9418af
  - [bpf-next,v2,7/8] bpf: support for void/primitive __arg_untrusted global func params
    https://git.kernel.org/bpf/bpf-next/c/c4aa454c64ae
  - [bpf-next,v2,8/8] selftests/bpf: tests for __arg_untrusted void * global func params
    https://git.kernel.org/bpf/bpf-next/c/68cca81fd57f

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2025-07-07 15:29 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-04 23:03 [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 1/8] bpf: make makr_btf_ld_reg return error for unexpected reg types Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 2/8] bpf: rdonly_untrusted_mem for btf id walk pointer leafs Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 3/8] selftests/bpf: ptr_to_btf_id struct walk ending with primitive pointer Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 4/8] bpf: attribute __arg_untrusted for global function parameters Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 5/8] libbpf: __arg_untrusted in bpf_helpers.h Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 6/8] selftests/bpf: test cases for __arg_untrusted Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 7/8] bpf: support for void/primitive __arg_untrusted global func params Eduard Zingerman
2025-07-04 23:03 ` [PATCH bpf-next v2 8/8] selftests/bpf: tests for __arg_untrusted void * " Eduard Zingerman
2025-07-07 15:30 ` [PATCH bpf-next v2 0/8] bpf: additional use-cases for untrusted PTR_TO_MEM patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).