* [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).