bpf: verifier (add branch/goto checks)
check that control flow graph of eBPF program is a directed acyclic graph check_cfg() does: - detect loops - detect unreachable instructions - check that program terminates with BPF_EXIT insn - check that all branches are within program boundary Signed-off-by: Alexei Starovoitov <ast@plumgrid.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
0246e64d9a
commit
475fb78fbf
@ -313,6 +313,191 @@ static struct bpf_map *ld_imm64_to_map_ptr(struct bpf_insn *insn)
|
|||||||
return (struct bpf_map *) (unsigned long) imm64;
|
return (struct bpf_map *) (unsigned long) imm64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* non-recursive DFS pseudo code
|
||||||
|
* 1 procedure DFS-iterative(G,v):
|
||||||
|
* 2 label v as discovered
|
||||||
|
* 3 let S be a stack
|
||||||
|
* 4 S.push(v)
|
||||||
|
* 5 while S is not empty
|
||||||
|
* 6 t <- S.pop()
|
||||||
|
* 7 if t is what we're looking for:
|
||||||
|
* 8 return t
|
||||||
|
* 9 for all edges e in G.adjacentEdges(t) do
|
||||||
|
* 10 if edge e is already labelled
|
||||||
|
* 11 continue with the next edge
|
||||||
|
* 12 w <- G.adjacentVertex(t,e)
|
||||||
|
* 13 if vertex w is not discovered and not explored
|
||||||
|
* 14 label e as tree-edge
|
||||||
|
* 15 label w as discovered
|
||||||
|
* 16 S.push(w)
|
||||||
|
* 17 continue at 5
|
||||||
|
* 18 else if vertex w is discovered
|
||||||
|
* 19 label e as back-edge
|
||||||
|
* 20 else
|
||||||
|
* 21 // vertex w is explored
|
||||||
|
* 22 label e as forward- or cross-edge
|
||||||
|
* 23 label t as explored
|
||||||
|
* 24 S.pop()
|
||||||
|
*
|
||||||
|
* convention:
|
||||||
|
* 0x10 - discovered
|
||||||
|
* 0x11 - discovered and fall-through edge labelled
|
||||||
|
* 0x12 - discovered and fall-through and branch edges labelled
|
||||||
|
* 0x20 - explored
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
DISCOVERED = 0x10,
|
||||||
|
EXPLORED = 0x20,
|
||||||
|
FALLTHROUGH = 1,
|
||||||
|
BRANCH = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int *insn_stack; /* stack of insns to process */
|
||||||
|
static int cur_stack; /* current stack index */
|
||||||
|
static int *insn_state;
|
||||||
|
|
||||||
|
/* t, w, e - match pseudo-code above:
|
||||||
|
* t - index of current instruction
|
||||||
|
* w - next instruction
|
||||||
|
* e - edge
|
||||||
|
*/
|
||||||
|
static int push_insn(int t, int w, int e, struct verifier_env *env)
|
||||||
|
{
|
||||||
|
if (e == FALLTHROUGH && insn_state[t] >= (DISCOVERED | FALLTHROUGH))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (e == BRANCH && insn_state[t] >= (DISCOVERED | BRANCH))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (w < 0 || w >= env->prog->len) {
|
||||||
|
verbose("jump out of range from insn %d to %d\n", t, w);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insn_state[w] == 0) {
|
||||||
|
/* tree-edge */
|
||||||
|
insn_state[t] = DISCOVERED | e;
|
||||||
|
insn_state[w] = DISCOVERED;
|
||||||
|
if (cur_stack >= env->prog->len)
|
||||||
|
return -E2BIG;
|
||||||
|
insn_stack[cur_stack++] = w;
|
||||||
|
return 1;
|
||||||
|
} else if ((insn_state[w] & 0xF0) == DISCOVERED) {
|
||||||
|
verbose("back-edge from insn %d to %d\n", t, w);
|
||||||
|
return -EINVAL;
|
||||||
|
} else if (insn_state[w] == EXPLORED) {
|
||||||
|
/* forward- or cross-edge */
|
||||||
|
insn_state[t] = DISCOVERED | e;
|
||||||
|
} else {
|
||||||
|
verbose("insn state internal bug\n");
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* non-recursive depth-first-search to detect loops in BPF program
|
||||||
|
* loop == back-edge in directed graph
|
||||||
|
*/
|
||||||
|
static int check_cfg(struct verifier_env *env)
|
||||||
|
{
|
||||||
|
struct bpf_insn *insns = env->prog->insnsi;
|
||||||
|
int insn_cnt = env->prog->len;
|
||||||
|
int ret = 0;
|
||||||
|
int i, t;
|
||||||
|
|
||||||
|
insn_state = kcalloc(insn_cnt, sizeof(int), GFP_KERNEL);
|
||||||
|
if (!insn_state)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
insn_stack = kcalloc(insn_cnt, sizeof(int), GFP_KERNEL);
|
||||||
|
if (!insn_stack) {
|
||||||
|
kfree(insn_state);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
insn_state[0] = DISCOVERED; /* mark 1st insn as discovered */
|
||||||
|
insn_stack[0] = 0; /* 0 is the first instruction */
|
||||||
|
cur_stack = 1;
|
||||||
|
|
||||||
|
peek_stack:
|
||||||
|
if (cur_stack == 0)
|
||||||
|
goto check_state;
|
||||||
|
t = insn_stack[cur_stack - 1];
|
||||||
|
|
||||||
|
if (BPF_CLASS(insns[t].code) == BPF_JMP) {
|
||||||
|
u8 opcode = BPF_OP(insns[t].code);
|
||||||
|
|
||||||
|
if (opcode == BPF_EXIT) {
|
||||||
|
goto mark_explored;
|
||||||
|
} else if (opcode == BPF_CALL) {
|
||||||
|
ret = push_insn(t, t + 1, FALLTHROUGH, env);
|
||||||
|
if (ret == 1)
|
||||||
|
goto peek_stack;
|
||||||
|
else if (ret < 0)
|
||||||
|
goto err_free;
|
||||||
|
} else if (opcode == BPF_JA) {
|
||||||
|
if (BPF_SRC(insns[t].code) != BPF_K) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
/* unconditional jump with single edge */
|
||||||
|
ret = push_insn(t, t + insns[t].off + 1,
|
||||||
|
FALLTHROUGH, env);
|
||||||
|
if (ret == 1)
|
||||||
|
goto peek_stack;
|
||||||
|
else if (ret < 0)
|
||||||
|
goto err_free;
|
||||||
|
} else {
|
||||||
|
/* conditional jump with two edges */
|
||||||
|
ret = push_insn(t, t + 1, FALLTHROUGH, env);
|
||||||
|
if (ret == 1)
|
||||||
|
goto peek_stack;
|
||||||
|
else if (ret < 0)
|
||||||
|
goto err_free;
|
||||||
|
|
||||||
|
ret = push_insn(t, t + insns[t].off + 1, BRANCH, env);
|
||||||
|
if (ret == 1)
|
||||||
|
goto peek_stack;
|
||||||
|
else if (ret < 0)
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* all other non-branch instructions with single
|
||||||
|
* fall-through edge
|
||||||
|
*/
|
||||||
|
ret = push_insn(t, t + 1, FALLTHROUGH, env);
|
||||||
|
if (ret == 1)
|
||||||
|
goto peek_stack;
|
||||||
|
else if (ret < 0)
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_explored:
|
||||||
|
insn_state[t] = EXPLORED;
|
||||||
|
if (cur_stack-- <= 0) {
|
||||||
|
verbose("pop stack internal bug\n");
|
||||||
|
ret = -EFAULT;
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
goto peek_stack;
|
||||||
|
|
||||||
|
check_state:
|
||||||
|
for (i = 0; i < insn_cnt; i++) {
|
||||||
|
if (insn_state[i] != EXPLORED) {
|
||||||
|
verbose("unreachable insn %d\n", i);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = 0; /* cfg looks good */
|
||||||
|
|
||||||
|
err_free:
|
||||||
|
kfree(insn_state);
|
||||||
|
kfree(insn_stack);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* look for pseudo eBPF instructions that access map FDs and
|
/* look for pseudo eBPF instructions that access map FDs and
|
||||||
* replace them with actual map pointers
|
* replace them with actual map pointers
|
||||||
*/
|
*/
|
||||||
@ -462,6 +647,10 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto skip_full_check;
|
goto skip_full_check;
|
||||||
|
|
||||||
|
ret = check_cfg(env);
|
||||||
|
if (ret < 0)
|
||||||
|
goto skip_full_check;
|
||||||
|
|
||||||
/* ret = do_check(env); */
|
/* ret = do_check(env); */
|
||||||
|
|
||||||
skip_full_check:
|
skip_full_check:
|
||||||
|
Loading…
Reference in New Issue
Block a user