bpf_probe_write_use()系统过载

问题描述 投票:0回答:1

我对 eBPF 世界还很陌生,我从 https://eunomia.dev/tutorials/0-introduce/开始学习。

我按照示例进行操作,发现了https://eunomia.dev/tutorials/24-hide/。这是关于隐藏 PID 的教程,我在这个示例中发现有趣的一件事是使用函数

bpf_tail_call()

在下面的代码中如果PID匹配的话,

        int j = 0;
        for (j = 0; j < pid_to_hide_len; j++)
        {
            if (filename[j] != pid_to_hide[j])
            {
                break;
            }
        }
        if (j == pid_to_hide_len)
        {
            // ***********
            // We've found the folder!!!
            // Jump to handle_getdents_patch so we can remove it!
            // ***********
            bpf_map_delete_elem(&map_bytes_read, &pid_tgid);
            bpf_map_delete_elem(&map_buffs, &pid_tgid);
            bpf_tail_call(ctx, &map_prog_array, PROG_02);
            //
        }
        bpf_map_update_elem(&map_to_patch, &pid_tgid, &dirp, BPF_ANY);

        bpos += d_reclen;
    }

然后它会调用以下

_patch
函数,

SEC("tp/syscalls/sys_exit_getdents64")
int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
{
    
    // Only patch if we've already checked and found our pid's folder to hide
    size_t pid_tgid = bpf_get_current_pid_tgid();
    long unsigned int *pbuff_addr = bpf_map_lookup_elem(&map_to_patch, &pid_tgid);
    if (pbuff_addr == 0)
    {
        return 0;
    }

    // Unlink target, by reading in previous linux_dirent64 struct,
    // and setting it's d_reclen to cover itself and our target.
    // This will make the program skip over our folder.
    long unsigned int buff_addr = *pbuff_addr;
    struct linux_dirent64 *dirp_previous = (struct linux_dirent64 *)buff_addr;
    short unsigned int d_reclen_previous = 0;
    bpf_probe_read_user(&d_reclen_previous, sizeof(d_reclen_previous), &dirp_previous->d_reclen);

    struct linux_dirent64 *dirp2 = (struct linux_dirent64 *)(buff_addr + d_reclen_previous);
    short unsigned int d_reclen2 = 0;
    bpf_probe_read_user(&d_reclen2, sizeof(d_reclen2), &dirp2->d_reclen);

    // Debug print
    char filename[MAX_PID_LEN];
    bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp_previous->d_name);
    filename[pid_to_hide_len - 1] = 0x00;
    bpf_printk("[PID_HIDE] filename previous %s\n", filename);
    bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name);
    filename[pid_to_hide_len - 1] = 0x00;
    bpf_printk("[PID_HIDE] filename next one %s\n", filename);

    // Attempt to overwrite
    short unsigned int d_reclen_new = d_reclen_previous + d_reclen2;
    long ret = bpf_probe_write_user(&dirp_previous->d_reclen, &d_reclen_new, sizeof(d_reclen_new));

    // Send an event
    struct event *e;
    e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
    if (e)
    {
        e->success = (ret == 0);
        e->pid = (pid_tgid >> 32);
        bpf_get_current_comm(&e->comm, sizeof(e->comm));
        bpf_ringbuf_submit(e, 0);
    }

    bpf_map_delete_elem(&map_to_patch, &pid_tgid);

    return 0;
}

我不明白拥有

_patch
函数并使用
bpf_tail_call()
调用它的概念,这将导致
getdents64
系统调用返回。

在一次尝试中,我试图摆脱

bpf_tail_call()
,将逻辑从
handle_getdents_patch()
移至用
bpf_tail_call()
替换。下面是我更改后
handle_getdents_exit()
的代码的样子,

SEC("tp/syscalls/sys_exit_getdents64")
int handle_getdents_exit(struct trace_event_raw_sys_exit *ctx)
{
    size_t pid_tgid = bpf_get_current_pid_tgid();
    int total_bytes_read = ctx->ret;
    // if bytes_read is 0, everything's been read
    if (total_bytes_read <= 0)
    {
        return 0;
    }

    // Check we stored the address of the buffer from the syscall entry
    long unsigned int *pbuff_addr = bpf_map_lookup_elem(&map_buffs, &pid_tgid);
    if (pbuff_addr == 0)
    {
        return 0;
    }

    // All of this is quite complex, but basically boils down to
    // Calling 'handle_getdents_exit' in a loop to iterate over the file listing
    // in chunks of 200, and seeing if a folder with the name of our pid is in there.
    // If we find it, use 'bpf_tail_call' to jump to handle_getdents_patch to do the actual
    // patching
    long unsigned int buff_addr = *pbuff_addr;
    struct linux_dirent64 *dirp = 0;
    int pid = pid_tgid >> 32;
    short unsigned int d_reclen = 0;
    char filename[MAX_PID_LEN];

    unsigned int bpos = 0;
    unsigned int *pBPOS = bpf_map_lookup_elem(&map_bytes_read, &pid_tgid);
    if (pBPOS != 0)
    {
        bpos = *pBPOS;
        bpf_printk("bpos = *pBPOS -------> %d\n", bpos);
    }

    for (int i = 0; i < 200; i++)
    {
        if (bpos >= total_bytes_read)
        {
            break;
        }

        dirp = (struct linux_dirent64 *)(buff_addr + bpos);
        bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen);
        bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name);
        bpf_printk("> d_reclen : %d - filename : %s\n", pid_to_hide_len, filename);

        int j = 0;
        for (j = 0; j < pid_to_hide_len; j++)
        {
            if (filename[j] != pid_to_hide[j])
            {
                break;
            }
        }
        if (j == pid_to_hide_len)
        {
            // ***********
            // We've found the folder!!!
            // Jump to handle_getdents_patch so we can remove it!
            // ***********
            bpf_map_delete_elem(&map_bytes_read, &pid_tgid);
            bpf_map_delete_elem(&map_buffs, &pid_tgid);


            //
            long unsigned int buff_addr = *pbuff_addr;
            struct linux_dirent64 *dirp_previous = dirp;//(struct linux_dirent64 *)buff_addr;
            short unsigned int d_reclen_previous = 0;
            bpf_probe_read_user(&d_reclen_previous, sizeof(d_reclen_previous), &dirp_previous->d_reclen);

            struct linux_dirent64 *dirp2 = (struct linux_dirent64 *)(buff_addr + d_reclen_previous);
            short unsigned int d_reclen2 = 0;
            bpf_probe_read_user(&d_reclen2, sizeof(d_reclen2), &dirp2->d_reclen);

            // Attempt to overwrite
            short unsigned int d_reclen_new = d_reclen_previous + d_reclen2;
            long ret = bpf_probe_write_user(&dirp_previous->d_reclen, &d_reclen_new, sizeof(d_reclen_new));

            // bpf_tail_call(ctx, &map_prog_array, PROG_02);
            //
        }
        bpf_map_update_elem(&map_to_patch, &pid_tgid, &dirp, BPF_ANY);

        bpos += d_reclen;
    }



    // If we didn't find it, but there's still more to read,
    // jump back the start of this function and keep looking
    if (bpos < total_bytes_read)
    {
        bpf_map_update_elem(&map_bytes_read, &pid_tgid, &bpos, BPF_ANY);
        bpf_tail_call(ctx, &map_prog_array, PROG_01);
    }
    bpf_map_delete_elem(&map_bytes_read, &pid_tgid);
    bpf_map_delete_elem(&map_buffs, &pid_tgid);

    return 0;
}

问题在于,进行此更改后,系统速度超级慢,并且 PID 未隐藏。

我想要实现的是能够隐藏多个 PID,但是对

bpf_tail_call()
的调用使系统调用在第一场比赛中返回,所以这是我的问题,

  • 删除
    handle_getdents_patch()
    并将所有代码放在
    handle_getdents_exit()
    中我做错了什么?为什么隐藏 PID(即使是一个 PID)会被破坏,系统过载又是为了什么?
  • 实现我想做的事情的正确方法是什么?

PS:我尝试遵守所有提问规则,希望不要受到惩罚。

谢谢

c linux-kernel ebpf libbpf
1个回答
0
投票

TL;DR. 您没有保留程序的控制流。尾部调用不会返回给调用者,因此您应该在

handle_getdents_patch
逻辑之后退出。


你想做什么

如果我理解正确的话:

  • 第一个程序拦截
    sys_exit_getdents64
    以找到要隐藏的文件夹;
  • 如果找到它,它会进行尾部调用(第一次尾部调用),直到另一个 BPF 程序使用
    bpf_probe_write_user
    编辑结果并将事件发送到用户空间。
  • 它通过递归调用自身来不断寻找它(第二次尾调用);

您要做的就是通过将目标代码 (

handle_getdents_patch
) 移动到调用者 (
handle_getdents_exit
) 中来避免第一次尾部调用。


尾部调用如何工作

尾部调用 (

bpf_tail_call
) 与普通函数调用不同。假设程序A尾调用程序B。程序B完全执行后,控制权不会返回到程序A。我们将直接退出。

所以在原程序的情况下,当找到文件夹时

handle_getdents_exit
会跳转到
handle_getdents_patch
。一旦执行了
handle_getdents_patch
它就不会返回
handle_getdents_exit
来迭代其他文件夹。


如果您想在替换

handle_getdents_patch
时模仿上述尾部调用行为,则必须在复制的代码后面加上
return 0
。所以你的代码应该类似于:

if (j == pid_to_hide_len)
{
    // ***********
    // We've found the folder!!!
    // Jump to handle_getdents_patch so we can remove it!
    // ***********
    bpf_map_delete_elem(&map_bytes_read, &pid_tgid);
    bpf_map_delete_elem(&map_buffs, &pid_tgid);

    [...]

    // Attempt to overwrite
    short unsigned int d_reclen_new = d_reclen_previous + d_reclen2;
    long ret = bpf_probe_write_user(&dirp_previous->d_reclen, &d_reclen_new, sizeof(d_reclen_new));

    [... other code from handle_getdents_patch...]

    return 0;
}

我不知道这是否是故意的,但您也没有复制代码来发送事件(

bpf_ringbuf_reserve
和其他人)。

© www.soinside.com 2019 - 2024. All rights reserved.