提到容器安全,多数人首先想到的是命名空间和 cgroup,但这两者只能做到资源隔离与限制,并不能阻止容器内的进程执行危险的内核系统调用。如果容器中的应用存在远程代码执行漏洞,攻击者拿到 shell 之后,完全可以调用 keyctl、bpf、ptrace 之类的系统调用来窃取宿主机信息,甚至利用内核漏洞实现逃逸。seccomp 就是专门解决这个问题的机制:它允许你以白名单或黑名单的方式精确控制容器内可用的系统调用集合,是容器安全加固中成本最低、收益最明显的一环。

seccomp 的底层原理与默认策略
seccomp 是 Linux 内核自 2.6.12 起引入的安全计算模式,全称 Secure Computing Mode。它通过 BPF(Berkeley Packet Filter)规则在内核层面拦截进程发出的系统调用,根据规则返回允许、拒绝或杀死进程等动作。目前主流的使用模式是 seccomp-bpf,即 filter 模式,它比早期的 strict 模式灵活得多。strict 模式只允许 read、write、exit、sigreturn 四个调用,几乎无法支撑现代应用,而 filter 模式可以按系统调用号和参数编写任意复杂的过滤逻辑。
Docker 默认内置了一份 seccomp 配置,位于容器的安全配置中,约放行了三百多个系统调用,屏蔽了 mount、reboot、kexec_load、bpf、perf_event_open 等六十多个高风险调用。可以通过 docker info 查看输出中的 Security Options 一行,如果显示 seccomp 且未标明 profile 名称,说明默认策略已经生效。需要注意的是,只有在默认 seccomp 配置未被禁用的前提下这个保护才存在,如果容器启动时传入了 --privileged 参数,seccomp 保护会被完全关闭,等同于把容器大门敞开,这在生产环境中应当极力避免。
默认策略适合绝大多数场景,但它是按通用标准设计的,对不同业务来说既可能过松也可能过紧。比如运行一个静态编译的 Go 服务,实际只需要几十个系统调用,却继承了三百多个的放行范围,攻击面偏大;反过来,某些需要特殊调用的程序在默认策略下又会报 Operation not permitted。因此掌握自定义 seccomp 配置文件的编写方法十分必要。
编写并加载自定义 seccomp 配置文件
seccomp 配置文件是一个 JSON 文档,顶层包含 defaultAction、architectures、syscalls 三个核心字段。defaultAction 定义未匹配规则的系统调用的默认处置,通常设为 SCMP_ACT_ERRNO(返回错误码而非直接杀死进程,避免程序莫名其妙崩溃)。architectures 声明兼容的 CPU 架构,跨架构场景下必须正确填写,否则系统调用号映射会出错。syscalls 数组则逐条声明放行或拦截的调用。
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"accept4", "bind", "close", "connect",
"epoll_wait", "open", "read", "write",
"socket", "stat", "fstat", "exit_group"
],
"action": "SCMP_ACT_ALLOW"
}
]
}上面这份配置是一个极简白名单,只放行网络服务常用的十几个系统调用。将内容保存为 strict-profile.json 后,启动容器时通过 --security-opt 参数加载:
docker run -d --name webapp \ --security-opt seccomp=strict-profile.json \ nginx:alpine
如果希望恢复默认策略,将参数写成 --security-opt seccomp=docker-default 即可;写成 --security-opt seccomp=unconfined 则彻底关闭 seccomp,生产环境切勿这样做。除了白名单方式,也可以采用黑名单思路,即 defaultAction 设为允许,再针对少数危险调用单独声明拒绝,例如拦截 clone 时限制特定 flags 的组合,这种写法依赖 args 字段做参数级过滤,粒度更细。
实际操作中推荐的工作流是:先用宽松策略运行容器,同时配合 strace 或 oci-seccomp-bpf-hook 之类的工具采集程序真实使用到的系统调用,再据此生成精准的白名单。Red Hat 提供的 oci-seccomp-bpf-hook 可以在容器启动时挂载追踪,把结果直接输出成 seccomp JSON 文件,省去人工排查的麻烦。
验证生效与常见问题排查
配置加载后必须验证是否真的生效。最直接的办法是在容器内执行依赖被拦截调用的命令。例如配置中未放行 uname,执行 docker exec webapp uname -a 应返回 Operation not permitted 错误。也可以在宿主机上查看进程的 status 文件:
# 找到容器主进程 PID
PID=$(docker inspect -f '{{.State.Pid}}' webapp)
# 查看进程的 seccomp 状态,2 表示 filter 模式
grep Seccomp /proc/$PID/status输出中 Seccomp: 2 表示 filter 模式已启用,Seccomp: 0 表示未启用,Seccomp: 1 表示 strict 模式。这是判断策略是否挂载成功的权威依据。
排查报错时,最常见的问题有三类。第一类是架构不匹配,在 x86_64 宿主机上运行 arm 容器时,若 architectures 未包含对应架构,所有调用都会走默认拒绝分支,程序启动即失败,解决办法是补全架构声明或使用 SCMP_ARCH_AARCH64 等正确值。第二类是白名单遗漏关键调用,表现为程序启动后立刻退出或日志中出现 Operation not permitted,这时需要用 strace -f -c 统计被拒的调用并补充进列表。第三类是配置文件本身语法错误,Docker 启动时会直接报 failed to load seccomp profile,通常是 JSON 字段拼写错误或 action 值大小写不符,action 必须严格使用 SCMP_ACT_ALLOW 这样的全大写枚举值。
还有一个容易踩的坑:在 Kubernetes 环境中,Pod 的 securityContext 提供 seccompProfile 字段,可指定 RuntimeDefault 或 Localhost,选择 Localhost 时配置文件必须放在每个节点的 /var/lib/kubelet/seccomp/profiles 目录下,路径写错会导致 Pod 一直处于 ContainerCreating 状态。理解了这些细节之后,配合最小权限原则逐步收紧系统调用白名单,就能让容器的安全边界真正立起来。