BPF 验证者拒绝代码:"invalid bpf_context access"

BPF verifier rejects code: "invalid bpf_context access"

我正在尝试编写一个可以访问套接字缓冲区数据的简单套接字过滤器 eBPF 程序。

#include <linux/bpf.h>
#include <linux/if_ether.h>

#define SEC(NAME) __attribute__((section(NAME), used))

SEC("socket_filter")
int myprog(struct __sk_buff *skb) {
        void *data = (void *)(long)skb->data;
        void *data_end = (void *)(long)skb->data_end;
        struct ethhdr *eth = data;

        if ((void*)eth + sizeof(*eth) > data_end)
                return 0;
        return 1;
}

我正在使用 clang 进行编译:

clang -I./ -I/usr/include/x86_64-linux-gnu/asm \
        -I/usr/include/x86_64-linux-gnu/ -O2 -target bpf -c test.c  -o test.elf

然而,当我尝试加载程序时,出现以下验证程序错误:

invalid bpf_context access off=80 size=4

我对这个错误的理解是,当您尝试访问未被检查为在 data_end 范围内的上下文数据时应该抛出它,但是我的代码确实这样做了:

这是我的程序的说明

0000000000000000 packet_counter:
   0:       61 12 50 00 00 00 00 00 r2 = *(u32 *)(r1 + 80)
   1:       61 11 4c 00 00 00 00 00 r1 = *(u32 *)(r1 + 76)
   2:       07 01 00 00 0e 00 00 00 r1 += 14
   3:       b7 00 00 00 01 00 00 00 r0 = 1
   4:       3d 12 01 00 00 00 00 00 if r2 >= r1 goto +1 <LBB0_2>
   5:       b7 00 00 00 00 00 00 00 r0 = 0

这意味着错误是由读取指向 data_end 的指针引起的?但是,只有当我稍后不尝试检查边界时才会发生这种情况。

这是因为您的 BPF 程序是 “套接字过滤器”,并且此类程序 不允许进行直接数据包访问 (参见 sk_filter_is_valid_access(),其中我们 return false 尝试阅读 skb->dataskb->data_end 为例)。我不知道它不可用的具体原因,尽管我怀疑这是一种安全预防措施,因为套接字过滤器程序可能可供非特权用户使用。

例如,您的程序作为 TC 分类器加载得很好(bpftool prog load foo.o /sys/fs/bpf/foo type classifier -- 顺便感谢独立工作的复制器,非常感谢!)。

如果你想访问套接字过滤器的数据,你仍然可以使用 bpf_skb_load_bytes()(或 bpf_skb_store_bytes())助手,它会自动检查长度。像这样:

#include <linux/bpf.h>

#define SEC(NAME) __attribute__((section(NAME), used))

static void *(*bpf_skb_load_bytes)(const struct __sk_buff *, __u32,
                                   void *, __u32) =
        (void *) BPF_FUNC_skb_load_bytes;

SEC("socket_filter")
int myprog(struct __sk_buff *skb)
{
        __u32 foo;

        if (bpf_skb_load_bytes(skb, 0, &foo, sizeof(foo)))
                return 0;
        if (foo == 3)
                return 0;
        return 1;
}

关于您最后的评论:

However it only happens if I don't try to check the bounds later.

我怀疑如果您不在代码中使用它们,clang 会编译出 datadata_end 的分配,因此它们不再存在并且不再是验证程序的问题。