ebpf

Hook file deletion with EBPF


I want to deny file deletion on a linux machine if the filename starts by "t".

Here is what I've done:

#define __TARGET_ARCH_arm64 

#include <linux/ptrace.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

typedef struct {
        int counter;
} atomic_t;

struct filename {
        const char *name;
        const char *uptr;
        atomic_t refcnt;
        struct audit_names *aname;
        const char iname[0];
};

SEC("kprobe/do_unlinkat")
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
{
    char filename[128];
    const char *ptr_filename = BPF_CORE_READ(name, name);
    int ret = bpf_probe_read_kernel_str(filename, sizeof(filename), ptr_filename);
    if (filename[0]=='t')
    {
        bpf_printk("This file starts by 't'");

        // return 1;   <- returning 1 or -1 does not "cancel" the syscall...

        // I have also tried to change filename on the fly...
        filename[0]='w';
        bpf_copy_from_user((void *) ptr_filename, 1, filename);
        // bpf_copy_from_user((void *) name->name, 1, filename); // Also tried this...
   }

    bpf_printk("Deleting this file:%s", filename);
    return 0;
}

The first thing I don't understand is what's the return value of BPF_KPROBE ? I was expecting returning something else than 0 will cancel the syscall. But my file is still deleted...

I have tried to change the filename. But bpf_copy_from_user causes an error when I try to compile eBPF:

libbpf: prog 'do_unlinkat': BPF program load failed: Permission denied
libbpf: prog 'do_unlinkat': -- BEGIN PROG LOAD LOG -- arg#0 reference
type('FWD pt_regs') size cannot be determined: -22 0: R1=ctx() R10=fp0

Any idea ?


Solution

  • The first thing I don't understand is what's the return value of BPF_KPROBE ? I was expecting returning something else than 0 will cancel the syscall. But my file is still deleted...

    The return value of a kprobe is meaningless, it has no effect. A kprobe is meant to be an observation tool, not to implement features, hence this design.

    Some exceptions made its way into eBPF such as bpf_override_return which is a helper that set the return value and exits the probed function early. It does require your kernel to be compiled with the CONFIG_BPF_KPROBE_OVERRIDE=y kconfig. This is a technique known to work and is also used by https://tetragon.io/ to implement runtime security enforcement.

    If the above is not a solution for you for any reason you can consider LSM programs. LSM programs allow you to hook eBPF code into LSM hooks also used by SELinux/AppArmor. These are meant for enforcement, and depending on the specific hook you can return a boolean or an error code. This feature requires least kernel version v5.7 and that your kernel be compiled with the CONFIG_BPF_LSM=y kconfig.

    I think the path_unlink hook is what you are after. Its description:

    @path_unlink:
     Check the permission to remove a hard link to a file.
     @dir contains the path structure of parent directory of the file.
     @dentry contains the dentry structure for file to be unlinked.
     Return 0 if permission is granted.
    

    So this hook expects you to return 0 to allow deletion or an error code which will be passed to the user. See the callsite of security_path_unlink in fs/namei.c.

    It is gated behind the CONFIG_SECURITY_PATH kconfig. If you kernel does not have it them inode_unlink is a similar hook, but does not give you the path directly, so more work.


    I have tried to change the filename. But bpf_copy_from_user causes an error when I try to compile eBPF:

    Does not seem to be related to bpf_copy_from_user.

    arg#0 reference type('FWD pt_regs') size cannot be determined: -22 0: R1=ctx() R10=fp0

    It is trying to tell you that you are using struct pt_regs but it only has a forward declaration, while the verifier wants type info for the full type. You typically see this when the bpf/bpf_helpers.h is included but not bpf/bpf_tracing.h.