Skip to content

Commit 6e5cae9

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-additional-use-cases-for-untrusted-ptr_to_mem'
Eduard Zingerman says: ==================== bpf: additional use-cases for untrusted PTR_TO_MEM 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/[email protected]/ 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. ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents 03fe01d + 68cca81 commit 6e5cae9

File tree

7 files changed

+274
-29
lines changed

7 files changed

+274
-29
lines changed

include/linux/btf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ u32 btf_nr_types(const struct btf *btf);
223223
struct btf *btf_base_btf(const struct btf *btf);
224224
bool btf_type_is_i32(const struct btf_type *t);
225225
bool btf_type_is_i64(const struct btf_type *t);
226+
bool btf_type_is_primitive(const struct btf_type *t);
226227
bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s,
227228
const struct btf_member *m,
228229
u32 expected_offset, u32 expected_size);

kernel/bpf/btf.c

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,12 @@ bool btf_type_is_i64(const struct btf_type *t)
891891
return btf_type_is_int(t) && __btf_type_int_is_regular(t, 8);
892892
}
893893

894+
bool btf_type_is_primitive(const struct btf_type *t)
895+
{
896+
return (btf_type_is_int(t) && btf_type_int_is_regular(t)) ||
897+
btf_is_any_enum(t);
898+
}
899+
894900
/*
895901
* Check that given struct member is a regular int with expected
896902
* offset and size.
@@ -6915,6 +6921,7 @@ enum bpf_struct_walk_result {
69156921
/* < 0 error */
69166922
WALK_SCALAR = 0,
69176923
WALK_PTR,
6924+
WALK_PTR_UNTRUSTED,
69186925
WALK_STRUCT,
69196926
};
69206927

@@ -7156,6 +7163,8 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf,
71567163
*field_name = mname;
71577164
return WALK_PTR;
71587165
}
7166+
7167+
return WALK_PTR_UNTRUSTED;
71597168
}
71607169

71617170
/* Allow more flexible access within an int as long as
@@ -7228,6 +7237,9 @@ int btf_struct_access(struct bpf_verifier_log *log,
72287237
*next_btf_id = id;
72297238
*flag = tmp_flag;
72307239
return PTR_TO_BTF_ID;
7240+
case WALK_PTR_UNTRUSTED:
7241+
*flag = MEM_RDONLY | PTR_UNTRUSTED;
7242+
return PTR_TO_MEM;
72317243
case WALK_SCALAR:
72327244
return SCALAR_VALUE;
72337245
case WALK_STRUCT:
@@ -7640,11 +7652,12 @@ static int btf_get_ptr_to_btf_id(struct bpf_verifier_log *log, int arg_idx,
76407652
}
76417653

76427654
enum btf_arg_tag {
7643-
ARG_TAG_CTX = BIT_ULL(0),
7644-
ARG_TAG_NONNULL = BIT_ULL(1),
7645-
ARG_TAG_TRUSTED = BIT_ULL(2),
7646-
ARG_TAG_NULLABLE = BIT_ULL(3),
7647-
ARG_TAG_ARENA = BIT_ULL(4),
7655+
ARG_TAG_CTX = BIT_ULL(0),
7656+
ARG_TAG_NONNULL = BIT_ULL(1),
7657+
ARG_TAG_TRUSTED = BIT_ULL(2),
7658+
ARG_TAG_UNTRUSTED = BIT_ULL(3),
7659+
ARG_TAG_NULLABLE = BIT_ULL(4),
7660+
ARG_TAG_ARENA = BIT_ULL(5),
76487661
};
76497662

76507663
/* Process BTF of a function to produce high-level expectation of function
@@ -7752,6 +7765,8 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
77527765
tags |= ARG_TAG_CTX;
77537766
} else if (strcmp(tag, "trusted") == 0) {
77547767
tags |= ARG_TAG_TRUSTED;
7768+
} else if (strcmp(tag, "untrusted") == 0) {
7769+
tags |= ARG_TAG_UNTRUSTED;
77557770
} else if (strcmp(tag, "nonnull") == 0) {
77567771
tags |= ARG_TAG_NONNULL;
77577772
} else if (strcmp(tag, "nullable") == 0) {
@@ -7812,6 +7827,38 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
78127827
sub->args[i].btf_id = kern_type_id;
78137828
continue;
78147829
}
7830+
if (tags & ARG_TAG_UNTRUSTED) {
7831+
struct btf *vmlinux_btf;
7832+
int kern_type_id;
7833+
7834+
if (tags & ~ARG_TAG_UNTRUSTED) {
7835+
bpf_log(log, "arg#%d untrusted cannot be combined with any other tags\n", i);
7836+
return -EINVAL;
7837+
}
7838+
7839+
ref_t = btf_type_skip_modifiers(btf, t->type, NULL);
7840+
if (btf_type_is_void(ref_t) || btf_type_is_primitive(ref_t)) {
7841+
sub->args[i].arg_type = ARG_PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED;
7842+
sub->args[i].mem_size = 0;
7843+
continue;
7844+
}
7845+
7846+
kern_type_id = btf_get_ptr_to_btf_id(log, i, btf, t);
7847+
if (kern_type_id < 0)
7848+
return kern_type_id;
7849+
7850+
vmlinux_btf = bpf_get_btf_vmlinux();
7851+
ref_t = btf_type_by_id(vmlinux_btf, kern_type_id);
7852+
if (!btf_type_is_struct(ref_t)) {
7853+
tname = __btf_name_by_offset(vmlinux_btf, t->name_off);
7854+
bpf_log(log, "arg#%d has type %s '%s', but only struct or primitive types are allowed\n",
7855+
i, btf_type_str(ref_t), tname);
7856+
return -EINVAL;
7857+
}
7858+
sub->args[i].arg_type = ARG_PTR_TO_BTF_ID | PTR_UNTRUSTED;
7859+
sub->args[i].btf_id = kern_type_id;
7860+
continue;
7861+
}
78157862
if (tags & ARG_TAG_ARENA) {
78167863
if (tags & ~ARG_TAG_ARENA) {
78177864
bpf_log(log, "arg#%d arena cannot be combined with any other tags\n", i);

kernel/bpf/verifier.c

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,22 +2796,33 @@ static void mark_reg_not_init(struct bpf_verifier_env *env,
27962796
__mark_reg_not_init(env, regs + regno);
27972797
}
27982798

2799-
static void mark_btf_ld_reg(struct bpf_verifier_env *env,
2800-
struct bpf_reg_state *regs, u32 regno,
2801-
enum bpf_reg_type reg_type,
2802-
struct btf *btf, u32 btf_id,
2803-
enum bpf_type_flag flag)
2799+
static int mark_btf_ld_reg(struct bpf_verifier_env *env,
2800+
struct bpf_reg_state *regs, u32 regno,
2801+
enum bpf_reg_type reg_type,
2802+
struct btf *btf, u32 btf_id,
2803+
enum bpf_type_flag flag)
28042804
{
2805-
if (reg_type == SCALAR_VALUE) {
2805+
switch (reg_type) {
2806+
case SCALAR_VALUE:
28062807
mark_reg_unknown(env, regs, regno);
2807-
return;
2808+
return 0;
2809+
case PTR_TO_BTF_ID:
2810+
mark_reg_known_zero(env, regs, regno);
2811+
regs[regno].type = PTR_TO_BTF_ID | flag;
2812+
regs[regno].btf = btf;
2813+
regs[regno].btf_id = btf_id;
2814+
if (type_may_be_null(flag))
2815+
regs[regno].id = ++env->id_gen;
2816+
return 0;
2817+
case PTR_TO_MEM:
2818+
mark_reg_known_zero(env, regs, regno);
2819+
regs[regno].type = PTR_TO_MEM | flag;
2820+
regs[regno].mem_size = 0;
2821+
return 0;
2822+
default:
2823+
verifier_bug(env, "unexpected reg_type %d in %s\n", reg_type, __func__);
2824+
return -EFAULT;
28082825
}
2809-
mark_reg_known_zero(env, regs, regno);
2810-
regs[regno].type = PTR_TO_BTF_ID | flag;
2811-
regs[regno].btf = btf;
2812-
regs[regno].btf_id = btf_id;
2813-
if (type_may_be_null(flag))
2814-
regs[regno].id = ++env->id_gen;
28152826
}
28162827

28172828
#define DEF_NOT_SUBREG (0)
@@ -5965,6 +5976,7 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
59655976
struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
59665977
int class = BPF_CLASS(insn->code);
59675978
struct bpf_reg_state *val_reg;
5979+
int ret;
59685980

59695981
/* Things we already checked for in check_map_access and caller:
59705982
* - Reject cases where variable offset may touch kptr
@@ -5998,8 +6010,11 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
59986010
/* We can simply mark the value_regno receiving the pointer
59996011
* value from map as PTR_TO_BTF_ID, with the correct type.
60006012
*/
6001-
mark_btf_ld_reg(env, cur_regs(env), value_regno, PTR_TO_BTF_ID, kptr_field->kptr.btf,
6002-
kptr_field->kptr.btf_id, btf_ld_kptr_type(env, kptr_field));
6013+
ret = mark_btf_ld_reg(env, cur_regs(env), value_regno, PTR_TO_BTF_ID,
6014+
kptr_field->kptr.btf, kptr_field->kptr.btf_id,
6015+
btf_ld_kptr_type(env, kptr_field));
6016+
if (ret < 0)
6017+
return ret;
60036018
} else if (class == BPF_STX) {
60046019
val_reg = reg_state(env, value_regno);
60056020
if (!register_is_null(val_reg) &&
@@ -7298,8 +7313,11 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
72987313
clear_trusted_flags(&flag);
72997314
}
73007315

7301-
if (atype == BPF_READ && value_regno >= 0)
7302-
mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
7316+
if (atype == BPF_READ && value_regno >= 0) {
7317+
ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
7318+
if (ret < 0)
7319+
return ret;
7320+
}
73037321

73047322
return 0;
73057323
}
@@ -7353,13 +7371,19 @@ static int check_ptr_to_map_access(struct bpf_verifier_env *env,
73537371

73547372
/* Simulate access to a PTR_TO_BTF_ID */
73557373
memset(&map_reg, 0, sizeof(map_reg));
7356-
mark_btf_ld_reg(env, &map_reg, 0, PTR_TO_BTF_ID, btf_vmlinux, *map->ops->map_btf_id, 0);
7374+
ret = mark_btf_ld_reg(env, &map_reg, 0, PTR_TO_BTF_ID,
7375+
btf_vmlinux, *map->ops->map_btf_id, 0);
7376+
if (ret < 0)
7377+
return ret;
73577378
ret = btf_struct_access(&env->log, &map_reg, off, size, atype, &btf_id, &flag, NULL);
73587379
if (ret < 0)
73597380
return ret;
73607381

7361-
if (value_regno >= 0)
7362-
mark_btf_ld_reg(env, regs, value_regno, ret, btf_vmlinux, btf_id, flag);
7382+
if (value_regno >= 0) {
7383+
ret = mark_btf_ld_reg(env, regs, value_regno, ret, btf_vmlinux, btf_id, flag);
7384+
if (ret < 0)
7385+
return ret;
7386+
}
73637387

73647388
return 0;
73657389
}
@@ -10413,6 +10437,12 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
1041310437
bpf_log(log, "R%d is not a scalar\n", regno);
1041410438
return -EINVAL;
1041510439
}
10440+
} else if (arg->arg_type & PTR_UNTRUSTED) {
10441+
/*
10442+
* Anything is allowed for untrusted arguments, as these are
10443+
* read-only and probe read instructions would protect against
10444+
* invalid memory access.
10445+
*/
1041610446
} else if (arg->arg_type == ARG_PTR_TO_CTX) {
1041710447
ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
1041810448
if (ret < 0)
@@ -23122,11 +23152,12 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
2312223152
__mark_dynptr_reg(reg, BPF_DYNPTR_TYPE_LOCAL, true, ++env->id_gen);
2312323153
} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
2312423154
reg->type = PTR_TO_MEM;
23125-
if (arg->arg_type & PTR_MAYBE_NULL)
23126-
reg->type |= PTR_MAYBE_NULL;
23155+
reg->type |= arg->arg_type &
23156+
(PTR_MAYBE_NULL | PTR_UNTRUSTED | MEM_RDONLY);
2312723157
mark_reg_known_zero(env, regs, i);
2312823158
reg->mem_size = arg->mem_size;
23129-
reg->id = ++env->id_gen;
23159+
if (arg->arg_type & PTR_MAYBE_NULL)
23160+
reg->id = ++env->id_gen;
2313023161
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
2313123162
reg->type = PTR_TO_BTF_ID;
2313223163
if (arg->arg_type & PTR_MAYBE_NULL)

tools/lib/bpf/bpf_helpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ enum libbpf_tristate {
215215
#define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
216216
#define __arg_nullable __attribute((btf_decl_tag("arg:nullable")))
217217
#define __arg_trusted __attribute((btf_decl_tag("arg:trusted")))
218+
#define __arg_untrusted __attribute((btf_decl_tag("arg:untrusted")))
218219
#define __arg_arena __attribute((btf_decl_tag("arg:arena")))
219220

220221
#ifndef ___bpf_concat

tools/testing/selftests/bpf/prog_tests/linked_list.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static struct {
7272
{ "new_null_ret", "R0 invalid mem access 'ptr_or_null_'" },
7373
{ "obj_new_acq", "Unreleased reference id=" },
7474
{ "use_after_drop", "invalid mem access 'scalar'" },
75-
{ "ptr_walk_scalar", "type=scalar expected=percpu_ptr_" },
75+
{ "ptr_walk_scalar", "type=rdonly_untrusted_mem expected=percpu_ptr_" },
7676
{ "direct_read_lock", "direct access to bpf_spin_lock is disallowed" },
7777
{ "direct_write_lock", "direct access to bpf_spin_lock is disallowed" },
7878
{ "direct_read_head", "direct access to bpf_list_head is disallowed" },

tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@
55
#include "bpf_misc.h"
66
#include "../test_kmods/bpf_testmod_kfunc.h"
77

8+
SEC("tp_btf/sys_enter")
9+
__success
10+
__log_level(2)
11+
__msg("r8 = *(u64 *)(r7 +0) ; R7_w=ptr_nameidata(off={{[0-9]+}}) R8_w=rdonly_untrusted_mem(sz=0)")
12+
__msg("r9 = *(u8 *)(r8 +0) ; R8_w=rdonly_untrusted_mem(sz=0) R9_w=scalar")
13+
int btf_id_to_ptr_mem(void *ctx)
14+
{
15+
struct task_struct *task;
16+
struct nameidata *idata;
17+
u64 ret, off;
18+
19+
task = bpf_get_current_task_btf();
20+
idata = task->nameidata;
21+
off = bpf_core_field_offset(struct nameidata, pathname);
22+
/*
23+
* asm block to have reliable match target for __msg, equivalent of:
24+
* ret = task->nameidata->pathname[0];
25+
*/
26+
asm volatile (
27+
"r7 = %[idata];"
28+
"r7 += %[off];"
29+
"r8 = *(u64 *)(r7 + 0);"
30+
"r9 = *(u8 *)(r8 + 0);"
31+
"%[ret] = r9;"
32+
: [ret]"=r"(ret)
33+
: [idata]"r"(idata),
34+
[off]"r"(off)
35+
: "r7", "r8", "r9");
36+
return ret;
37+
}
38+
839
SEC("socket")
940
__success
1041
__retval(0)

0 commit comments

Comments
 (0)