bpf: Simplify checking size of helper accesses
Subsystems (4)
v4 cover bpf: Simplify checking size of helper accesses
v3->v4:
- kept only the minimal change, undoing debatable changes (Andrii)
- dropped the second patch from before, with changes to the error message (Andrii)
- extracted the new test into a separate patch (Andrii)
- added Acked by Andrii
v2->v3:
- split the error-logging function to a separate patch (Andrii)
- make the error buffers smaller (Andrii)
- include size of memory region for PTR_TO_MEM (Andrii)
- nits from Andrii and Eduard
v1->v2:
- make the error message include more info about the context of the zero-sized access (Andrii)
Andrei Matei (2):
bpf: Simplify checking size of helper accesses
bpf: add a possibly-zero-sized read test
kernel/bpf/verifier.c | 10 ++--- .../bpf/progs/verifier_helper_value_access.c | 45 +++++++++++++++++-- .../selftests/bpf/progs/verifier_raw_stack.c | 2 +- 3 files changed, 46 insertions(+), 11 deletions(-)
Signature · 1 line
2.40.1
PATCH v4 1/2 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.
--- kernel/bpf/verifier.c | 10 ++++------ .../selftests/bpf/progs/verifier_helper_value_access.c | 8 ++++---- tools/testing/selftests/bpf/progs/verifier_raw_stack.c | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-)
kernel/bpf/verifier.c +4 -6
@@ -7296,12 +7296,10 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,return -EACCES ; } -if (reg->umin_value ==0 ) { - err =check_helper_mem_access (env, regno -1 ,0 , - zero_size_allowed, - meta); -if (err) -return err; +if (reg->umin_value ==0 && !zero_size_allowed) { +verbose (env,"R%d invalid zero-sized read: u64=[%lld,%lld]\n" , + regno, reg->umin_value, reg->umax_value); +return -EACCES ; }if (reg->umax_value >=BPF_MAX_VAR_SIZ ) {
tools/testing/selftests/bpf/progs/verifier_helper_value_access.c +4 -4
@@ -91,7 +91,7 @@ l0_%=: exit; \ SEC("tracepoint") __description("helper access to map: empty range") -__failure __msg("invalid access to map value, value_size=48 off=0 size=0") +__failure __msg("R2 invalid zero-sized read") __naked void access_to_map_empty_range(void) { asm volatile (" \
@@ -221,7 +221,7 @@ l0_%=: exit; \ SEC("tracepoint") __description("helper access to adjusted map (via const imm): empty range") -__failure __msg("invalid access to map value, value_size=48 off=4 size=0") +__failure __msg("R2 invalid zero-sized read") __naked void via_const_imm_empty_range(void) { asm volatile (" \
@@ -386,7 +386,7 @@ l0_%=: exit; \ SEC("tracepoint") __description("helper access to adjusted map (via const reg): empty range") -__failure __msg("R1 min value is outside of the allowed memory range") +__failure __msg("R2 invalid zero-sized read") __naked void via_const_reg_empty_range(void) { asm volatile (" \
@@ -556,7 +556,7 @@ l0_%=: exit; \ SEC("tracepoint") __description("helper access to adjusted map (via variable): empty range") -__failure __msg("R1 min value is outside of the allowed memory range") +__failure __msg("R2 invalid zero-sized read") __naked void map_via_variable_empty_range(void) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_raw_stack.c +1 -1
@@ -64,7 +64,7 @@ __naked void load_bytes_negative_len_2(void) SEC("tc") __description("raw_stack: skb_load_bytes, zero len") -__failure __msg("invalid zero-sized read") +__failure __msg("R4 invalid zero-sized read: u64=[0,0]")__naked void skb_load_bytes_zero_len (void ) { asm volatile (" \
Signature · 1 line
2.40.1
PATCH v4 2/2 bpf: add a possibly-zero-sized read test
In reply to Andrei MateiThis patch adds a test for the condition that the previous patch mucked with - illegal zero-sized helper memory access. As opposed to existing tests, this new one uses a size whose lower bound is zero, as opposed to a known-zero one.
--- .../bpf/progs/verifier_helper_value_access.c | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-)
tools/testing/selftests/bpf/progs/verifier_helper_value_access.c +38 -1
@@ -89,9 +89,14 @@ l0_%=: exit; \ : __clobber_all); } +/* Call a function taking a pointer and a size which doesn't allow the size to +* be zero (i.e. bpf_trace_printk() declares the second argument to be +* ARG_CONST_SIZE, not ARG_CONST_SIZE_OR_ZERO). We attempt to pass zero for the +* size and expect to fail. +*/ SEC("tracepoint") __description("helper access to map: empty range") -__failure __msg("R2 invalid zero-sized read") +__failure __msg("R2 invalid zero-sized read: u64=[0,0]") __naked void access_to_map_empty_range(void) { asm volatile (" \
@@ -113,6 +118,38 @@ l0_%=: exit; \ : __clobber_all); } +/* Like the test above, but this time the size register is not known to be zero; +* its lower-bound is zero though, which is still unacceptable. +*/ +SEC("tracepoint") +__description("helper access to map: possibly-empty ange") +__failure __msg("R2 invalid zero-sized read: u64=[0,4]") +__naked void access_to_map_possibly_empty_range(void) +{ + asmvolatile (" \ +r2 = r10; \ +r2 += -8; \ +r1 = 0; \ +*(u64*)(r2 + 0) = r1; \ +r1 = %[map_hash_48b] ll; \ +call %[bpf_map_lookup_elem]; \ +if r0 == 0 goto l0_%=; \ +r1 = r0; \ +/* Read an unknown value */ \ +r7 = *(u64*)(r0 + 0); \ +/* Make it small and positive, to avoid other errors */ \ +r7 &= 4; \ +r2 = 0; \ +r2 += r7; \ +call %[bpf_trace_printk]; \ +l0_%=: exit; \ +" : + : __imm(bpf_map_lookup_elem), +__imm (bpf_trace_printk), +__imm_addr (map_hash_48b) + : __clobber_all); +} + SEC("tracepoint") __description("helper access to map: out-of-bound range") __failure __msg("invalid access to map value, value_size=48 off=0 size=56")
Signature · 1 line
2.40.1
This series was applied to bpf/bpf-next.git (master) by Andrii Nakryiko <[email protected]>:
Hello:
This series was applied to bpf/bpf-next.git (master) by Andrii Nakryiko <[email protected]>:
Quoted 8 lines · On Thu, 21 Dec 2023 18:22:23 -0500 you wrote:
v3->v4:
- kept only the minimal change, undoing debatable changes (Andrii)
- dropped the second patch from before, with changes to the error message (Andrii)
- extracted the new test into a separate patch (Andrii)
- added Acked by Andrii[...]
Here is the summary with links:
- [bpf-next,v4,1/2] bpf: Simplify checking size of helper accesses
https://git.kernel.org/bpf/bpf-next/c/da0391947c10 - [bpf-next,v4,2/2] bpf: add a possibly-zero-sized read test https://git.kernel.org/bpf/bpf-next/c/faf05f4d56a4
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html