Skip to content

Commit b33ce2a

Browse files
committed
bpf: allow void* cast using bpf_rdonly_cast()
Introduce support for `bpf_rdonly_cast(v, 0)`, which casts the value `v` to an untyped, untrusted pointer, logically similar to a `void *`. The memory pointed to by such a pointer is treated as read-only. As with other untrusted pointers, memory access violations on loads return zero instead of causing a fault. Technically: - The resulting pointer is represented as a register of type `PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED` with size zero. - Offsets within such pointers are not tracked. - Same load instructions are allowed to have both `PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED` and `PTR_TO_BTF_ID` as the base pointer types. In such cases, `bpf_insn_aux_data->ptr_type` is considered the weaker of the two: `PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED`. The following constraints apply to the new pointer type: - can be used as a base for LDX instructions; - can't be used as a base for ST/STX or atomic instructions; - can't be used as parameter for kfuncs or helpers. These constraints are enforced by existing handling of `MEM_RDONLY` flag and `PTR_TO_MEM` of size zero. Suggested-by: Alexei Starovoitov <[email protected]> Suggested-by: Andrii Nakryiko <[email protected]> Signed-off-by: Eduard Zingerman <[email protected]>
1 parent 6a98d0c commit b33ce2a

File tree

1 file changed

+62
-13
lines changed

1 file changed

+62
-13
lines changed

kernel/bpf/verifier.c

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
4545
};
4646

4747
enum bpf_features {
48-
__MAX_BPF_FEAT = 0,
48+
BPF_FEAT_RDONLY_CAST_TO_VOID = 0,
49+
__MAX_BPF_FEAT = 1,
4950
};
5051

5152
struct bpf_mem_alloc bpf_global_percpu_ma;
@@ -7539,6 +7540,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
75397540
}
75407541
} else if (base_type(reg->type) == PTR_TO_MEM) {
75417542
bool rdonly_mem = type_is_rdonly_mem(reg->type);
7543+
bool rdonly_untrusted = rdonly_mem && (reg->type & PTR_UNTRUSTED);
75427544

75437545
if (type_may_be_null(reg->type)) {
75447546
verbose(env, "R%d invalid mem access '%s'\n", regno,
@@ -7558,8 +7560,13 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
75587560
return -EACCES;
75597561
}
75607562

7561-
err = check_mem_region_access(env, regno, off, size,
7562-
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);
75637570
if (!err && value_regno >= 0 && (t == BPF_READ || rdonly_mem))
75647571
mark_reg_unknown(env, regs, value_regno);
75657572
} else if (reg->type == PTR_TO_CTX) {
@@ -13606,16 +13613,24 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_ca
1360613613
regs[BPF_REG_0].btf_id = meta->ret_btf_id;
1360713614
} else if (meta->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
1360813615
ret_t = btf_type_by_id(desc_btf, meta->arg_constant.value);
13609-
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 {
1361013630
verbose(env,
13611-
"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");
1361213632
return -EINVAL;
1361313633
}
13614-
13615-
mark_reg_known_zero(env, regs, BPF_REG_0);
13616-
regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
13617-
regs[BPF_REG_0].btf = desc_btf;
13618-
regs[BPF_REG_0].btf_id = meta->arg_constant.value;
1361913634
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice] ||
1362013635
meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice_rdwr]) {
1362113636
enum bpf_type_flag type_flag = get_dynptr_type_flag(meta->initialized_dynptr.type);
@@ -14414,6 +14429,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
1441414429
return -EACCES;
1441514430
}
1441614431

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+
1441714439
switch (base_type(ptr_reg->type)) {
1441814440
case PTR_TO_CTX:
1441914441
case PTR_TO_MAP_VALUE:
@@ -19571,10 +19593,27 @@ static bool reg_type_mismatch(enum bpf_reg_type src, enum bpf_reg_type prev)
1957119593
!reg_type_mismatch_ok(prev));
1957219594
}
1957319595

19596+
static bool is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
19597+
{
19598+
switch (base_type(type)) {
19599+
case PTR_TO_MEM:
19600+
case PTR_TO_BTF_ID:
19601+
return true;
19602+
default:
19603+
return false;
19604+
}
19605+
}
19606+
19607+
static bool is_ptr_to_mem(enum bpf_reg_type type)
19608+
{
19609+
return base_type(type) == PTR_TO_MEM;
19610+
}
19611+
1957419612
static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
1957519613
bool allow_trust_mismatch)
1957619614
{
1957719615
enum bpf_reg_type *prev_type = &env->insn_aux_data[env->insn_idx].ptr_type;
19616+
enum bpf_reg_type merged_type;
1957819617

1957919618
if (*prev_type == NOT_INIT) {
1958019619
/* Saw a valid insn
@@ -19591,15 +19630,24 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
1959119630
* Reject it.
1959219631
*/
1959319632
if (allow_trust_mismatch &&
19594-
base_type(type) == PTR_TO_BTF_ID &&
19595-
base_type(*prev_type) == PTR_TO_BTF_ID) {
19633+
is_ptr_to_mem_or_btf_id(type) &&
19634+
is_ptr_to_mem_or_btf_id(*prev_type)) {
1959619635
/*
1959719636
* Have to support a use case when one path through
1959819637
* the program yields TRUSTED pointer while another
1959919638
* is UNTRUSTED. Fallback to UNTRUSTED to generate
1960019639
* BPF_PROBE_MEM/BPF_PROBE_MEMSX.
19640+
* Same behavior of MEM_RDONLY flag.
1960119641
*/
19602-
*prev_type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
19642+
if (is_ptr_to_mem(type) || is_ptr_to_mem(*prev_type))
19643+
merged_type = PTR_TO_MEM;
19644+
else
19645+
merged_type = PTR_TO_BTF_ID;
19646+
if ((type & PTR_UNTRUSTED) || (*prev_type & PTR_UNTRUSTED))
19647+
merged_type |= PTR_UNTRUSTED;
19648+
if ((type & MEM_RDONLY) || (*prev_type & MEM_RDONLY))
19649+
merged_type |= MEM_RDONLY;
19650+
*prev_type = merged_type;
1960319651
} else {
1960419652
verbose(env, "same insn cannot be used with different pointers\n");
1960519653
return -EINVAL;
@@ -21207,6 +21255,7 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
2120721255
* for this case.
2120821256
*/
2120921257
case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
21258+
case PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED:
2121021259
if (type == BPF_READ) {
2121121260
if (BPF_MODE(insn->code) == BPF_MEM)
2121221261
insn->code = BPF_LDX | BPF_PROBE_MEM |

0 commit comments

Comments
 (0)