Ошибка: недопустимый доступ к памяти 'inv' при использовании bpf_probe_read_*()

Вот проблема:

Я пишу программу BPF, проверяющую функцию ядра vfs_read() с помощью kprobe:

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)

Соответствующая информация (внутри *file) собирается с использованием карты типа BPF_MAP_TYPE_HASH. Код программы BPF (kern.c) выглядит следующим образом:

#define __KERNEL__
#define __TARGET_ARCH_x86 

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

struct data {
    char filename[16];
    u32 pid;
    char comm[16];
};

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(u64),
    .value_size = sizeof(struct data),
    .max_entries = 120
};

SEC("kprobe/vfs_read")
int vfs_read_probe(struct pt_regs *ctx)
{
    struct data value = {};

    struct file *f = (struct file *)PT_REGS_PARM1(ctx);
    struct dentry *de = f->f_path.dentry;
    struct qstr d_name = de->d_name;
    bpf_probe_read_kernel_str(&value.filename, sizeof(value.filename), d_name.name);

    u64 key = bpf_ktime_get_coarse_ns();
    value.pid = (u32)bpf_get_current_pid_tgid();
    bpf_get_current_comm(&value.comm, sizeof(value.comm));
    bpf_map_update_elem(&my_map, &key, &value, BPF_ANY);

    return 0;
}

char _license[] SEC("license") = "GPL";

vmlinux.h создается с использованием:

bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

Код компилируется с использованием:

clang -O2 -target bpf -c kern.c -o kern.o

Ниже приведена пользовательская программа (user.c) для загрузки kern.o:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h> 
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>

struct data {
    char filename[16];
    unsigned int pid;
    char comm[16];
};

static int fd;

static void handler(int sig)
{
    unsigned long long key = 0, next_key;
    struct data value;
    while (bpf_map_get_next_key(fd, &key, &next_key) == 0) {
        bpf_map_lookup_elem(fd, &next_key, &value);
        printf("Key: %llx, Next Key: %llx, PID: %d, COMM: %s, file: %s\n", key, next_key, value.pid, value.comm, value.filename);
        key = next_key;
    }
    exit(0);
}

int main()
{
    struct bpf_object *obj;
    struct bpf_program *prog;
    struct bpf_link *link;
    
    struct rlimit rlim = {
        .rlim_cur = RLIM_INFINITY,
        .rlim_max = RLIM_INFINITY
    };
    if(setrlimit(RLIMIT_MEMLOCK, &rlim)) {
        fprintf(stderr, "ERROR adjusting memlock limit\n");
        goto cleanup;
    }

    char path[] = "kern.o";
    obj = bpf_object__open(path);
    if (libbpf_get_error(obj)) {
        fprintf(stderr, "ERROR: opening BPF object file failed\n");
        obj = NULL;
        goto cleanup;
    }
    
    prog = bpf_object__find_program_by_title(obj, "kprobe/vfs_read");
    
    if (bpf_object__load(obj)) {
        fprintf(stderr, "ERROR: loading BPF object file failed\n");
        goto cleanup;
    }
    
    link = bpf_program__attach(prog);
    
    fd = bpf_object__find_map_fd_by_name(obj, "my_map");

    signal(SIGINT, handler);
    printf("Press ^C to stop\n");

    sleep(99999);
    
    bpf_link__destroy(link);
    bpf_object__close(obj);
    
    return 0;

cleanup:
    bpf_link__destroy(link);
    bpf_object__close(obj);
    return -1;
}

user.c компилируется как user.o.

Я хочу получить имя файла из аргумента *file vfs_read(), поэтому я привожу PT_REGS_PARM1(ctx) к struct file * и т. д.

Но при загрузке с использованием user.o возникает ошибка:

libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf: 
0: (b7) r2 = 0
1: (63) *(u32 *)(r10 -8) = r2
last_idx 1 first_idx 0
regs=4 stack=0 before 0: (b7) r2 = 0
2: (7b) *(u64 *)(r10 -16) = r2
3: (7b) *(u64 *)(r10 -24) = r2
4: (7b) *(u64 *)(r10 -32) = r2
5: (7b) *(u64 *)(r10 -40) = r2
6: (79) r1 = *(u64 *)(r1 +112)
7: (79) r1 = *(u64 *)(r1 +24)
R1 invalid mem access 'inv'
processed 8 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 'vfs_read_probe'
libbpf: failed to load object 'kern.o'
ERROR: loading BPF object file failed

После некоторого анализа я обнаружил, что до тех пор, пока я не использую bpf_probe_read_*() и друзей и жестко запрограммирую значение в data.filename, все в порядке.

Я использую Archlinux, ядро ​​5.12.9-zen1-1-zen.

Как я должен правильно получить имя файла? Спасибо за вашу помощь!


person WEREWOLFGHOST    schedule 07.06.2021    source источник


Ответы (1)


Вам нужно использовать bpf_probe_read для разыменования указателей ядра.

Таким образом, ваш код для чтения d_name должен выглядеть примерно так:

struct dentry de;
struct qstr d_name;

bpf_probe_read_kernel_str(&de, sizeof(struct dentry), &f->f_path.dentry);
bpf_probe_read_kernel_str(&d_name, sizeof(struct qstr), &de->d_name);

Пояснения.

Здесь требуется использование хелперов bpf_probe_read, потому что разыменованный адрес памяти должен проверяться во время выполнения, чтобы избежать сбоя из-за недопустимого доступа к памяти.

В качестве альтернативы вы можете использовать BPF CO-RE, чтобы полагаться на BTF для выполнения проверок перед выполнением, используя информацию о типе BTF. См. https://nakryiko.com/posts/bpf-portability-and-co-re/ для получения дополнительной информации.

person pchaigno    schedule 07.06.2021
comment
Спасибо! Другая проблема кода заключается в том, что я не определяю, действительно ли vfs_read() имеет отношение к обычному файлу... - person WEREWOLFGHOST; 14.06.2021