导读:本期聚焦于苏沐橙创作的《如何通过bpf_get_current_task追踪process_vm_readv实现跨进程读取分析?》,敬请观看详情。进程A读取进程B的内存,内核层面发生了什么?process_vm_readv系统调用提供了无需ptrace附加的跨进程读取能力,这让调试与监控变得方便,也带来潜在的数据泄露风险。借助eBPF的kprobe挂载点和bpf_get_current_task辅助函数,可以高效捕获每次跨进程读取的发起进程、目标PID以及读取长度。本文从process_vm_readv的内核执行流程入手,展示如何用BPF_KPROBE宏提取系统调用参数,如何通过bpf_probe_read_kernel读取当前task_struct的tgid,以及如何使用bpf_probe_read_user读取用户态iovec结构。示例代码同时挂载kprobe和kretprobe,输出发起者、目标进程和返回值,为安全审计提供直接可用的追踪方案。

跨进程读取在调试器、进程监控和安全审计中非常常见。Linux内核提供的process_vm_readv系统调用允许一个进程直接读取另一个进程的地址空间,而无需使用ptrace附加。借助eBPF的kprobe挂载点与bpf_get_current_task辅助函数,可以在不修改内核的前提下捕获每一次跨进程读取操作。接下来将深入分析该系统调用的参数结构、eBPF追踪程序的编写方法以及监控数据的实际应用。

如何通过bpf_get_current_task追踪process_vm_readv实现跨进程读取分析?

process_vm_readv的参数与内核执行流程

process_vm_readv的系统调用原型为ssize_t process_vm_readv(pid_t pid, const struct iovec *local_iov, unsigned long liovcnt, const struct iovec *remote_iov, unsigned long riovcnt, unsigned long flags)。其中pid是目标进程的进程号,local_iov和remote_iov分别指向本地和目标进程的I/O向量数组,liovcnt与riovcnt表示数组长度。flags目前没有实际用途,通常传0。

当用户态调用process_vm_readv时,内核会通过get_task_struct查找pid对应的task_struct,并获取其内存描述符mm_struct。接着内核遍历remote_iov中的每个iovec结构,将目标进程虚拟地址iov_base处的数据读取出来,复制到本地进程的local_iov对应缓冲区中。由于整个过程由内核完成,调用进程不需要与目标进程建立ptrace关系,也不需要目标进程主动配合。

从安全角度看,这种读取能力受ptrace权限模型约束。调用进程必须拥有对目标进程的ptrace访问权限,否则系统调用会返回EPERM或ESRCH。因此追踪process_vm_readv不仅能看到合法的调试行为,也能发现某些恶意软件尝试跨进程窃取敏感信息的尝试。

为什么用bpf_get_current_task捕获上下文

bpf_get_current_task是eBPF提供的一个辅助函数,它返回指向当前进程task_struct的指针。task_struct是内核描述进程的核心结构,包含进程名、PID、内存描述符、父进程关系等大量信息。然而eBPF程序运行在内核上下文,直接解引用task_struct字段会被验证器拒绝,必须使用bpf_probe_read_kernel或bpf_core_read系列函数来安全读取内核内存。

在本场景中,我们不仅需要当前进程的task_struct指针,还需要通过该指针获得发起者的PID。例如读取task->tgid可以得到线程组ID,也就是通常意义上的进程PID。虽然bpf_get_current_pid_tgid辅助函数也能直接返回PID,但使用bpf_get_current_task可以展示如何从task_struct中提取其他需要的信息,比如后续想要获取进程的可执行文件路径、uid、父进程等信息时,可以基于该指针继续读取。

在kprobe挂载点中,当前task就是发起process_vm_readv调用的进程。通过PT_REGS_PARM1或BPF_KPROBE宏可以直接获取系统调用的第一个参数pid,也就是目标进程号。x86_64架构下,系统调用参数依次放在rdi、rsi、rdx、r10、r8、r9寄存器中,BPF_KPROBE宏已经屏蔽了这些底层差异。

完整eBPF程序编写与加载

下面给出一个完整的eBPF追踪程序示例,它同时挂载了kprobe和kretprobe。kprobe部分获取发起进程的PID、目标PID,并读取remote_iov数组中第一个元素的长度;kretprobe部分打印系统调用的返回值。

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

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

SEC("kprobe/sys_process_vm_readv")
int BPF_KPROBE(trace_process_vm_readv, pid_t pid, const struct iovec *local_iov,
               unsigned long liovcnt, const struct iovec *remote_iov,
               unsigned long riovcnt, unsigned long flags)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    pid_t current_pid = 0;
    struct iovec riov;

    if (!task || !remote_iov)
        return 0;

    bpf_probe_read_kernel(&current_pid, sizeof(current_pid), &task->tgid);
    bpf_probe_read_user(&riov, sizeof(riov), remote_iov);

    bpf_printk("vm_read pid=%d target=%d len=%lu",
               current_pid, pid, riov.iov_len);
    return 0;
}

SEC("kretprobe/sys_process_vm_readv")
int BPF_KRETPROBE(trace_process_vm_readv_ret, ssize_t ret)
{
    pid_t current_pid = bpf_get_current_pid_tgid() >> 32;
    bpf_printk("vm_read_ret pid=%d ret=%ld", current_pid, ret);
    return 0;
}

代码中BPF_KPROBE宏来自bpf_tracing.h,它会自动展开成符合kprobe要求的函数签名。remote_iov是用户态指针,因此必须使用bpf_probe_read_user来读取iovec结构体。由于iovec数组可能非常大,这里只读取了第一个元素,避免验证器因为无界循环而拒绝加载。bpf_printk会将格式化信息输出到trace_pipe,便于调试。

编译时需要使用vmlinux.h获得完整的内核类型定义。如果使用BCC或传统libbpf,也可以手动定义struct iovec。加载成功后,通过cat /sys/kernel/debug/tracing/trace_pipe可以实时看到类似vm_read pid=1234 target=5678 len=4096的输出。返回值则通过kretprobe单独输出,形成完整的一次调用记录。

输出分析与安全监控实践

追踪输出能反映出跨进程读取的几个关键维度:谁在读、读谁、读多少、结果如何。发起进程PID用于定位工具或可疑程序;目标PID指向被读取的进程;len表示一次请求尝试读取的字节数,结合返回值可以判断读取成功与否。如果某个进程在短时间内对多个不同目标发起大量读取,或者读取对象是sshd、systemd等关键服务,就值得安全团队关注。

在安全监控场景中,可以将process_vm_readv的调用频率、读取长度和目标进程敏感性作为检测指标。例如数据库进程被其他非信任进程读取时,可能意味着凭据窃取。eBPF的优势在于这种监控几乎不产生额外开销,并且可以动态加载卸载,非常适合生产环境。

需要提醒的是,bpf_printk输出数量过大时会影响性能,生产环境建议改用BPF ring buffer或perf event array将事件传递给用户态程序进行过滤和聚合。同时kprobe挂载点依赖内核符号名,不同内核版本可能略有差异,使用前需要确认/proc/kallsyms中存在sys_process_vm_readv符号。

eBPFprocess_vm_readvbpf_get_current_task修改时间:2026-09-20 06:07:54

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0920/59533.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。