Skip to content

tonywu6/ebpf-packet-first-byte

Repository files navigation

ebpf-packet-first-byte

A corner case with the eBPF verifier when trying to load exactly the first byte of packet data via direct packet access, using Aya.

Problem

The eBPF verifier requires programs to check that pointers are within the range skb.data..skb.data_end before accessing packet data. The following logic is commonly used:

fn ptr_at<T>(skb: &SkBuff, offset: usize) -> Result<*const T, ()> {
    let skb = unsafe { &*skb.skb };
    let ptr = skb.data as usize;
    let ptr = ptr + offset;
    let end = skb.data_end as usize;
    if ptr + size_of::<T>() > end {
        Err(())
    } else {
        Ok(ptr as _)
    }
}

However, loading from a pointer returned by ptr_at::<u8>(skb, 0) (loading only the first byte) apparently fails the verification:

the BPF_PROG_LOAD syscall failed. Verifier output: 0: R1=ctx() R10=fp0
0: (61) r2 = *(u32 *)(r1 +76)         ; R1=ctx() R2_w=pkt(r=0)
1: (61) r1 = *(u32 *)(r1 +80)         ; R1_w=pkt_end()
2: (3d) if r2 >= r1 goto pc+11        ; R1_w=pkt_end() R2_w=pkt(r=0)
3: (71) r6 = *(u8 *)(r2 +0)
invalid access to packet, off=0 size=1, R2(id=0,off=0,r=0)
R2 offset is outside of the packet

This does not happen when loading more than 1 byte or loading not from the first byte (such as ptr_at::<[u8; 2]>(skb, 0) or ptr_at::<u8>(skb, 1)).

Comparing verifier outputs:

- ; ptr_at::<u8>(skb, 0)
- 2: (3d) if r2 >= r1 goto pc+11        ; R1_w=pkt_end() R2_w=pkt(r=0)
+ ; ptr_at::<u8>(skb, 1)
+ 3: (07) r3 += 2                       ; R3_w=pkt(off=2,r=0)
+ 4: (2d) if r3 > r2 goto pc+8          ; R2_w=pkt_end() R3_w=pkt(off=2,r=2)

it appears that the compiler is optimizing the comparison skb.data + 0 + 1 > skb.data_end into skb.data >= skb.data_end, which results in the verifier not updating known packet length (pkt(r=0) indicates 0 bytes starting from skb.data is safe to read).

Solution

Rewriting ptr_at to use pointer arithmetic instead of integer arithmetic causes the correct instruction to be generated:

#[inline]
fn ptr_at<T>(skb: &SkBuff, offset: usize) -> Result<*const T, ()> {
    let skb = unsafe { &*skb.skb };
    let ptr = skb.data as *const u8;
    let ptr = unsafe { ptr.add(offset) };
    let end = skb.data_end as *const u8;
    if unsafe { ptr.add(size_of::<T>()) } > end {
        Err(())
    } else {
        Ok(ptr as _)
    }
}
  0: (61) r2 = *(u32 *)(r1 +80)         ; R1=ctx() R2_w=pkt_end()
  1: (61) r1 = *(u32 *)(r1 +76)         ; R1_w=pkt(r=0)
  2: (bf) r3 = r1                       ; R1_w=pkt(r=0) R3_w=pkt(r=0)
+ 3: (07) r3 += 1                       ; R3_w=pkt(off=1,r=0)
+ 4: (2d) if r3 > r2 goto pc+8          ; R2_w=pkt_end() R3_w=pkt(off=1,r=1)

Environment

License

With the exception of eBPF code, ebpf-packet-first-byte is distributed under the terms of either the MIT license or the Apache License (version 2.0), at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

eBPF

All eBPF code is distributed under either the terms of the GNU General Public License, Version 2 or the MIT license, at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the GPL-2 license, shall be dual licensed as above, without any additional terms or conditions.

About

No description or website provided.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages