现代Linux内核(5.7+)已禁用sys_call_table导出,硬编码地址会导致panic;真实可用路径仅剩kprobe或ftrace动态插桩,eBPF+tracepoint是用户态syscall审计唯一稳定方案。

内核态实现系统调用过滤:用 kprobe + hook sys_call_table 不再可行
Linux 5.7+ 默认禁用 sys_call_table 导出,且 KPTI、SMAP、CONFIG_KALLSYMS_ALL=y 缺失时,硬编码地址会直接 panic。真实可用的路径只剩 kprobe 或 ftrace 动态插桩——但注意:kprobe 只能拦截进入点(如 do_syscall_64),无法在真正 dispatch 前做决策。
实操建议:
- 优先用
register_kprobe拦截do_syscall_64,读取regs->ax获取 syscall number,再查白名单表(需预加载到 module data) - 黑名单场景下,若想阻断,必须在 probe handler 中返回非零值(仅对
kretprobe无效;kprobe返回非零会跳过原函数,但风险极高——破坏寄存器/栈状态易导致 oops) - 行为审计日志务必用
trace_printk()或ring_buffer_event_write(),避免用printk()在高频率 syscall 下触发 soft lockup - 不要尝试 patch
sys_call_table:现代内核开启CONFIG_STRICT_KERNEL_RWX后,写保护页表不可绕过
用户态 syscall 审计:eBPF + tracepoint 是唯一稳定方案
用户态无法直接拦截内核 syscall 入口,但可通过 tracepoint/syscalls/sys_enter_* 和 sys_exit_* 实现无侵入审计。eBPF 程序挂载后,可提取 pid、comm、syscall number、参数值(如 args->args[0] 对应 openat 的 filename),并决定是否丢弃事件或上报。
常见错误现象:
- 用
perf_event_open()手动读 tracepoint —— 效率低、易丢事件、不支持过滤逻辑 - 在 eBPF 中调用
bpf_trace_printk()过多 → 触发 verifier 拒绝(instruction limit)或 ringbuf 溢出 - 未用
bpf_get_current_pid_tgid()+bpf_get_current_comm()关联进程上下文 → 日志无法对应到具体程序
实操建议:
- 用
libbpf加载 CO-RE eBPF 程序,targettracepoint/syscalls/sys_enter_openat,检查args->flags & O_CREAT做行为标记 - 黑白名单逻辑放在用户态守护进程里:eBPF 只负责采集和打标(如设置
ctx->audit_flag = 1),由 userspace agent 查内存映射的 hash table 做决策 - 避免在 eBPF 中做字符串匹配(如比对 filename):用
bpf_probe_read_user_str()易失败,应只读指针+长度,交由 userspace 解析
权限与兼容性陷阱:CAP_SYS_ADMIN 不是万能钥匙
即使有 root 权限,eBPF 加载仍受 /proc/sys/kernel/unprivileged_bpf_disabled 和 RLIMIT_MEMLOCK 限制;内核模块则依赖 CONFIG_MODULE_SIG_FORCE=n(否则签名失败)和 secure_boot 状态。
关键差异点:
-
unprivileged_bpf_disabled=2:完全禁止非 CAP_SYS_ADMIN 用户加载 eBPF;设为 1 仅禁用非特权用户,但依然要求rlimit -l≥ 8MB - 内核模块编译必须匹配运行内核的
KERNELRELEASE,modinfo xxx.ko中的vermagic字段不一致会导致 insmod 失败,错误信息是Invalid module format - ARM64 上
kprobe对某些 syscall(如sys_clone)可能因寄存器重用而读错regs->ax,需改用ftrace_set_filter_ip()配合自定义 handler
审计数据落地:别把 ringbuf 当通用队列用
eBPF ringbuf 虽快,但无 ACK 机制,userspace 消费慢时内核会丢事件(libbpf 默认行为)。真实生产环境必须配双缓冲或降频采样。
实操建议:
- 用
bpf_ringbuf_output()发送结构体前,先bpf_ringbuf_reserve()并检查返回指针非 NULL;失败时记录丢弃计数(用 percpu array) - 审计字段精简:只传 syscall number、pid、timestamp、flag(黑白名单命中结果),文件路径等大字段用 userspace 根据 fd 反查
/proc/pid/fd/ - 不要在 eBPF 中调用
bpf_map_lookup_elem()查黑名单 map 超过 1 次——verifier 会拒绝复杂循环,应把黑白名单预计算为 bitmap 存入 percpu array
真正难的是跨 namespace 和容器逃逸场景下的 syscall 关联:宿主机 eBPF 看不到容器内部 pid namespace 的 1 号进程,pid 字段需要结合 getpid() / gettid() 和 bpf_get_current_pid_tgid() 的高位区分。这点容易被忽略,一查日志就发现“所有容器进程都显示 pid=0”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











