如何通过eBPF在sched_process_fork追踪点获取父进程完整路径
以下是绑定到sched_process_fork追踪点用于跟踪进程fork的代码:
// fork.bpf.c // clang -O2 -target bpf -c fork.bpf.c -o fork.bpf.o #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #define TASK_COMM_LEN 16 struct event { __u32 pid; // tgid char comm[TASK_COMM_LEN]; // command name }; /* ring buffer map */ struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 1 << 24); // 16MB } events SEC(".maps"); /* Tracepoint: sched:sched_process_fork * When a process forks, this program reserves an event from the ring buffer, * populates pid+comm and submits it. */ SEC("tracepoint/sched/sched_process_fork") int trace_sched_process_fork(struct trace_event_raw_sched_process_fork *ctx) { struct event *e; u64 pid_tgid; /* get tgid (upper 32 bits) */ pid_tgid = bpf_get_current_pid_tgid(); __u32 tgid = pid_tgid >> 32; e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); if (!e) return 0; e->pid = tgid; /* get current comm (task->comm) */ bpf_get_current_comm(&e->comm, sizeof(e->comm)); bpf_ringbuf_submit(e, 0); return 0; } char LICENSE[] SEC("license") = "GPL";
我需要获取父进程的完整路径,但查看vmlinux.h文件后发现,struct trace_event_raw_sched_process_fork结构体没有文件名成员,仅包含PID和命令名。请问如何获取父进程路径?
若该追踪点无法实现,应使用哪个钩子(比如LSM钩子)来在fork时获取父进程路径?
一、在sched_process_fork追踪点获取父进程路径
尽管sched_process_fork的追踪点上下文没有直接提供路径信息,但可以通过内核task_struct结构间接提取父进程的可执行文件路径,具体实现步骤如下:
获取父进程的
task_struct指针:
使用bpf_get_current_task()获取当前父进程的task_struct指针,注意程序必须声明为GPL许可证才能安全访问内核内部结构体。安全读取内核结构字段:
由于BPF验证器禁止直接访问内核结构体的嵌套字段,必须使用bpf_core_read()进行安全读取,避免验证失败或内核崩溃。提取可执行文件路径:
通过task_struct的mm->exe_file获取可执行文件的file结构体,再调用bpf_d_path()生成完整路径。
修改后的核心代码示例:
#include <linux/sched.h> #include <linux/fs.h> // 扩展event结构体,添加路径字段 struct event { __u32 pid; char comm[TASK_COMM_LEN]; char path[256]; // 存储父进程完整路径 }; SEC("tracepoint/sched/sched_process_fork") int trace_sched_process_fork(struct trace_event_raw_sched_process_fork *ctx) { struct event *e; struct task_struct *task; struct mm_struct *mm; struct file *exe_file; u64 pid_tgid; pid_tgid = bpf_get_current_pid_tgid(); __u32 tgid = pid_tgid >> 32; // 预留包含路径的event空间 e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); if (!e) return 0; e->pid = tgid; bpf_get_current_comm(&e->comm, sizeof(e->comm)); // 获取父进程task_struct task = (struct task_struct *)bpf_get_current_task(); // 安全读取mm_struct(跳过内核线程,无用户空间地址空间) bpf_core_read(&mm, sizeof(mm), &task->mm); if (!mm) { bpf_ringbuf_submit(e, 0); return 0; } // 安全读取可执行文件的file结构体 bpf_core_read(&exe_file, sizeof(exe_file), &mm->exe_file); if (!exe_file) { bpf_ringbuf_submit(e, 0); return 0; } // 生成完整路径并写入event bpf_d_path(&exe_file->f_path, e->path, sizeof(e->path)); bpf_ringbuf_submit(e, 0); return 0; }
二、替代钩子选择
如果sched_process_fork追踪点存在兼容性限制,可选择以下钩子实现需求:
1. LSM钩子security_task_alloc
这是最适合的替代方案,security_task_alloc会在进程fork创建新任务时触发,此时可直接访问父进程的task_struct,且钩子接口稳定,跨内核版本兼容性好。
核心代码框架:
SEC("lsm/security_task_alloc") int BPF_PROG(security_task_alloc, struct task_struct *p, unsigned long clone_flags) { struct task_struct *parent = (struct task_struct *)bpf_get_current_task(); struct mm_struct *mm; struct file *exe_file; char parent_path[256]; bpf_core_read(&mm, sizeof(mm), &parent->mm); if (!mm) return 0; bpf_core_read(&exe_file, sizeof(exe_file), &mm->exe_file); if (!exe_file) return 0; bpf_d_path(&exe_file->f_path, parent_path, sizeof(parent_path)); // 将父进程PID、路径等信息存入ring buffer或map // ... 后续处理逻辑 ... return 0; }
2. kprobe钩子do_fork
通过kprobe挂钩内核fork核心函数do_fork,可在进程创建时获取父进程上下文。优点是灵活性高,缺点是do_fork的函数签名可能随内核版本变化,兼容性较差。
3. sched_process_exec追踪点(间接方案)
该追踪点在进程执行新程序时触发,可预先记录进程的路径与PID映射,后续fork时通过父PID查询路径。但此方案无法覆盖未执行过exec的进程(如直接fork的子进程)。
内容的提问来源于stack exchange,提问作者user31736034

