因为containerd prestart hook仅提供静态配置,无法动态拦截execve等系统调用;需用seccomp-bpf实时检测进程行为,结合cgroup事件清理bpf程序,避免资源泄漏与策略错位。

为什么不用 containerd 原生 hook 而要自己写运行时拦截
因为 containerd 的 prestart hook 只能拿到容器启动前的静态配置,无法感知进程实际执行的二进制、参数或文件打开行为;安全策略需要动态判断,比如“禁止容器内执行 /bin/sh”或“限制 openat 访问路径”,必须在内核态或运行时层做实时拦截。
实操建议:
系统功能强大、操作便捷并具有高度延续开发的内容与知识管理系统,并可集合系统强大的新闻、产品、下载、人才、留言、搜索引擎优化、等功能模块,为企业部门提供一个简单、易用、开放、可扩展的企业信息门户平台或电子商务运行平台。开发人员为脆弱页面专门设计了防刷新系统,自动阻止恶意访问和攻击;安全检查应用于每一处代码中,每个提交到系统查询语句中的变量都经过过滤,可自动屏蔽恶意攻击代码,从而全面防止SQL注入攻击
- 用
seccomp-bpf配合自定义bpf_prog拦截关键系统调用,比用户态 hook 延迟低、逃逸面小 - 避免依赖
runc的exechook —— 容器内fork+exec启动的子进程不会触发该 hook - 若需读取进程命令行,优先从
/proc/[pid]/cmdline获取,而非信任args字段(可被篡改)
syscall.Execve 拦截后如何安全放行合法命令
直接拒绝所有 execve 会导致容器初始化失败(如 pause 进程、init 系统调用),必须白名单 + 上下文识别。
常见错误现象:containerd-shim 报错 "failed to create container: rpc error: code = Unknown desc = failed to create container: exec: \"runc\": executable file not found",其实是拦截逻辑误杀了 shim 自身的 execve。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 通过
pidfd_getfd或/proc/[pid]/status中的PPid判断是否属于容器 init 进程(即容器第一个进程) - 白名单只匹配
pathname的绝对路径,不依赖argv[0](易被argv[0] = "/bin/ls"伪装成其他命令) - 对 shell 脚本类启动(如
sh -c 'echo hello'),需额外检查argv[1]是否含可疑字符串,但不要全量解析 —— 性能代价高且易绕过
如何让防御逻辑不拖慢容器冷启动
在 fork 或 clone 后立即注入 BPF 程序,比等容器进程起来再 attach perf_event_open 更快;但 BPF 校验器会拒绝复杂逻辑,容易因栈溢出或循环被拒载。
性能影响点:
- BPF 程序中避免使用大数组或嵌套循环,校验器默认栈上限 512 字节
- 路径匹配用
bpf_probe_read_kernel_str+ 哈希比对(如xxh32),别逐字节strcmp - 把白名单规则存在
BPF_MAP_TYPE_HASH中,而非硬编码进 BPF 字节码 —— 更新策略无需重载程序
为什么容器退出后还要清理 BPF_PROG_TYPE_CGROUP_SKB 关联
不清理会导致残留 BPF 程序持续消耗内核资源,且下次同名 cgroup 创建时可能复用旧 map,造成策略错位。这不是 bug,是 cgroup v2 的设计约束:BPF 程序绑定到 cgroup 后,即使进程退出,只要 cgroup 目录存在,程序就仍在生效。
实操建议:
- 监听
containerd的TaskExitevent,收到后立即调用bpf_prog_detach解绑 - 用
cgroup_path而非 PID 查找目标 cgroup,避免容器快速启停导致 PID 复用误删 - 清理失败时记录
"failed to detach bpf prog from /sys/fs/cgroup/...: operation not permitted",通常是权限不足,需确认是否以CAP_SYS_ADMIN运行
最麻烦的是 cgroup v1 和 v2 混用场景 —— 同一个容器运行时可能在不同节点挂载不同版本,BPF_PROG_TYPE_CGROUP_SKB 在 v1 下不生效,得 fallback 到 tracepoint/syscalls/sys_enter_execve,这个细节很多人上线后才踩到。









