bpf: introduce function calls (function boundaries)

Allow arbitrary function calls from bpf function to another bpf function.

Since the beginning of bpf all bpf programs were represented as a single function
and program authors were forced to use always_inline for all functions
in their C code. That was causing llvm to unnecessary inflate the code size
and forcing developers to move code to header files with little code reuse.

With a bit of additional complexity teach verifier to recognize
arbitrary function calls from one bpf function to another as long as
all of functions are presented to the verifier as a single bpf program.
New program layout:
r6 = r1    // some code
..
r1 = ..    // arg1
r2 = ..    // arg2
call pc+1  // function call pc-relative
exit
.. = r1    // access arg1
.. = r2    // access arg2
..
call pc+20 // second level of function call
...

It allows for better optimized code and finally allows to introduce
the core bpf libraries that can be reused in different projects,
since programs are no longer limited by single elf file.
With function calls bpf can be compiled into multiple .o files.

This patch is the first step. It detects programs that contain
multiple functions and checks that calls between them are valid.
It splits the sequence of bpf instructions (one program) into a set
of bpf functions that call each other. Calls to only known
functions are allowed. In the future the verifier may allow
calls to unresolved functions and will do dynamic linking.
This logic supports statically linked bpf functions only.

Such function boundary detection could have been done as part of
control flow graph building in check_cfg(), but it's cleaner to
separate function boundary detection vs control flow checks within
a subprogram (function) into logically indepedent steps.
Follow up patches may split check_cfg() further, but not check_subprogs().

Only allow bpf-to-bpf calls for root only and for non-hw-offloaded programs.
These restrictions can be relaxed in the future.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
Alexei Starovoitov 2017-12-14 17:55:05 -08:00 committed by Daniel Borkmann
parent 0bce7c9a60
commit cc8b0b92a1
4 changed files with 155 additions and 5 deletions

View File

@ -141,6 +141,8 @@ struct bpf_ext_analyzer_ops {
int insn_idx, int prev_insn_idx); int insn_idx, int prev_insn_idx);
}; };
#define BPF_MAX_SUBPROGS 256
/* single container for all structs /* single container for all structs
* one verifier_env per bpf_check() call * one verifier_env per bpf_check() call
*/ */
@ -159,8 +161,9 @@ struct bpf_verifier_env {
bool allow_ptr_leaks; bool allow_ptr_leaks;
bool seen_direct_write; bool seen_direct_write;
struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */ struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
struct bpf_verifer_log log; struct bpf_verifer_log log;
u32 subprog_starts[BPF_MAX_SUBPROGS];
u32 subprog_cnt;
}; };
static inline struct bpf_reg_state *cur_regs(struct bpf_verifier_env *env) static inline struct bpf_reg_state *cur_regs(struct bpf_verifier_env *env)

View File

@ -197,8 +197,14 @@ enum bpf_attach_type {
*/ */
#define BPF_F_STRICT_ALIGNMENT (1U << 0) #define BPF_F_STRICT_ALIGNMENT (1U << 0)
/* when bpf_ldimm64->src_reg == BPF_PSEUDO_MAP_FD, bpf_ldimm64->imm == fd */
#define BPF_PSEUDO_MAP_FD 1 #define BPF_PSEUDO_MAP_FD 1
/* when bpf_call->src_reg == BPF_PSEUDO_CALL, bpf_call->imm == pc-relative
* offset to another bpf function
*/
#define BPF_PSEUDO_CALL 1
/* flags for BPF_MAP_UPDATE_ELEM command */ /* flags for BPF_MAP_UPDATE_ELEM command */
#define BPF_ANY 0 /* create new element or update existing */ #define BPF_ANY 0 /* create new element or update existing */
#define BPF_NOEXIST 1 /* create new element if it didn't exist */ #define BPF_NOEXIST 1 /* create new element if it didn't exist */

View File

@ -189,8 +189,12 @@ void print_bpf_insn(bpf_insn_print_cb verbose, struct bpf_verifier_env *env,
u8 opcode = BPF_OP(insn->code); u8 opcode = BPF_OP(insn->code);
if (opcode == BPF_CALL) { if (opcode == BPF_CALL) {
verbose(env, "(%02x) call %s#%d\n", insn->code, if (insn->src_reg == BPF_PSEUDO_CALL)
func_id_name(insn->imm), insn->imm); verbose(env, "(%02x) call pc%+d\n", insn->code,
insn->imm);
else
verbose(env, "(%02x) call %s#%d\n", insn->code,
func_id_name(insn->imm), insn->imm);
} else if (insn->code == (BPF_JMP | BPF_JA)) { } else if (insn->code == (BPF_JMP | BPF_JA)) {
verbose(env, "(%02x) goto pc%+d\n", verbose(env, "(%02x) goto pc%+d\n",
insn->code, insn->off); insn->code, insn->off);

View File

@ -20,6 +20,8 @@
#include <linux/file.h> #include <linux/file.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/stringify.h> #include <linux/stringify.h>
#include <linux/bsearch.h>
#include <linux/sort.h>
#include "disasm.h" #include "disasm.h"
@ -636,6 +638,113 @@ enum reg_arg_type {
DST_OP_NO_MARK /* same as above, check only, don't mark */ DST_OP_NO_MARK /* same as above, check only, don't mark */
}; };
static int cmp_subprogs(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
static int find_subprog(struct bpf_verifier_env *env, int off)
{
u32 *p;
p = bsearch(&off, env->subprog_starts, env->subprog_cnt,
sizeof(env->subprog_starts[0]), cmp_subprogs);
if (!p)
return -ENOENT;
return p - env->subprog_starts;
}
static int add_subprog(struct bpf_verifier_env *env, int off)
{
int insn_cnt = env->prog->len;
int ret;
if (off >= insn_cnt || off < 0) {
verbose(env, "call to invalid destination\n");
return -EINVAL;
}
ret = find_subprog(env, off);
if (ret >= 0)
return 0;
if (env->subprog_cnt >= BPF_MAX_SUBPROGS) {
verbose(env, "too many subprograms\n");
return -E2BIG;
}
env->subprog_starts[env->subprog_cnt++] = off;
sort(env->subprog_starts, env->subprog_cnt,
sizeof(env->subprog_starts[0]), cmp_subprogs, NULL);
return 0;
}
static int check_subprogs(struct bpf_verifier_env *env)
{
int i, ret, subprog_start, subprog_end, off, cur_subprog = 0;
struct bpf_insn *insn = env->prog->insnsi;
int insn_cnt = env->prog->len;
/* determine subprog starts. The end is one before the next starts */
for (i = 0; i < insn_cnt; i++) {
if (insn[i].code != (BPF_JMP | BPF_CALL))
continue;
if (insn[i].src_reg != BPF_PSEUDO_CALL)
continue;
if (!env->allow_ptr_leaks) {
verbose(env, "function calls to other bpf functions are allowed for root only\n");
return -EPERM;
}
if (bpf_prog_is_dev_bound(env->prog->aux)) {
verbose(env, "funcation calls in offloaded programs are not supported yet\n");
return -EINVAL;
}
ret = add_subprog(env, i + insn[i].imm + 1);
if (ret < 0)
return ret;
}
if (env->log.level > 1)
for (i = 0; i < env->subprog_cnt; i++)
verbose(env, "func#%d @%d\n", i, env->subprog_starts[i]);
/* now check that all jumps are within the same subprog */
subprog_start = 0;
if (env->subprog_cnt == cur_subprog)
subprog_end = insn_cnt;
else
subprog_end = env->subprog_starts[cur_subprog++];
for (i = 0; i < insn_cnt; i++) {
u8 code = insn[i].code;
if (BPF_CLASS(code) != BPF_JMP)
goto next;
if (BPF_OP(code) == BPF_EXIT || BPF_OP(code) == BPF_CALL)
goto next;
off = i + insn[i].off + 1;
if (off < subprog_start || off >= subprog_end) {
verbose(env, "jump out of range from insn %d to %d\n", i, off);
return -EINVAL;
}
next:
if (i == subprog_end - 1) {
/* to avoid fall-through from one subprog into another
* the last insn of the subprog should be either exit
* or unconditional jump back
*/
if (code != (BPF_JMP | BPF_EXIT) &&
code != (BPF_JMP | BPF_JA)) {
verbose(env, "last insn is not an exit or jmp\n");
return -EINVAL;
}
subprog_start = subprog_end;
if (env->subprog_cnt == cur_subprog)
subprog_end = insn_cnt;
else
subprog_end = env->subprog_starts[cur_subprog++];
}
}
return 0;
}
static void mark_reg_read(const struct bpf_verifier_state *state, u32 regno) static void mark_reg_read(const struct bpf_verifier_state *state, u32 regno)
{ {
struct bpf_verifier_state *parent = state->parent; struct bpf_verifier_state *parent = state->parent;
@ -3284,6 +3393,10 @@ static int check_cfg(struct bpf_verifier_env *env)
int ret = 0; int ret = 0;
int i, t; int i, t;
ret = check_subprogs(env);
if (ret < 0)
return ret;
insn_state = kcalloc(insn_cnt, sizeof(int), GFP_KERNEL); insn_state = kcalloc(insn_cnt, sizeof(int), GFP_KERNEL);
if (!insn_state) if (!insn_state)
return -ENOMEM; return -ENOMEM;
@ -3316,6 +3429,14 @@ static int check_cfg(struct bpf_verifier_env *env)
goto err_free; goto err_free;
if (t + 1 < insn_cnt) if (t + 1 < insn_cnt)
env->explored_states[t + 1] = STATE_LIST_MARK; env->explored_states[t + 1] = STATE_LIST_MARK;
if (insns[t].src_reg == BPF_PSEUDO_CALL) {
env->explored_states[t] = STATE_LIST_MARK;
ret = push_insn(t, t + insns[t].imm + 1, BRANCH, env);
if (ret == 1)
goto peek_stack;
else if (ret < 0)
goto err_free;
}
} else if (opcode == BPF_JA) { } else if (opcode == BPF_JA) {
if (BPF_SRC(insns[t].code) != BPF_K) { if (BPF_SRC(insns[t].code) != BPF_K) {
ret = -EINVAL; ret = -EINVAL;
@ -4245,6 +4366,19 @@ static int adjust_insn_aux_data(struct bpf_verifier_env *env, u32 prog_len,
return 0; return 0;
} }
static void adjust_subprog_starts(struct bpf_verifier_env *env, u32 off, u32 len)
{
int i;
if (len == 1)
return;
for (i = 0; i < env->subprog_cnt; i++) {
if (env->subprog_starts[i] < off)
continue;
env->subprog_starts[i] += len - 1;
}
}
static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
const struct bpf_insn *patch, u32 len) const struct bpf_insn *patch, u32 len)
{ {
@ -4255,6 +4389,7 @@ static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 of
return NULL; return NULL;
if (adjust_insn_aux_data(env, new_prog->len, off, len)) if (adjust_insn_aux_data(env, new_prog->len, off, len))
return NULL; return NULL;
adjust_subprog_starts(env, off, len);
return new_prog; return new_prog;
} }
@ -4408,6 +4543,8 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
for (i = 0; i < insn_cnt; i++, insn++) { for (i = 0; i < insn_cnt; i++, insn++) {
if (insn->code != (BPF_JMP | BPF_CALL)) if (insn->code != (BPF_JMP | BPF_CALL))
continue; continue;
if (insn->src_reg == BPF_PSEUDO_CALL)
continue;
if (insn->imm == BPF_FUNC_get_route_realm) if (insn->imm == BPF_FUNC_get_route_realm)
prog->dst_needed = 1; prog->dst_needed = 1;
@ -4589,12 +4726,12 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr)
if (!env->explored_states) if (!env->explored_states)
goto skip_full_check; goto skip_full_check;
env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);
ret = check_cfg(env); ret = check_cfg(env);
if (ret < 0) if (ret < 0)
goto skip_full_check; goto skip_full_check;
env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);
ret = do_check(env); ret = do_check(env);
if (env->cur_state) { if (env->cur_state) {
free_verifier_state(env->cur_state, true); free_verifier_state(env->cur_state, true);