An open API service indexing awesome lists of open source software.

https://github.com/tonywu6/ebpf-packet-first-byte


https://github.com/tonywu6/ebpf-packet-first-byte

ebpf ebpf-rust

Last synced: about 1 month ago
JSON representation

Awesome Lists containing this project

README

          

# 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](example1/src/main.rs#L24-35) is commonly used:

```rs
fn ptr_at(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::() > end {
Err(())
} else {
Ok(ptr as _)
}
}
```

However, loading from a pointer returned by `ptr_at::(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::(skb, 1)`).

Comparing verifier outputs:

```diff
- ; ptr_at::(skb, 0)
- 2: (3d) if r2 >= r1 goto pc+11 ; R1_w=pkt_end() R2_w=pkt(r=0)
+ ; ptr_at::(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](example2/src/main.rs#L24-35) `ptr_at` to use **pointer arithmetic** instead
of integer arithmetic causes the correct instruction to be generated:

```rs
#[inline]
fn ptr_at(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::()) } > end {
Err(())
} else {
Ok(ptr as _)
}
}
```

```diff
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

- Linux 6.15.11 ([`mcr.microsoft.com/devcontainers/rust:bookworm`][bookworm])
- LLVM 21.1.1

## License

With the exception of eBPF code, ebpf-packet-first-byte is distributed under the terms
of either the [MIT license][mit] or the [Apache License][apache] (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][gplv2] or the [MIT license][mit], 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.

[apache]: LICENSE-APACHE
[Aya]: https://aya-rs.dev
[bookworm]: https://github.com/devcontainers/images/blob/main/src/rust/README.md
[direct packet access]: https://docs.kernel.org/bpf/verifier.html#direct-packet-access
[gplv2]: LICENSE-GPL2
[mit]: LICENSE-MIT