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

bpf: verifier: Refactor helper access type tracking

Previously, the verifier was treating all PTR_TO_STACK registers passed
to a helper call as potentially written to by the helper. However, all
calls to check_stack_range_initialized() already have precise access type
information available.

Rather than treat ACCESS_HELPER as a proxy for BPF_WRITE, pass
enum bpf_access_type to check_stack_range_initialized() to more
precisely track helper arguments.

One benefit from this precision is that registers tracked as valid
spills and passed as a read-only helper argument remain tracked after
the call. Rather than being marked STACK_MISC afterwards.

An additional benefit is the verifier logs are also more precise. For
this particular error, users will enjoy a slightly clearer message. See
included selftest updates for examples.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Daniel Xu <dxu@dxuuu.xyz>
Link: https://lore.kernel.org/r/ff885c0e5859e0cd12077c3148ff0754cad4f7ed.1736886479.git.dxu@dxuuu.xyz
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Daniel Xu and committed by
Alexei Starovoitov
37cce22d 8ac412a3

+42 -54
+16 -29
kernel/bpf/verifier.c
··· 5303 5303 static int check_stack_range_initialized(struct bpf_verifier_env *env, 5304 5304 int regno, int off, int access_size, 5305 5305 bool zero_size_allowed, 5306 - enum bpf_access_src type, 5306 + enum bpf_access_type type, 5307 5307 struct bpf_call_arg_meta *meta); 5308 5308 5309 5309 static struct bpf_reg_state *reg_state(struct bpf_verifier_env *env, int regno) ··· 5336 5336 /* Note that we pass a NULL meta, so raw access will not be permitted. 5337 5337 */ 5338 5338 err = check_stack_range_initialized(env, ptr_regno, off, size, 5339 - false, ACCESS_DIRECT, NULL); 5339 + false, BPF_READ, NULL); 5340 5340 if (err) 5341 5341 return err; 5342 5342 ··· 7190 7190 static int check_stack_access_within_bounds( 7191 7191 struct bpf_verifier_env *env, 7192 7192 int regno, int off, int access_size, 7193 - enum bpf_access_src src, enum bpf_access_type type) 7193 + enum bpf_access_type type) 7194 7194 { 7195 7195 struct bpf_reg_state *regs = cur_regs(env); 7196 7196 struct bpf_reg_state *reg = regs + regno; ··· 7199 7199 int err; 7200 7200 char *err_extra; 7201 7201 7202 - if (src == ACCESS_HELPER) 7203 - /* We don't know if helpers are reading or writing (or both). */ 7204 - err_extra = " indirect access to"; 7205 - else if (type == BPF_READ) 7202 + if (type == BPF_READ) 7206 7203 err_extra = " read from"; 7207 7204 else 7208 7205 err_extra = " write to"; ··· 7417 7420 7418 7421 } else if (reg->type == PTR_TO_STACK) { 7419 7422 /* Basic bounds checks. */ 7420 - err = check_stack_access_within_bounds(env, regno, off, size, ACCESS_DIRECT, t); 7423 + err = check_stack_access_within_bounds(env, regno, off, size, t); 7421 7424 if (err) 7422 7425 return err; 7423 7426 ··· 7637 7640 static int check_stack_range_initialized( 7638 7641 struct bpf_verifier_env *env, int regno, int off, 7639 7642 int access_size, bool zero_size_allowed, 7640 - enum bpf_access_src type, struct bpf_call_arg_meta *meta) 7643 + enum bpf_access_type type, struct bpf_call_arg_meta *meta) 7641 7644 { 7642 7645 struct bpf_reg_state *reg = reg_state(env, regno); 7643 7646 struct bpf_func_state *state = func(env, reg); 7644 7647 int err, min_off, max_off, i, j, slot, spi; 7645 - char *err_extra = type == ACCESS_HELPER ? " indirect" : ""; 7646 - enum bpf_access_type bounds_check_type; 7647 7648 /* Some accesses can write anything into the stack, others are 7648 7649 * read-only. 7649 7650 */ ··· 7652 7657 return -EACCES; 7653 7658 } 7654 7659 7655 - if (type == ACCESS_HELPER) { 7656 - /* The bounds checks for writes are more permissive than for 7657 - * reads. However, if raw_mode is not set, we'll do extra 7658 - * checks below. 7659 - */ 7660 - bounds_check_type = BPF_WRITE; 7660 + if (type == BPF_WRITE) 7661 7661 clobber = true; 7662 - } else { 7663 - bounds_check_type = BPF_READ; 7664 - } 7665 - err = check_stack_access_within_bounds(env, regno, off, access_size, 7666 - type, bounds_check_type); 7662 + 7663 + err = check_stack_access_within_bounds(env, regno, off, access_size, type); 7667 7664 if (err) 7668 7665 return err; 7669 7666 ··· 7672 7685 char tn_buf[48]; 7673 7686 7674 7687 tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off); 7675 - verbose(env, "R%d%s variable offset stack access prohibited for !root, var_off=%s\n", 7676 - regno, err_extra, tn_buf); 7688 + verbose(env, "R%d variable offset stack access prohibited for !root, var_off=%s\n", 7689 + regno, tn_buf); 7677 7690 return -EACCES; 7678 7691 } 7679 7692 /* Only initialized buffer on stack is allowed to be accessed ··· 7754 7767 } 7755 7768 7756 7769 if (tnum_is_const(reg->var_off)) { 7757 - verbose(env, "invalid%s read from stack R%d off %d+%d size %d\n", 7758 - err_extra, regno, min_off, i - min_off, access_size); 7770 + verbose(env, "invalid read from stack R%d off %d+%d size %d\n", 7771 + regno, min_off, i - min_off, access_size); 7759 7772 } else { 7760 7773 char tn_buf[48]; 7761 7774 7762 7775 tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off); 7763 - verbose(env, "invalid%s read from stack R%d var_off %s+%d size %d\n", 7764 - err_extra, regno, tn_buf, i - min_off, access_size); 7776 + verbose(env, "invalid read from stack R%d var_off %s+%d size %d\n", 7777 + regno, tn_buf, i - min_off, access_size); 7765 7778 } 7766 7779 return -EACCES; 7767 7780 mark: ··· 7836 7849 return check_stack_range_initialized( 7837 7850 env, 7838 7851 regno, reg->off, access_size, 7839 - zero_size_allowed, ACCESS_HELPER, meta); 7852 + zero_size_allowed, access_type, meta); 7840 7853 case PTR_TO_BTF_ID: 7841 7854 return check_ptr_to_btf_access(env, regs, regno, reg->off, 7842 7855 access_size, BPF_READ, -1);
+3 -3
tools/testing/selftests/bpf/progs/dynptr_fail.c
··· 192 192 193 193 /* Can't add a dynptr to a map */ 194 194 SEC("?raw_tp") 195 - __failure __msg("invalid indirect read from stack") 195 + __failure __msg("invalid read from stack") 196 196 int add_dynptr_to_map1(void *ctx) 197 197 { 198 198 struct bpf_dynptr ptr; ··· 210 210 211 211 /* Can't add a struct with an embedded dynptr to a map */ 212 212 SEC("?raw_tp") 213 - __failure __msg("invalid indirect read from stack") 213 + __failure __msg("invalid read from stack") 214 214 int add_dynptr_to_map2(void *ctx) 215 215 { 216 216 struct test_info x; ··· 398 398 * dynptr argument 399 399 */ 400 400 SEC("?raw_tp") 401 - __failure __msg("invalid indirect read from stack") 401 + __failure __msg("invalid read from stack") 402 402 int invalid_helper1(void *ctx) 403 403 { 404 404 struct bpf_dynptr ptr;
+1 -1
tools/testing/selftests/bpf/progs/test_global_func10.c
··· 26 26 } 27 27 28 28 SEC("cgroup_skb/ingress") 29 - __failure __msg("invalid indirect access to stack") 29 + __failure __msg("invalid read from stack") 30 30 int global_func10(struct __sk_buff *skb) 31 31 { 32 32 const struct Small small = {.x = skb->len };
+3 -2
tools/testing/selftests/bpf/progs/uninit_stack.c
··· 70 70 r1 = r10; \ 71 71 r1 += -128; \ 72 72 r2 = 32; \ 73 - call %[bpf_trace_printk]; \ 73 + r3 = 0; \ 74 + call %[bpf_probe_read_user]; \ 74 75 /* Call to dummy() forces print_verifier_state(..., true), \ 75 76 * thus showing the stack state, matched by __msg(). \ 76 77 */ \ ··· 80 79 exit; \ 81 80 " 82 81 : 83 - : __imm(bpf_trace_printk), 82 + : __imm(bpf_probe_read_user), 84 83 __imm(dummy) 85 84 : __clobber_all); 86 85 }
+1 -1
tools/testing/selftests/bpf/progs/verifier_basic_stack.c
··· 28 28 SEC("socket") 29 29 __description("uninitialized stack1") 30 30 __success __log_level(4) __msg("stack depth 8") 31 - __failure_unpriv __msg_unpriv("invalid indirect read from stack") 31 + __failure_unpriv __msg_unpriv("invalid read from stack") 32 32 __naked void uninitialized_stack1(void) 33 33 { 34 34 asm volatile (" \
+2 -2
tools/testing/selftests/bpf/progs/verifier_const_or.c
··· 25 25 26 26 SEC("tracepoint") 27 27 __description("constant register |= constant should not bypass stack boundary checks") 28 - __failure __msg("invalid indirect access to stack R1 off=-48 size=58") 28 + __failure __msg("invalid write to stack R1 off=-48 size=58") 29 29 __naked void not_bypass_stack_boundary_checks_1(void) 30 30 { 31 31 asm volatile (" \ ··· 62 62 63 63 SEC("tracepoint") 64 64 __description("constant register |= constant register should not bypass stack boundary checks") 65 - __failure __msg("invalid indirect access to stack R1 off=-48 size=58") 65 + __failure __msg("invalid write to stack R1 off=-48 size=58") 66 66 __naked void not_bypass_stack_boundary_checks_2(void) 67 67 { 68 68 asm volatile (" \
+6 -6
tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
··· 67 67 __description("helper access to variable memory: stack, bitwise AND, zero included") 68 68 /* in privileged mode reads from uninitialized stack locations are permitted */ 69 69 __success __failure_unpriv 70 - __msg_unpriv("invalid indirect read from stack R2 off -64+0 size 64") 70 + __msg_unpriv("invalid read from stack R2 off -64+0 size 64") 71 71 __retval(0) 72 72 __naked void stack_bitwise_and_zero_included(void) 73 73 { ··· 100 100 101 101 SEC("tracepoint") 102 102 __description("helper access to variable memory: stack, bitwise AND + JMP, wrong max") 103 - __failure __msg("invalid indirect access to stack R1 off=-64 size=65") 103 + __failure __msg("invalid write to stack R1 off=-64 size=65") 104 104 __naked void bitwise_and_jmp_wrong_max(void) 105 105 { 106 106 asm volatile (" \ ··· 187 187 188 188 SEC("tracepoint") 189 189 __description("helper access to variable memory: stack, JMP, bounds + offset") 190 - __failure __msg("invalid indirect access to stack R1 off=-64 size=65") 190 + __failure __msg("invalid write to stack R1 off=-64 size=65") 191 191 __naked void memory_stack_jmp_bounds_offset(void) 192 192 { 193 193 asm volatile (" \ ··· 211 211 212 212 SEC("tracepoint") 213 213 __description("helper access to variable memory: stack, JMP, wrong max") 214 - __failure __msg("invalid indirect access to stack R1 off=-64 size=65") 214 + __failure __msg("invalid write to stack R1 off=-64 size=65") 215 215 __naked void memory_stack_jmp_wrong_max(void) 216 216 { 217 217 asm volatile (" \ ··· 260 260 __description("helper access to variable memory: stack, JMP, no min check") 261 261 /* in privileged mode reads from uninitialized stack locations are permitted */ 262 262 __success __failure_unpriv 263 - __msg_unpriv("invalid indirect read from stack R2 off -64+0 size 64") 263 + __msg_unpriv("invalid read from stack R2 off -64+0 size 64") 264 264 __retval(0) 265 265 __naked void stack_jmp_no_min_check(void) 266 266 { ··· 750 750 __description("helper access to variable memory: 8 bytes leak") 751 751 /* in privileged mode reads from uninitialized stack locations are permitted */ 752 752 __success __failure_unpriv 753 - __msg_unpriv("invalid indirect read from stack R2 off -64+32 size 64") 753 + __msg_unpriv("invalid read from stack R2 off -64+32 size 64") 754 754 __retval(0) 755 755 __naked void variable_memory_8_bytes_leak(void) 756 756 {
+1 -1
tools/testing/selftests/bpf/progs/verifier_int_ptr.c
··· 96 96 97 97 SEC("cgroup/sysctl") 98 98 __description("arg pointer to long size < sizeof(long)") 99 - __failure __msg("invalid indirect access to stack R4 off=-4 size=8") 99 + __failure __msg("invalid write to stack R4 off=-4 size=8") 100 100 __naked void to_long_size_sizeof_long(void) 101 101 { 102 102 asm volatile (" \
+1 -1
tools/testing/selftests/bpf/progs/verifier_mtu.c
··· 8 8 __description("uninit/mtu: write rejected") 9 9 __success 10 10 __caps_unpriv(CAP_BPF|CAP_NET_ADMIN) 11 - __failure_unpriv __msg_unpriv("invalid indirect read from stack") 11 + __failure_unpriv __msg_unpriv("invalid read from stack") 12 12 int tc_uninit_mtu(struct __sk_buff *ctx) 13 13 { 14 14 __u32 mtu;
+2 -2
tools/testing/selftests/bpf/progs/verifier_raw_stack.c
··· 236 236 237 237 SEC("tc") 238 238 __description("raw_stack: skb_load_bytes, invalid access 1") 239 - __failure __msg("invalid indirect access to stack R3 off=-513 size=8") 239 + __failure __msg("invalid write to stack R3 off=-513 size=8") 240 240 __naked void load_bytes_invalid_access_1(void) 241 241 { 242 242 asm volatile (" \ ··· 255 255 256 256 SEC("tc") 257 257 __description("raw_stack: skb_load_bytes, invalid access 2") 258 - __failure __msg("invalid indirect access to stack R3 off=-1 size=8") 258 + __failure __msg("invalid write to stack R3 off=-1 size=8") 259 259 __naked void load_bytes_invalid_access_2(void) 260 260 { 261 261 asm volatile (" \
+1 -1
tools/testing/selftests/bpf/progs/verifier_unpriv.c
··· 199 199 SEC("socket") 200 200 __description("unpriv: indirectly pass pointer on stack to helper function") 201 201 __success __failure_unpriv 202 - __msg_unpriv("invalid indirect read from stack R2 off -8+0 size 8") 202 + __msg_unpriv("invalid read from stack R2 off -8+0 size 8") 203 203 __retval(0) 204 204 __naked void on_stack_to_helper_function(void) 205 205 {
+4 -4
tools/testing/selftests/bpf/progs/verifier_var_off.c
··· 203 203 204 204 SEC("sockops") 205 205 __description("indirect variable-offset stack access, unbounded") 206 - __failure __msg("invalid unbounded variable-offset indirect access to stack R4") 206 + __failure __msg("invalid unbounded variable-offset write to stack R4") 207 207 __naked void variable_offset_stack_access_unbounded(void) 208 208 { 209 209 asm volatile (" \ ··· 236 236 237 237 SEC("lwt_in") 238 238 __description("indirect variable-offset stack access, max out of bound") 239 - __failure __msg("invalid variable-offset indirect access to stack R2") 239 + __failure __msg("invalid variable-offset read from stack R2") 240 240 __naked void access_max_out_of_bound(void) 241 241 { 242 242 asm volatile (" \ ··· 269 269 */ 270 270 SEC("socket") 271 271 __description("indirect variable-offset stack access, zero-sized, max out of bound") 272 - __failure __msg("invalid variable-offset indirect access to stack R1") 272 + __failure __msg("invalid variable-offset write to stack R1") 273 273 __naked void zero_sized_access_max_out_of_bound(void) 274 274 { 275 275 asm volatile (" \ ··· 294 294 295 295 SEC("lwt_in") 296 296 __description("indirect variable-offset stack access, min out of bound") 297 - __failure __msg("invalid variable-offset indirect access to stack R2") 297 + __failure __msg("invalid variable-offset read from stack R2") 298 298 __naked void access_min_out_of_bound(void) 299 299 { 300 300 asm volatile (" \
+1 -1
tools/testing/selftests/bpf/verifier/calls.c
··· 2252 2252 BPF_EXIT_INSN(), 2253 2253 }, 2254 2254 .fixup_map_hash_48b = { 7 }, 2255 - .errstr_unpriv = "invalid indirect read from stack R2 off -8+0 size 8", 2255 + .errstr_unpriv = "invalid read from stack R2 off -8+0 size 8", 2256 2256 .result_unpriv = REJECT, 2257 2257 /* in privileged mode reads from uninitialized stack locations are permitted */ 2258 2258 .result = ACCEPT,