On Mon, 10 Dec 2018 11:35:12 -0800, Jakub Kicinski wrote: > Currently for liveness and state pruning the register parentage > chains don't include states of the callee. This makes some sense > as the callee can't access those registers. However, this means > that READs done after the callee returns will not propagate into > the states of the callee. Callee will then perform pruning > disregarding differences in caller state. > > Example: > > 0: (85) call bpf_user_rnd_u32 > 1: (b7) r8 = 0 > 2: (55) if r0 != 0x0 goto pc+1 > 3: (b7) r8 = 1 > 4: (bf) r1 = r8 > 5: (85) call pc+4 > 6: (15) if r8 == 0x1 goto pc+1 > 7: (05) *(u64 *)(r9 - 8) = r3 > 8: (b7) r0 = 0 > 9: (95) exit > > 10: (15) if r1 == 0x0 goto pc+0 > 11: (95) exit > > Here we acquire unknown state with call to get_random() [1]. Then > we store this random state in r8 (either 0 or 1) [1 - 3], and make > a call on line 5. Callee does nothing but a trivial conditional > jump (to create a pruning point). Upon return caller checks the > state of r8 and either performs an unsafe read or not. > > Verifier will first explore the path with r8 == 1, creating a pruning > point at [11]. The parentage chain for r8 will include only callers > states so once verifier reaches [6] it will mark liveness only on states > in the caller, and not [11]. Now when verifier walks the paths with > r8 == 0 it will reach [11] and since REG_LIVE_READ on r8 was not > propagated there it will prune the walk entirely (stop walking > the entire program, not just the callee). Since [6] was never walked > with r8 == 0, [7] will be considered dead and replaced with "goto -1" > causing hang at runtime. > > This patch weaves the callee's explored states onto the callers > parentage chain. > > v1: don't unnecessarily link caller saved regs (Jiong) > > Fixes: f4d7e40a5b71 ("bpf: introduce function calls (verification)") > Reported-by: David Beckett <david.beck...@netronome.com> > Signed-off-by: Jakub Kicinski <jakub.kicin...@netronome.com> > Reviewed-by: Jiong Wang <jiong.w...@netronome.com> > --- > resend with netdev included
And CC Ed, sorry.. > kernel/bpf/verifier.c | 10 +++++++- > tools/testing/selftests/bpf/test_verifier.c | 28 +++++++++++++++++++++ > 2 files changed, 37 insertions(+), 1 deletion(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index fc760d00a38c..60d57d9d3663 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -5114,11 +5114,19 @@ static int is_state_visited(struct bpf_verifier_env > *env, int insn_idx) > for (i = 0; i < BPF_REG_FP; i++) > cur->frame[cur->curframe]->regs[i].live = REG_LIVE_NONE; > > - /* all stack frames are accessible from callee, clear them all */ > + /* connect new state to parentage chain: > + * - all stack frames are accessible from callee > + * - even though other stack frames' registers are not accessible > + * liveness must propagate through the callees' states otherwise > + * not knowing the liveness callee may prune caller > + */ > for (j = 0; j <= cur->curframe; j++) { > struct bpf_func_state *frame = cur->frame[j]; > struct bpf_func_state *newframe = new->frame[j]; > > + for (i = BPF_REG_6; i < BPF_REG_FP; i++) > + frame->regs[i].parent = &newframe->regs[i]; > + > for (i = 0; i < frame->allocated_stack / BPF_REG_SIZE; i++) { > frame->stack[i].spilled_ptr.live = REG_LIVE_NONE; > frame->stack[i].spilled_ptr.parent = > diff --git a/tools/testing/selftests/bpf/test_verifier.c > b/tools/testing/selftests/bpf/test_verifier.c > index df6f751cc1e8..373611ae9f52 100644 > --- a/tools/testing/selftests/bpf/test_verifier.c > +++ b/tools/testing/selftests/bpf/test_verifier.c > @@ -13915,6 +13915,34 @@ static struct bpf_test tests[] = { > .result_unpriv = REJECT, > .result = ACCEPT, > }, > + { > + "calls: cross frame pruning", > + .insns = { > + /* r8 = !!random(); > + * call pruner() > + * if (r8) > + * do something bad; > + */ > + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, > + BPF_FUNC_get_prandom_u32), > + BPF_MOV64_IMM(BPF_REG_8, 0), > + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), > + BPF_MOV64_IMM(BPF_REG_8, 1), > + BPF_MOV64_REG(BPF_REG_1, BPF_REG_8), > + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 4), > + BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, 1, 1), > + BPF_LDX_MEM(BPF_B, BPF_REG_9, BPF_REG_1, 0), > + BPF_MOV64_IMM(BPF_REG_0, 0), > + BPF_EXIT_INSN(), > + BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 0), > + BPF_EXIT_INSN(), > + }, > + .prog_type = BPF_PROG_TYPE_SOCKET_FILTER, > + .errstr_unpriv = "function calls to other bpf functions are > allowed for root only", > + .result_unpriv = REJECT, > + .errstr = "!read_ok", > + .result = REJECT, > + }, > }; > > static int probe_filter_length(const struct bpf_insn *fp)