内核空间判断PID所属命名空间及eBPF容器系统调用日志实现问询
内核空间识别PID命名空间及eBPF中容器进程的判断方法
针对你的两个问题,我来一步步拆解解答:
一、内核空间中确定PID所属的命名空间
内核里每个进程的task_struct结构体包含了指向命名空间代理nsproxy的指针,而nsproxy中直接存储了进程所属的PID命名空间pid_ns。你可以通过以下步骤判断:
- 获取当前进程的
task_struct:内核中常用current宏指向当前进程的task_struct;如果是eBPF程序,则用bpf_get_current_task()helper函数获取。 - 从
task_struct中取出nsproxy指针,注意要先检查指针是否为空(避免空指针访问)。 - 从
nsproxy中获取pid_ns,这就是进程所属的PID命名空间实例。 - 将这个
pid_ns与内核根PID命名空间&init_pid_ns比较:如果地址不同,说明进程处于非根命名空间(比如容器的PID命名空间)。
二、eBPF程序中实现容器进程识别(无需用户空间proc文件系统)
用bcc编写eBPF程序时,你可以直接在内核态完成容器进程的判断,不需要依赖用户空间的proc文件系统。下面是具体实现思路和代码示例:
核心思路
利用内核结构体偏移量来安全访问task_struct和nsproxy(避免不同内核版本的结构体定义差异问题),然后比较PID命名空间是否为根命名空间。
代码示例
#include <linux/sched.h> #include <stddef.h> #include <uapi/linux/ptrace.h> // 定义结构体字段的偏移量(适配不同内核版本) #define TASK_NSPROXY_OFF offsetof(struct task_struct, nsproxy) #define NSPROXY_PID_NS_OFF offsetof(struct nsproxy, pid_ns) // 引用内核的根PID命名空间 extern struct pid_namespace init_pid_ns __kptr; int trace_target_syscall(struct pt_regs *ctx) { // 获取当前进程的task_struct指针 struct task_struct *task = (struct task_struct *)bpf_get_current_task(); if (!task) { return 0; } // 通过偏移量获取nsproxy指针 struct nsproxy *nsproxy = (struct nsproxy *)((char *)task + TASK_NSPROXY_OFF); if (!nsproxy) { return 0; } // 通过偏移量获取PID命名空间指针 struct pid_namespace *pid_ns = (struct pid_namespace *)((char *)nsproxy + NSPROXY_PID_NS_OFF); if (!pid_ns) { return 0; } // 判断是否为容器进程:非根PID命名空间即为容器进程 if (pid_ns != &init_pid_ns) { // 获取进程PID(bpf_get_current_pid_tgid返回的是tgid<<32 | pid,所以右移32位得到tgid,也就是用户态看到的PID) u32 pid = bpf_get_current_pid_tgid() >> 32; // 这里添加你的系统调用记录逻辑,比如打印PID、系统调用号等 bpf_trace_printk("Container process %d called target syscall\n", pid); } return 0; }
注意事项
- 内核版本兼容性:不同内核版本的
task_struct和nsproxy结构体字段偏移可能变化,使用offsetof宏是最安全的方式,bcc会自动处理内核头文件的适配。 - 空指针检查:一定要检查每个指针是否为空,避免eBPF程序因空指针访问被内核拒绝加载。
- 命名空间判断的局限性:这种方法基于PID命名空间判断,适用于绝大多数容器场景,但如果用户手动创建了非容器的PID命名空间,也会被误判,不过这种情况在生产环境中很少见。
内容的提问来源于stack exchange,提问作者dippynark
相关产品推荐
相关产品推荐

