Skip to content

eBPF: Resetting tail-contexts

Objective

to understand ways of resetting/zeroing the perCPU contexts used in tail-calls


Reasoning

The tail-context must be zeroed before reusing it, to make sure values from previous executions are cleared.

Following techniques can be used

  1. Using __builtin_memset()

    • this fails to reset if size exceeds 1000-bytes
  2. By writing null_bytes in tail-context

    • requires additional read-only map containing null-bytes
    • can reset context of any size
  3. Using for-loop

    • using this can sometimes lead to instructions limit exceeded error

Snippets

memset

memset.c
1
2
3
4
// using __builtin_memset
void reset_context1(struct tail_context* c){
    __builtin_memset(c, 0, 2); // reset only 2 bytes
}

null_bytes

null_bytes.c
u32 zero = 0;
void reset_context2(struct tail_context* c){

    __builtin_memset(c, 0, 2); // reset only 2 bytes

    struct null_bytes* nb = (struct null_bytes*)bpf_lookup_elem(&null_bytes_map, &zero);
    if(!nb){
        return;
    }
    // write null_bytes into payload
    bpf_probe_read_kernel(c->payload, 512, nb->data);
}
maps.c
// -------> sample tail-context
struct tail_context {
    bool f1;
    bool f2;
    u8 payload[512];
}

struct {
     __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
     __type(key, u32);
     __type(value, struct tail_context);
     __uint(max_entries, 1);
} tail_ctx_map SEC(".maps");
// --------


// data from below map is read only for resetting maps
struct null_bytes {
  u8 data[8192]; // maximum null_bytes
};

struct {
  __uint(type, BPF_MAP_TYPE_ARRAY);
  __type(key, u32);
  __type(value, struct null_bytes);
  __uint(max_entries, 1);
  __uint(map_flags,
         BPF_F_RDONLY_PROG | BPF_F_RDONLY); // makes the map read-only
} null_bytes_map SEC(".maps");

TODO Experiment

  • Use bpf_loop for zeroing the struct