Skip to content

Commit 0ed5f79

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-allow-void-cast-using-bpf_rdonly_cast'
Eduard Zingerman says: ==================== bpf: allow void* cast using bpf_rdonly_cast() Currently, pointers returned by `bpf_rdonly_cast()` have a type of "pointer to btf id", and only casts to structure types are allowed. Access to memory pointed to by these pointers is done through `BPF_PROBE_{MEM,MEMSX}` instructions and does not produce errors on invalid memory access. This patch set extends `bpf_rdonly_cast()` to allow casts to an equivalent of 'void *', effectively replacing `bpf_probe_read_kernel()` calls in situations where access to individual bytes or integers is necessary. The mechanism was suggested and explored by Andrii Nakryiko in [1]. To help with detecting support for this feature, an `enum bpf_features` is added with intended usage as follows: if (bpf_core_enum_value_exists(enum bpf_features, BPF_FEAT_RDONLY_CAST_TO_VOID)) ... [1] https://github.com/anakryiko/linux/tree/bpf-mem-cast Changelog: v2: https://lore.kernel.org/bpf/[email protected]/ v2 -> v3: - dropped direct numbering for __MAX_BPF_FEAT. v1: https://lore.kernel.org/bpf/[email protected]/ v1 -> v2: - renamed BPF_FEAT_TOTAL to __MAX_BPF_FEAT and moved patch introducing bpf_features enum to the start of the series (Alexei); - dropped patch #3 allowing optout from CAP_SYS_ADMIN drop in prog_tests/verifier.c, use a separate runner in prog_tests/* instead. ==================== Acked-by: Andrii Nakryiko <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents 0967f53 + 12ed81f commit 0ed5f79

File tree

3 files changed

+212
-12
lines changed

3 files changed

+212
-12
lines changed

kernel/bpf/verifier.c

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
4444
#undef BPF_LINK_TYPE
4545
};
4646

47+
enum bpf_features {
48+
BPF_FEAT_RDONLY_CAST_TO_VOID = 0,
49+
__MAX_BPF_FEAT,
50+
};
51+
4752
struct bpf_mem_alloc bpf_global_percpu_ma;
4853
static bool bpf_global_percpu_ma_set;
4954

@@ -7535,6 +7540,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
75357540
}
75367541
} else if (base_type(reg->type) == PTR_TO_MEM) {
75377542
bool rdonly_mem = type_is_rdonly_mem(reg->type);
7543+
bool rdonly_untrusted = rdonly_mem && (reg->type & PTR_UNTRUSTED);
75387544

75397545
if (type_may_be_null(reg->type)) {
75407546
verbose(env, "R%d invalid mem access '%s'\n", regno,
@@ -7554,8 +7560,13 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
75547560
return -EACCES;
75557561
}
75567562

7557-
err = check_mem_region_access(env, regno, off, size,
7558-
reg->mem_size, false);
7563+
/*
7564+
* Accesses to untrusted PTR_TO_MEM are done through probe
7565+
* instructions, hence no need to check bounds in that case.
7566+
*/
7567+
if (!rdonly_untrusted)
7568+
err = check_mem_region_access(env, regno, off, size,
7569+
reg->mem_size, false);
75597570
if (!err && value_regno >= 0 && (t == BPF_READ || rdonly_mem))
75607571
mark_reg_unknown(env, regs, value_regno);
75617572
} else if (reg->type == PTR_TO_CTX) {
@@ -13602,16 +13613,24 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_ca
1360213613
regs[BPF_REG_0].btf_id = meta->ret_btf_id;
1360313614
} else if (meta->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
1360413615
ret_t = btf_type_by_id(desc_btf, meta->arg_constant.value);
13605-
if (!ret_t || !btf_type_is_struct(ret_t)) {
13616+
if (!ret_t) {
13617+
verbose(env, "Unknown type ID %lld passed to kfunc bpf_rdonly_cast\n",
13618+
meta->arg_constant.value);
13619+
return -EINVAL;
13620+
} else if (btf_type_is_struct(ret_t)) {
13621+
mark_reg_known_zero(env, regs, BPF_REG_0);
13622+
regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
13623+
regs[BPF_REG_0].btf = desc_btf;
13624+
regs[BPF_REG_0].btf_id = meta->arg_constant.value;
13625+
} else if (btf_type_is_void(ret_t)) {
13626+
mark_reg_known_zero(env, regs, BPF_REG_0);
13627+
regs[BPF_REG_0].type = PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED;
13628+
regs[BPF_REG_0].mem_size = 0;
13629+
} else {
1360613630
verbose(env,
13607-
"kfunc bpf_rdonly_cast type ID argument must be of a struct\n");
13631+
"kfunc bpf_rdonly_cast type ID argument must be of a struct or void\n");
1360813632
return -EINVAL;
1360913633
}
13610-
13611-
mark_reg_known_zero(env, regs, BPF_REG_0);
13612-
regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
13613-
regs[BPF_REG_0].btf = desc_btf;
13614-
regs[BPF_REG_0].btf_id = meta->arg_constant.value;
1361513634
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice] ||
1361613635
meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice_rdwr]) {
1361713636
enum bpf_type_flag type_flag = get_dynptr_type_flag(meta->initialized_dynptr.type);
@@ -14410,6 +14429,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
1441014429
return -EACCES;
1441114430
}
1441214431

14432+
/*
14433+
* Accesses to untrusted PTR_TO_MEM are done through probe
14434+
* instructions, hence no need to track offsets.
14435+
*/
14436+
if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED))
14437+
return 0;
14438+
1441314439
switch (base_type(ptr_reg->type)) {
1441414440
case PTR_TO_CTX:
1441514441
case PTR_TO_MAP_VALUE:
@@ -19618,10 +19644,27 @@ static bool reg_type_mismatch(enum bpf_reg_type src, enum bpf_reg_type prev)
1961819644
!reg_type_mismatch_ok(prev));
1961919645
}
1962019646

19647+
static bool is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
19648+
{
19649+
switch (base_type(type)) {
19650+
case PTR_TO_MEM:
19651+
case PTR_TO_BTF_ID:
19652+
return true;
19653+
default:
19654+
return false;
19655+
}
19656+
}
19657+
19658+
static bool is_ptr_to_mem(enum bpf_reg_type type)
19659+
{
19660+
return base_type(type) == PTR_TO_MEM;
19661+
}
19662+
1962119663
static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
1962219664
bool allow_trust_mismatch)
1962319665
{
1962419666
enum bpf_reg_type *prev_type = &env->insn_aux_data[env->insn_idx].ptr_type;
19667+
enum bpf_reg_type merged_type;
1962519668

1962619669
if (*prev_type == NOT_INIT) {
1962719670
/* Saw a valid insn
@@ -19638,15 +19681,24 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
1963819681
* Reject it.
1963919682
*/
1964019683
if (allow_trust_mismatch &&
19641-
base_type(type) == PTR_TO_BTF_ID &&
19642-
base_type(*prev_type) == PTR_TO_BTF_ID) {
19684+
is_ptr_to_mem_or_btf_id(type) &&
19685+
is_ptr_to_mem_or_btf_id(*prev_type)) {
1964319686
/*
1964419687
* Have to support a use case when one path through
1964519688
* the program yields TRUSTED pointer while another
1964619689
* is UNTRUSTED. Fallback to UNTRUSTED to generate
1964719690
* BPF_PROBE_MEM/BPF_PROBE_MEMSX.
19691+
* Same behavior of MEM_RDONLY flag.
1964819692
*/
19649-
*prev_type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
19693+
if (is_ptr_to_mem(type) || is_ptr_to_mem(*prev_type))
19694+
merged_type = PTR_TO_MEM;
19695+
else
19696+
merged_type = PTR_TO_BTF_ID;
19697+
if ((type & PTR_UNTRUSTED) || (*prev_type & PTR_UNTRUSTED))
19698+
merged_type |= PTR_UNTRUSTED;
19699+
if ((type & MEM_RDONLY) || (*prev_type & MEM_RDONLY))
19700+
merged_type |= MEM_RDONLY;
19701+
*prev_type = merged_type;
1965019702
} else {
1965119703
verbose(env, "same insn cannot be used with different pointers\n");
1965219704
return -EINVAL;
@@ -21254,6 +21306,7 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
2125421306
* for this case.
2125521307
*/
2125621308
case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
21309+
case PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED:
2125721310
if (type == BPF_READ) {
2125821311
if (BPF_MODE(insn->code) == BPF_MEM)
2125921312
insn->code = BPF_LDX | BPF_PROBE_MEM |
@@ -24439,6 +24492,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
2443924492
u32 log_true_size;
2444024493
bool is_priv;
2444124494

24495+
BTF_TYPE_EMIT(enum bpf_features);
24496+
2444224497
/* no program is valid */
2444324498
if (ARRAY_SIZE(bpf_verifier_ops) == 0)
2444424499
return -EINVAL;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
3+
#include <test_progs.h>
4+
#include "mem_rdonly_untrusted.skel.h"
5+
6+
void test_mem_rdonly_untrusted(void)
7+
{
8+
RUN_TESTS(mem_rdonly_untrusted);
9+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_core_read.h>
5+
#include "bpf_misc.h"
6+
#include "../test_kmods/bpf_testmod_kfunc.h"
7+
8+
SEC("socket")
9+
__success
10+
__retval(0)
11+
int ldx_is_ok_bad_addr(void *ctx)
12+
{
13+
char *p;
14+
15+
if (!bpf_core_enum_value_exists(enum bpf_features, BPF_FEAT_RDONLY_CAST_TO_VOID))
16+
return 42;
17+
18+
p = bpf_rdonly_cast(0, 0);
19+
return p[0x7fff];
20+
}
21+
22+
SEC("socket")
23+
__success
24+
__retval(1)
25+
int ldx_is_ok_good_addr(void *ctx)
26+
{
27+
int v, *p;
28+
29+
v = 1;
30+
p = bpf_rdonly_cast(&v, 0);
31+
return *p;
32+
}
33+
34+
SEC("socket")
35+
__success
36+
int offset_not_tracked(void *ctx)
37+
{
38+
int *p, i, s;
39+
40+
p = bpf_rdonly_cast(0, 0);
41+
s = 0;
42+
bpf_for(i, 0, 1000 * 1000 * 1000) {
43+
p++;
44+
s += *p;
45+
}
46+
return s;
47+
}
48+
49+
SEC("socket")
50+
__failure
51+
__msg("cannot write into rdonly_untrusted_mem")
52+
int stx_not_ok(void *ctx)
53+
{
54+
int v, *p;
55+
56+
v = 1;
57+
p = bpf_rdonly_cast(&v, 0);
58+
*p = 1;
59+
return 0;
60+
}
61+
62+
SEC("socket")
63+
__failure
64+
__msg("cannot write into rdonly_untrusted_mem")
65+
int atomic_not_ok(void *ctx)
66+
{
67+
int v, *p;
68+
69+
v = 1;
70+
p = bpf_rdonly_cast(&v, 0);
71+
__sync_fetch_and_add(p, 1);
72+
return 0;
73+
}
74+
75+
SEC("socket")
76+
__failure
77+
__msg("cannot write into rdonly_untrusted_mem")
78+
int atomic_rmw_not_ok(void *ctx)
79+
{
80+
long v, *p;
81+
82+
v = 1;
83+
p = bpf_rdonly_cast(&v, 0);
84+
return __sync_val_compare_and_swap(p, 0, 42);
85+
}
86+
87+
SEC("socket")
88+
__failure
89+
__msg("invalid access to memory, mem_size=0 off=0 size=4")
90+
__msg("R1 min value is outside of the allowed memory range")
91+
int kfunc_param_not_ok(void *ctx)
92+
{
93+
int *p;
94+
95+
p = bpf_rdonly_cast(0, 0);
96+
bpf_kfunc_trusted_num_test(p);
97+
return 0;
98+
}
99+
100+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
101+
__failure
102+
__msg("R1 type=rdonly_untrusted_mem expected=")
103+
int helper_param_not_ok(void *ctx)
104+
{
105+
char *p;
106+
107+
p = bpf_rdonly_cast(0, 0);
108+
/*
109+
* Any helper with ARG_CONST_SIZE_OR_ZERO constraint will do,
110+
* the most permissive constraint
111+
*/
112+
bpf_copy_from_user(p, 0, (void *)42);
113+
return 0;
114+
}
115+
116+
static __noinline u64 *get_some_addr(void)
117+
{
118+
if (bpf_get_prandom_u32())
119+
return bpf_rdonly_cast(0, bpf_core_type_id_kernel(struct sock));
120+
else
121+
return bpf_rdonly_cast(0, 0);
122+
}
123+
124+
SEC("socket")
125+
__success
126+
__retval(0)
127+
int mixed_mem_type(void *ctx)
128+
{
129+
u64 *p;
130+
131+
/* Try to avoid compiler hoisting load to if branches by using __noinline func. */
132+
p = get_some_addr();
133+
return *p;
134+
}
135+
136+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)