On Mon, Dec 6, 2021 at 10:09 PM Andrii Nakryiko <andrii.nakryiko@xxxxxxxxx> wrote: > > On Mon, Dec 6, 2021 at 3:22 PM Hao Luo <haoluo@xxxxxxxxxx> wrote: > > > > We have introduced a new type to make bpf_reg composable, by > > allocating bits in the type to represent flags. > > > > One of the flags is PTR_MAYBE_NULL which indicates a pointer > > may be NULL. This patch switches the qualified reg_types to > > use this flag. The reg_types changed in this patch include: > > > > 1. PTR_TO_MAP_VALUE_OR_NULL > > 2. PTR_TO_SOCKET_OR_NULL > > 3. PTR_TO_SOCK_COMMON_OR_NULL > > 4. PTR_TO_TCP_SOCK_OR_NULL > > 5. PTR_TO_BTF_ID_OR_NULL > > 6. PTR_TO_MEM_OR_NULL > > 7. PTR_TO_RDONLY_BUF_OR_NULL > > 8. PTR_TO_RDWR_BUF_OR_NULL > > > > Signed-off-by: Hao Luo <haoluo@xxxxxxxxxx> > > --- > > drivers/net/ethernet/netronome/nfp/bpf/fw.h | 4 +- > > include/linux/bpf.h | 16 +- > > kernel/bpf/btf.c | 7 +- > > kernel/bpf/map_iter.c | 4 +- > > kernel/bpf/verifier.c | 278 ++++++++------------ > > net/core/bpf_sk_storage.c | 2 +- > > net/core/sock_map.c | 2 +- > > 7 files changed, 126 insertions(+), 187 deletions(-) > > > > [...] > > > @@ -535,38 +520,35 @@ static bool is_cmpxchg_insn(const struct bpf_insn *insn) > > } > > > > /* string representation of 'enum bpf_reg_type' */ > > -static const char * const reg_type_str[] = { > > - [NOT_INIT] = "?", > > - [SCALAR_VALUE] = "inv", > > - [PTR_TO_CTX] = "ctx", > > - [CONST_PTR_TO_MAP] = "map_ptr", > > - [PTR_TO_MAP_VALUE] = "map_value", > > - [PTR_TO_MAP_VALUE_OR_NULL] = "map_value_or_null", > > - [PTR_TO_STACK] = "fp", > > - [PTR_TO_PACKET] = "pkt", > > - [PTR_TO_PACKET_META] = "pkt_meta", > > - [PTR_TO_PACKET_END] = "pkt_end", > > - [PTR_TO_FLOW_KEYS] = "flow_keys", > > - [PTR_TO_SOCKET] = "sock", > > - [PTR_TO_SOCKET_OR_NULL] = "sock_or_null", > > - [PTR_TO_SOCK_COMMON] = "sock_common", > > - [PTR_TO_SOCK_COMMON_OR_NULL] = "sock_common_or_null", > > - [PTR_TO_TCP_SOCK] = "tcp_sock", > > - [PTR_TO_TCP_SOCK_OR_NULL] = "tcp_sock_or_null", > > - [PTR_TO_TP_BUFFER] = "tp_buffer", > > - [PTR_TO_XDP_SOCK] = "xdp_sock", > > - [PTR_TO_BTF_ID] = "ptr_", > > - [PTR_TO_BTF_ID_OR_NULL] = "ptr_or_null_", > > - [PTR_TO_PERCPU_BTF_ID] = "percpu_ptr_", > > - [PTR_TO_MEM] = "mem", > > - [PTR_TO_MEM_OR_NULL] = "mem_or_null", > > - [PTR_TO_RDONLY_BUF] = "rdonly_buf", > > - [PTR_TO_RDONLY_BUF_OR_NULL] = "rdonly_buf_or_null", > > - [PTR_TO_RDWR_BUF] = "rdwr_buf", > > - [PTR_TO_RDWR_BUF_OR_NULL] = "rdwr_buf_or_null", > > - [PTR_TO_FUNC] = "func", > > - [PTR_TO_MAP_KEY] = "map_key", > > -}; > > +static const char *reg_type_str(enum bpf_reg_type type) > > +{ > > + static const char * const str[] = { > > + [NOT_INIT] = "?", > > + [SCALAR_VALUE] = "inv", > > + [PTR_TO_CTX] = "ctx", > > + [CONST_PTR_TO_MAP] = "map_ptr", > > + [PTR_TO_MAP_VALUE] = "map_value", > > + [PTR_TO_STACK] = "fp", > > + [PTR_TO_PACKET] = "pkt", > > + [PTR_TO_PACKET_META] = "pkt_meta", > > + [PTR_TO_PACKET_END] = "pkt_end", > > + [PTR_TO_FLOW_KEYS] = "flow_keys", > > + [PTR_TO_SOCKET] = "sock", > > + [PTR_TO_SOCK_COMMON] = "sock_common", > > + [PTR_TO_TCP_SOCK] = "tcp_sock", > > + [PTR_TO_TP_BUFFER] = "tp_buffer", > > + [PTR_TO_XDP_SOCK] = "xdp_sock", > > + [PTR_TO_BTF_ID] = "ptr_", > > + [PTR_TO_PERCPU_BTF_ID] = "percpu_ptr_", > > + [PTR_TO_MEM] = "mem", > > + [PTR_TO_RDONLY_BUF] = "rdonly_buf", > > + [PTR_TO_RDWR_BUF] = "rdwr_buf", > > + [PTR_TO_FUNC] = "func", > > + [PTR_TO_MAP_KEY] = "map_key", > > + }; > > + > > + return str[base_type(type)]; > > well... now we lose the "_or_null" part, that can be pretty important. > Same will happen with RDONLY flag, right? > > What can we do about that? > We can format the string into a global buffer and return the buffer to the caller. A little overhead on string formatting. I assume there is already synchronization around the verifier to prevent race on the buffer. If not, we have to ask the caller of reg_type_str() to pass in a buffer. > > +} > > > > static char slot_type_char[] = { > > [STACK_INVALID] = '?', > > @@ -631,7 +613,7 @@ static void print_verifier_state(struct bpf_verifier_env *env, > > continue; > > verbose(env, " R%d", i); > > print_liveness(env, reg->live); > > - verbose(env, "=%s", reg_type_str[t]); > > + verbose(env, "=%s", reg_type_str(t)); > > if (t == SCALAR_VALUE && reg->precise) > > verbose(env, "P"); > > if ((t == SCALAR_VALUE || t == PTR_TO_STACK) && > > [...] > > > if (smin >= BPF_MAX_VAR_OFF || smin <= -BPF_MAX_VAR_OFF) { > > verbose(env, "value %lld makes %s pointer be out of bounds\n", > > - smin, reg_type_str[type]); > > + smin, reg_type_str(type)); > > return false; > > } > > > > @@ -7209,11 +7151,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, > > return -EACCES; > > } > > > > - switch (ptr_reg->type) { > > - case PTR_TO_MAP_VALUE_OR_NULL: > > + if (ptr_reg->type == PTR_TO_MAP_VALUE_OR_NULL) { > > verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n", > > the same message can be output for other pointer types, so instead of > special-casing, let's just combine them with more helpful message > Ok. Just want to confirm my understanding, do you mean checking for NULL? Like the following replacement: - if (ptr_reg->type == PTR_TO_MAP_VALUE_OR_NULL) { + if (ptr_reg->type & PTR_MAYBE_NULL) { > > - dst, reg_type_str[ptr_reg->type]); > > + dst, reg_type_str(ptr_reg->type)); > > return -EACCES; > > + } > > + > > + switch (base_type(ptr_reg->type)) { > > case CONST_PTR_TO_MAP: > > /* smin_val represents the known value */ > > if (known && smin_val == 0 && opcode == BPF_ADD) > > @@ -7221,14 +7165,11 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, > > fallthrough; > > case PTR_TO_PACKET_END: > > case PTR_TO_SOCKET: > > - case PTR_TO_SOCKET_OR_NULL: > > case PTR_TO_SOCK_COMMON: > > - case PTR_TO_SOCK_COMMON_OR_NULL: > > case PTR_TO_TCP_SOCK: > > - case PTR_TO_TCP_SOCK_OR_NULL: > > case PTR_TO_XDP_SOCK: > > verbose(env, "R%d pointer arithmetic on %s prohibited\n", > > - dst, reg_type_str[ptr_reg->type]); > > + dst, reg_type_str(ptr_reg->type)); > > return -EACCES; > > default: > > break; > > [...]