A call that a cleanup record covers can also reach the record's landing pad. Add that edge to the CFG walk, to bpf_insn_successors(), and to stack liveness, where a slot is alive across a call when the call's pad reads it. The CFG walk pushes the pad in a visit of its own, and the fall-through on the next visit. bpf_check_cfg() re-visits the top of its stack until DONE_EXPLORING, so a visit pushing both would leave the pad DISCOVERED off the current path, and push_insn() would take a branch from one pad into another -- how a frame with two regions chains them -- for a back-edge. Without the liveness change, a slot only the pad reads looked dead, and clean_verifier_state() poisoned it while the callee ran. Signed-off-by: Yonghong Song --- kernel/bpf/cfg.c | 38 ++++++++++++++++++++++++++++++++++++++ kernel/bpf/liveness.c | 24 +++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c index d8a579680e5b..63afbc5fb296 100644 --- a/kernel/bpf/cfg.c +++ b/kernel/bpf/cfg.c @@ -6,6 +6,7 @@ #include #include "diagnostics.h" +#include "exception.h" #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args) @@ -160,6 +161,38 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env) return DONE_EXPLORING; } +static int visit_cleanup_pad_edge(int t, struct bpf_verifier_env *env) +{ + int *insn_stack = env->cfg.insn_stack; + int *insn_state = env->cfg.insn_state; + int w; + + if (!env->cleanup_info_cnt) + return DONE_EXPLORING; + w = bpf_exc_pad_of_call(env, t); + if (w < 0) + return DONE_EXPLORING; + + /* + * @t is a call that may branch here, and @w is the target of that + * branch, so both are prune points. @w especially: every covered call + * site in a region unwinds to the same pad, and without a prune point + * at its head the verifier walks the pad again for each of them. + */ + mark_prune_point(env, t); + mark_prune_point(env, w); + mark_jmp_point(env, w); + mark_jump_target(env, w); + + if (insn_state[w]) + return DONE_EXPLORING; + if (env->cfg.cur_stack >= env->prog->len) + return -E2BIG; + insn_stack[env->cfg.cur_stack++] = w; + insn_state[w] |= DISCOVERED; + return KEEP_EXPLORING; +} + static int visit_func_call_insn(int t, struct bpf_insn *insns, struct bpf_verifier_env *env, bool visit_callee) @@ -167,6 +200,11 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns, int ret, insn_sz; int w; + /* One push per visit: @t is revisited once the pad is explored. */ + ret = visit_cleanup_pad_edge(t, env); + if (ret != DONE_EXPLORING) + return ret; + insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1; ret = push_insn(t, t + insn_sz, FALLTHROUGH, env); if (ret) diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c index cd9523f69298..2e7eead71c93 100644 --- a/kernel/bpf/liveness.c +++ b/kernel/bpf/liveness.c @@ -8,6 +8,8 @@ #include #include +#include "exception.h" + #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args) /* @@ -384,6 +386,18 @@ bpf_insn_successors(struct bpf_verifier_env *env, u32 idx) succ->items[succ->cnt++] = exit_idx; } + /* + * A covered call can also leave through its landing pad. Only a call to + * a subprog or to bpf_unwind() is covered, so the two entries of + * env->succ are enough. + */ + if (unlikely(env->cleanup_info_cnt)) { + int pad = bpf_exc_pad_of_call(env, idx); + + if (pad >= 0) + succ->items[succ->cnt++] = pad; + } + return succ; } @@ -510,7 +524,8 @@ bool bpf_stack_slot_alive(struct bpf_verifier_env *env, u32 frameno, u32 half_sp * Slot is alive if it is read before q->insn_idx in current func instance, * or if for some outer func instance: * - alive before callsite if callsite calls callback or is callx, otherwise - * - alive after callsite + * - alive after callsite, + * - or alive at the landing pad a cleanup record gives the callsite */ struct live_stack_query *q = &env->liveness->live_stack_query; struct func_instance *instance, *curframe_instance; @@ -545,6 +560,13 @@ bool bpf_stack_slot_alive(struct bpf_verifier_env *env, u32 frameno, u32 half_sp alive = callee_stack_access_at_callsite(env, callsite) ? is_live_before(instance, callsite, rel, half_spi) : is_live_before(instance, callsite + 1, rel, half_spi); + + if (!alive && unlikely(env->cleanup_info_cnt)) { + int pad = bpf_exc_pad_of_call(env, callsite); + + if (pad >= 0) + alive = is_live_before(instance, pad, rel, half_spi); + } if (alive) return true; } -- 2.53.0-Meta