Search code examples
ebpfxdp-bpf

Is it possible to access the packet when using bpf_loop?


I have tried a number of ways to access the packet when using bpf_loop with XDP. However verification always fails.

The following is an example of a program that I thought should pass verification but does not. It will fail verification reporting: invalid access to packet, off=0 size=1, R2(id=2,off=0,r=0) R2 offset is outside of the packet

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

typedef struct context {
    struct xdp_md *ctx;
} context_t;

static int do_check(__u32 index, context_t *search_ctx)
{
    __u8 *data = (__u8 *)(long)search_ctx->ctx->data;
    __u8 *data_end = (__u8 *)(long)search_ctx->ctx->data_end;
    __u8 *this_byte;

    if (index > 0 && index <= 100)
    {        
        this_byte = data + index;

        // When run with this change it will pass verification
        //this_byte = data + 100;
        
        if (this_byte >= data && this_byte < data_end) 
        {
            if (*this_byte == 123)
            {
                return 1;
            }
        }
    }
    return 0;
}

SEC("filter")
int  xdp_parser_func(struct xdp_md *ctx)
{
    context_t search_ctx = (context_t) 
    { 
        .ctx = ctx
    };
    
    bpf_loop(1, do_check, &search_ctx, 0);
    return XDP_PASS;
}

Interestingly one can access the packet at a hard coded location. The above will work if the commented out line is put back in.

Is there something about how the verifier works with bpf_loop that means it will never allow access to the packet at a calculated location? Or am I simply missing a check to convince the verifier that it is safe?

If it's important my Kernel version is 6.5.0-17-generic


Solution

  • There are two things with the code above that the verifier trips over.

    The verifier has a condition that asserts that the maximum possible value of a packet pointer can be 0xFFFF (max value of a unsigned 16-bit int). But since index is a u32 and we add it to data we exceed this max value. The verifier isn't smart enough in this case to recognize the index <= 100 in the if. So what we can do instead is to do a binary AND with 0x7FFF (index = index & 0x7FFF;). This doesn't change anything practically, execpt that the verifier can now be certain that index is never bigger than 0x7FFF which is smaller than 0xFFFF.

    The second catch is simpler, when bounds checking to see if you can access a given value we have to account for the size we want to access. So instead of this_byte < data_end we need to go this_byte + 1 < data_end. If you were to access a u16 you would have to make sure that 2 bytes are available this_u16 + 2 < data_end.

    Those two combined allow the program to pass, at least for me locally (v6.5):

    static int do_check(__u32 index, context_t *search_ctx)
    {
        __u8 *data = (__u8 *)(long)search_ctx->ctx->data;
        __u8 *data_end = (__u8 *)(long)search_ctx->ctx->data_end;
        __u8 *this_byte;
    
        index = index & 0x7FFF;
        if (index > 0 && index <= 100)
        {
            this_byte = data + index;
    
            // When run with this change it will pass verification
            // this_byte = data + 100;
    
            if (this_byte >= data && this_byte + 1 < data_end)
            {
                if (*this_byte == 123)
                {
                    return 1;
                }
            }
        }
        return 0;
    }