0

I wrote a bpf code and compiled with clang, while trying to load, I face an error. I am not able to understand why and how to resolve it, need experts advice.

I am running this code in a VM OS : Ubuntu 18.04.2 Kernel : Linux 4.18.0-15-generic x86_64

I tried simple programs and I able to load but not with this program.

static __inline int clone_netflow_record (struct __sk_buff *skb, unsigned long dstIpAddr)
{
    return XDP_PASS;
}

static __inline int process_netflow_records( struct __sk_buff *skb)
{
    int i = 0;

    #pragma clang loop unroll(full)
    for (i = 0; i < MAX_REPLICATIONS; i++) {
        clone_netflow_record (skb, ipAddr[i]);
    }

    return XDP_DROP;
}

__section("action")
static int probe_packets(struct __sk_buff *skb)
{
    /* We will access all data through pointers to structs */
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    if (data > data_end)
        return XDP_DROP;

    /* for easy access we re-use the Kernel's struct definitions */
    struct ethhdr  *eth  = data;
    struct iphdr   *ip   = (data + sizeof(struct ethhdr));

    /* Only actual IP packets are allowed */
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_DROP;
    /* If Netflow packets process it */
    if (ip->protocol != IPPROTO_ICMP)
    {
        process_netflow_records (skb);
    }

    return XDP_PASS;
}

ERROR Seen:

$ sudo ip link set dev enp0s8 xdp object clone.o sec action

Prog section 'action' rejected: Permission denied (13)!
 - Type:         6
 - Instructions: 41 (0 over limit)
 - License:      GPL

Verifier analysis:

0: (bf) r2 = r1
1: (7b) *(u64 *)(r10 -16) = r1
2: (79) r1 = *(u64 *)(r10 -16)
3: (61) r1 = *(u32 *)(r1 +76)
invalid bpf_context access off=76 size=4

Error fetching program/map!
arnt
  • 8,949
  • 5
  • 24
  • 32
Fernando
  • 163
  • 1
  • 9

1 Answers1

1

The kernel verifier that enforces checks on your program in the Linux kernel ensures that no out-of-bound accesses are attempted. Your program is rejected because it may trigger such out-of-bound access.

If we have a closer look at your snippet:

    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

So here we get pointers to data (start of packet) and data_end.

    if (data > data_end)
        return XDP_DROP;

The above check is unnecessary (data will not be higher than data_end). But there's another check you should do here instead. Let's see below:

    /* for easy access we re-use the Kernel's struct definitions */
    struct ethhdr  *eth  = data;
    struct iphdr   *ip   = (data + sizeof(struct ethhdr));

    /* Only actual IP packets are allowed */
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_DROP;

What you do here is, first, making eth and ip point to the start of the packet and (supposedly) the start of the IP header. This step is fine. But then, you try to dereference eth to access its h_proto field.

Now, what would happen if the packet was not Ethernet, and it was not long enough to have an h_proto field in it? You would try to read some data outside of the bounds of the packet, this is the out-of-bound access I mentioned earlier. Note that it does not mean your program actually tried to read this data (as a matter of fact, I don't see how you could get a packet shorter than 14 bytes). But from the verifier's point of view, it is technically possible that this forbidden access could occur, so it rejects your program. This is what it means with invalid bpf_context access: your code tries to access the context (for XDP: packet data) in an invalid way.

So how do we fix that? The check that you should have before trying to dereference the pointer should not be on data > data_end, it should be instead:

    if (data + sizeof(struct ethhdr) > data_end)
        return XDP_DROP;

So if we pass the check without returning XDP_DROP, we are sure that the packet is long enough to contain a full struct ethhdr (and hence a h_proto field).

Note that a similar check on data + sizeof(struct ethhdr) + sizeof(struct iphdr) will be necessary before trying to dereference ip, for the same reason. Each time you try to access data from the packet (the context), you should make sure that your packet is long enough to dereference the pointer safely.

Qeole
  • 8,284
  • 1
  • 24
  • 52