Struct rbpf::EbpfVmFixedMbuff

source ·
pub struct EbpfVmFixedMbuff<'a> { /* private fields */ }
Expand description

A virtual machine to run eBPF program. This kind of VM is used for programs expecting to work on a metadata buffer containing pointers to packet data, but it internally handles the buffer so as to save the effort to manually handle the metadata buffer for the user.

This struct implements a static internal buffer that is passed to the program. The user has to indicate the offset values at which the eBPF program expects to find the start and the end of packet data in the buffer. On calling the execute_program() or execute_program_jit() functions, the struct automatically updates the addresses in this static buffer, at the appointed offsets, for the start and the end of the packet data the program is called upon.

Examples

This was compiled with clang from the following program, in C:

#include <linux/bpf.h>
#include "path/to/linux/samples/bpf/bpf_helpers.h"

SEC(".classifier")
int classifier(struct __sk_buff *skb)
{
  void *data = (void *)(long)skb->data;
  void *data_end = (void *)(long)skb->data_end;

  // Check program is long enough.
  if (data + 5 > data_end)
    return 0;

  return *((char *)data + 5);
}

Some small modifications have been brought to have it work, see comments.

let prog = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    // Here opcode 0x61 had to be replace by 0x79 so as to load a 8-bytes long address.
    // Also, offset 0x4c had to be replace with e.g. 0x40 so as to prevent the two pointers
    // from overlapping in the buffer.
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load pointer to mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    // Here opcode 0x61 had to be replace by 0x79 so as to load a 8-bytes long address.
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load ptr to mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
    0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
    0x67, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, // r0 >>= 56
    0xc7, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, // r0 <<= 56 (arsh) extend byte sign to u64
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];
let mem1 = &mut [
    0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd
];
let mem2 = &mut [
    0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x27
];

// Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();

// Provide only a reference to the packet data. We do not manage the metadata buffer.
let res = vm.execute_program(mem1).unwrap();
assert_eq!(res, 0xffffffffffffffdd);

let res = vm.execute_program(mem2).unwrap();
assert_eq!(res, 0x27);

Implementations

Create a new virtual machine instance, and load an eBPF program into that instance. When attempting to load the program, it passes through a simple verifier.

Examples
let prog = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
    0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];

// Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();

Load a new eBPF program into the virtual machine instance.

At the same time, load new offsets for storing pointers to start and end of packet data in the internal metadata buffer.

Examples
let prog1 = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];
let prog2 = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
    0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];

let mem = &mut [
    0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x27,
];

let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog1), 0, 0).unwrap();
vm.set_program(prog2, 0x40, 0x50);

let res = vm.execute_program(mem).unwrap();
assert_eq!(res, 0x27);

Set a new verifier function. The function should return an Error if the program should be rejected by the virtual machine. If a program has been loaded to the VM already, the verifier is immediately run.

Examples
use std::io::{Error, ErrorKind};
use rbpf::ebpf;

// Define a simple verifier function.
fn verifier(prog: &[u8]) -> Result<(), Error> {
    let last_insn = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1);
    if last_insn.opc != ebpf::EXIT {
        return Err(Error::new(ErrorKind::Other, 
                   "[Verifier] Error: program does not end with “EXIT” instruction"));
    }
    Ok(())
}

let prog1 = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];

// Instantiate a VM.
let mut vm = rbpf::EbpfVmMbuff::new(Some(prog1)).unwrap();
// Change the verifier.
vm.set_verifier(verifier).unwrap();

Register a built-in or user-defined helper function in order to use it later from within the eBPF program. The helper is registered into a hashmap, so the key can be any u32.

If using JIT-compiled eBPF programs, be sure to register all helpers before compiling the program. You should be able to change registered helpers after compiling, but not to add new ones (i.e. with new keys).

Examples
use rbpf::helpers;

// This program was compiled with clang, from a C program containing the following single
// instruction: `return bpf_trace_printk("foo %c %c %c\n", 10, 1, 2, 3);`
let prog = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 6 instructions
    0x71, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r1
    0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
    0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
    0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
    0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
    0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];

let mem = &mut [
    0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x09,
];

// Instantiate a VM.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();

// Register a helper. This helper will store the result of the square root of r1 into r0.
vm.register_helper(1, helpers::sqrti);

let res = vm.execute_program(mem).unwrap();
assert_eq!(res, 3);

Execute the program loaded, with the given packet data.

If the program is made to be compatible with Linux kernel, it is expected to load the address of the beginning and of the end of the memory area used for packet data from some metadata buffer, which in the case of this VM is handled internally. The offsets at which the addresses should be placed should have be set at the creation of the VM.

Examples
let prog = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
    0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];
let mem = &mut [
    0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd
];

// Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();

// Provide only a reference to the packet data. We do not manage the metadata buffer.
let res = vm.execute_program(mem).unwrap();
assert_eq!(res, 0xdd);

JIT-compile the loaded program. No argument required for this.

If using helper functions, be sure to register them into the VM before calling this function.

Examples
let prog = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
    0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];

// Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();

vm.jit_compile();

Execute the previously JIT-compiled program, with the given packet data, in a manner very similar to execute_program().

If the program is made to be compatible with Linux kernel, it is expected to load the address of the beginning and of the end of the memory area used for packet data from some metadata buffer, which in the case of this VM is handled internally. The offsets at which the addresses should be placed should have be set at the creation of the VM.

Safety

WARNING: JIT-compiled assembly code is not safe, in particular there is no runtime check for memory access; so if the eBPF program attempts erroneous accesses, this may end very bad (program may segfault). It may be wise to check that the program works with the interpreter before running the JIT-compiled version of it.

For this reason the function should be called from within an unsafe bloc.

Examples
let prog = &[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
    0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
    0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
    0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
    0x2d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
    0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
];
let mem = &mut [
    0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd
];

// Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();

vm.jit_compile();

// Provide only a reference to the packet data. We do not manage the metadata buffer.
unsafe {
    let res = vm.execute_program_jit(mem).unwrap();
    assert_eq!(res, 0xdd);
}

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.