BPF verifier rejects code: "invalid bpf_context access"
Asked Answered
H

1

6

I'm trying to write a simple socket filter eBPF program that can access the socket buffer data.

#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;
}

And I'm compiling using 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

However when I try to load the program I get the following verifier error:

invalid bpf_context access off=80 size=4

My understanding of this error is that it should be thrown when you try to access context data that hasn't been checked to be within data_end, however my code does do that:

Here is the instructions for my program

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

which would imply that the error is being caused by reading the pointer to data_end? However it only happens if I don't try to check the bounds later.

Homotaxis answered 9/5, 2020 at 19:3 Comment(1)
Haven't tried running the example yet, but I'm wondering what kernel version you tried it on and whether you made sure the kernel headers match that version?Philomel
I
6

This is because your BPF program is a “socket filter”, and that such programs are not allowed to do direct packet access (see sk_filter_is_valid_access(), where we return false on trying to read skb->data or skb->data_end for example). I do not know the specific reason why it is not available, although I suspect this would be a security precaution as socket filter programs may be available to unprivileged users.

Your program loads just fine as a TC classifier, for example (bpftool prog load foo.o /sys/fs/bpf/foo type classifier -- By the way thanks for the standalone working reproducer, much appreciated!).

If you want to access data for a socket filter, you can still use the bpf_skb_load_bytes() (or bpf_skb_store_bytes()) helper, which automatically does the check on length. Something like this:

#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;
}

Regarding your last comment:

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

I suspect clang compiles out the assignments for data and data_end if you do not use them in your code, so they are no longer present and no longer a problem for the verifier.

Inconsistency answered 11/5, 2020 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.