#include <linux/bpf.h>
#include <linux/bpf_verifier.h>
#include <linux/filter.h>
#include <linux/sort.h>
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
enum {
DISCOVERED = 0x10,
EXPLORED = 0x20,
FALLTHROUGH = 1,
BRANCH = 2,
};
static void mark_subprog_changes_pkt_data(struct bpf_verifier_env *env, int off)
{
struct bpf_subprog_info *subprog;
subprog = bpf_find_containing_subprog(env, off);
subprog->changes_pkt_data = true;
}
static void mark_subprog_might_sleep(struct bpf_verifier_env *env, int off)
{
struct bpf_subprog_info *subprog;
subprog = bpf_find_containing_subprog(env, off);
subprog->might_sleep = true;
}
static void merge_callee_effects(struct bpf_verifier_env *env, int t, int w)
{
struct bpf_subprog_info *caller, *callee;
caller = bpf_find_containing_subprog(env, t);
callee = bpf_find_containing_subprog(env, w);
caller->changes_pkt_data |= callee->changes_pkt_data;
caller->might_sleep |= callee->might_sleep;
}
enum {
DONE_EXPLORING = 0,
KEEP_EXPLORING = 1,
};
static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
{
int *insn_stack = env->cfg.insn_stack;
int *insn_state = env->cfg.insn_state;
if (e == FALLTHROUGH && insn_state[t] >= (DISCOVERED | FALLTHROUGH))
return DONE_EXPLORING;
if (e == BRANCH && insn_state[t] >= (DISCOVERED | BRANCH))
return DONE_EXPLORING;
if (w < 0 || w >= env->prog->len) {
verbose_linfo(env, t, "%d: ", t);
verbose(env, "jump out of range from insn %d to %d\n", t, w);
return -EINVAL;
}
if (e == BRANCH) {
mark_prune_point(env, w);
mark_jmp_point(env, w);
}
if (insn_state[w] == 0) {
insn_state[t] = DISCOVERED | e;
insn_state[w] = DISCOVERED;
if (env->cfg.cur_stack >= env->prog->len)
return -E2BIG;
insn_stack[env->cfg.cur_stack++] = w;
return KEEP_EXPLORING;
} else if ((insn_state[w] & 0xF0) == DISCOVERED) {
if (env->bpf_capable)
return DONE_EXPLORING;
verbose_linfo(env, t, "%d: ", t);
verbose_linfo(env, w, "%d: ", w);
verbose(env, "back-edge from insn %d to %d\n", t, w);
return -EINVAL;
} else if (insn_state[w] == EXPLORED) {
insn_state[t] = DISCOVERED | e;
} else {
verifier_bug(env, "insn state internal bug");
return -EFAULT;
}
return DONE_EXPLORING;
}
static int visit_func_call_insn(int t, struct bpf_insn *insns,
struct bpf_verifier_env *env,
bool visit_callee)
{
int ret, insn_sz;
int w;
insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;
ret = push_insn(t, t + insn_sz, FALLTHROUGH, env);
if (ret)
return ret;
mark_prune_point(env, t + insn_sz);
mark_jmp_point(env, t + insn_sz);
if (visit_callee) {
w = t + insns[t].imm + 1;
mark_prune_point(env, t);
merge_callee_effects(env, t, w);
ret = push_insn(t, w, BRANCH, env);
}
return ret;
}
struct bpf_iarray *bpf_iarray_realloc(struct bpf_iarray *old, size_t n_elem)
{
size_t new_size = sizeof(struct bpf_iarray) + n_elem * sizeof(old->items[0]);
struct bpf_iarray *new;
new = kvrealloc(old, new_size, GFP_KERNEL_ACCOUNT);
if (!new) {
kvfree(old);
return NULL;
}
new->cnt = n_elem;
return new;
}
static int copy_insn_array(struct bpf_map *map, u32 start, u32 end, u32 *items)
{
struct bpf_insn_array_value *value;
u32 i;
for (i = start; i <= end; i++) {
value = map->ops->map_lookup_elem(map, &i);
if (IS_ERR(value))
return PTR_ERR(value);
else if (!value)
return -EINVAL;
items[i - start] = value->xlated_off;
}
return 0;
}
static int cmp_ptr_to_u32(const void *a, const void *b)
{
return *(u32 *)a - *(u32 *)b;
}
static int sort_insn_array_uniq(u32 *items, int cnt)
{
int unique = 1;
int i;
sort(items, cnt, sizeof(items[0]), cmp_ptr_to_u32, NULL);
for (i = 1; i < cnt; i++)
if (items[i] != items[unique - 1])
items[unique++] = items[i];
return unique;
}
int bpf_copy_insn_array_uniq(struct bpf_map *map, u32 start, u32 end, u32 *off)
{
u32 n = end - start + 1;
int err;
err = copy_insn_array(map, start, end, off);
if (err)
return err;
return sort_insn_array_uniq(off, n);
}
static struct bpf_iarray *jt_from_map(struct bpf_map *map)
{
struct bpf_iarray *jt;
int err;
int n;
jt = bpf_iarray_realloc(NULL, map->max_entries);
if (!jt)
return ERR_PTR(-ENOMEM);
n = bpf_copy_insn_array_uniq(map, 0, map->max_entries - 1, jt->items);
if (n < 0) {
err = n;
goto err_free;
}
if (n == 0) {
err = -EINVAL;
goto err_free;
}
jt->cnt = n;
return jt;
err_free:
kvfree(jt);
return ERR_PTR(err);
}
static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,
int subprog_start, int subprog_end)
{
struct bpf_iarray *jt = NULL;
struct bpf_map *map;
struct bpf_iarray *jt_cur;
int i;
for (i = 0; i < env->insn_array_map_cnt; i++) {
map = env->insn_array_maps[i];
jt_cur = jt_from_map(map);
if (IS_ERR(jt_cur)) {
kvfree(jt);
return jt_cur;
}
if (jt_cur->items[0] >= subprog_start && jt_cur->items[0] < subprog_end) {
u32 old_cnt = jt ? jt->cnt : 0;
jt = bpf_iarray_realloc(jt, old_cnt + jt_cur->cnt);
if (!jt) {
kvfree(jt_cur);
return ERR_PTR(-ENOMEM);
}
memcpy(jt->items + old_cnt, jt_cur->items, jt_cur->cnt << 2);
}
kvfree(jt_cur);
}
if (!jt) {
verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
return ERR_PTR(-EINVAL);
}
jt->cnt = sort_insn_array_uniq(jt->items, jt->cnt);
return jt;
}
static struct bpf_iarray *
create_jt(int t, struct bpf_verifier_env *env)
{
struct bpf_subprog_info *subprog;
int subprog_start, subprog_end;
struct bpf_iarray *jt;
int i;
subprog = bpf_find_containing_subprog(env, t);
subprog_start = subprog->start;
subprog_end = (subprog + 1)->start;
jt = jt_from_subprog(env, subprog_start, subprog_end);
if (IS_ERR(jt))
return jt;
for (i = 0; i < jt->cnt; i++) {
if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {
verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",
t, subprog_start, subprog_end);
kvfree(jt);
return ERR_PTR(-EINVAL);
}
}
return jt;
}
static int visit_gotox_insn(int t, struct bpf_verifier_env *env)
{
int *insn_stack = env->cfg.insn_stack;
int *insn_state = env->cfg.insn_state;
bool keep_exploring = false;
struct bpf_iarray *jt;
int i, w;
jt = env->insn_aux_data[t].jt;
if (!jt) {
jt = create_jt(t, env);
if (IS_ERR(jt))
return PTR_ERR(jt);
env->insn_aux_data[t].jt = jt;
}
mark_prune_point(env, t);
for (i = 0; i < jt->cnt; i++) {
w = jt->items[i];
if (w < 0 || w >= env->prog->len) {
verbose(env, "indirect jump out of range from insn %d to %d\n", t, w);
return -EINVAL;
}
mark_jmp_point(env, w);
if (insn_state[w])
continue;
if (env->cfg.cur_stack >= env->prog->len)
return -E2BIG;
insn_stack[env->cfg.cur_stack++] = w;
insn_state[w] |= DISCOVERED;
keep_exploring = true;
}
return keep_exploring ? KEEP_EXPLORING : DONE_EXPLORING;
}
static int visit_abnormal_return_insn(struct bpf_verifier_env *env, int t)
{
struct bpf_subprog_info *subprog;
struct bpf_iarray *jt;
if (env->insn_aux_data[t].jt)
return 0;
jt = bpf_iarray_realloc(NULL, 2);
if (!jt)
return -ENOMEM;
subprog = bpf_find_containing_subprog(env, t);
jt->items[0] = t + 1;
jt->items[1] = subprog->exit_idx;
env->insn_aux_data[t].jt = jt;
return 0;
}
static int visit_insn(int t, struct bpf_verifier_env *env)
{
struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t];
int ret, off, insn_sz;
if (bpf_pseudo_func(insn))
return visit_func_call_insn(t, insns, env, true);
if (BPF_CLASS(insn->code) != BPF_JMP &&
BPF_CLASS(insn->code) != BPF_JMP32) {
if (BPF_CLASS(insn->code) == BPF_LD &&
(BPF_MODE(insn->code) == BPF_ABS ||
BPF_MODE(insn->code) == BPF_IND)) {
ret = visit_abnormal_return_insn(env, t);
if (ret)
return ret;
}
insn_sz = bpf_is_ldimm64(insn) ? 2 : 1;
return push_insn(t, t + insn_sz, FALLTHROUGH, env);
}
switch (BPF_OP(insn->code)) {
case BPF_EXIT:
return DONE_EXPLORING;
case BPF_CALL:
if (bpf_is_async_callback_calling_insn(insn))
mark_prune_point(env, t);
if (bpf_is_sync_callback_calling_insn(insn)) {
mark_calls_callback(env, t);
mark_force_checkpoint(env, t);
mark_prune_point(env, t);
mark_jmp_point(env, t);
}
if (bpf_helper_call(insn)) {
const struct bpf_func_proto *fp;
ret = bpf_get_helper_proto(env, insn->imm, &fp);
if (ret == 0 && fp->might_sleep)
mark_subprog_might_sleep(env, t);
if (bpf_helper_changes_pkt_data(insn->imm))
mark_subprog_changes_pkt_data(env, t);
if (insn->imm == BPF_FUNC_tail_call) {
ret = visit_abnormal_return_insn(env, t);
if (ret)
return ret;
}
} else if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
struct bpf_kfunc_call_arg_meta meta;
ret = bpf_fetch_kfunc_arg_meta(env, insn->imm, insn->off, &meta);
if (ret == 0 && bpf_is_iter_next_kfunc(&meta)) {
mark_prune_point(env, t);
mark_force_checkpoint(env, t);
}
if (ret == 0 && bpf_is_kfunc_sleepable(&meta))
mark_subprog_might_sleep(env, t);
if (ret == 0 && bpf_is_kfunc_pkt_changing(&meta))
mark_subprog_changes_pkt_data(env, t);
}
return visit_func_call_insn(t, insns, env, insn->src_reg == BPF_PSEUDO_CALL);
case BPF_JA:
if (BPF_SRC(insn->code) == BPF_X)
return visit_gotox_insn(t, env);
if (BPF_CLASS(insn->code) == BPF_JMP)
off = insn->off;
else
off = insn->imm;
ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
if (ret)
return ret;
mark_prune_point(env, t + off + 1);
mark_jmp_point(env, t + off + 1);
return ret;
default:
mark_prune_point(env, t);
if (bpf_is_may_goto_insn(insn))
mark_force_checkpoint(env, t);
ret = push_insn(t, t + 1, FALLTHROUGH, env);
if (ret)
return ret;
return push_insn(t, t + insn->off + 1, BRANCH, env);
}
}
int bpf_check_cfg(struct bpf_verifier_env *env)
{
int insn_cnt = env->prog->len;
int *insn_stack, *insn_state;
int ex_insn_beg, i, ret = 0;
insn_state = env->cfg.insn_state = kvzalloc_objs(int, insn_cnt,
GFP_KERNEL_ACCOUNT);
if (!insn_state)
return -ENOMEM;
insn_stack = env->cfg.insn_stack = kvzalloc_objs(int, insn_cnt,
GFP_KERNEL_ACCOUNT);
if (!insn_stack) {
kvfree(insn_state);
return -ENOMEM;
}
ex_insn_beg = env->exception_callback_subprog
? env->subprog_info[env->exception_callback_subprog].start
: 0;
insn_state[0] = DISCOVERED;
insn_stack[0] = 0;
env->cfg.cur_stack = 1;
walk_cfg:
while (env->cfg.cur_stack > 0) {
int t = insn_stack[env->cfg.cur_stack - 1];
ret = visit_insn(t, env);
switch (ret) {
case DONE_EXPLORING:
insn_state[t] = EXPLORED;
env->cfg.cur_stack--;
break;
case KEEP_EXPLORING:
break;
default:
if (ret > 0) {
verifier_bug(env, "visit_insn internal bug");
ret = -EFAULT;
}
goto err_free;
}
}
if (env->cfg.cur_stack < 0) {
verifier_bug(env, "pop stack internal bug");
ret = -EFAULT;
goto err_free;
}
if (ex_insn_beg && insn_state[ex_insn_beg] != EXPLORED) {
insn_state[ex_insn_beg] = DISCOVERED;
insn_stack[0] = ex_insn_beg;
env->cfg.cur_stack = 1;
goto walk_cfg;
}
for (i = 0; i < insn_cnt; i++) {
struct bpf_insn *insn = &env->prog->insnsi[i];
if (insn_state[i] != EXPLORED) {
verbose(env, "unreachable insn %d\n", i);
ret = -EINVAL;
goto err_free;
}
if (bpf_is_ldimm64(insn)) {
if (insn_state[i + 1] != 0) {
verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
ret = -EINVAL;
goto err_free;
}
i++;
}
}
ret = 0;
env->prog->aux->changes_pkt_data = env->subprog_info[0].changes_pkt_data;
env->prog->aux->might_sleep = env->subprog_info[0].might_sleep;
err_free:
kvfree(insn_state);
kvfree(insn_stack);
env->cfg.insn_state = env->cfg.insn_stack = NULL;
return ret;
}
int bpf_compute_postorder(struct bpf_verifier_env *env)
{
u32 cur_postorder, i, top, stack_sz, s;
int *stack = NULL, *postorder = NULL, *state = NULL;
struct bpf_iarray *succ;
postorder = kvzalloc_objs(int, env->prog->len, GFP_KERNEL_ACCOUNT);
state = kvzalloc_objs(int, env->prog->len, GFP_KERNEL_ACCOUNT);
stack = kvzalloc_objs(int, env->prog->len, GFP_KERNEL_ACCOUNT);
if (!postorder || !state || !stack) {
kvfree(postorder);
kvfree(state);
kvfree(stack);
return -ENOMEM;
}
cur_postorder = 0;
for (i = 0; i < env->subprog_cnt; i++) {
env->subprog_info[i].postorder_start = cur_postorder;
stack[0] = env->subprog_info[i].start;
stack_sz = 1;
do {
top = stack[stack_sz - 1];
state[top] |= DISCOVERED;
if (state[top] & EXPLORED) {
postorder[cur_postorder++] = top;
stack_sz--;
continue;
}
succ = bpf_insn_successors(env, top);
for (s = 0; s < succ->cnt; ++s) {
if (!state[succ->items[s]]) {
stack[stack_sz++] = succ->items[s];
state[succ->items[s]] |= DISCOVERED;
}
}
state[top] |= EXPLORED;
} while (stack_sz);
}
env->subprog_info[i].postorder_start = cur_postorder;
env->cfg.insn_postorder = postorder;
env->cfg.cur_postorder = cur_postorder;
kvfree(stack);
kvfree(state);
return 0;
}
int bpf_compute_scc(struct bpf_verifier_env *env)
{
const u32 NOT_ON_STACK = U32_MAX;
struct bpf_insn_aux_data *aux = env->insn_aux_data;
const u32 insn_cnt = env->prog->len;
int stack_sz, dfs_sz, err = 0;
u32 *stack, *pre, *low, *dfs;
u32 i, j, t, w;
u32 next_preorder_num;
u32 next_scc_id;
bool assign_scc;
struct bpf_iarray *succ;
next_preorder_num = 1;
next_scc_id = 1;
stack = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT);
pre = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT);
low = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT);
dfs = kvcalloc(insn_cnt, sizeof(*dfs), GFP_KERNEL_ACCOUNT);
if (!stack || !pre || !low || !dfs) {
err = -ENOMEM;
goto exit;
}
for (i = 0; i < insn_cnt; i++) {
if (pre[i])
continue;
stack_sz = 0;
dfs_sz = 1;
dfs[0] = i;
dfs_continue:
while (dfs_sz) {
w = dfs[dfs_sz - 1];
if (pre[w] == 0) {
low[w] = next_preorder_num;
pre[w] = next_preorder_num;
next_preorder_num++;
stack[stack_sz++] = w;
}
succ = bpf_insn_successors(env, w);
for (j = 0; j < succ->cnt; ++j) {
if (pre[succ->items[j]]) {
low[w] = min(low[w], low[succ->items[j]]);
} else {
dfs[dfs_sz++] = succ->items[j];
goto dfs_continue;
}
}
if (low[w] < pre[w]) {
dfs_sz--;
goto dfs_continue;
}
assign_scc = stack[stack_sz - 1] != w;
for (j = 0; j < succ->cnt; ++j) {
if (succ->items[j] == w) {
assign_scc = true;
break;
}
}
if (bpf_calls_callback(env, w))
assign_scc = true;
do {
t = stack[--stack_sz];
low[t] = NOT_ON_STACK;
if (assign_scc)
aux[t].scc = next_scc_id;
} while (t != w);
if (assign_scc)
next_scc_id++;
dfs_sz--;
}
}
env->scc_info = kvzalloc_objs(*env->scc_info, next_scc_id,
GFP_KERNEL_ACCOUNT);
if (!env->scc_info) {
err = -ENOMEM;
goto exit;
}
env->scc_cnt = next_scc_id;
exit:
kvfree(stack);
kvfree(pre);
kvfree(low);
kvfree(dfs);
return err;
}