Search code examples
linuxlinux-kernelsystem-callsebpfbpf

eBPF: Loading and attaching an eBPF program to sys_enter_execve using only system calls


Context: I am attempting to introduce mutation to eBPF bytecode. As such I'd like to load eBPF bytecode from a simple object file, not from an ELF (as is required by libbpf to my knowledge).

I have a very simple eBPF program that writes 'hello world' to the trace pipe:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

int hello_world(void *ctx) {

    char msg[12] = "Hello World\n";
    bpf_trace_printk(msg, sizeof(msg));
    return 0;
}

In summary, I do the following to load and attach this program to sys_enter_execve:

1) load the program from an object file into a buffer
2) call bpf(BPF_PROG_LOAD)
3) call perf_event_open(PERF_TYPE_TRACEPOINT, sys_enter_execve_id)
4) call bpf(BPF_LINK_CREATE, prog_fd, perf_fd)
5) call ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0)

This is the same sequence of syscalls that the libbpf equivalent of my program performs. All of the syscalls return successfully.

However, when listing all eBPF programs with bpftool prog show I see:

46: tracepoint  tag 57b707c2f01b5190  gpl
    loaded_at 2023-09-05T16:55:39+0100  uid 0
    xlated 88B  jited 61B  memlock 4096B

This shows that the program is not attached. Running bpftool prog show on a libbpf equivalent of my program shows:

51: tracepoint  name tracepoint__syscalls__sys_enter_execve  tag 327022fb55d6e9d0  gpl
    loaded_at 2023-09-05T16:59:45+0100  uid 0
    xlated 48B  jited 35B  memlock 4096B  map_ids 27
    btf_id 69

Clearly my sequence of syscalls does not attach my eBPF program to sys_enter_execve, while the libbpf sequence does. Why is this?

Here are the precise values I'm passing to the syscalls:

    //open performance event
    int perf_fd;
    struct perf_event_attr pe = {
        .type = PERF_TYPE_TRACEPOINT,
        .size = sizeof(struct perf_event_attr),
        .config = EXECVE_ID

        .sample_period = 1,
        .sample_type = PERF_SAMPLE_RAW,
        .wakeup_events = 1,

        .disabled = 0,
        .bpf_event = 1
    };
    perf_fd = syscall(__NR_perf_event_open, &pe, -1, 0, -1, PERF_FLAG_FD_CLOEXEC);


    //load the bpf program
    int prog_fd;
    union bpf_attr load_attr;
    memset(&load_attr, 0, sizeof(load_attr));
    load_attr.prog_type = BPF_PROG_TYPE_TRACEPOINT;
    load_attr.insn_cnt = INSTRUCTION_COUNT;
    load_attr.insns = (__aligned_u64) insns_buf;
    load_attr.license = "GPL";

    prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &load_attr, sizeof(load_attr));


    //create bpf link(?)
    int link_fd;
    union bpf_attr link_attr;
    link_attr.link_create.prog_fd = prog_fd;
    link_attr.link_create.target_fd = perf_fd;
    link_attr.link_create.attach_type = BPF_PERF_EVENT;
    link_attr.link_create.flags = 0;
    int link_fd = syscall(__NR_bpf, BPF_LINK_CREATE, &link_attr, sizeof(link_attr));

    ret = ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);

Here is an strace extract from the libbpf version:

openat(AT_FDCWD, "/sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/id", O_RDONLY) = 6
newfstatat(6, "", {st_mode=S_IFREG|0440, st_size=0, ...}, AT_EMPTY_PATH) = 0
read(6, "807\n", 4096)                  = 4
read(6, "", 4096)                       = 0
close(6)                                = 0
perf_event_open({type=PERF_TYPE_TRACEPOINT, size=0x88 /* PERF_ATTR_SIZE_??? */, config=807, sample_period=0, sample_type=0, read_format=0, precise_ip=0 /* arbitrary skid */, ...}, -1, 0, -1, PERF_FLAG_FD_CLOEXEC) = 6
bpf(BPF_LINK_CREATE, {link_create={prog_fd=5, target_fd=6, attach_type=BPF_PERF_EVENT, flags=0, perf_event={bpf_cookie=0}}}, 48) = 7
ioctl(6, PERF_EVENT_IOC_ENABLE, 0)      = 0

EDIT:

Here are the full syscall traces for:

    syscall version: https://bpa.st/D44AY
    libbpf version: https://bpa.st/S5WLK

Solution

  • What byte code are you using for the bpf_trace_printk(msg, sizeof(msg)); code ? Looks like the byte code not work as expected, it should like this:

    BPF_MOV64_IMM(BPF_REG_2, 14),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_trace_printk),
    

    Here is a full Hello World example for attaching the tracepoint ebpf program only with c and syscall, maybe it can help you:

    https://github.com/mozillazg/hello-libbpfgo/blob/691030b64fa1a4942abd8be2a519e30cfcbb0164/27-attach-tracepoint-with-syscall/main.c