mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
SCEV needs information about the program's basic-block structure. Piggyback on compute_predecessors() and set insn_aux_data->bb_end for an instruction if: - it has multiple successors, or - its sole successor has multiple predecessors, or - it is a jump instruction. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20261009-scev-minimal-rebase-v3-31-bcba9bc53ac9@gmail.com
617 lines
15 KiB
C
617 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/bpf_verifier.h>
|
|
|
|
static bool is_cfg_jump(struct bpf_insn *insn)
|
|
{
|
|
u8 class = BPF_CLASS(insn->code);
|
|
u8 opcode = BPF_OP(insn->code);
|
|
|
|
switch (class) {
|
|
case BPF_JMP:
|
|
case BPF_JMP32:
|
|
return opcode != BPF_CALL;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static struct bpf_iarray **compute_predecessors(struct bpf_verifier_env *env)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
struct bpf_iarray *succ, *preds, **result;
|
|
struct bpf_prog *prog = env->prog;
|
|
u32 *num_preds, i, s, len = prog->len;
|
|
struct bpf_insn *insn;
|
|
size_t sz;
|
|
void *tmp;
|
|
|
|
num_preds = kvcalloc(prog->len, sizeof(u32), GFP_KERNEL_ACCOUNT);
|
|
if (!num_preds)
|
|
return NULL;
|
|
|
|
/*
|
|
* 'result' layout:
|
|
* - array of pointers (struct bpf_iarray *)[len]
|
|
* - struct bpf_iarray one after another
|
|
*/
|
|
sz = size_mul(len, sizeof(*result) + sizeof(**result));
|
|
for (i = 0; i < len; i++) {
|
|
insn = env->prog->insnsi + i;
|
|
succ = bpf_insn_successors(env, i);
|
|
sz = size_add(sz, array_size(succ->cnt, sizeof(*succ->items)));
|
|
iarray_for_each(s, succ) {
|
|
num_preds[s]++;
|
|
}
|
|
if (bpf_is_ldimm64(insn))
|
|
i++;
|
|
}
|
|
|
|
result = kvzalloc(sz, GFP_KERNEL_ACCOUNT);
|
|
if (!result) {
|
|
kvfree(num_preds);
|
|
return NULL;
|
|
}
|
|
|
|
tmp = (void *)&result[len];
|
|
for (i = 0; i < len; i++) {
|
|
result[i] = tmp;
|
|
tmp += struct_size(result[i], items, num_preds[i]);
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
insn = env->prog->insnsi + i;
|
|
succ = bpf_insn_successors(env, i);
|
|
iarray_for_each(s, succ) {
|
|
preds = result[s];
|
|
preds->items[preds->cnt++] = i;
|
|
}
|
|
if (succ->cnt > 1 ||
|
|
(succ->cnt == 1 && num_preds[succ->items[0]] > 1) ||
|
|
is_cfg_jump(insn))
|
|
aux[i].bb_end = true;
|
|
if (bpf_is_ldimm64(insn))
|
|
i++;
|
|
}
|
|
|
|
kvfree(num_preds);
|
|
return result;
|
|
}
|
|
|
|
static int idoms_intersect(struct bpf_verifier_env *env, int a, int b)
|
|
{
|
|
int *postorder_nums = env->cfg.postorder_nums;
|
|
int *idoms = env->idoms;
|
|
|
|
while (a != b) {
|
|
while (postorder_nums[a] < postorder_nums[b]) {
|
|
a = idoms[a];
|
|
}
|
|
while (postorder_nums[b] < postorder_nums[a]) {
|
|
b = idoms[b];
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/* See "A Simple, Fast Dominance Algorithm" by Cooper et al. for details. */
|
|
static int compute_subprog_idoms(struct bpf_verifier_env *env, struct bpf_iarray **preds,
|
|
int subprog_idx)
|
|
{
|
|
struct bpf_subprog_info *subprog = &env->subprog_info[subprog_idx];
|
|
int start = subprog->start;
|
|
int po_first = subprog->postorder_start;
|
|
int po_last = (subprog + 1)->postorder_start - 1;
|
|
int *idoms = env->idoms;
|
|
int po_num, pred;
|
|
u32 work = 0;
|
|
bool changed;
|
|
|
|
idoms[start] = start;
|
|
changed = true;
|
|
do {
|
|
changed = false;
|
|
/*
|
|
* Iterate in reverse postorder, skipping the subprog entry:
|
|
* insn_postorder[po_last] is the subprogram entry.
|
|
*/
|
|
for (po_num = po_last - 1; po_num >= po_first; po_num--) {
|
|
int idx = env->cfg.insn_postorder[po_num];
|
|
int new_idom = -1;
|
|
|
|
iarray_for_each(pred, preds[idx]) {
|
|
if (++work % 1024 == 0) {
|
|
if (signal_pending(current))
|
|
return -EAGAIN;
|
|
cond_resched();
|
|
}
|
|
|
|
if (idoms[pred] == -1)
|
|
continue;
|
|
if (new_idom == -1)
|
|
new_idom = pred;
|
|
else
|
|
new_idom = idoms_intersect(env, pred, new_idom);
|
|
}
|
|
if (new_idom != -1 && idoms[idx] != new_idom) {
|
|
idoms[idx] = new_idom;
|
|
changed = true;
|
|
}
|
|
}
|
|
} while (changed);
|
|
idoms[start] = -1;
|
|
return 0;
|
|
}
|
|
|
|
int bpf_compute_idoms(struct bpf_verifier_env *env)
|
|
{
|
|
struct bpf_iarray **preds;
|
|
u32 len = env->prog->len;
|
|
int *idoms, i, err = 0;
|
|
|
|
preds = compute_predecessors(env);
|
|
if (!preds)
|
|
return -ENOMEM;
|
|
|
|
idoms = kvcalloc(len, sizeof(*idoms), GFP_KERNEL_ACCOUNT);
|
|
if (!idoms) {
|
|
kvfree(preds);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
env->idoms = idoms;
|
|
for (i = 0; i < len; i++)
|
|
idoms[i] = -1;
|
|
|
|
for (i = 0; i < env->subprog_cnt; i++) {
|
|
err = compute_subprog_idoms(env, preds, i);
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
kvfree(preds);
|
|
return err;
|
|
}
|
|
|
|
struct dfs_state {
|
|
u32 traversed:1;
|
|
u32 next_succ:31;
|
|
};
|
|
|
|
struct loops_dfs {
|
|
struct dfs_state *state;
|
|
int *dfs_pos;
|
|
int *stack;
|
|
};
|
|
|
|
static void mark_irreducible(struct bpf_verifier_env *env, int h)
|
|
{
|
|
env->insn_aux_data[h].loop->irreducible = true;
|
|
}
|
|
|
|
static void add_backedge(struct bpf_verifier_env *env, int from, int h)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
struct bpf_loop *loop = aux[h].loop;
|
|
int cnt = loop->backedges_cnt;
|
|
|
|
if (cnt == MAX_BACKEDGES) {
|
|
loop->backedges_overflow = true;
|
|
return;
|
|
}
|
|
loop->backedges[cnt].from = from;
|
|
loop->backedges[cnt].latches_cnt = 0;
|
|
loop->backedges_cnt++;
|
|
}
|
|
|
|
static int add_exit(struct bpf_loop *loop, int from, int to)
|
|
{
|
|
if (loop->exits_overflow)
|
|
return 0;
|
|
if (loop->exits_cnt == MAX_LOOP_EXITS) {
|
|
loop->exits_overflow = true;
|
|
return 0;
|
|
}
|
|
if (!loop->exits) {
|
|
loop->exits = kvcalloc(MAX_LOOP_EXITS, sizeof(*loop->exits), GFP_KERNEL_ACCOUNT);
|
|
if (!loop->exits)
|
|
return -ENOMEM;
|
|
}
|
|
loop->exits[loop->exits_cnt] = (struct bpf_loop_exit) {
|
|
.from = from,
|
|
.to = to,
|
|
};
|
|
loop->exits_cnt++;
|
|
return 0;
|
|
}
|
|
|
|
static int mark_as_header(struct bpf_verifier_env *env, int h)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
|
|
if (!aux[h].loop) {
|
|
aux[h].loop = kvzalloc_obj(struct bpf_loop, GFP_KERNEL_ACCOUNT);
|
|
if (!aux[h].loop)
|
|
return -ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int assign_header(struct bpf_verifier_env *env, struct loops_dfs *dfs, int n, int h)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
int *dfs_pos = dfs->dfs_pos;
|
|
int err, nh;
|
|
|
|
err = mark_as_header(env, h);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Don't encode self-loops, otherwise can't reflect loops nesting structure. */
|
|
if (n == h)
|
|
return 0;
|
|
|
|
/* Make sure that loop headers up the chain are sorted by dfs_pos. */
|
|
while (aux[n].loop_header != -1) {
|
|
nh = aux[n].loop_header;
|
|
if (nh == h)
|
|
return 0;
|
|
if (dfs_pos[nh] < dfs_pos[h]) {
|
|
aux[n].loop_header = h;
|
|
n = h;
|
|
h = nh;
|
|
} else {
|
|
n = nh;
|
|
}
|
|
}
|
|
aux[n].loop_header = h;
|
|
return 0;
|
|
}
|
|
|
|
static bool is_cond_jmp_insn(struct bpf_insn *insn)
|
|
{
|
|
u8 class = BPF_CLASS(insn->code);
|
|
u8 opcode = BPF_OP(insn->code);
|
|
|
|
if (class != BPF_JMP && class != BPF_JMP32)
|
|
return false;
|
|
|
|
switch (opcode) {
|
|
case BPF_JEQ:
|
|
case BPF_JGE:
|
|
case BPF_JGT:
|
|
case BPF_JLE:
|
|
case BPF_JLT:
|
|
case BPF_JNE:
|
|
case BPF_JSET:
|
|
case BPF_JSGE:
|
|
case BPF_JSGT:
|
|
case BPF_JSLE:
|
|
case BPF_JSLT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int bpf_loop_at_index(struct bpf_verifier_env *env, u32 idx)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
|
|
return aux[idx].loop ? idx : aux[idx].loop_header;
|
|
}
|
|
|
|
/*
|
|
* Collect conditional jumps within n's loop that dominate 'n' and have
|
|
* a successor outside the loop, nearest first.
|
|
*/
|
|
static void find_dominating_conditions(struct bpf_verifier_env *env, int n,
|
|
struct bpf_backedge *backedge)
|
|
{
|
|
struct bpf_insn *insns = env->prog->insnsi;
|
|
bool t_exits_loop, f_exits_loop;
|
|
int n_loop, t_loop, f_loop;
|
|
int *idoms = env->idoms;
|
|
|
|
n_loop = bpf_loop_at_index(env, n);
|
|
if (idoms_intersect(env, n, n_loop) != n_loop)
|
|
return;
|
|
while (n >= 0) {
|
|
if (is_cond_jmp_insn(&insns[n]) && bpf_loop_at_index(env, n) == n_loop) {
|
|
t_loop = bpf_loop_at_index(env, n + insns[n].off + 1);
|
|
f_loop = bpf_loop_at_index(env, n + 1);
|
|
t_exits_loop = t_loop != n_loop && !bpf_is_nested_loop(env, t_loop, n_loop);
|
|
f_exits_loop = f_loop != n_loop && !bpf_is_nested_loop(env, f_loop, n_loop);
|
|
if (t_exits_loop || f_exits_loop) {
|
|
if (backedge->latches_cnt == MAX_LATCHES)
|
|
return;
|
|
backedge->latches[backedge->latches_cnt++] = n;
|
|
}
|
|
}
|
|
if (n == n_loop)
|
|
break;
|
|
n = idoms[n];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* As described in "A New Algorithm for Identifying Loops in Decompilation" by Wei et al,
|
|
* adapted to be non-recursive.
|
|
*/
|
|
static int compute_loops_in_subprog(struct bpf_verifier_env *env, struct loops_dfs *dfs,
|
|
int subprog_idx)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
struct dfs_state *state = dfs->state;
|
|
int start = env->subprog_info[subprog_idx].start;
|
|
int *dfs_pos = dfs->dfs_pos;
|
|
int *stack = dfs->stack;
|
|
int s, h, err, cur, stack_sz;
|
|
struct bpf_iarray *succ;
|
|
u32 i;
|
|
|
|
stack[0] = start;
|
|
state[start].traversed = true;
|
|
state[start].next_succ = 0;
|
|
dfs_pos[start] = 1;
|
|
stack_sz = 1;
|
|
i = 0;
|
|
do {
|
|
/*
|
|
* The algorithm should be very fast in practice,
|
|
* guard against pathological inputs, just in case.
|
|
*/
|
|
if ((++i % 1024) == 0) {
|
|
if (signal_pending(current))
|
|
return -EAGAIN;
|
|
cond_resched();
|
|
}
|
|
|
|
cur = stack[stack_sz - 1];
|
|
succ = bpf_insn_successors(env, cur);
|
|
if (state[cur].next_succ == succ->cnt) {
|
|
dfs_pos[cur] = 0;
|
|
stack_sz--;
|
|
continue;
|
|
}
|
|
s = succ->items[state[cur].next_succ];
|
|
if (!state[s].traversed) {
|
|
/* Case A: start -> ... -> cur -> s [unexplored] */
|
|
state[s].traversed = true;
|
|
state[s].next_succ = 0;
|
|
stack[stack_sz] = s;
|
|
dfs_pos[s] = stack_sz + 1;
|
|
stack_sz++;
|
|
continue;
|
|
}
|
|
/* 's' is fully explored at this point */
|
|
if (dfs_pos[s]) {
|
|
/*
|
|
* start -> ... -> s -> cur --.
|
|
* ^ |
|
|
* '----------'
|
|
* Case B: 's' is in the current DFS path.
|
|
*/
|
|
err = assign_header(env, dfs, cur, s);
|
|
if (err)
|
|
return err;
|
|
add_backedge(env, cur, s);
|
|
} else if (aux[s].loop_header == -1) {
|
|
/*
|
|
* start -> ... -> ... -> s -> ... -> end
|
|
* | ^
|
|
* '---> cur ---'
|
|
* Case C: 's' is explored, not in the current DFS path,
|
|
* and not a part of any loop.
|
|
*/
|
|
} else if (dfs_pos[aux[s].loop_header]) {
|
|
/*
|
|
* .----------------------.
|
|
* v |
|
|
* start -> ... -> h -> ... -> ... -> s --'
|
|
* | ^
|
|
* '---> cur ---'
|
|
* Case D: 's' is explored, not in current DFS path,
|
|
* but its innermost loop header is.
|
|
*/
|
|
err = assign_header(env, dfs, cur, aux[s].loop_header);
|
|
if (err)
|
|
return err;
|
|
} else {
|
|
/*
|
|
* case E: 's' is explored, not in current DFS path,
|
|
* its innermost loop header is not in current DFS path,
|
|
* hence 's' is another entry into the same loop.
|
|
*/
|
|
h = aux[s].loop_header;
|
|
mark_irreducible(env, h);
|
|
while (aux[h].loop_header != -1) {
|
|
h = aux[h].loop_header;
|
|
if (dfs_pos[h]) {
|
|
err = assign_header(env, dfs, cur, h);
|
|
if (err)
|
|
return err;
|
|
break;
|
|
}
|
|
mark_irreducible(env, h);
|
|
}
|
|
}
|
|
state[cur].next_succ++;
|
|
} while (stack_sz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool bpf_is_nested_loop(struct bpf_verifier_env *env, int inner_header, int outer_header)
|
|
{
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
int idx;
|
|
|
|
for (idx = inner_header; idx >= 0; idx = aux[idx].loop_header)
|
|
if (aux[idx].loop_header == outer_header)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int bpf_compute_loops(struct bpf_verifier_env *env)
|
|
{
|
|
int i, j, k, s, t, iloop, sloop, err = 0, len = env->prog->len;
|
|
struct bpf_insn_aux_data *aux = env->insn_aux_data;
|
|
struct bpf_verifier_log *log = &env->log;
|
|
struct bpf_backedge *backedge;
|
|
struct loops_dfs dfs = {};
|
|
struct bpf_iarray *succ;
|
|
struct bpf_loop *loop;
|
|
|
|
dfs.dfs_pos = kvcalloc(len, sizeof(int), GFP_KERNEL_ACCOUNT);
|
|
dfs.state = kvcalloc(len, sizeof(struct dfs_state), GFP_KERNEL_ACCOUNT);
|
|
dfs.stack = kvcalloc(len, sizeof(int), GFP_KERNEL_ACCOUNT);
|
|
if (!dfs.dfs_pos || !dfs.state || !dfs.stack) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
for (i = 0; i < len; i++)
|
|
aux[i].loop_header = -1;
|
|
for (i = 0; i < env->subprog_cnt; i++) {
|
|
err = compute_loops_in_subprog(env, &dfs, i);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
/* find latches */
|
|
for (i = 0; i < len; i++) {
|
|
loop = aux[i].loop;
|
|
if (!loop)
|
|
continue;
|
|
for (j = 0; j < loop->backedges_cnt; j++) {
|
|
backedge = &loop->backedges[j];
|
|
/*
|
|
* In theory, the backedge->from and it's latch can reside in
|
|
* an inner loop of `i`, e.g.:
|
|
*
|
|
* 1: r7 += 1;
|
|
* 2: r6 += 1;
|
|
* if r7 == 2 goto 1b;
|
|
* if r6 < 2 goto 2b;
|
|
*
|
|
* For now, let's assume that latches are unknown for such cases.
|
|
* (GCC/LLVM handle this by inserting artificial cfg nodes).
|
|
*/
|
|
if (bpf_loop_at_index(env, backedge->from) != i)
|
|
continue;
|
|
find_dominating_conditions(env, backedge->from, backedge);
|
|
}
|
|
}
|
|
/* find exits */
|
|
for (i = 0; i < len; i++) {
|
|
iloop = aux[i].loop ? i : aux[i].loop_header;
|
|
if (iloop < 0)
|
|
continue;
|
|
succ = bpf_insn_successors(env, i);
|
|
iarray_for_each(s, succ) {
|
|
/*
|
|
* Nothing left to record once the innermost loop of 'i'
|
|
* overflowed: the walk below would stop at it right away.
|
|
*/
|
|
if (aux[iloop].loop->exits_overflow)
|
|
break;
|
|
sloop = aux[s].loop ? s : aux[s].loop_header;
|
|
if (iloop == sloop)
|
|
continue;
|
|
if (bpf_is_nested_loop(env, sloop, iloop))
|
|
continue;
|
|
/*
|
|
* At this point 'sloop' is either -1, an outer loop,
|
|
* or a loop in another branch of the loop hierarchy.
|
|
*/
|
|
for (t = iloop; t >= 0 && t != sloop; t = aux[t].loop_header) {
|
|
/*
|
|
* Account for the following configuration:
|
|
*
|
|
* tloop {
|
|
* iloop {
|
|
* ... i: goto s;
|
|
* }
|
|
* sloop {
|
|
* s:
|
|
* ...
|
|
* }
|
|
* }
|
|
*/
|
|
if (bpf_is_nested_loop(env, sloop, t))
|
|
break;
|
|
/*
|
|
* Record edges i -> s as exits from tloop when:
|
|
*
|
|
* sloop {
|
|
* tloop {
|
|
* iloop {
|
|
* ... i: goto s;
|
|
* }
|
|
* }
|
|
* s: ...
|
|
* }
|
|
*/
|
|
/* Enclosing loops inherit the overflow, see below */
|
|
if (aux[t].loop->exits_overflow)
|
|
break;
|
|
err = add_exit(aux[t].loop, i, s);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
}
|
|
if (bpf_is_ldimm64(env->prog->insnsi + i))
|
|
i++;
|
|
}
|
|
/* A nested loop with a truncated exit list can't be abstracted by SCEV */
|
|
for (i = 0; i < len; i++) {
|
|
loop = aux[i].loop;
|
|
if (!loop || !loop->exits_overflow)
|
|
continue;
|
|
for (t = aux[i].loop_header; t >= 0; t = aux[t].loop_header)
|
|
aux[t].loop->exits_overflow = true;
|
|
}
|
|
|
|
if (env->log.level & BPF_LOG_LEVEL2) {
|
|
for (i = 0; i < len; i++) {
|
|
loop = aux[i].loop;
|
|
if (!loop)
|
|
continue;
|
|
bpf_log(log, "loop at %d", i);
|
|
if (aux[i].loop_header >= 0)
|
|
bpf_log(log, ", nested in %d", aux[i].loop_header);
|
|
if (loop->irreducible)
|
|
bpf_log(log, ", irreducible");
|
|
if (loop->exits_overflow)
|
|
bpf_log(log, ", too many exits");
|
|
bpf_log(log, "\n");
|
|
for (j = 0; j < loop->backedges_cnt; j++) {
|
|
backedge = &loop->backedges[j];
|
|
bpf_log(log, " backedge from %d", backedge->from);
|
|
for (k = 0; k < backedge->latches_cnt; k++) {
|
|
if (k == 0)
|
|
bpf_log(log, ", latch at ");
|
|
if (k == 1)
|
|
bpf_log(log, ", dominating exits at ");
|
|
if (k > 1)
|
|
bpf_log(log, ", ");
|
|
bpf_log(log, "%d", backedge->latches[k]);
|
|
}
|
|
bpf_log(log, "\n");
|
|
}
|
|
for (j = 0; j < loop->exits_cnt; j++)
|
|
bpf_log(log, " exit from %d to %d\n",
|
|
loop->exits[j].from, loop->exits[j].to);
|
|
}
|
|
}
|
|
|
|
out:
|
|
kvfree(dfs.dfs_pos);
|
|
kvfree(dfs.stack);
|
|
kvfree(dfs.state);
|
|
return err;
|
|
}
|