提交 9bac3d6d 编写于 作者: A Alexei Starovoitov 提交者: David S. Miller

bpf: allow extended BPF programs access skb fields

introduce user accessible mirror of in-kernel 'struct sk_buff':
struct __sk_buff {
    __u32 len;
    __u32 pkt_type;
    __u32 mark;
    __u32 queue_mapping;
};

bpf programs can do:

int bpf_prog(struct __sk_buff *skb)
{
    __u32 var = skb->pkt_type;

which will be compiled to bpf assembler as:

dst_reg = *(u32 *)(src_reg + 4) // 4 == offsetof(struct __sk_buff, pkt_type)

bpf verifier will check validity of access and will convert it to:

dst_reg = *(u8 *)(src_reg + offsetof(struct sk_buff, __pkt_type_offset))
dst_reg &= 7

since skb->pkt_type is a bitfield.
Signed-off-by: NAlexei Starovoitov <ast@plumgrid.com>
Signed-off-by: NDavid S. Miller <davem@davemloft.net>
上级 a498cfe9
...@@ -103,6 +103,9 @@ struct bpf_verifier_ops { ...@@ -103,6 +103,9 @@ struct bpf_verifier_ops {
* with 'type' (read or write) is allowed * with 'type' (read or write) is allowed
*/ */
bool (*is_valid_access)(int off, int size, enum bpf_access_type type); bool (*is_valid_access)(int off, int size, enum bpf_access_type type);
u32 (*convert_ctx_access)(int dst_reg, int src_reg, int ctx_off,
struct bpf_insn *insn);
}; };
struct bpf_prog_type_list { struct bpf_prog_type_list {
...@@ -133,7 +136,7 @@ struct bpf_map *bpf_map_get(struct fd f); ...@@ -133,7 +136,7 @@ struct bpf_map *bpf_map_get(struct fd f);
void bpf_map_put(struct bpf_map *map); void bpf_map_put(struct bpf_map *map);
/* verify correctness of eBPF program */ /* verify correctness of eBPF program */
int bpf_check(struct bpf_prog *fp, union bpf_attr *attr); int bpf_check(struct bpf_prog **fp, union bpf_attr *attr);
#else #else
static inline void bpf_register_prog_type(struct bpf_prog_type_list *tl) static inline void bpf_register_prog_type(struct bpf_prog_type_list *tl)
{ {
......
...@@ -170,4 +170,14 @@ enum bpf_func_id { ...@@ -170,4 +170,14 @@ enum bpf_func_id {
__BPF_FUNC_MAX_ID, __BPF_FUNC_MAX_ID,
}; };
/* user accessible mirror of in-kernel sk_buff.
* new fields can only be added to the end of this structure
*/
struct __sk_buff {
__u32 len;
__u32 pkt_type;
__u32 mark;
__u32 queue_mapping;
};
#endif /* _UAPI__LINUX_BPF_H__ */ #endif /* _UAPI__LINUX_BPF_H__ */
...@@ -519,7 +519,7 @@ static int bpf_prog_load(union bpf_attr *attr) ...@@ -519,7 +519,7 @@ static int bpf_prog_load(union bpf_attr *attr)
goto free_prog; goto free_prog;
/* run eBPF verifier */ /* run eBPF verifier */
err = bpf_check(prog, attr); err = bpf_check(&prog, attr);
if (err < 0) if (err < 0)
goto free_used_maps; goto free_used_maps;
......
...@@ -1620,11 +1620,10 @@ static int do_check(struct verifier_env *env) ...@@ -1620,11 +1620,10 @@ static int do_check(struct verifier_env *env)
return err; return err;
} else if (class == BPF_LDX) { } else if (class == BPF_LDX) {
if (BPF_MODE(insn->code) != BPF_MEM || enum bpf_reg_type src_reg_type;
insn->imm != 0) {
verbose("BPF_LDX uses reserved fields\n"); /* check for reserved fields is already done */
return -EINVAL;
}
/* check src operand */ /* check src operand */
err = check_reg_arg(regs, insn->src_reg, SRC_OP); err = check_reg_arg(regs, insn->src_reg, SRC_OP);
if (err) if (err)
...@@ -1643,6 +1642,29 @@ static int do_check(struct verifier_env *env) ...@@ -1643,6 +1642,29 @@ static int do_check(struct verifier_env *env)
if (err) if (err)
return err; return err;
src_reg_type = regs[insn->src_reg].type;
if (insn->imm == 0 && BPF_SIZE(insn->code) == BPF_W) {
/* saw a valid insn
* dst_reg = *(u32 *)(src_reg + off)
* use reserved 'imm' field to mark this insn
*/
insn->imm = src_reg_type;
} else if (src_reg_type != insn->imm &&
(src_reg_type == PTR_TO_CTX ||
insn->imm == PTR_TO_CTX)) {
/* ABuser program is trying to use the same insn
* dst_reg = *(u32*) (src_reg + off)
* with different pointer types:
* src_reg == ctx in one branch and
* src_reg == stack|map in some other branch.
* Reject it.
*/
verbose("same insn cannot be used with different pointers\n");
return -EINVAL;
}
} else if (class == BPF_STX) { } else if (class == BPF_STX) {
if (BPF_MODE(insn->code) == BPF_XADD) { if (BPF_MODE(insn->code) == BPF_XADD) {
err = check_xadd(env, insn); err = check_xadd(env, insn);
...@@ -1790,6 +1812,13 @@ static int replace_map_fd_with_map_ptr(struct verifier_env *env) ...@@ -1790,6 +1812,13 @@ static int replace_map_fd_with_map_ptr(struct verifier_env *env)
int i, j; int i, j;
for (i = 0; i < insn_cnt; i++, insn++) { for (i = 0; i < insn_cnt; i++, insn++) {
if (BPF_CLASS(insn->code) == BPF_LDX &&
(BPF_MODE(insn->code) != BPF_MEM ||
insn->imm != 0)) {
verbose("BPF_LDX uses reserved fields\n");
return -EINVAL;
}
if (insn[0].code == (BPF_LD | BPF_IMM | BPF_DW)) { if (insn[0].code == (BPF_LD | BPF_IMM | BPF_DW)) {
struct bpf_map *map; struct bpf_map *map;
struct fd f; struct fd f;
...@@ -1881,6 +1910,92 @@ static void convert_pseudo_ld_imm64(struct verifier_env *env) ...@@ -1881,6 +1910,92 @@ static void convert_pseudo_ld_imm64(struct verifier_env *env)
insn->src_reg = 0; insn->src_reg = 0;
} }
static void adjust_branches(struct bpf_prog *prog, int pos, int delta)
{
struct bpf_insn *insn = prog->insnsi;
int insn_cnt = prog->len;
int i;
for (i = 0; i < insn_cnt; i++, insn++) {
if (BPF_CLASS(insn->code) != BPF_JMP ||
BPF_OP(insn->code) == BPF_CALL ||
BPF_OP(insn->code) == BPF_EXIT)
continue;
/* adjust offset of jmps if necessary */
if (i < pos && i + insn->off + 1 > pos)
insn->off += delta;
else if (i > pos && i + insn->off + 1 < pos)
insn->off -= delta;
}
}
/* convert load instructions that access fields of 'struct __sk_buff'
* into sequence of instructions that access fields of 'struct sk_buff'
*/
static int convert_ctx_accesses(struct verifier_env *env)
{
struct bpf_insn *insn = env->prog->insnsi;
int insn_cnt = env->prog->len;
struct bpf_insn insn_buf[16];
struct bpf_prog *new_prog;
u32 cnt;
int i;
if (!env->prog->aux->ops->convert_ctx_access)
return 0;
for (i = 0; i < insn_cnt; i++, insn++) {
if (insn->code != (BPF_LDX | BPF_MEM | BPF_W))
continue;
if (insn->imm != PTR_TO_CTX) {
/* clear internal mark */
insn->imm = 0;
continue;
}
cnt = env->prog->aux->ops->
convert_ctx_access(insn->dst_reg, insn->src_reg,
insn->off, insn_buf);
if (cnt == 0 || cnt >= ARRAY_SIZE(insn_buf)) {
verbose("bpf verifier is misconfigured\n");
return -EINVAL;
}
if (cnt == 1) {
memcpy(insn, insn_buf, sizeof(*insn));
continue;
}
/* several new insns need to be inserted. Make room for them */
insn_cnt += cnt - 1;
new_prog = bpf_prog_realloc(env->prog,
bpf_prog_size(insn_cnt),
GFP_USER);
if (!new_prog)
return -ENOMEM;
new_prog->len = insn_cnt;
memmove(new_prog->insnsi + i + cnt, new_prog->insns + i + 1,
sizeof(*insn) * (insn_cnt - i - cnt));
/* copy substitute insns in place of load instruction */
memcpy(new_prog->insnsi + i, insn_buf, sizeof(*insn) * cnt);
/* adjust branches in the whole program */
adjust_branches(new_prog, i, cnt - 1);
/* keep walking new program and skip insns we just inserted */
env->prog = new_prog;
insn = new_prog->insnsi + i + cnt - 1;
i += cnt - 1;
}
return 0;
}
static void free_states(struct verifier_env *env) static void free_states(struct verifier_env *env)
{ {
struct verifier_state_list *sl, *sln; struct verifier_state_list *sl, *sln;
...@@ -1903,13 +2018,13 @@ static void free_states(struct verifier_env *env) ...@@ -1903,13 +2018,13 @@ static void free_states(struct verifier_env *env)
kfree(env->explored_states); kfree(env->explored_states);
} }
int bpf_check(struct bpf_prog *prog, union bpf_attr *attr) int bpf_check(struct bpf_prog **prog, union bpf_attr *attr)
{ {
char __user *log_ubuf = NULL; char __user *log_ubuf = NULL;
struct verifier_env *env; struct verifier_env *env;
int ret = -EINVAL; int ret = -EINVAL;
if (prog->len <= 0 || prog->len > BPF_MAXINSNS) if ((*prog)->len <= 0 || (*prog)->len > BPF_MAXINSNS)
return -E2BIG; return -E2BIG;
/* 'struct verifier_env' can be global, but since it's not small, /* 'struct verifier_env' can be global, but since it's not small,
...@@ -1919,7 +2034,7 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr) ...@@ -1919,7 +2034,7 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr)
if (!env) if (!env)
return -ENOMEM; return -ENOMEM;
env->prog = prog; env->prog = *prog;
/* grab the mutex to protect few globals used by verifier */ /* grab the mutex to protect few globals used by verifier */
mutex_lock(&bpf_verifier_lock); mutex_lock(&bpf_verifier_lock);
...@@ -1951,7 +2066,7 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr) ...@@ -1951,7 +2066,7 @@ 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;
env->explored_states = kcalloc(prog->len, env->explored_states = kcalloc(env->prog->len,
sizeof(struct verifier_state_list *), sizeof(struct verifier_state_list *),
GFP_USER); GFP_USER);
ret = -ENOMEM; ret = -ENOMEM;
...@@ -1968,6 +2083,10 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr) ...@@ -1968,6 +2083,10 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr)
while (pop_stack(env, NULL) >= 0); while (pop_stack(env, NULL) >= 0);
free_states(env); free_states(env);
if (ret == 0)
/* program is valid, convert *(u32*)(ctx + off) accesses */
ret = convert_ctx_accesses(env);
if (log_level && log_len >= log_size - 1) { if (log_level && log_len >= log_size - 1) {
BUG_ON(log_len >= log_size); BUG_ON(log_len >= log_size);
/* verifier log exceeded user supplied buffer */ /* verifier log exceeded user supplied buffer */
...@@ -1983,18 +2102,18 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr) ...@@ -1983,18 +2102,18 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr)
if (ret == 0 && env->used_map_cnt) { if (ret == 0 && env->used_map_cnt) {
/* if program passed verifier, update used_maps in bpf_prog_info */ /* if program passed verifier, update used_maps in bpf_prog_info */
prog->aux->used_maps = kmalloc_array(env->used_map_cnt, env->prog->aux->used_maps = kmalloc_array(env->used_map_cnt,
sizeof(env->used_maps[0]), sizeof(env->used_maps[0]),
GFP_KERNEL); GFP_KERNEL);
if (!prog->aux->used_maps) { if (!env->prog->aux->used_maps) {
ret = -ENOMEM; ret = -ENOMEM;
goto free_log_buf; goto free_log_buf;
} }
memcpy(prog->aux->used_maps, env->used_maps, memcpy(env->prog->aux->used_maps, env->used_maps,
sizeof(env->used_maps[0]) * env->used_map_cnt); sizeof(env->used_maps[0]) * env->used_map_cnt);
prog->aux->used_map_cnt = env->used_map_cnt; env->prog->aux->used_map_cnt = env->used_map_cnt;
/* program is valid. Convert pseudo bpf_ld_imm64 into generic /* program is valid. Convert pseudo bpf_ld_imm64 into generic
* bpf_ld_imm64 instructions * bpf_ld_imm64 instructions
...@@ -2006,11 +2125,12 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr) ...@@ -2006,11 +2125,12 @@ int bpf_check(struct bpf_prog *prog, union bpf_attr *attr)
if (log_level) if (log_level)
vfree(log_buf); vfree(log_buf);
free_env: free_env:
if (!prog->aux->used_maps) if (!env->prog->aux->used_maps)
/* if we didn't copy map pointers into bpf_prog_info, release /* if we didn't copy map pointers into bpf_prog_info, release
* them now. Otherwise free_bpf_prog_info() will release them. * them now. Otherwise free_bpf_prog_info() will release them.
*/ */
release_maps(env); release_maps(env);
*prog = env->prog;
kfree(env); kfree(env);
mutex_unlock(&bpf_verifier_lock); mutex_unlock(&bpf_verifier_lock);
return ret; return ret;
......
...@@ -150,10 +150,43 @@ static u64 __get_random_u32(u64 ctx, u64 a, u64 x, u64 r4, u64 r5) ...@@ -150,10 +150,43 @@ static u64 __get_random_u32(u64 ctx, u64 a, u64 x, u64 r4, u64 r5)
return prandom_u32(); return prandom_u32();
} }
static u32 convert_skb_access(int skb_field, int dst_reg, int src_reg,
struct bpf_insn *insn_buf)
{
struct bpf_insn *insn = insn_buf;
switch (skb_field) {
case SKF_AD_MARK:
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, mark) != 4);
*insn++ = BPF_LDX_MEM(BPF_W, dst_reg, src_reg,
offsetof(struct sk_buff, mark));
break;
case SKF_AD_PKTTYPE:
*insn++ = BPF_LDX_MEM(BPF_B, dst_reg, src_reg, PKT_TYPE_OFFSET());
*insn++ = BPF_ALU32_IMM(BPF_AND, dst_reg, PKT_TYPE_MAX);
#ifdef __BIG_ENDIAN_BITFIELD
*insn++ = BPF_ALU32_IMM(BPF_RSH, dst_reg, 5);
#endif
break;
case SKF_AD_QUEUE:
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, queue_mapping) != 2);
*insn++ = BPF_LDX_MEM(BPF_H, dst_reg, src_reg,
offsetof(struct sk_buff, queue_mapping));
break;
}
return insn - insn_buf;
}
static bool convert_bpf_extensions(struct sock_filter *fp, static bool convert_bpf_extensions(struct sock_filter *fp,
struct bpf_insn **insnp) struct bpf_insn **insnp)
{ {
struct bpf_insn *insn = *insnp; struct bpf_insn *insn = *insnp;
u32 cnt;
switch (fp->k) { switch (fp->k) {
case SKF_AD_OFF + SKF_AD_PROTOCOL: case SKF_AD_OFF + SKF_AD_PROTOCOL:
...@@ -167,13 +200,8 @@ static bool convert_bpf_extensions(struct sock_filter *fp, ...@@ -167,13 +200,8 @@ static bool convert_bpf_extensions(struct sock_filter *fp,
break; break;
case SKF_AD_OFF + SKF_AD_PKTTYPE: case SKF_AD_OFF + SKF_AD_PKTTYPE:
*insn++ = BPF_LDX_MEM(BPF_B, BPF_REG_A, BPF_REG_CTX, cnt = convert_skb_access(SKF_AD_PKTTYPE, BPF_REG_A, BPF_REG_CTX, insn);
PKT_TYPE_OFFSET()); insn += cnt - 1;
*insn = BPF_ALU32_IMM(BPF_AND, BPF_REG_A, PKT_TYPE_MAX);
#ifdef __BIG_ENDIAN_BITFIELD
insn++;
*insn = BPF_ALU32_IMM(BPF_RSH, BPF_REG_A, 5);
#endif
break; break;
case SKF_AD_OFF + SKF_AD_IFINDEX: case SKF_AD_OFF + SKF_AD_IFINDEX:
...@@ -197,10 +225,8 @@ static bool convert_bpf_extensions(struct sock_filter *fp, ...@@ -197,10 +225,8 @@ static bool convert_bpf_extensions(struct sock_filter *fp,
break; break;
case SKF_AD_OFF + SKF_AD_MARK: case SKF_AD_OFF + SKF_AD_MARK:
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, mark) != 4); cnt = convert_skb_access(SKF_AD_MARK, BPF_REG_A, BPF_REG_CTX, insn);
insn += cnt - 1;
*insn = BPF_LDX_MEM(BPF_W, BPF_REG_A, BPF_REG_CTX,
offsetof(struct sk_buff, mark));
break; break;
case SKF_AD_OFF + SKF_AD_RXHASH: case SKF_AD_OFF + SKF_AD_RXHASH:
...@@ -211,10 +237,8 @@ static bool convert_bpf_extensions(struct sock_filter *fp, ...@@ -211,10 +237,8 @@ static bool convert_bpf_extensions(struct sock_filter *fp,
break; break;
case SKF_AD_OFF + SKF_AD_QUEUE: case SKF_AD_OFF + SKF_AD_QUEUE:
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, queue_mapping) != 2); cnt = convert_skb_access(SKF_AD_QUEUE, BPF_REG_A, BPF_REG_CTX, insn);
insn += cnt - 1;
*insn = BPF_LDX_MEM(BPF_H, BPF_REG_A, BPF_REG_CTX,
offsetof(struct sk_buff, queue_mapping));
break; break;
case SKF_AD_OFF + SKF_AD_VLAN_TAG: case SKF_AD_OFF + SKF_AD_VLAN_TAG:
...@@ -1151,13 +1175,55 @@ sk_filter_func_proto(enum bpf_func_id func_id) ...@@ -1151,13 +1175,55 @@ sk_filter_func_proto(enum bpf_func_id func_id)
static bool sk_filter_is_valid_access(int off, int size, static bool sk_filter_is_valid_access(int off, int size,
enum bpf_access_type type) enum bpf_access_type type)
{ {
/* skb fields cannot be accessed yet */ /* only read is allowed */
return false; if (type != BPF_READ)
return false;
/* check bounds */
if (off < 0 || off >= sizeof(struct __sk_buff))
return false;
/* disallow misaligned access */
if (off % size != 0)
return false;
/* all __sk_buff fields are __u32 */
if (size != 4)
return false;
return true;
}
static u32 sk_filter_convert_ctx_access(int dst_reg, int src_reg, int ctx_off,
struct bpf_insn *insn_buf)
{
struct bpf_insn *insn = insn_buf;
switch (ctx_off) {
case offsetof(struct __sk_buff, len):
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, len) != 4);
*insn++ = BPF_LDX_MEM(BPF_W, dst_reg, src_reg,
offsetof(struct sk_buff, len));
break;
case offsetof(struct __sk_buff, mark):
return convert_skb_access(SKF_AD_MARK, dst_reg, src_reg, insn);
case offsetof(struct __sk_buff, pkt_type):
return convert_skb_access(SKF_AD_PKTTYPE, dst_reg, src_reg, insn);
case offsetof(struct __sk_buff, queue_mapping):
return convert_skb_access(SKF_AD_QUEUE, dst_reg, src_reg, insn);
}
return insn - insn_buf;
} }
static const struct bpf_verifier_ops sk_filter_ops = { static const struct bpf_verifier_ops sk_filter_ops = {
.get_func_proto = sk_filter_func_proto, .get_func_proto = sk_filter_func_proto,
.is_valid_access = sk_filter_is_valid_access, .is_valid_access = sk_filter_is_valid_access,
.convert_ctx_access = sk_filter_convert_ctx_access,
}; };
static struct bpf_prog_type_list sk_filter_type __read_mostly = { static struct bpf_prog_type_list sk_filter_type __read_mostly = {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册