【问题标题】:bpf how to inspect syscall argumentsbpf 如何检查系统调用参数
【发布时间】:2020-06-18 02:29:26
【问题描述】:

trace_output_kern.c 跟踪 sys_write 系统调用并在用户区打印 pid:

#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    .key_size = sizeof(int),
    .value_size = sizeof(u32),
    .max_entries = 2,
};

SEC("kprobe/sys_write")
int bpf_prog1(struct pt_regs *ctx)
{
    struct S {
        u64 pid;
        u64 cookie;
    } data;

    data.pid = bpf_get_current_pid_tgid();
    data.cookie = 0x12345678;

    bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));

    return 0;
}

char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;

sys_read 的签名是sys_read(unsigned int fd, char __user *buf, size_t count);,目前我们只能看到 PID。追踪的前提是我们可以截获并检查参数。我也试图看到被传递的论点。

如果我将 struct S 更改为保存一个字符数组以保存 char *buf

    struct S {
            u64 pid;
            u64 cookie;
            char bleh[128]; //<-- added this 
    } data;

它很适合:

/usr/src/linux-5.4/samples/bpf# ./trace_output
bpf_load_program() err=13
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (b7) r1 = 305419896
3: (7b) *(u64 *)(r10 -136) = r1
4: (7b) *(u64 *)(r10 -144) = r0
5: (bf) r4 = r10
6: (07) r4 += -144
7: (bf) r1 = r6
8: (18) r2 = 0xffff8975bd44aa00
10: (b7) r3 = 0
11: (b7) r5 = 144
12: (85) call bpf_perf_event_output#25
invalid indirect read from stack off -144+16 size 144
processed 12 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (b7) r1 = 305419896
3: (7b) *(u64 *)(r10 -136) = r1
4: (7b) *(u64 *)(r10 -144) = r0
5: (bf) r4 = r10
6: (07) r4 += -144
7: (bf) r1 = r6
8: (18) r2 = 0xffff8975bd44aa00
10: (b7) r3 = 0
11: (b7) r5 = 144
12: (85) call bpf_perf_event_output#25
invalid indirect read from stack off -144+16 size 144
processed 12 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

如果sys_write 是一个不好的(问题)示例,我也一直在尝试追踪sys_execve,它的arg 列表为

asmlinkage long sys_execve(const char __user *filename,
                const char __user *const __user *argv,
                const char __user *const __user *envp);

请指出正确的方向,谢谢!

编辑 1

如何截取用于__x64_sys_execve 的参数?

当我在下面尝试这个时,

#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"

struct bpf_map_def SEC("maps") my_map = {
        .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
        .key_size = sizeof(int),
        .value_size = sizeof(u32),
        .max_entries = 2,
};

//SEC("kprobe/sys_write")
SEC("kprobe/__x64_sys_execve")

/* Signature of sys_execve: 
asmlinkage long sys_execve(const char __user *filename,
                const char __user *const __user *argv,
                const char __user *const __user *envp);
*/

int bpf_prog1(struct pt_regs *ctx, const char *filename)
{
        struct S {
                u64 pid;
                u64 cookie;
                char bleh[128];
        } data;

        data.pid = bpf_get_current_pid_tgid();
        data.cookie = 0x12345678;
        //bpf_get_current_comm(&data.bleh, 128);
        bpf_probe_read(&data.bleh, 128, (void *)filename);

        bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));

        return 0;
}

char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;

它就这样爆炸了:

/usr/src/linux-5.4/samples/bpf# ./borky
bpf_load_program() err=13
0: (bf) r6 = r2
R2 !read_ok
processed 1 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
0: (bf) r6 = r2
R2 !read_ok
processed 1 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

【问题讨论】:

  • 您是否在bleh 字段中写入了一些内容,或者您​​真的只是添加了该字段?您需要完全初始化 S 结构,否则验证器会抱怨您正在读取未初始化的位。
  • 谢谢@pchaigno,请您看看上面的Edit 1。如何拦截 __x64_sys_execve 的参数?谢谢!
  • 你不能像你一样将系统调用参数传递给你的函数bpf_prog1(),它只需要struct pt_regs *ctx。您可能在密件抄送中看到了这种语法,但bcc rewrites it under the hood。首选PT_REGS_PARM*(ctx) 宏来访问它们(exampledefinition)。
  • @Qeole,谢谢。您简洁的答复对我有很大帮助-我在密件抄送和 bpf 之间迷路了,感谢您指出了差异。如果您将此添加为答案,我很乐意将其标记为已接受的答案。

标签: c ebpf


【解决方案1】:

pchaigno 回答了您问题的第一部分:如果您扩展 struct S 并尝试在未初始化的情况下读取它 (bpf_perf_event_output(ctx, &amp;my_map, 0, &amp;data, sizeof(data));),验证程序会抱怨,因为从内核读取未初始化的内存会引入安全性风险。例如,您可以做的是在声明它时将整个结构归零:

        struct S {
                u64 pid;
                u64 cookie;
                char bleh[128];
        } data = {0};

关于sys_execve 问题的第二部分,事实证明您无法将系统调用参数传递给您的函数bpf_prog1(),就像您尝试做的那样。你的函数应该只接受struct pt_regs *ctx

混淆可能来自 bcc 中使用的语法,其中参数以这种方式传递,但重要的是要了解 bcc rewrites some parts 在后台,特别是关于访问参数的事情。

您可以改用一组PT_REGS_PARM*(ctx) 宏,它们专门定义用于从相关计算机寄存器(exampledefinition)访问被探测函数的参数。我认为 bcc 在进行重写工作时也会使用它们,但您不会看到它。

【讨论】:

  • 感谢您的回复以及向@struggling_learner 提出的问题。几天来,我一直在为 tcp_v4_connect 的 kprobe 函数的参数苦苦挣扎,现在我可以弄清楚了。
猜你喜欢
  • 1970-01-01
  • 2013-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-03
  • 2014-04-22
  • 1970-01-01
相关资源
最近更新 更多