foniod / redbpf

Rust library for building and running BPF/eBPF modules
Apache License 2.0
1.71k stars 136 forks source link

Tail call issues in XDP -- Function parameter has wrong type in verifier #336

Closed FelixMcFelix closed 2 years ago

FelixMcFelix commented 2 years ago

I'm trying to get XDP tail calls working through redbpf binaries, but they seem to be failing at the verification stage. A simple example below (double braces are used due to string interpolation on the source files elsewhere):

#![no_std]
#![no_main]
use redbpf_probes::{{
    maps::ProgramArray,
    xdp::prelude::*,
}};

program!(0xFFFFFFFE, "GPL");

#[map(link_section = "maps")]
static mut progs_map: ProgramArray = ProgramArray::with_max_entries(8);

#[xdp]
fn xdp_sock_prog(mut ctx: XdpContext) -> XdpResult {{
    let out = 0;

    let tail_call_succ = unsafe {{progs_map.tail_call((&mut ctx) as *mut XdpContext, out as u32)}};
    Ok(XdpAction::Tx)
}}

Fails to load with the following output:

libbpf: Error loading BTF: Invalid argument(22)
libbpf: magic: 0xeb9f
version: 1
flags: 0x0
hdr_len: 24
type_off: 0
type_len: 468
str_off: 468
str_len: 592
btf_total_size: 1084
[1] PTR *mut xdp_md type_id=2 Invalid name

libbpf: Error loading .BTF into kernel: -22. BTF is optional, ignoring.
libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf: 
0: R1=ctx(off=0,imm=0) R10=fp0
0: (7b) *(u64 *)(r10 -8) = r1         ; R1=ctx(off=0,imm=0) R10=fp0 fp-8_w=ctx
1: (bf) r1 = r10                      ; R1_w=fp0 R10=fp0
2: (07) r1 += -8                      ; R1_w=fp-8
3: (18) r2 = 0xffff954d64e9ae00       ; R2_w=map_ptr(off=0,ks=4,vs=4,imm=0)
5: (b7) r3 = 0                        ; R3_w=0
6: (85) call bpf_tail_call#12
R1 type=fp expected=ctx
processed 6 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

libbpf: -- END LOG --
libbpf: failed to load program 'outer_xdp_sock_prog'
libbpf: failed to load object 'outer_xdp_sock_prog'

I've tried this with packet processing functions of various complexity, but the verifier consistently fails to realise that fp-<offset> has type ctx -- even though this is obviously and always the case according to the comment in line 0. I'm unsure if this is due to the types in line 2 (i.e. fp-8 vs fp-8_w) -- is there a way to coerce the compiler to do the right thing here? I've tried to temporarily coerce the pointer to a u32 in case it expected it to have type _w, but this was rightfully rejected. Any ideas?


cargo-bpf revision: c0b04010bd78ab1b1837346828a4ae63d5759369 LLVM version: 13.0.1 rustc version: 1.59 kernel: 5.18.5-arch1-1

FelixMcFelix commented 2 years ago

Some notes from looking into this. I had a look at sockex3_kern.c to see how this should be compiling. Tailcalls are in section socket/0 (i.e., main_prog with parse_eth_proto inlined):

$ llvm-objdump --section=socket/0 sockex3_kern.o --disassemble

sockex3_kern.o:    file format elf64-bpf

Disassembly of section socket/0:

0000000000000000 <main_prog>:
       0:       bf 16 00 00 00 00 00 00 r6 = r1
       1:       28 00 00 00 0c 00 00 00 r0 = *(u16 *)skb[12]
       2:       b7 01 00 00 0e 00 00 00 r1 = 14
       3:       63 16 30 00 00 00 00 00 *(u32 *)(r6 + 48) = r1
       4:       b7 03 00 00 01 00 00 00 r3 = 1
       5:       67 00 00 00 20 00 00 00 r0 <<= 32
       6:       77 00 00 00 20 00 00 00 r0 >>= 32
       7:       65 00 06 00 46 88 00 00 if r0 s> 34886 goto +6 <LBB4_4>
       8:       15 00 0d 00 00 08 00 00 if r0 == 2048 goto +13 <LBB4_7>
       9:       15 00 0d 00 00 81 00 00 if r0 == 33024 goto +13 <LBB4_9>
      10:       15 00 01 00 dd 86 00 00 if r0 == 34525 goto +1 <LBB4_8>
      11:       05 00 0f 00 00 00 00 00 goto +15 <LBB4_10>

0000000000000060 <LBB4_8>:
      12:       b7 03 00 00 04 00 00 00 r3 = 4
      13:       05 00 09 00 00 00 00 00 goto +9 <LBB4_9>

0000000000000070 <LBB4_4>:
      14:       bf 01 00 00 00 00 00 00 r1 = r0
      15:       07 01 00 00 b9 77 ff ff r1 += -34887
      16:       b7 02 00 00 02 00 00 00 r2 = 2
      17:       2d 12 02 00 00 00 00 00 if r2 > r1 goto +2 <LBB4_6>
      18:       15 00 04 00 a8 88 00 00 if r0 == 34984 goto +4 <LBB4_9>
      19:       05 00 07 00 00 00 00 00 goto +7 <LBB4_10>

00000000000000a0 <LBB4_6>:
      20:       b7 03 00 00 02 00 00 00 r3 = 2
      21:       05 00 01 00 00 00 00 00 goto +1 <LBB4_9>

00000000000000b0 <LBB4_7>:
      22:       b7 03 00 00 03 00 00 00 r3 = 3

00000000000000b8 <LBB4_9>:
      23:       bf 61 00 00 00 00 00 00 r1 = r6
      24:       18 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r2 = 0 ll
      26:       85 00 00 00 0c 00 00 00 call 12

00000000000000d8 <LBB4_10>:
      27:       b7 00 00 00 00 00 00 00 r0 = 0
      28:       95 00 00 00 00 00 00 00 exit

Here, the compiler seems to deliberately preserve ctx in R6 rather than recomputing it relative to R10 (=FP). An equivalent C program to the above (i.e., using XDP instead) also recognises that R1 already suffices, rather than recomputing it:

Disassembly of section socket/0:

0000000000000000 <main_prog>:
       0:       18 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r2 = 0 ll
       2:       b7 03 00 00 00 00 00 00 r3 = 0
       3:       85 00 00 00 0c 00 00 00 call 12
       4:       b7 00 00 00 00 00 00 00 r0 = 0
       5:       95 00 00 00 00 00 00 00 exit

I'll try and poke around to see if there's a way to make the redbpf compiler behave itself here.

FelixMcFelix commented 2 years ago

I realised that the solution is to extract the inner parameter:

let tail_call_succ = unsafe {progs_map.tail_call(ctx.ctx, out as u32)};

Hopefully this helps someone in future!