Skip to content
Permalink
Browse files
net: flow_dissector: extend bpf flow dissector support with vnet hdr
Amend the bpf flow dissector program type to be able to process
virtio-net headers. Do this to enable bpf flow dissector programs to
perform virtio-net header validation. The next patch in this series
will add a flow dissection hook in virtio_net_hdr_to_skb and make use
of this extended functionality. That commit message has more
background on the use case.

Add two new members to struct bpf_flow_keys: a pointer to struct
virtio_net_hdr, and vhdr_is_little_endian. The latter is required to
inform the BPF program of the endianness of the virtio-net header
fields, to handle the case of a version 1+ header on a big endian
machine.

Changes
v6:
  - Move bpf_flow_dissector_btf_ids, check_flow_keys_access() to
    filter.c
  - Verify (off % size == 0) in check_flow_keys_access()
  - Check bpf_flow_dissector_btf_ids[0] is nonzero in
    check_flow_keys_access()
v5:
  - Use PTR_TO_BTF_ID_OR_NULL instead of defining new
    PTR_TO_VNET_HDR_OR_NULL
  - Make check_flow_keys_access() disallow writes to keys->vhdr
  - Make check_flow_keys_access() check loading keys->vhdr is in
    sizeof(__u64)
  - Use BPF_REG_AX instead of BPF_REG_TMP as scratch reg
  - Describe parameter vhdr_is_little_endian in __skb_flow_dissect
    documentation
v4:
  - Add virtio_net_hdr pointer to struct bpf_flow_keys
  - Add vhdr_is_little_endian to struct bpf_flow_keys
v2:
  - Describe parameter vhdr in __skb_flow_dissect documentation

Signed-off-by: Tanner Love <tannerlove@google.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Petar Penkov <ppenkov@google.com>
Reviewed-by: Stanislav Fomichev <sdf@google.com>
  • Loading branch information
tannerlove authored and intel-lab-lkp committed Jun 16, 2021
1 parent c765449 commit 7d159f648961a7849f67e1c3d7ecb3d18bf5c7c2
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 28 deletions.
@@ -3554,7 +3554,7 @@ static bool bond_flow_dissect(struct bonding *bond, struct sk_buff *skb,
case BOND_XMIT_POLICY_ENCAP34:
memset(fk, 0, sizeof(*fk));
return __skb_flow_dissect(NULL, skb, &flow_keys_bonding,
fk, NULL, 0, 0, 0, 0);
fk, NULL, 0, 0, 0, 0, NULL, false);
default:
break;
}
@@ -1515,6 +1515,9 @@ static inline int bpf_map_attr_numa_node(const union bpf_attr *attr)
attr->numa_node : NUMA_NO_NODE;
}

int check_flow_keys_access(int off, int size, enum bpf_access_type t,
struct bpf_insn_access_aux *info);

struct bpf_prog *bpf_prog_get_type_path(const char *name, enum bpf_prog_type type);
int array_map_alloc_check(union bpf_attr *attr);

@@ -1314,21 +1314,27 @@ void skb_flow_dissector_init(struct flow_dissector *flow_dissector,
unsigned int key_count);

struct bpf_flow_dissector;
struct virtio_net_hdr;
bool bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx,
__be16 proto, int nhoff, int hlen, unsigned int flags);
__be16 proto, int nhoff, int hlen, unsigned int flags,
const struct virtio_net_hdr *vhdr,
bool vhdr_is_little_endian);

bool __skb_flow_dissect(const struct net *net,
const struct sk_buff *skb,
struct flow_dissector *flow_dissector,
void *target_container, const void *data,
__be16 proto, int nhoff, int hlen, unsigned int flags);
__be16 proto, int nhoff, int hlen, unsigned int flags,
const struct virtio_net_hdr *vhdr,
bool vhdr_is_little_endian);

static inline bool skb_flow_dissect(const struct sk_buff *skb,
struct flow_dissector *flow_dissector,
void *target_container, unsigned int flags)
{
return __skb_flow_dissect(NULL, skb, flow_dissector,
target_container, NULL, 0, 0, 0, flags);
target_container, NULL, 0, 0, 0, flags, NULL,
false);
}

static inline bool skb_flow_dissect_flow_keys(const struct sk_buff *skb,
@@ -1337,7 +1343,22 @@ static inline bool skb_flow_dissect_flow_keys(const struct sk_buff *skb,
{
memset(flow, 0, sizeof(*flow));
return __skb_flow_dissect(NULL, skb, &flow_keys_dissector,
flow, NULL, 0, 0, 0, flags);
flow, NULL, 0, 0, 0, flags, NULL, false);
}

static inline bool
__skb_flow_dissect_flow_keys_basic(const struct net *net,
const struct sk_buff *skb,
struct flow_keys_basic *flow,
const void *data, __be16 proto,
int nhoff, int hlen, unsigned int flags,
const struct virtio_net_hdr *vhdr,
bool vhdr_is_little_endian)
{
memset(flow, 0, sizeof(*flow));
return __skb_flow_dissect(net, skb, &flow_keys_basic_dissector, flow,
data, proto, nhoff, hlen, flags, vhdr,
vhdr_is_little_endian);
}

static inline bool
@@ -1347,9 +1368,9 @@ skb_flow_dissect_flow_keys_basic(const struct net *net,
const void *data, __be16 proto,
int nhoff, int hlen, unsigned int flags)
{
memset(flow, 0, sizeof(*flow));
return __skb_flow_dissect(net, skb, &flow_keys_basic_dissector, flow,
data, proto, nhoff, hlen, flags);
return __skb_flow_dissect_flow_keys_basic(net, skb, flow, data, proto,
nhoff, hlen, flags, NULL,
false);
}

void skb_flow_dissect_meta(const struct sk_buff *skb,
@@ -6017,6 +6017,8 @@ struct bpf_flow_keys {
};
__u32 flags;
__be32 flow_label;
__bpf_md_ptr(const struct virtio_net_hdr *, vhdr);
__u8 vhdr_is_little_endian;
};

struct bpf_func_info {
@@ -22,6 +22,7 @@
#include <linux/error-injection.h>
#include <linux/bpf_lsm.h>
#include <linux/btf_ids.h>
#include <linux/virtio_net.h>

#include "disasm.h"

@@ -3372,18 +3373,6 @@ static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off,
return -EACCES;
}

static int check_flow_keys_access(struct bpf_verifier_env *env, int off,
int size)
{
if (size < 0 || off < 0 ||
(u64)off + size > sizeof(struct bpf_flow_keys)) {
verbose(env, "invalid access to flow keys off=%d size=%d\n",
off, size);
return -EACCES;
}
return 0;
}

static int check_sock_access(struct bpf_verifier_env *env, int insn_idx,
u32 regno, int off, int size,
enum bpf_access_type t)
@@ -4210,16 +4199,32 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else if (reg->type == PTR_TO_FLOW_KEYS) {
struct bpf_insn_access_aux info = {};

if (t == BPF_WRITE && value_regno >= 0 &&
is_pointer_value(env, value_regno)) {
verbose(env, "R%d leaks addr into flow keys\n",
value_regno);
return -EACCES;
}

err = check_flow_keys_access(env, off, size);
if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
err = check_flow_keys_access(off, size, t, &info);
if (err) {
verbose(env,
"invalid access to flow keys off=%d size=%d\n",
off, size);
} else if (t == BPF_READ && value_regno >= 0) {
if (off == offsetof(struct bpf_flow_keys, vhdr)) {
mark_reg_known_zero(env, regs, value_regno);
regs[value_regno].type = PTR_TO_BTF_ID_OR_NULL;
regs[value_regno].btf = btf_vmlinux;
regs[value_regno].btf_id = info.btf_id;
/* required for dropping or_null */
regs[value_regno].id = ++env->id_gen;
} else {
mark_reg_unknown(env, regs, value_regno);
}
}
} else if (type_is_sk_pointer(reg->type)) {
if (t == BPF_WRITE) {
verbose(env, "R%d cannot write into %s\n",
@@ -797,7 +797,7 @@ int bpf_prog_test_run_flow_dissector(struct bpf_prog *prog,
bpf_test_timer_enter(&t);
do {
retval = bpf_flow_dissect(prog, &ctx, eth->h_proto, ETH_HLEN,
size, flags);
size, flags, NULL, false);
} while (bpf_test_timer_continue(&t, repeat, &ret, &duration));
bpf_test_timer_leave(&t);

@@ -8329,6 +8329,36 @@ static bool sk_msg_is_valid_access(int off, int size,
return true;
}

BTF_ID_LIST_SINGLE(bpf_flow_dissector_btf_ids, struct, virtio_net_hdr);

int check_flow_keys_access(int off, int size, enum bpf_access_type t,
struct bpf_insn_access_aux *info)
{
if (size < 0 || off < 0 ||
(u64)off + size > sizeof(struct bpf_flow_keys))
return -EACCES;

switch (off) {
case bpf_ctx_range_ptr(struct bpf_flow_keys, vhdr):
if (t == BPF_WRITE || off % size != 0 || size != sizeof(__u64))
return -EACCES;

if (!bpf_flow_dissector_btf_ids[0])
return -EINVAL;

info->btf_id = bpf_flow_dissector_btf_ids[0];

break;
case offsetof(struct bpf_flow_keys, vhdr_is_little_endian):
if (t == BPF_WRITE)
return -EACCES;

break;
}

return 0;
}

static bool flow_dissector_is_valid_access(int off, int size,
enum bpf_access_type type,
const struct bpf_prog *prog,
@@ -8358,6 +8388,8 @@ static bool flow_dissector_is_valid_access(int off, int size,
return false;
info->reg_type = PTR_TO_FLOW_KEYS;
return true;
case bpf_ctx_range(struct __sk_buff, len):
return size == size_default;
default:
return false;
}
@@ -8390,6 +8422,30 @@ static u32 flow_dissector_convert_ctx_access(enum bpf_access_type type,
si->dst_reg, si->src_reg,
offsetof(struct bpf_flow_dissector, flow_keys));
break;

case offsetof(struct __sk_buff, len):
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct bpf_flow_dissector, skb),
si->dst_reg, si->src_reg,
offsetof(struct bpf_flow_dissector, skb));
*insn++ = BPF_JMP_IMM(BPF_JNE, si->dst_reg, 0, 4);
/* bpf_flow_dissector->skb == NULL */
/* dst_reg = bpf_flow_dissector->data_end */
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct bpf_flow_dissector, data_end),
si->dst_reg, si->src_reg,
offsetof(struct bpf_flow_dissector, data_end));
/* AX = bpf_flow_dissector->data */
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct bpf_flow_dissector, data),
BPF_REG_AX, si->src_reg,
offsetof(struct bpf_flow_dissector, data));
/* dst_reg -= bpf_flow_dissector->data */
*insn++ = BPF_ALU64_REG(BPF_SUB, si->dst_reg, BPF_REG_AX);
*insn++ = BPF_JMP_A(1);
/* bpf_flow_dissector->skb != NULL */
/* bpf_flow_dissector->skb->len */
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, len),
si->dst_reg, si->dst_reg,
offsetof(struct sk_buff, len));
break;
}

return insn - insn_buf;
@@ -28,6 +28,7 @@
#include <scsi/fc/fc_fcoe.h>
#include <uapi/linux/batadv_packet.h>
#include <linux/bpf.h>
#include <linux/virtio_net.h>
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_labels.h>
@@ -864,7 +865,9 @@ static void __skb_flow_bpf_to_target(const struct bpf_flow_keys *flow_keys,
}

bool bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx,
__be16 proto, int nhoff, int hlen, unsigned int flags)
__be16 proto, int nhoff, int hlen, unsigned int flags,
const struct virtio_net_hdr *vhdr,
bool vhdr_is_little_endian)
{
struct bpf_flow_keys *flow_keys = ctx->flow_keys;
u32 result;
@@ -874,6 +877,8 @@ bool bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx,
flow_keys->n_proto = proto;
flow_keys->nhoff = nhoff;
flow_keys->thoff = flow_keys->nhoff;
flow_keys->vhdr = vhdr;
flow_keys->vhdr_is_little_endian = vhdr_is_little_endian;

BUILD_BUG_ON((int)BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG !=
(int)FLOW_DISSECTOR_F_PARSE_1ST_FRAG);
@@ -904,6 +909,8 @@ bool bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx,
* @hlen: packet header length, if @data is NULL use skb_headlen(skb)
* @flags: flags that control the dissection process, e.g.
* FLOW_DISSECTOR_F_STOP_AT_ENCAP.
* @vhdr: virtio_net_header to include in kernel context for BPF flow dissector
* @vhdr_is_little_endian: whether virtio_net_hdr fields are little endian
*
* The function will try to retrieve individual keys into target specified
* by flow_dissector from either the skbuff or a raw buffer specified by the
@@ -915,7 +922,9 @@ bool __skb_flow_dissect(const struct net *net,
const struct sk_buff *skb,
struct flow_dissector *flow_dissector,
void *target_container, const void *data,
__be16 proto, int nhoff, int hlen, unsigned int flags)
__be16 proto, int nhoff, int hlen, unsigned int flags,
const struct virtio_net_hdr *vhdr,
bool vhdr_is_little_endian)
{
struct flow_dissector_key_control *key_control;
struct flow_dissector_key_basic *key_basic;
@@ -1012,7 +1021,8 @@ bool __skb_flow_dissect(const struct net *net,

prog = READ_ONCE(run_array->items[0].prog);
ret = bpf_flow_dissect(prog, &ctx, n_proto, nhoff,
hlen, flags);
hlen, flags, vhdr,
vhdr_is_little_endian);
__skb_flow_bpf_to_target(&flow_keys, flow_dissector,
target_container);
rcu_read_unlock();
@@ -1610,7 +1620,7 @@ u32 __skb_get_hash_symmetric(const struct sk_buff *skb)
memset(&keys, 0, sizeof(keys));
__skb_flow_dissect(NULL, skb, &flow_keys_dissector_symmetric,
&keys, NULL, 0, 0, 0,
FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL);
FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL, NULL, false);

return __flow_hash_from_keys(&keys, &hashrnd);
}
@@ -6017,6 +6017,8 @@ struct bpf_flow_keys {
};
__u32 flags;
__be32 flow_label;
__bpf_md_ptr(const struct virtio_net_hdr *, vhdr);
__u8 vhdr_is_little_endian;
};

struct bpf_func_info {

0 comments on commit 7d159f6

Please sign in to comment.