bpf: consolidate pointer offset tracking in var_off
Subsystems (8)
Eduard Zingerman
maintainerv2 cover bpf: consolidate pointer offset tracking in var_off
Consolidate static and varying pointer offset tracking logic in the BPF verifier. All pointer offsets are now represented solely using reg->var_off and min/max fields, simplifying pointer tracking code and making it easier to widen pointer registers for loop convergence checks.
Patch 1 is a preparatory refactoring of check_reg_sane_offset(). Patch 2 is the main change, moving pointer offsets from reg->off
to reg->var_off.
Patch 3 removes references to reg->off in netronome code. Patch 4 renames the now-repurposed reg->off field to reg->delta,
reflecting its remaining role as a constant delta between linked scalar registers.
Note: netronome changes are compile-tested only!
Changelog:
v1 -> v2:
- put back WARN_ON_ONCE in mark_ptr_or_null_reg() (Alexei).
- references to ptr->off field are removed from netronome code (bot+bpf-ci, kernel test robot).
- fix for a comment referencing ptr->off in bpf_verifier.h (bot+bpf-ci).
v1: https://lore.kernel.org/bpf/[email protected]/
---
Eduard Zingerman (4):
bpf: split check_reg_sane_offset() in two parts
bpf: use reg->var_off instead of reg->off for pointers
nfp: bpf: remove references to bpf_reg_state->off for pointers
bpf: rename bpf_reg_state->off to bpf_reg_state->deltadrivers/net/ethernet/netronome/nfp/bpf/jit.c | 18 +- drivers/net/ethernet/netronome/nfp/bpf/verifier.c | 12 +- include/linux/bpf_verifier.h | 9 +- kernel/bpf/log.c | 10 +- kernel/bpf/verifier.c | 368 +++++++++------------ .../testing/selftests/bpf/prog_tests/linked_list.c | 4 +- .../selftests/bpf/progs/exceptions_assert.c | 2 +- tools/testing/selftests/bpf/progs/iters.c | 6 +- .../selftests/bpf/progs/mem_rdonly_untrusted.c | 2 +- tools/testing/selftests/bpf/progs/verifier_align.c | 40 +-- .../testing/selftests/bpf/progs/verifier_bounds.c | 2 +- .../bpf/progs/verifier_direct_packet_access.c | 4 +- tools/testing/selftests/bpf/progs/verifier_gotox.c | 4 +- .../bpf/progs/verifier_helper_packet_access.c | 2 +- .../bpf/progs/verifier_helper_value_access.c | 4 +- .../testing/selftests/bpf/progs/verifier_int_ptr.c | 2 +- .../selftests/bpf/progs/verifier_meta_access.c | 2 +- .../selftests/bpf/progs/verifier_spill_fill.c | 8 +- .../selftests/bpf/progs/verifier_stack_ptr.c | 4 +- .../selftests/bpf/progs/verifier_value_ptr_arith.c | 10 +- .../bpf/progs/verifier_xdp_direct_packet_access.c | 64 ++-- tools/testing/selftests/bpf/verifier/calls.c | 2 +- 22 files changed, 260 insertions(+), 319 deletions(-)
---
base-commit: a86c608c067034f7e9da626cee81b75baeef16e8
change-id: 20260211-ptrs-off-migration-9ad00d0c3f93
Eduard Zingerman
maintainerPATCH v2 1/4 bpf: split check_reg_sane_offset() in two parts
check_reg_sane_offset() is used when verifying operations like:
dst_reg += src_reg
^ ^
| '-------- scalar
'------------------- pointer
To verify range for both dst_reg and src_reg. Split it in two parts:
- one to check a pointer offset
- another to check scalar offset
This would be useful for further refactoring.
--- kernel/bpf/verifier.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-)
kernel/bpf/verifier.c +27 -12
@@ -14426,9 +14426,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,return 0 ; } -static bool check_reg_sane_offset (struct bpf_verifier_env *env, -const struct bpf_reg_state *reg, -enum bpf_reg_type type) +static bool check_reg_sane_offset_scalar (struct bpf_verifier_env *env, +const struct bpf_reg_state *reg, +enum bpf_reg_type type) {bool known =tnum_is_const (reg->var_off);s64 val = reg->var_off.value;
@@ -14440,12 +14440,6 @@ static bool check_reg_sane_offset(struct bpf_verifier_env *env,return false; } -if (reg->off >=BPF_MAX_VAR_OFF || reg->off <= -BPF_MAX_VAR_OFF ) { -verbose (env,"%s pointer offset %d is not allowed\n" , -reg_type_str (env, type), reg->off); -return false; - } -if (smin ==S64_MIN ) {verbose (env,"math between %s pointer and register with unbounded min value is not allowed\n" ,reg_type_str (env, type));
@@ -14461,6 +14455,27 @@ static bool check_reg_sane_offset(struct bpf_verifier_env *env,return true; } +static bool check_reg_sane_offset_ptr (struct bpf_verifier_env *env, +const struct bpf_reg_state *reg, +enum bpf_reg_type type) +{ +s64 smin = reg->smin_value; + +if (reg->off >=BPF_MAX_VAR_OFF || reg->off <= -BPF_MAX_VAR_OFF ) { +verbose (env,"%s pointer offset %d is not allowed\n" , +reg_type_str (env, type), reg->off); +return false; + } + +if (smin >=BPF_MAX_VAR_OFF || smin <= -BPF_MAX_VAR_OFF ) { +verbose (env,"%s pointer offset %lld is not allowed\n" , +reg_type_str (env, type), smin); +return false; + } + +return true; +} +enum {REASON_BOUNDS =-1 ,REASON_TYPE =-2 ,
@@ -14874,8 +14889,8 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, dst_reg->type = ptr_reg->type; dst_reg->id = ptr_reg->id; -if (!check_reg_sane_offset (env, off_reg, ptr_reg->type) || - !check_reg_sane_offset (env, ptr_reg, ptr_reg->type)) +if (!check_reg_sane_offset_scalar (env, off_reg, ptr_reg->type) || + !check_reg_sane_offset_ptr (env, ptr_reg, ptr_reg->type))return -EINVAL ;/* pointer types do not carry 32-bit bounds at the moment. */
@@ -15004,7 +15019,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,return -EACCES ; } -if (!check_reg_sane_offset (env, dst_reg, ptr_reg->type)) +if (!check_reg_sane_offset_ptr (env, dst_reg, ptr_reg->type))return -EINVAL ;reg_bounds_sync (dst_reg); bounds_ret =sanitize_check_bounds (env, insn, dst_reg);
Signature · 1 line
2.51.1
Eduard Zingerman
maintainerPATCH v2 2/4 bpf: use reg->var_off instead of reg->off for pointers
In reply to Eduard ZingermanThis commit consolidates static and varying pointer offset tracking logic. All offsets are now represented solely using .var_off and min/max fields. The reasons are twofold:
- This simplifies pointer tracking code, as each relevant function needs to check the .var_off field anyway.
- It makes it easier to widen pointer registers for the purpose of loop convergence checks, by forgoing the regsafe() logic demanding .off fields to be identical.
The changes are spread across many functions and are hard to group into smaller patches. Some of the logical changes include:
- Checks in __check_ptr_off_reg() are reordered so that the tnum_is_const() check is done before operating on reg->var_off.value.
- check_packet_access() now uses check_mem_region_access() to handle possible 'off' overflow cases.
- In check_helper_mem_access() utility functions like check_packet_access() are now called with 'off=0', as these utility functions now account for the complete register offset range.
- In check_reg_type() a call to __check_ptr_off_reg() is added before a call to btf_struct_ids_match(). This prevents btf_struct_ids_match() from potentially working on non-constant reg->var_off.value.
- regsafe() is relaxed to avoid comparing '.off' field for pointers.
As a precaution, the changes are verified in [1] by adding a pass checking that no pointer has non-zero '.off' field on each do_check_insn() iteration.
[1] https://github.com/eddyz87/bpf/tree/ptrs-off-migration
Notable selftests changes:
- .var_off value changed because it now combines static and varying offsets. Affected tests:
- linked_list/incorrect_node_var_off
- linked_list/incorrect_head_var_off2
- verifier_align/packet_variable_offset
- Overflowing smax_value bound leads to a pointer with big negative or positive offset to be rejected immediately (previously overflowing rX += const instruction updated .off field avoiding the overflow). Affected tests:
- verifier_align/dubious_pointer_arithmetic
- verifier_bounds/var_off_insn_off_test1
- Invalid access to packet now reports full offset inside a packet. Affected tests:
- verifier_direct_packet_access/test23_x_pkt_ptr_4
- A change in check_mem_region_access() behavior:
when register .smin_value is negative, it reports
"rX min value is negative..." before calling into __check_mem_access() which reports "invalid access to ...".
In the tests below, the .off field was negative, while .smin_value remained positive. This is no longer the case after the changes in this commit. Affected tests:
- verifier_gotox/jump_table_invalid_mem_acceess_neg
- verifier_helper_packet_access/test15_cls_helper_fail_sub
- verifier_helper_value_access/imm_out_of_bound_2
- verifier_helper_value_access/reg_out_of_bound_2
- verifier_meta_access/meta_access_test2
- verifier_value_ptr_arith/known_scalar_from_different_maps
- lower_oob_arith_test_1
- value_ptr_known_scalar_3
- access_value_ptr_known_scalar
- Usage of check_mem_region_access() instead of __check_mem_access() in check_packet_access() changes the reported message from "rX offset is outside ..." to "rX min/max value is outside ...". Affected tests:
- verifier_xdp_direct_packet_access/*
- In check_func_arg_reg_off() the check for zero offset now operates on .var_off field instead of .off field. For tests where the pattern looks like kfunc(reg_with_var_off, ...), this changes the reported error:
- previously the error "variable ... access ... disallowed"
was reported by __check_ptr_off_reg();
- now "R1 must have zero offset ..." is reported by check_func_arg_reg_off() itself.
Affected tests:
- verifier/calls.c
"calls: invalid kfunc call: PTR_TO_BTF_ID with variable offset"
--- include/linux/bpf_verifier.h | 3 +- kernel/bpf/log.c | 2 + kernel/bpf/verifier.c | 317 ++++++++------------- .../testing/selftests/bpf/prog_tests/linked_list.c | 4 +- .../selftests/bpf/progs/exceptions_assert.c | 2 +- tools/testing/selftests/bpf/progs/iters.c | 6 +- .../selftests/bpf/progs/mem_rdonly_untrusted.c | 2 +- tools/testing/selftests/bpf/progs/verifier_align.c | 40 ++- .../testing/selftests/bpf/progs/verifier_bounds.c | 2 +- .../bpf/progs/verifier_direct_packet_access.c | 4 +- tools/testing/selftests/bpf/progs/verifier_gotox.c | 4 +- .../bpf/progs/verifier_helper_packet_access.c | 2 +- .../bpf/progs/verifier_helper_value_access.c | 4 +- .../testing/selftests/bpf/progs/verifier_int_ptr.c | 2 +- .../selftests/bpf/progs/verifier_meta_access.c | 2 +- .../selftests/bpf/progs/verifier_spill_fill.c | 8 +- .../selftests/bpf/progs/verifier_stack_ptr.c | 4 +- .../selftests/bpf/progs/verifier_value_ptr_arith.c | 10 +- .../bpf/progs/verifier_xdp_direct_packet_access.c | 64 ++--- tools/testing/selftests/bpf/verifier/calls.c | 2 +- 20 files changed, 206 insertions(+), 278 deletions(-)
include/linux/bpf_verifier.h +1 -2
@@ -38,8 +38,7 @@ struct bpf_reg_state {/* Ordering of fields matters. See states_equal() */ enum bpf_reg_type type;/* -* Fixed part of pointer offset, pointer types only. -* Or constant delta between "linked" scalars with the same ID. +* Constant delta between "linked" scalars with the same ID. */ s32 off;union {
kernel/bpf/log.c +2 -0
@@ -581,6 +581,8 @@ int tnum_strn(char *str, size_t size, struct tnum a)if (a.mask ==0 ) {if (is_unum_decimal (a.value))return snprintf (str, size,"%llu" , a.value); +if (is_snum_decimal (a.value)) +return snprintf (str, size,"%lld" , a.value);else return snprintf (str, size,"%#llx" , a.value); }
kernel/bpf/verifier.c +126 -191
764 lines not on this page · raw message
tools/testing/selftests/bpf/prog_tests/linked_list.c +2 -2
@@ -87,12 +87,12 @@ static struct { {"incorrect_value_type" ,"operation on bpf_list_head expects arg#1 bpf_list_node at offset=48 in struct foo, " "but arg is at offset=0 in struct bar" }, - { "incorrect_node_var_off", "variable ptr_ access var_off=(0x0; 0xffffffff) disallowed" }, + { "incorrect_node_var_off", "variable ptr_ access var_off=(0x0; 0x1ffffffff) disallowed" }, { "incorrect_node_off1", "bpf_list_node not found at offset=49" }, { "incorrect_node_off2", "arg#1 offset=0, but expected bpf_list_node at offset=48 in struct foo" }, { "no_head_type", "bpf_list_head not found at offset=0" }, { "incorrect_head_var_off1", "R1 doesn't have constant offset" }, - { "incorrect_head_var_off2", "variable ptr_ access var_off=(0x0; 0xffffffff) disallowed" }, + { "incorrect_head_var_off2", "variable ptr_ access var_off=(0x0; 0x1ffffffff) disallowed" }, { "incorrect_head_off1", "bpf_list_head not found at offset=25" }, { "incorrect_head_off2", "bpf_list_head not found at offset=1" }, { "pop_front_off", "off 48 doesn't point to 'struct bpf_spin_lock' that is at 40" },
tools/testing/selftests/bpf/progs/exceptions_assert.c +1 -1
@@ -114,7 +114,7 @@ int check_assert_single_range_u64(struct __sk_buff *ctx) SEC("?tc") __log_level(2) __failure -__msg(": R1=pkt(off=64,r=64) R2=pkt_end() R6=pkt(r=64) R10=fp0") +__msg(": R1=pkt(r=64,imm=64) R2=pkt_end() R6=pkt(r=64) R10=fp0")int check_assert_generic (struct __sk_buff *ctx) {u8 *data_end = (void *)(long )ctx->data_end;
tools/testing/selftests/bpf/progs/iters.c +3 -3
@@ -1651,7 +1651,7 @@ int clean_live_states(const void *ctx) SEC("?raw_tp")__flag (BPF_F_TEST_STATE_FREQ) -__failure __msg("misaligned stack access off 0+-31+0 size 8") +__failure __msg("misaligned stack access off -31+0 size 8")__naked int absent_mark_in_the_middle_state (void ) {/* This is equivalent to C program below.
@@ -1726,7 +1726,7 @@ static int noop(void) SEC("?raw_tp")__flag (BPF_F_TEST_STATE_FREQ) -__failure __msg("misaligned stack access off 0+-31+0 size 8") +__failure __msg("misaligned stack access off -31+0 size 8")__naked int absent_mark_in_the_middle_state2 (void ) {/* This is equivalent to C program below.
@@ -1802,7 +1802,7 @@ __naked int absent_mark_in_the_middle_state2(void) SEC("?raw_tp")__flag (BPF_F_TEST_STATE_FREQ) -__failure __msg("misaligned stack access off 0+-31+0 size 8") +__failure __msg("misaligned stack access off -31+0 size 8")__naked int absent_mark_in_the_middle_state3 (void ) {/*
tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c +1 -1
@@ -8,7 +8,7 @@ SEC("tp_btf/sys_enter") __success __log_level(2) -__msg("r8 = *(u64 *)(r7 +0) ; R7=ptr_nameidata(off={{[0-9]+}}) R8=rdonly_untrusted_mem(sz=0)") +__msg("r8 = *(u64 *)(r7 +0) ; R7=ptr_nameidata(imm={{[0-9]+}}) R8=rdonly_untrusted_mem(sz=0)") __msg("r9 = *(u8 *)(r8 +0) ; R8=rdonly_untrusted_mem(sz=0) R9=scalar")int btf_id_to_ptr_mem (void *ctx) {
tools/testing/selftests/bpf/progs/verifier_align.c +17 -23
@@ -131,7 +131,7 @@ LBL ":" \ SEC("tc") __success __log_level(2) __flag(BPF_F_ANY_ALIGNMENT) -__msg("6: R0=pkt(off=8,r=8)") +__msg("6: R0=pkt(r=8,imm=8)") __msg("6: {{.*}} R3={{[^)]*}}var_off=(0x0; 0xff)") __msg("7: {{.*}} R3={{[^)]*}}var_off=(0x0; 0x1fe)")__msg ("8: {{.*}} R3={{[^)]*}}var_off=(0x0; 0x3fc)" )
@@ -203,10 +203,10 @@ __naked void unknown_mul(void) SEC("tc") __success __log_level(2) __msg("2: {{.*}} R5=pkt(r=0)") -__msg("4: {{.*}} R5=pkt(off=14,r=0)") -__msg("5: {{.*}} R4=pkt(off=14,r=0)") +__msg("4: {{.*}} R5=pkt(r=0,imm=14)") +__msg("5: {{.*}} R4=pkt(r=0,imm=14)") __msg("9: {{.*}} R2=pkt(r=18)") -__msg("10: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff){{.*}} R5=pkt(off=14,r=18)") +__msg("10: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff){{.*}} R5=pkt(r=18,imm=14)") __msg("13: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xffff)") __msg("14: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xffff)")__naked void packet_const_offset (void )
@@ -247,14 +247,14 @@ __msg("7: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")/* Offset is added to packet pointer R5, resulting in * known fixed offset, and variable offset from R6. */ -__msg ("11: {{.*}} R5=pkt(id=1,off=14," ) +__msg ("11: {{.*}} R5=pkt(id=1,{{[^)]*}},var_off=(0x2; 0x7fc)" )/* At the time the word size load is performed from R5, * it's total offset is NET_IP_ALIGN + reg->off (0) + * reg->aux_off (14) which is 16. Then the variable * offset is considered using reg->aux_off_align which * is 4 and meets the load's requirements. */ -__msg ("15: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc){{.*}} R5={{[^)]*}}var_off=(0x0; 0x3fc)" ) +__msg ("15: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)" )/* Variable offset is added to R5 packet pointer, * resulting in auxiliary alignment of 4. To avoid BPF * verifier's precision backtracking logging
@@ -266,37 +266,37 @@ __msg("18: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc){{.*}} R5={{[^)]*}}var_off=(0x/* Constant offset is added to R5, resulting in * reg->off of 14. */ -__msg ("19: {{.*}} R5=pkt(id=2,off=14," ) +__msg ("19: {{.*}} R5=pkt(id=2,{{[^)]*}}var_off=(0x2; 0x7fc)" )/* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off * (14) which is 16. Then the variable offset is 4-byte * aligned, so the total offset is 4-byte aligned and * meets the load's requirements. */ -__msg ("24: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc){{.*}} R5={{[^)]*}}var_off=(0x0; 0x3fc)" ) +__msg ("24: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)" )/* Constant offset is added to R5 packet pointer, * resulting in reg->off value of 14. */ -__msg ("26: {{.*}} R5=pkt(off=14,r=8)" ) +__msg ("26: {{.*}} R5=pkt(r=8,imm=14)" )/* Variable offset is added to R5, resulting in a * variable offset of (4n). See comment for insn #18 * for R4 = R5 trick. */ -__msg ("28: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc){{.*}} R5={{[^)]*}}var_off=(0x0; 0x3fc)" ) +__msg ("28: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)" ) /* Constant is added to R5 again, setting reg->off to 18. */ -__msg ("29: {{.*}} R5=pkt(id=3,off=18," ) +__msg ("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0x7fc)" )/* And once more we add a variable; resulting {{[^)]*}}var_off * is still (4n), fixed offset is not changed. * Also, we create a new reg->id. */ -__msg ("31: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x0; 0x7fc)" ) +__msg ("31: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc){{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)" )/* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (18) * which is 20. Then the variable offset is (4n), so * the total offset is 4-byte aligned and meets the * load's requirements. */ -__msg("35: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x0; 0x7fc)") +__msg("35: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc){{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)")__naked void packet_variable_offset (void ) { asm volatile (" \
@@ -430,16 +430,10 @@ __msg("6: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xfffffffffffffffc)")/* Checked s>=0 */ __msg ("9: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)" ) /* packet pointer + nonnegative (4n+2) */ -__msg("11: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)") -__msg ("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)" ) -/* NET_IP_ALIGN + (4n+2) == (4n), alignment is fine. -* We checked the bounds, but it might have been able -* to overflow if the packet pointer started in the -* upper half of the address space. -* So we did not get a 'range' on R6, and the access -* attempt will fail. - */ -__msg("15: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)") +__msg("11: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc){{.*}} R6={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)") +__msg ("12: (07) r4 += 4" ) +/* packet smax bound overflow */ +__msg("pkt pointer offset -9223372036854775808 is not allowed")__naked void dubious_pointer_arithmetic (void ) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_bounds.c +1 -1
@@ -202,7 +202,7 @@ l0_%=: /* exit */ \ SEC("tc") __description("bounds check based on reg_off + var_off + insn_off. test1") -__failure __msg("value_size=8 off=1073741825") +__failure __msg("map_value pointer offset 1073741822 is not allowed")__naked void var_off_insn_off_test1 (void ) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c +2 -2
@@ -412,7 +412,7 @@ l0_%=: r0 = 0; \ SEC("tc") __description("direct packet access: test17 (pruning, alignment)") -__failure __msg("misaligned packet access off 2+0+15+-4 size 4") +__failure __msg("misaligned packet access off 2+15+-4 size 4")__flag (BPF_F_STRICT_ALIGNMENT) __naked void packet_access_test17_pruning_alignment(void) {
@@ -569,7 +569,7 @@ l0_%=: r0 = 0; \ SEC("tc") __description("direct packet access: test23 (x += pkt_ptr, 4)") -__failure __msg("invalid access to packet, off=0 size=8, R5(id=3,off=0,r=0)") +__failure __msg("invalid access to packet, off=31 size=8, R5(id=3,off=31,r=0)")__flag (BPF_F_ANY_ALIGNMENT) __naked void test23_x_pkt_ptr_4(void) {
tools/testing/selftests/bpf/progs/verifier_gotox.c +2 -2
@@ -131,7 +131,7 @@ DEFINE_INVALID_SIZE_PROG(u16, __failure __msg("Invalid read of 2 bytes from insn DEFINE_INVALID_SIZE_PROG(u8, __failure __msg("Invalid read of 1 bytes from insn_array")) SEC("socket") -__failure __msg("misaligned value access off 0+1+0 size 8") +__failure __msg("misaligned value access off 1+0 size 8") __naked void jump_table_misaligned_access(void) { asm volatile (" \
@@ -187,7 +187,7 @@ jt0_%=: \ } SEC("socket") -__failure __msg("invalid access to map value, value_size=16 off=-24 size=8") +__failure __msg("R0 min value is negative") __naked void jump_table_invalid_mem_acceess_neg(void) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c +1 -1
@@ -360,7 +360,7 @@ l0_%=: r0 = 0; \ SEC("tc") __description("helper access to packet: test15, cls helper fail sub") -__failure __msg("invalid access to packet") +__failure __msg("R1 min value is negative")__naked void test15_cls_helper_fail_sub (void ) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_helper_value_access.c +2 -2
@@ -1100,7 +1100,7 @@ l0_%=: exit; \ SEC("tracepoint") __description("map helper access to adjusted map (via const imm): out-of-bound 2") -__failure __msg("invalid access to map value, value_size=16 off=-4 size=8") +__failure __msg("R2 min value is negative") __naked void imm_out_of_bound_2(void) { asm volatile (" \
@@ -1176,7 +1176,7 @@ l0_%=: exit; \ SEC("tracepoint") __description("map helper access to adjusted map (via const reg): out-of-bound 2") -__failure __msg("invalid access to map value, value_size=16 off=-4 size=8") +__failure __msg("R2 min value is negative") __naked void reg_out_of_bound_2(void) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_int_ptr.c +1 -1
@@ -65,7 +65,7 @@ __naked void ptr_to_long_half_uninitialized(void) SEC("cgroup/sysctl") __description("arg pointer to long misaligned") -__failure __msg("misaligned stack access off 0+-20+0 size 8") +__failure __msg("misaligned stack access off -20+0 size 8") __naked void arg_ptr_to_long_misaligned(void) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_meta_access.c +1 -1
@@ -27,7 +27,7 @@ l0_%=: r0 = 0; \ SEC("xdp") __description("meta access, test2") -__failure __msg("invalid access to packet, off=-8") +__failure __msg("R0 min value is negative")__naked void meta_access_test2 (void ) { asm volatile (" \
tools/testing/selftests/bpf/progs/verifier_spill_fill.c +4 -4
@@ -656,7 +656,7 @@ __msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-8 before 5: (7b) *(u64 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1") __msg("mark_precise: frame0: regs= stack=-8 before 3: (7a) *(u64 *)(r10 -8) = 1") -__msg ("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1" ) +__msg ("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1" ) /* validate load from fp-16, which was initialized using BPF_STX_MEM */ __msg("12: (79) r2 = *(u64 *)(r10 -16) ; R2=1 R10=fp0 fp-16=1")__msg ("13: (0f) r1 += r2" )
@@ -673,7 +673,7 @@ __msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-16 before 5: (7b) *(u64 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1") -__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1") +__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1")__naked void stack_load_preserves_const_precision (void ) { asm volatile (
@@ -732,7 +732,7 @@ __msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-8 before 5: (63) *(u32 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1") __msg("mark_precise: frame0: regs= stack=-8 before 3: (62) *(u32 *)(r10 -8) = 1") -__msg ("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1" ) +__msg ("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1" ) /* validate load from fp-16, which was initialized using BPF_STX_MEM */ __msg("12: (61) r2 = *(u32 *)(r10 -16) ; R2=1 R10=fp0 fp-16=????1")__msg ("13: (0f) r1 += r2" )
@@ -748,7 +748,7 @@ __msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-16 before 5: (63) *(u32 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1") -__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1") +__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1")__naked void stack_load_preserves_const_precision_subreg (void ) { asm volatile (
tools/testing/selftests/bpf/progs/verifier_stack_ptr.c +2 -2
@@ -37,7 +37,7 @@ __naked void ptr_to_stack_store_load(void) SEC("socket") __description("PTR_TO_STACK store/load - bad alignment on off") -__failure __msg("misaligned stack access off 0+-8+2 size 8") +__failure __msg("misaligned stack access off -8+2 size 8") __failure_unpriv__naked void load_bad_alignment_on_off (void ) {
@@ -53,7 +53,7 @@ __naked void load_bad_alignment_on_off(void) SEC("socket") __description("PTR_TO_STACK store/load - bad alignment on reg") -__failure __msg("misaligned stack access off 0+-10+8 size 8") +__failure __msg("misaligned stack access off -10+8 size 8") __failure_unpriv__naked void load_bad_alignment_on_reg (void ) {
tools/testing/selftests/bpf/progs/verifier_value_ptr_arith.c +4 -6
@@ -346,7 +346,7 @@ l2_%=: r0 = 1; \ SEC("socket") __description("map access: value_ptr -= known scalar from different maps") __success __failure_unpriv -__msg_unpriv("R0 min value is outside of the allowed memory range") +__msg_unpriv("R0 min value is negative") __retval(1) __naked void known_scalar_from_different_maps(void) {
@@ -683,9 +683,7 @@ l0_%=: r0 = 1; \ SEC("socket") __description("map access: value_ptr -= known scalar, lower oob arith, test 1") -__failure __msg("R0 min value is outside of the allowed memory range") -__failure_unpriv -__msg_unpriv("R0 pointer arithmetic of map value goes out of range") +__failure __msg("R0 min value is negative") __naked void lower_oob_arith_test_1(void) { asm volatile (" \
@@ -840,7 +838,7 @@ l0_%=: r0 = 1; \ SEC("socket") __description("map access: value_ptr += known scalar, 3") -__failure __msg("invalid access to map value") +__failure __msg("R0 min value is negative") __failure_unpriv__naked void value_ptr_known_scalar_3 (void ) {
@@ -1207,7 +1205,7 @@ l0_%=: r0 = 1; \ SEC("socket") __description("map access: value_ptr -= known scalar") -__failure __msg("R0 min value is outside of the allowed memory range") +__failure __msg("R0 min value is negative") __failure_unpriv__naked void access_value_ptr_known_scalar (void ) {
tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c +32 -32
256 lines not on this page · raw message
tools/testing/selftests/bpf/verifier/calls.c +1 -1
@@ -220,7 +220,7 @@ }, .result_unpriv = REJECT, .result = REJECT, - .errstr = "variable trusted_ptr_ access var_off=(0x0; 0x7) disallowed", + .errstr = "R1 must have zero offset when passed to release func or trusted arg to kfunc", }, { "calls: invalid kfunc call: referenced arg needs refcounted PTR_TO_BTF_ID",
Signature · 1 line
2.51.1
Eduard Zingerman
maintainerPATCH v2 3/4 nfp: bpf: remove references to bpf_reg_state->off for pointers
In reply to Eduard ZingermanPrevious commit changed BPF verifier to track pointer offsets entirely in bpf_reg_state->var_off. This commit removes references to bpf_reg_state->off in netronome code.
- In most of the places .off field is used as a part of a sum:
reg->off + reg->var_off.value. In such code the reg->off addend is removed.
- Function jit.c:cross_mem_access() uses .off but ignores .var_off to compute memory access offset. Here access to .off is replaced with access to .var_off.value. verifier.c:nfp_bpf_check_ptr() rejects programs for which !tnum_is_const(reg->var_off), so the above change will read a valid constant.
Not sure why .var_off was ignored in this function previously.
--- drivers/net/ethernet/netronome/nfp/bpf/jit.c | 18 ++++++++---------- drivers/net/ethernet/netronome/nfp/bpf/verifier.c | 12 ++++++------ 2 files changed, 14 insertions(+), 16 deletions(-)
drivers/net/ethernet/netronome/nfp/bpf/jit.c +8 -10
@@ -1731,7 +1731,7 @@ map_call_stack_common(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)/* We only have to reload LM0 if the key is not at start of stack */ lm_off = nfp_prog->stack_frame_depth; - lm_off += meta->arg2.reg.var_off.value + meta->arg2.reg.off; + lm_off += meta->arg2.reg.var_off.value; load_lm_ptr = meta->arg2.var_off || lm_off;/* Set LM0 to start of key */
@@ -2874,8 +2874,7 @@ mem_ldx(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta, }if (meta->ptr.type ==PTR_TO_STACK ) -return mem_ldx_stack (nfp_prog, meta, size, - meta->ptr.off + meta->ptr.var_off.value); +return mem_ldx_stack (nfp_prog, meta, size, meta->ptr.var_off.value);if (meta->ptr.type ==PTR_TO_MAP_VALUE )return mem_ldx_emem (nfp_prog, meta, size);
@@ -2985,8 +2984,7 @@ mem_stx(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,return mem_stx_data (nfp_prog, meta, size);if (meta->ptr.type ==PTR_TO_STACK ) -return mem_stx_stack (nfp_prog, meta, size, - meta->ptr.off + meta->ptr.var_off.value); +return mem_stx_stack (nfp_prog, meta, size, meta->ptr.var_off.value);return -EOPNOTSUPP ; }
@@ -4153,9 +4151,9 @@ cross_mem_access(struct bpf_insn *ld, struct nfp_insn_meta *head_ld_meta,/* Canonicalize the offsets. Turn all of them against the original * base register. */ - head_ld_off = head_ld_meta->insn.off + head_ld_meta->ptr.off; - head_st_off = head_st_meta->insn.off + head_st_meta->ptr.off; - ld_off = ld->off + head_ld_meta->ptr.off; + head_ld_off = head_ld_meta->insn.off + head_ld_meta->ptr.var_off.value; + head_st_off = head_st_meta->insn.off + head_st_meta->ptr.var_off.value; + ld_off = ld->off + head_ld_meta->ptr.var_off.value;/* Ascending order cross. */ if (ld_off > head_ld_off &&
@@ -4326,7 +4324,7 @@ static void nfp_bpf_opt_pkt_cache(struct nfp_prog *nfp_prog)* support this. */ if (meta->ptr.id == range_ptr_id && - meta->ptr.off == range_ptr_off) { + meta->ptr.var_off.value == range_ptr_off) {s16 new_start = range_start;s16 end, off = insn->off;s16 new_end = range_end;
@@ -4361,7 +4359,7 @@ static void nfp_bpf_opt_pkt_cache(struct nfp_prog *nfp_prog) range_node = meta; range_node->pkt_cache.do_init = true; range_ptr_id = range_node->ptr.id; - range_ptr_off = range_node->ptr.off; + range_ptr_off = range_node->ptr.var_off.value; range_start = insn->off; range_end = insn->off +BPF_LDST_BYTES (insn); }
drivers/net/ethernet/netronome/nfp/bpf/verifier.c +6 -6
@@ -98,7 +98,7 @@ static bool nfp_bpf_map_update_value_ok(struct bpf_verifier_env *env) offmap =map_to_offmap (reg1->map_ptr); nfp_map = offmap->dev_priv; - off = reg3->off + reg3->var_off.value; + off = reg3->var_off.value;for (i =0 ; i < offmap->map.value_size; i++) {struct bpf_stack_state *stack_entry;
@@ -137,7 +137,7 @@ nfp_bpf_stack_arg_ok(const char *fname, struct bpf_verifier_env *env,return false; } - off = reg->var_off.value + reg->off; + off = reg->var_off.value;if (-off %4 ) {pr_vlog (env,"%s: unaligned stack pointer %lld\n" , fname, -off);return false;
@@ -147,7 +147,7 @@ nfp_bpf_stack_arg_ok(const char *fname, struct bpf_verifier_env *env,if (!old_arg)return true; - old_off = old_arg->reg.var_off.value + old_arg->reg.off; + old_off = old_arg->reg.var_off.value; old_arg->var_off |= off != old_off;return true;
@@ -358,8 +358,8 @@ nfp_bpf_check_stack_access(struct nfp_prog *nfp_prog,if (meta->ptr.type ==NOT_INIT )return 0 ; - old_off = meta->ptr.off + meta->ptr.var_off.value; - new_off = reg->off + reg->var_off.value; + old_off = meta->ptr.var_off.value; + new_off = reg->var_off.value; meta->ptr_not_const |= old_off != new_off;
@@ -428,7 +428,7 @@ nfp_bpf_map_mark_used(struct bpf_verifier_env *env, struct nfp_insn_meta *meta,return -EOPNOTSUPP ; } - off = reg->var_off.value + meta->insn.off + reg->off; + off = reg->var_off.value + meta->insn.off; size =BPF_LDST_BYTES (&meta->insn); offmap =map_to_offmap (reg->map_ptr); nfp_map = offmap->dev_priv;
Signature · 1 line
2.51.1
Eduard Zingerman
maintainerPATCH v2 4/4 bpf: rename bpf_reg_state->off to bpf_reg_state->delta
In reply to Eduard ZingermanThis field is now used only for linked scalar registers tracking. Rename it to 'delta' to better describe it's purpose:
constant delta between "linked" scalars with the same ID.
--- include/linux/bpf_verifier.h | 6 +++--- kernel/bpf/log.c | 8 ++++---- kernel/bpf/verifier.c | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-)
include/linux/bpf_verifier.h +3 -3
@@ -40,7 +40,7 @@ struct bpf_reg_state {/* * Constant delta between "linked" scalars with the same ID. */ -s32 off; +s32 delta;union {/* valid when type == PTR_TO_PACKET */ int range;
@@ -145,9 +145,9 @@ struct bpf_reg_state {* Upper bit of ID is used to remember relationship between "linked" * registers. Example: * r1 = r2; both will have r1->id == r2->id == N -* r1 += 10; r1->id == N | BPF_ADD_CONST and r1->off == 10 +* r1 += 10; r1->id == N | BPF_ADD_CONST and r1->delta == 10 * r3 = r2; both will have r3->id == r2->id == N -* w3 += 10; r3->id == N | BPF_ADD_CONST32 and r3->off == 10 +* w3 += 10; r3->id == N | BPF_ADD_CONST32 and r3->delta == 10 */ #define BPF_ADD_CONST64 (1U << 31)#define BPF_ADD_CONST32 (1U << 30)
kernel/bpf/log.c +4 -4
@@ -694,7 +694,7 @@ static void print_reg_state(struct bpf_verifier_env *env,if (state->frameno != reg->frameno)verbose (env,"[%d]" , reg->frameno);if (tnum_is_const (reg->var_off)) { -verbose_snum (env, reg->var_off.value + reg->off); +verbose_snum (env, reg->var_off.value + reg->delta);return ; } }
@@ -704,7 +704,7 @@ static void print_reg_state(struct bpf_verifier_env *env,if (reg->id)verbose_a ("id=%d" , reg->id & ~BPF_ADD_CONST );if (reg->id &BPF_ADD_CONST ) -verbose (env,"%+d" , reg->off); +verbose (env,"%+d" , reg->delta);if (reg->ref_obj_id)verbose_a ("ref_obj_id=%d" , reg->ref_obj_id);if (type_is_non_owning_ref (reg->type))
@@ -716,9 +716,9 @@ static void print_reg_state(struct bpf_verifier_env *env, reg->map_ptr->key_size, reg->map_ptr->value_size); } -if (t !=SCALAR_VALUE && reg->off) { +if (t !=SCALAR_VALUE && reg->delta) {verbose_a ("off=" ); -verbose_snum (env, reg->off); +verbose_snum (env, reg->delta); }if (type_is_pkt_pointer (t)) {verbose_a ("r=" );
kernel/bpf/verifier.c +9 -9
@@ -5095,7 +5095,7 @@ static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,* Cleared it, since multiple rX += const are not supported. */ src_reg->id =0 ; - src_reg->off =0 ; + src_reg->delta =0 ; }if (!src_reg->id && !tnum_is_const (src_reg->var_off))
@@ -16219,14 +16219,14 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,* we cannot accumulate another val into rx->off. */ clear_id: - dst_reg->off =0 ; + dst_reg->delta =0 ; dst_reg->id =0 ; }else {if (alu32) dst_reg->id |=BPF_ADD_CONST32 ;else dst_reg->id |=BPF_ADD_CONST64 ; - dst_reg->off = off; + dst_reg->delta = off; } }else {/*
@@ -17305,18 +17305,18 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_sif ((reg->id & ~BPF_ADD_CONST ) != (known_reg->id & ~BPF_ADD_CONST ))continue ;if ((!(reg->id &BPF_ADD_CONST ) && !(known_reg->id &BPF_ADD_CONST )) || - reg->off == known_reg->off) { + reg->delta == known_reg->delta) {s32 saved_subreg_def = reg->subreg_def;copy_register_state (reg, known_reg); reg->subreg_def = saved_subreg_def; }else {s32 saved_subreg_def = reg->subreg_def; -s32 saved_off = reg->off; +s32 saved_off = reg->delta;u32 saved_id = reg->id; fake_reg.type =SCALAR_VALUE ; -__mark_reg_known (&fake_reg, (s64 )reg->off - (s64 )known_reg->off); +__mark_reg_known (&fake_reg, (s64 )reg->delta - (s64 )known_reg->delta);/* reg = known_reg; reg += delta */ copy_register_state (reg, known_reg);
@@ -17324,7 +17324,7 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s* Must preserve off, id and subreg_def flag, * otherwise another sync_linked_regs() will be incorrect. */ - reg->off = saved_off; + reg->delta = saved_off; reg->id = saved_id; reg->subreg_def = saved_subreg_def;
@@ -19629,7 +19629,7 @@ static void clear_singular_ids(struct bpf_verifier_env *env,continue ;if (idset_cnt_get (idset, reg->id & ~BPF_ADD_CONST ) ==1 ) { reg->id =0 ; - reg->off =0 ; + reg->delta =0 ; } })); }
@@ -19766,7 +19766,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,return false;/* Both have offset linkage: offsets must match */ -if ((rold->id &BPF_ADD_CONST ) && rold->off != rcur->off) +if ((rold->id &BPF_ADD_CONST ) && rold->delta != rcur->delta)return false;if (!check_scalar_ids (rold->id, rcur->id, idmap))
Signature · 1 line
2.51.1
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:
Hello:
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:
Quoted 14 lines · On Thu, 12 Feb 2026 13:34:20 -0800 you wrote:
Consolidate static and varying pointer offset tracking logic in the BPF verifier. All pointer offsets are now represented solely using
reg->var_offand min/max fields, simplifying pointer tracking code and making it easier to widen pointer registers for loop convergence checks.Patch 1 is a preparatory refactoring of
check_reg_sane_offset(). Patch 2 is the main change, moving pointer offsets fromreg->off
toreg->var_off.
Patch 3 removes references toreg->offin netronome code. Patch 4 renames the now-repurposedreg->offfield toreg->delta,
reflecting its remaining role as a constant delta between linked scalar registers.
Quoted 1 line
[...]
Here is the summary with links:
- [bpf-next,v2,1/4] bpf: split check_reg_sane_offset() in two parts
https://git.kernel.org/bpf/bpf-next/c/ed20a14309e0 - [bpf-next,v2,2/4] bpf: use reg->var_off instead of reg->off for pointers https://git.kernel.org/bpf/bpf-next/c/022ac0750883 - [bpf-next,v2,3/4] nfp: bpf: remove references to bpf_reg_state->off for pointers https://git.kernel.org/bpf/bpf-next/c/6363fc17c9f5 - [bpf-next,v2,4/4] bpf: rename bpf_reg_state->off to bpf_reg_state->delta https://git.kernel.org/bpf/bpf-next/c/3d91c618aca4
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html