bpf: selftest: A bpf prog that has a 32bit scalar spill

It is a simplified example that can trigger a 32bit scalar spill.
The const scalar is refilled and added to a skb->data later.
Since the reg state of the 32bit scalar spill is not saved now,
adding the refilled reg to skb->data and then comparing it with
skb->data_end cannot verify the skb->data access.

With the earlier verifier patch and the llvm patch [1].  The verifier
can correctly verify the bpf prog.

Here is the snippet of the verifier log that leads to verifier conclusion
that the packet data is unsafe to read.  The log is from the kerne
without the previous verifier patch to save the <8-byte scalar spill.
67: R0=inv1 R1=inv17 R2=invP2 R3=inv1 R4=pkt(id=0,off=68,r=102,imm=0) R5=inv102 R6=pkt(id=0,off=62,r=102,imm=0) R7=pkt(id=0,off=0,r=102,imm=0) R8=pkt_end(id=0,off=0,imm=0) R9=inv17 R10=fp0
67: (63) *(u32 *)(r10 -12) = r5
68: R0=inv1 R1=inv17 R2=invP2 R3=inv1 R4=pkt(id=0,off=68,r=102,imm=0) R5=inv102 R6=pkt(id=0,off=62,r=102,imm=0) R7=pkt(id=0,off=0,r=102,imm=0) R8=pkt_end(id=0,off=0,imm=0) R9=inv17 R10=fp0 fp-16=mmmm????
...
101: R0_w=map_value_or_null(id=2,off=0,ks=16,vs=1,imm=0) R6_w=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=0,off=0,r=102,imm=0) R8=pkt_end(id=0,off=0,imm=0) R9=inv17 R10=fp0 fp-16=mmmmmmmm
101: (61) r1 = *(u32 *)(r10 -12)
102: R0_w=map_value_or_null(id=2,off=0,ks=16,vs=1,imm=0) R1_w=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6_w=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=0,off=0,r=102,imm=0) R8=pkt_end(id=0,off=0,imm=0) R9=inv17 R10=fp0 fp-16=mmmmmmmm
102: (bc) w1 = w1
103: R0_w=map_value_or_null(id=2,off=0,ks=16,vs=1,imm=0) R1_w=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6_w=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=0,off=0,r=102,imm=0) R8=pkt_end(id=0,off=0,imm=0) R9=inv17 R10=fp0 fp-16=mmmmmmmm
103: (0f) r7 += r1
last_idx 103 first_idx 67
regs=2 stack=0 before 102: (bc) w1 = w1
regs=2 stack=0 before 101: (61) r1 = *(u32 *)(r10 -12)
104: R0_w=map_value_or_null(id=2,off=0,ks=16,vs=1,imm=0) R1_w=invP(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6_w=pkt(id=0,off=70,r=102,imm=0) R7_w=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9=inv17 R10=fp0 fp-16=mmmmmmmm
...
127: R0_w=inv1 R1=invP(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9_w=invP17 R10=fp0 fp-16=mmmmmmmm
127: (bf) r1 = r7
128: R0_w=inv1 R1_w=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9_w=invP17 R10=fp0 fp-16=mmmmmmmm
128: (07) r1 += 8
129: R0_w=inv1 R1_w=pkt(id=3,off=8,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9_w=invP17 R10=fp0 fp-16=mmmmmmmm
129: (b4) w0 = 1
130: R0=inv1 R1=pkt(id=3,off=8,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9=invP17 R10=fp0 fp-16=mmmmmmmm
130: (2d) if r1 > r8 goto pc-66
 R0=inv1 R1=pkt(id=3,off=8,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9=invP17 R10=fp0 fp-16=mmmmmmmm
131: R0=inv1 R1=pkt(id=3,off=8,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R6=pkt(id=0,off=70,r=102,imm=0) R7=pkt(id=3,off=0,r=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=pkt_end(id=0,off=0,imm=0) R9=invP17 R10=fp0 fp-16=mmmmmmmm
131: (69) r6 = *(u16 *)(r7 +0)
invalid access to packet, off=0 size=2, R7(id=3,off=0,r=0)
R7 offset is outside of the packet

[1]: https://reviews.llvm.org/D109073

Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20210922004947.626286-1-kafai@fb.com
This commit is contained in:
Martin KaFai Lau 2021-09-21 17:49:47 -07:00 коммит произвёл Alexei Starovoitov
Родитель 354e8f1970
Коммит 54ea6079b7
3 изменённых файлов: 393 добавлений и 0 удалений

Просмотреть файл

@ -242,3 +242,16 @@ To fix this issue, user newer libbpf.
.. Links
.. _clang reloc patch: https://reviews.llvm.org/D102712
.. _kernel llvm reloc: /Documentation/bpf/llvm_reloc.rst
Clang dependencies for the u32 spill test (xdpwall)
===================================================
The xdpwall selftest requires a change in `Clang 14`__.
Without it, the xdpwall selftest will fail and the error message
from running test_progs will look like:
.. code-block:: console
test_xdpwall:FAIL:Does LLVM have https://reviews.llvm.org/D109073? unexpected error: -4007
__ https://reviews.llvm.org/D109073

Просмотреть файл

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021 Facebook */
#include "test_progs.h"
#include "xdpwall.skel.h"
void test_xdpwall(void)
{
struct xdpwall *skel;
skel = xdpwall__open_and_load();
ASSERT_OK_PTR(skel, "Does LLMV have https://reviews.llvm.org/D109073?");
xdpwall__destroy(skel);
}

Просмотреть файл

@ -0,0 +1,365 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021 Facebook */
#include <stdbool.h>
#include <stdint.h>
#include <linux/stddef.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/bpf.h>
#include <linux/types.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
enum pkt_parse_err {
NO_ERR,
BAD_IP6_HDR,
BAD_IP4GUE_HDR,
BAD_IP6GUE_HDR,
};
enum pkt_flag {
TUNNEL = 0x1,
TCP_SYN = 0x2,
QUIC_INITIAL_FLAG = 0x4,
TCP_ACK = 0x8,
TCP_RST = 0x10
};
struct v4_lpm_key {
__u32 prefixlen;
__u32 src;
};
struct v4_lpm_val {
struct v4_lpm_key key;
__u8 val;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 16);
__type(key, struct in6_addr);
__type(value, bool);
} v6_addr_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 16);
__type(key, __u32);
__type(value, bool);
} v4_addr_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__uint(max_entries, 16);
__uint(key_size, sizeof(struct v4_lpm_key));
__uint(value_size, sizeof(struct v4_lpm_val));
__uint(map_flags, BPF_F_NO_PREALLOC);
} v4_lpm_val_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 16);
__type(key, int);
__type(value, __u8);
} tcp_port_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 16);
__type(key, int);
__type(value, __u16);
} udp_port_map SEC(".maps");
enum ip_type { V4 = 1, V6 = 2 };
struct fw_match_info {
__u8 v4_src_ip_match;
__u8 v6_src_ip_match;
__u8 v4_src_prefix_match;
__u8 v4_dst_prefix_match;
__u8 tcp_dp_match;
__u16 udp_sp_match;
__u16 udp_dp_match;
bool is_tcp;
bool is_tcp_syn;
};
struct pkt_info {
enum ip_type type;
union {
struct iphdr *ipv4;
struct ipv6hdr *ipv6;
} ip;
int sport;
int dport;
__u16 trans_hdr_offset;
__u8 proto;
__u8 flags;
};
static __always_inline struct ethhdr *parse_ethhdr(void *data, void *data_end)
{
struct ethhdr *eth = data;
if (eth + 1 > data_end)
return NULL;
return eth;
}
static __always_inline __u8 filter_ipv6_addr(const struct in6_addr *ipv6addr)
{
__u8 *leaf;
leaf = bpf_map_lookup_elem(&v6_addr_map, ipv6addr);
return leaf ? *leaf : 0;
}
static __always_inline __u8 filter_ipv4_addr(const __u32 ipaddr)
{
__u8 *leaf;
leaf = bpf_map_lookup_elem(&v4_addr_map, &ipaddr);
return leaf ? *leaf : 0;
}
static __always_inline __u8 filter_ipv4_lpm(const __u32 ipaddr)
{
struct v4_lpm_key v4_key = {};
struct v4_lpm_val *lpm_val;
v4_key.src = ipaddr;
v4_key.prefixlen = 32;
lpm_val = bpf_map_lookup_elem(&v4_lpm_val_map, &v4_key);
return lpm_val ? lpm_val->val : 0;
}
static __always_inline void
filter_src_dst_ip(struct pkt_info* info, struct fw_match_info* match_info)
{
if (info->type == V6) {
match_info->v6_src_ip_match =
filter_ipv6_addr(&info->ip.ipv6->saddr);
} else if (info->type == V4) {
match_info->v4_src_ip_match =
filter_ipv4_addr(info->ip.ipv4->saddr);
match_info->v4_src_prefix_match =
filter_ipv4_lpm(info->ip.ipv4->saddr);
match_info->v4_dst_prefix_match =
filter_ipv4_lpm(info->ip.ipv4->daddr);
}
}
static __always_inline void *
get_transport_hdr(__u16 offset, void *data, void *data_end)
{
if (offset > 255 || data + offset > data_end)
return NULL;
return data + offset;
}
static __always_inline bool tcphdr_only_contains_flag(struct tcphdr *tcp,
__u32 FLAG)
{
return (tcp_flag_word(tcp) &
(TCP_FLAG_ACK | TCP_FLAG_RST | TCP_FLAG_SYN | TCP_FLAG_FIN)) == FLAG;
}
static __always_inline void set_tcp_flags(struct pkt_info *info,
struct tcphdr *tcp) {
if (tcphdr_only_contains_flag(tcp, TCP_FLAG_SYN))
info->flags |= TCP_SYN;
else if (tcphdr_only_contains_flag(tcp, TCP_FLAG_ACK))
info->flags |= TCP_ACK;
else if (tcphdr_only_contains_flag(tcp, TCP_FLAG_RST))
info->flags |= TCP_RST;
}
static __always_inline bool
parse_tcp(struct pkt_info *info, void *transport_hdr, void *data_end)
{
struct tcphdr *tcp = transport_hdr;
if (tcp + 1 > data_end)
return false;
info->sport = bpf_ntohs(tcp->source);
info->dport = bpf_ntohs(tcp->dest);
set_tcp_flags(info, tcp);
return true;
}
static __always_inline bool
parse_udp(struct pkt_info *info, void *transport_hdr, void *data_end)
{
struct udphdr *udp = transport_hdr;
if (udp + 1 > data_end)
return false;
info->sport = bpf_ntohs(udp->source);
info->dport = bpf_ntohs(udp->dest);
return true;
}
static __always_inline __u8 filter_tcp_port(int port)
{
__u8 *leaf = bpf_map_lookup_elem(&tcp_port_map, &port);
return leaf ? *leaf : 0;
}
static __always_inline __u16 filter_udp_port(int port)
{
__u16 *leaf = bpf_map_lookup_elem(&udp_port_map, &port);
return leaf ? *leaf : 0;
}
static __always_inline bool
filter_transport_hdr(void *transport_hdr, void *data_end,
struct pkt_info *info, struct fw_match_info *match_info)
{
if (info->proto == IPPROTO_TCP) {
if (!parse_tcp(info, transport_hdr, data_end))
return false;
match_info->is_tcp = true;
match_info->is_tcp_syn = (info->flags & TCP_SYN) > 0;
match_info->tcp_dp_match = filter_tcp_port(info->dport);
} else if (info->proto == IPPROTO_UDP) {
if (!parse_udp(info, transport_hdr, data_end))
return false;
match_info->udp_dp_match = filter_udp_port(info->dport);
match_info->udp_sp_match = filter_udp_port(info->sport);
}
return true;
}
static __always_inline __u8
parse_gue_v6(struct pkt_info *info, struct ipv6hdr *ip6h, void *data_end)
{
struct udphdr *udp = (struct udphdr *)(ip6h + 1);
void *encap_data = udp + 1;
if (udp + 1 > data_end)
return BAD_IP6_HDR;
if (udp->dest != bpf_htons(6666))
return NO_ERR;
info->flags |= TUNNEL;
if (encap_data + 1 > data_end)
return BAD_IP6GUE_HDR;
if (*(__u8 *)encap_data & 0x30) {
struct ipv6hdr *inner_ip6h = encap_data;
if (inner_ip6h + 1 > data_end)
return BAD_IP6GUE_HDR;
info->type = V6;
info->proto = inner_ip6h->nexthdr;
info->ip.ipv6 = inner_ip6h;
info->trans_hdr_offset += sizeof(struct ipv6hdr) + sizeof(struct udphdr);
} else {
struct iphdr *inner_ip4h = encap_data;
if (inner_ip4h + 1 > data_end)
return BAD_IP6GUE_HDR;
info->type = V4;
info->proto = inner_ip4h->protocol;
info->ip.ipv4 = inner_ip4h;
info->trans_hdr_offset += sizeof(struct iphdr) + sizeof(struct udphdr);
}
return NO_ERR;
}
static __always_inline __u8 parse_ipv6_gue(struct pkt_info *info,
void *data, void *data_end)
{
struct ipv6hdr *ip6h = data + sizeof(struct ethhdr);
if (ip6h + 1 > data_end)
return BAD_IP6_HDR;
info->proto = ip6h->nexthdr;
info->ip.ipv6 = ip6h;
info->type = V6;
info->trans_hdr_offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
if (info->proto == IPPROTO_UDP)
return parse_gue_v6(info, ip6h, data_end);
return NO_ERR;
}
SEC("xdp")
int edgewall(struct xdp_md *ctx)
{
void *data_end = (void *)(long)(ctx->data_end);
void *data = (void *)(long)(ctx->data);
struct fw_match_info match_info = {};
struct pkt_info info = {};
__u8 parse_err = NO_ERR;
void *transport_hdr;
struct ethhdr *eth;
bool filter_res;
__u32 proto;
eth = parse_ethhdr(data, data_end);
if (!eth)
return XDP_DROP;
proto = eth->h_proto;
if (proto != bpf_htons(ETH_P_IPV6))
return XDP_DROP;
if (parse_ipv6_gue(&info, data, data_end))
return XDP_DROP;
if (info.proto == IPPROTO_ICMPV6)
return XDP_PASS;
if (info.proto != IPPROTO_TCP && info.proto != IPPROTO_UDP)
return XDP_DROP;
filter_src_dst_ip(&info, &match_info);
transport_hdr = get_transport_hdr(info.trans_hdr_offset, data,
data_end);
if (!transport_hdr)
return XDP_DROP;
filter_res = filter_transport_hdr(transport_hdr, data_end,
&info, &match_info);
if (!filter_res)
return XDP_DROP;
if (match_info.is_tcp && !match_info.is_tcp_syn)
return XDP_PASS;
return XDP_DROP;
}
char LICENSE[] SEC("license") = "GPL";