On Wed, Jun 7, 2023 at 6:26 PM Eduard Zingerman <eddyz87@xxxxxxxxx> wrote: > > On Wed, 2023-06-07 at 14:40 -0700, Andrii Nakryiko wrote: > > On Tue, Jun 6, 2023 at 3:24 PM Eduard Zingerman <eddyz87@xxxxxxxxx> wrote: > > > > > > Make sure that the following unsafe example is rejected by verifier: > > > > > > 1: r9 = ... some pointer with range X ... > > > 2: r6 = ... unbound scalar ID=a ... > > > 3: r7 = ... unbound scalar ID=b ... > > > 4: if (r6 > r7) goto +1 > > > 5: r6 = r7 > > > 6: if (r6 > X) goto ... > > > --- checkpoint --- > > > 7: r9 += r7 > > > 8: *(u64 *)r9 = Y > > > > > > This example is unsafe because not all execution paths verify r7 range. > > > Because of the jump at (4) the verifier would arrive at (6) in two states: > > > I. r6{.id=b}, r7{.id=b} via path 1-6; > > > II. r6{.id=a}, r7{.id=b} via path 1-4, 6. > > > > > > Currently regsafe() does not call check_ids() for scalar registers, > > > thus from POV of regsafe() states (I) and (II) are identical. If the > > > path 1-6 is taken by verifier first, and checkpoint is created at (6) > > > the path [1-4, 6] would be considered safe. > > > > > > This commit updates regsafe() to call check_ids() for precise scalar > > > registers. > > > > > > To minimize the impact on verification performance, avoid generating > > > bpf_reg_state::id for constant scalar values when processing BPF_MOV > > > in check_alu_op(). Scalar IDs are utilized by find_equal_scalars() to > > > propagate information about value ranges for registers that hold the > > > same value. However, there is no need to propagate range information > > > for constants. > > > > > > Still, there is some performance impact because of this change. > > > Using veristat to compare number of processed states for selftests > > > object files listed in tools/testing/selftests/bpf/veristat.cfg and > > > Cilium object files from [1] gives the following statistics: > > > > > > $ ./veristat -e file,prog,states -f "states_pct>10" \ > > > -C master-baseline.log current.log > > > File Program States (DIFF) > > > ----------- ------------------------------ -------------- > > > bpf_xdp.o tail_handle_nat_fwd_ipv6 +155 (+23.92%) > > > bpf_xdp.o tail_nodeport_nat_ingress_ipv4 +102 (+27.20%) > > > bpf_xdp.o tail_rev_nodeport_lb4 +83 (+20.85%) > > > loop6.bpf.o trace_virtqueue_add_sgs +25 (+11.06%) > > > > > > Also test case verifier_search_pruning/allocated_stack has to be > > > updated to avoid conflicts in register ID assignments between cached > > > and new states. > > > > > > [1] git@xxxxxxxxxx:anakryiko/cilium.git > > > > > > Fixes: 75748837b7e5 ("bpf: Propagate scalar ranges through register assignments.") > > > Signed-off-by: Eduard Zingerman <eddyz87@xxxxxxxxx> > > > --- > > > > So I checked it also on our internal BPF object files, and it looks > > mostly good. Here are the only regressions: > > > > Program States (A) States (B) > > States (DIFF) > > ---------------------------------------- ---------- ---------- > > --------------- > > balancer_ingress 29219 34531 > > +5312 (+18.18%) > > syar_bind6_protect6 3257 3599 > > +342 (+10.50%) > > syar_bind4_protect4 2590 2931 > > +341 (+13.17%) > > on_alloc 415 526 > > +111 (+26.75%) > > on_free 406 517 > > +111 (+27.34%) > > pycallcount 395 506 > > +111 (+28.10%) > > resume_context 405 516 > > +111 (+27.41%) > > on_py_event 395 506 > > +111 (+28.10%) > > on_event 284 394 > > +110 (+38.73%) > > handle_cuda_event 268 378 > > +110 (+41.04%) > > handle_cuda_launch 276 386 > > +110 (+39.86%) > > handle_cuda_malloc_ret 272 382 > > +110 (+40.44%) > > handle_cuda_memcpy 270 380 > > +110 (+40.74%) > > handle_cuda_memcpy_async 270 380 > > +110 (+40.74%) > > handle_pytorch_allocate_ret 271 381 > > +110 (+40.59%) > > handle_pytorch_malloc_ret 272 382 > > +110 (+40.44%) > > on_event 284 394 > > +110 (+38.73%) > > on_event 284 394 > > +110 (+38.73%) > > syar_task_enter_execve 309 329 > > +20 (+6.47%) > > kprobe__security_inode_link 968 986 > > +18 (+1.86%) > > kprobe__security_inode_symlink 838 854 > > +16 (+1.91%) > > tw_twfw_egress 249 251 > > +2 (+0.80%) > > tw_twfw_ingress 250 252 > > +2 (+0.80%) > > tw_twfw_tc_eg 248 250 > > +2 (+0.81%) > > tw_twfw_tc_in 250 252 > > +2 (+0.80%) > > raw_tracepoint__sched_process_exec 136 139 > > +3 (+2.21%) > > kprobe_ret__do_filp_open 869 871 > > +2 (+0.23%) > > read_erlang_stack 572 573 > > +1 (+0.17%) > > > > > > They are mostly on small-ish programs. The only mild concern from my > > side is balancer_ingress, which is one of Katran BPF programs. It add > > +18% of states (which translates to about 70K more instructions > > verified, up from 350K). I think we can live with this, but would be > > nice to check why it's happening. > > Thank you for reviewing this series. > > I looked at the logs that you've shared, the difference is indeed > caused by some scalar registers having a unique ID in cached state and > no ID in current state or vice versa. The !rold->id trick that we > discussed for V2 helps :) > > What do you think about an alternative way to exclude unique scalars > as in the patch below? (on top of this patch-set): > > --- 8< ------------------------- > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 235d7eded565..ece9722dff3b 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -15149,6 +15149,13 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_id_pair *idmap) > return false; > } > > +static bool check_scalar_ids(u32 old_id, u32 cur_id, struct bpf_verifier_env *env) > +{ > + old_id = old_id ? old_id : env->id_gen++; > + cur_id = cur_id ? cur_id : env->id_gen++; > + return check_ids(old_id, cur_id, env->idmap_scratch); > +} > + > static void clean_func_state(struct bpf_verifier_env *env, > struct bpf_func_state *st) > { > @@ -15325,7 +15332,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, > */ > return range_within(rold, rcur) && > tnum_in(rold->var_off, rcur->var_off) && > - check_ids(rold->id, rcur->id, idmap); > + check_scalar_ids(rold->id, rcur->id, env); > case PTR_TO_MAP_KEY: > case PTR_TO_MAP_VALUE: > case PTR_TO_MEM: > > ------------------------- >8 --- > > For me this patch removes all veristat differences compared to the > master. If doing it for real, I'd like to reset env->id_gen at exit > from states_equal() to the value it had at entry (to avoid allocating > too many ids). Hm.. It's clever and pretty minimal, I like it. We are basically allocating virtual ID for SCALAR that doesn't have id, just to make sure we get a conflict if the SCALAR with ID cannot be mapped into two different SCALARs, right? The only question would be if it's safe to do that for case when old_reg->id != 0 and cur_reg->id == 0? E.g., if in old (verified) state we have r6.id = r7.id = 123, and in new state we have r6.id = 0 and r7.id = 0, then your implementation above will say that states are equivalent. But are they, given there is a link between r6 and r7 that might be required for correctness. Which we don't have in current state. So with this we are getting to my original concerns with your !rold->id approach, which tries to ignore the necessity of link between registers. What if we do this only for old registers? Then, (in old state) r6.id = 0, r7.id = 0, (in new state) r6.id = r7.id = 123. This will be rejected because first we'll map 123 to newly allocated X for r6.id, and then when we try to match r7.id=123 to another allocated ID X+1 we'll get a conflict, right? > > > > > I suspect that dropping SCALAR IDs as we discussed (after fixing > > register fill/spill ID generation) might completely mitigate that. > > > > Overall, LGTM: > > > > Acked-by: Andrii Nakryiko <andrii@xxxxxxxxxx> > > > > > kernel/bpf/verifier.c | 34 ++++++++++++++++--- > > > .../bpf/progs/verifier_search_pruning.c | 3 +- > > > 2 files changed, 32 insertions(+), 5 deletions(-) > > > > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > > index 2aa60b73f1b5..175ca22b868e 100644 > > > --- a/kernel/bpf/verifier.c > > > +++ b/kernel/bpf/verifier.c > > > @@ -12933,12 +12933,14 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) > > > if (BPF_SRC(insn->code) == BPF_X) { > > > struct bpf_reg_state *src_reg = regs + insn->src_reg; > > > struct bpf_reg_state *dst_reg = regs + insn->dst_reg; > > > + bool need_id = (src_reg->type == SCALAR_VALUE && !src_reg->id && > > > + !tnum_is_const(src_reg->var_off)); > > > > > > > nit: unnecessary outer () > > > > > if (BPF_CLASS(insn->code) == BPF_ALU64) { > > > /* case: R1 = R2 > > > * copy register state to dest reg > > > */ > > > - if (src_reg->type == SCALAR_VALUE && !src_reg->id) > > > + if (need_id) > > > /* Assign src and dst registers the same ID > > > * that will be used by find_equal_scalars() > > > * to propagate min/max range. > > > > [...] >