Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpf: Simplify checking size of helper accesses

This patch simplifies the verification of size arguments associated to
pointer arguments to helpers and kfuncs. Many helpers take a pointer
argument followed by the size of the memory access performed to be
performed through that pointer. Before this patch, the handling of the
size argument in check_mem_size_reg() was confusing and wasteful: if the
size register's lower bound was 0, then the verification was done twice:
once considering the size of the access to be the lower-bound of the
respective argument, and once considering the upper bound (even if the
two are the same). The upper bound checking is a super-set of the
lower-bound checking(*), except: the only point of the lower-bound check
is to handle the case where zero-sized-accesses are explicitly not
allowed and the lower-bound is zero. This static condition is now
checked explicitly, replacing a much more complex, expensive and
confusing verification call to check_helper_mem_access().

Error messages change in this patch. Before, messages about illegal
zero-size accesses depended on the type of the pointer and on other
conditions, and sometimes the message was plain wrong: in some tests
that changed you'll see that the old message was something like "R1 min
value is outside of the allowed memory range", where R1 is the pointer
register; the error was wrongly claiming that the pointer was bad
instead of the size being bad. Other times the information that the size
came for a register with a possible range of values was wrong, and the
error presented the size as a fixed zero. Now the errors refer to the
right register. However, the old error messages did contain useful
information about the pointer register which is now lost; recovering
this information was deemed not important enough.

(*) Besides standing to reason that the checks for a bigger size access
are a super-set of the checks for a smaller size access, I have also
mechanically verified this by reading the code for all types of
pointers. I could convince myself that it's true for all but
PTR_TO_BTF_ID (check_ptr_to_btf_access). There, simply looking
line-by-line does not immediately prove what we want. If anyone has any
qualms, let me know.

Signed-off-by: Andrei Matei <andreimatei1@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20231221232225.568730-2-andreimatei1@gmail.com

authored by

Andrei Matei and committed by
Andrii Nakryiko
8a021e7f 2ab1efad

+9 -11
+4 -6
kernel/bpf/verifier.c
··· 7279 7279 return -EACCES; 7280 7280 } 7281 7281 7282 - if (reg->umin_value == 0) { 7283 - err = check_helper_mem_access(env, regno - 1, 0, 7284 - zero_size_allowed, 7285 - meta); 7286 - if (err) 7287 - return err; 7282 + if (reg->umin_value == 0 && !zero_size_allowed) { 7283 + verbose(env, "R%d invalid zero-sized read: u64=[%lld,%lld]\n", 7284 + regno, reg->umin_value, reg->umax_value); 7285 + return -EACCES; 7288 7286 } 7289 7287 7290 7288 if (reg->umax_value >= BPF_MAX_VAR_SIZ) {
+4 -4
tools/testing/selftests/bpf/progs/verifier_helper_value_access.c
··· 91 91 92 92 SEC("tracepoint") 93 93 __description("helper access to map: empty range") 94 - __failure __msg("invalid access to map value, value_size=48 off=0 size=0") 94 + __failure __msg("R2 invalid zero-sized read") 95 95 __naked void access_to_map_empty_range(void) 96 96 { 97 97 asm volatile (" \ ··· 221 221 222 222 SEC("tracepoint") 223 223 __description("helper access to adjusted map (via const imm): empty range") 224 - __failure __msg("invalid access to map value, value_size=48 off=4 size=0") 224 + __failure __msg("R2 invalid zero-sized read") 225 225 __naked void via_const_imm_empty_range(void) 226 226 { 227 227 asm volatile (" \ ··· 386 386 387 387 SEC("tracepoint") 388 388 __description("helper access to adjusted map (via const reg): empty range") 389 - __failure __msg("R1 min value is outside of the allowed memory range") 389 + __failure __msg("R2 invalid zero-sized read") 390 390 __naked void via_const_reg_empty_range(void) 391 391 { 392 392 asm volatile (" \ ··· 556 556 557 557 SEC("tracepoint") 558 558 __description("helper access to adjusted map (via variable): empty range") 559 - __failure __msg("R1 min value is outside of the allowed memory range") 559 + __failure __msg("R2 invalid zero-sized read") 560 560 __naked void map_via_variable_empty_range(void) 561 561 { 562 562 asm volatile (" \
+1 -1
tools/testing/selftests/bpf/progs/verifier_raw_stack.c
··· 64 64 65 65 SEC("tc") 66 66 __description("raw_stack: skb_load_bytes, zero len") 67 - __failure __msg("invalid zero-sized read") 67 + __failure __msg("R4 invalid zero-sized read: u64=[0,0]") 68 68 __naked void skb_load_bytes_zero_len(void) 69 69 { 70 70 asm volatile (" \