A corner case with the eBPF verifier when trying to load exactly the first byte of packet data via direct packet access, using Aya.
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).
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)- Linux 6.15.11 (
mcr.microsoft.com/devcontainers/rust:bookworm) - LLVM 21.1.1
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.
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.