avatar🌌
TangentTangent Blog

技术博客 | Go & Kubernetes & Linux

技术博客 | Go & Kubernetes & Linux

runc 源码解读(五):从 SPEC.md 看容器安全与权限控制

前言 ​

在之前的文章中,我们从 SPEC.md 中了解了 Namespace(视图隔离)、Filesystem(文件系统)以及 Cgroups / Intel RDT(资源限制)。经过这三层包装,我们似乎已经得到了一个完整的容器。

但是,还面临一个致命的问题:如果容器内的应用是用 root 用户运行的,当应用被攻破时,黑客能直接控制宿主机吗?

答案是:通常不能。这就是容器安全机制的作用。在 runc 的 SPEC.md 中,专门有一个 ### Security 章节,定义了容器的**纵深防御(Defense in Depth)**体系。本文将带你逐一拆解这些安全防线。

防线一:Linux Capabilities(能力细分) ​

在传统的 Linux 系统中,权限是二元的:要么是普通用户(权限受限),要么是 root(UID=0,拥有所有权限)。为了实现更细粒度的控制,Linux 引入了 Capabilities(能力) 机制,把 root 的权限“切碎”成了几十个独立的特权操作。

SPEC.md 中给出了一个容器默认 Capabilities 的清单:

The standard set of Linux capabilities that are set in a container provide a good default for security and flexibility for the applications.

规范中明确规定了哪些能力默认开启(1),哪些默认关闭(0):

CapabilityEnabled作用简述
CAP_CHOWN1允许修改文件所有者
CAP_NET_BIND_SERVICE1允许绑定 1024 以下的特权端口
CAP_SETUID / CAP_SETGID1允许切换用户身份(如 su/sudo)
CAP_KILL1允许发送信号给进程
CAP_SYS_ADMIN0Linux 的终极权限,允许执行大量高危系统管理操作
CAP_NET_ADMIN0允许配置网络接口、路由表等
CAP_SYS_MODULE0允许加载/卸载内核模块
CAP_SYS_TIME0允许修改系统时间

解读: 你会发现,runc 默认开启的,都是维持一个普通应用(如 Nginx、数据库)正常运行所需的最小权限。 而那些被禁用的,往往是可以直接影响宿主机的操作。比如没有 CAP_SYS_ADMIN,你就无法在容器内挂载文件系统;没有 CAP_SYS_TIME,你就无法修改宿主机的时间(因为时间是全局的,直到较新的内核才支持 Time Namespace);没有 CAP_SYS_MODULE,黑客就无法植入恶意的内核 Rootkit。

这就是为什么即便你在容器里是 root,你依然会觉得这个 root 是个“阉割版”。

防线二:AppArmor 与 SELinux(强制访问控制) ​

即便是去掉了危险的 Capabilities,容器内的 root 依然有能力去读取或修改系统内的大量文件。为了进一步收缩攻击面,SPEC.md 提到了 MAC(Mandatory Access Control,强制访问控制):

Additional security layers like apparmor and selinux can be used with the containers. A container should support setting an apparmor profile or selinux process and mount labels if provided in the configuration.

文档中甚至直接给出了一个标准的 AppArmor Profile 配置示例。这段配置虽然短,但字字诛心:

c
profile <profile_name> flags=(attach_disconnected,mediate_deleted) {
  ...
  deny @{PROC}/sys/fs/** wklx,
  deny @{PROC}/sysrq-trigger rwklx,
  deny @{PROC}/mem rwklx,
  ...
  deny mount,
  ...
  deny /sys/kernel/security/** rwklx,
}

这段规则的核心目的只有一个:保护宿主机的内核状态不被篡改。

  • deny mount:从 MAC 层面再次死死封住挂载文件系统的口子。
  • deny @{PROC}/sys/fs/** wklx 和 deny /sys/...:禁止对 /proc 和 /sys 下敏感内核参数的写入操作。即使由于某种原因容器内的进程获得了较高的 Capabilities,AppArmor 依然会在最后关头将恶意写操作拦截(Permission denied)。

防线三:Seccomp(系统调用过滤) ​

如果说 Capabilities 是对“业务动作”的过滤,AppArmor 是对“文件路径”的过滤,那么 Seccomp (Secure Computing Mode) 就是对 Linux 内核**“系统调用 (Syscall)”**的硬核过滤。

seccomp can be used to apply filters to the system calls used in a container… The primary use-case is to provide an explicit allow-list of syscalls for a container, to reduce the kernel API attack surface exposed to the container.

容器本质上与宿主机共享同一个内核。如果内核某个系统调用存在 0-day 漏洞,容器内的程序就可以通过触发这个系统调用来提权逃逸。

Seccomp 通过 BPF (Berkeley Packet Filter) 技术拦截系统调用。runc 在这里做了一个很聪明的决定:

libcontainer does not provide a default filter, but higher-level runtimes tend to define their own filters for use with runc…

runc/libcontainer 本身不提供默认的 Seccomp 过滤规则,而是将定义权交给了上层运行时(如 Docker、containerd)。 实际上,Docker 默认配置的 Seccomp profile 禁用了 Linux 300 多个系统调用中的 40 多个不安全调用(如 kexec_load、open_by_handle_at 等)。历史上,Seccomp 已经无数次成功防御了内核提权 0-day 漏洞。

实验与动手 Demo:探测容器的“阉割版” root ​

我们可以通过一个简单的实验,直观地感受这层安全防御体系。我们启动一个普通的容器,并尝试在里面做一些“越权”的事情。

1. 尝试修改系统时间 ​

bash
$ docker run --rm -it ubuntu bash

root@container_id:/# id
uid=0(root) gid=0(root) groups=0(root)

root@container_id:/# date -s "2030-01-01"
date: 2026-03-19T10:00:00+08:00

失败原因:我们虽然是 root,但是缺少 CAP_SYS_TIME 能力。

2. 尝试挂载文件系统 ​

bash
root@container_id:/# mount -t tmpfs tmpfs /mnt
mount: /mnt: permission denied.

失败原因:缺少 CAP_SYS_ADMIN 能力,并且同时被 AppArmor 的 deny mount 规则拦截。

3. 查看容器当前拥有的 Capabilities ​

我们可以使用 capsh 工具来清晰地看到我们到底被赋予了哪些能力。

bash
root@container_id:/# apt-get update && apt-get install -y libcap2-bin

root@container_id:/# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip

对照我们打印出的 Current 列表和前面 SPEC.md 中的表格,你会发现它们惊人地一致!高危的特权能力已经被彻底剥离。

结语 ​

从 Namespaces 的“骗”(让你以为你有一台完整的机器),到 Cgroups 的“抠”(限制你能用的资源),再到 Security(Capabilities + AppArmor + Seccomp)的“防”(没收所有危险的武器),runc 通过对 Linux 内核特性的极致压榨,组合成了一套轻量却强大的沙箱体系。

至此,我们对 runc SPEC.md 的源码与规范解读也告一段落。深入理解这些底层规范,对于我们在日常工作中排查容器权限报错(Permission denied)、编写高安全的 Dockerfile,甚至是进行云原生安全防御,都有着极其重要的指导意义。

CapabilityEnabled作用简述
CAP_NET_RAW1允许使用原始套接字(如 ping 命令)
CAP_AUDIT_READ1允许读取审计日志
CAP_AUDIT_WRITE1允许写入审计日志
CAP_DAC_OVERRIDE1忽略文件的 DAC 访问权限(读、写、执行)
CAP_SETFCAP1允许设置文件上的 Capabilities
CAP_SETPCAP1允许修改进程的 Capabilities
CAP_MKNOD1允许创建特殊文件(使用 mknod)
CAP_FOWNER1忽略通常要求进程 UID 与文件 UID 匹配的限制
CAP_FSETID1当文件被修改时,不清除 setuid 和 setgid 位
CAP_SYS_CHROOT1允许调用 chroot()
CAP_NET_BROADCAST0允许网络广播和多播
CAP_SYS_RAWIO0允许原始 I/O 端口操作
CAP_SYS_PACCT0允许配置进程记账
CAP_SYS_NICE0允许提高进程优先级和设置特定的调度策略
CAP_SYS_RESOURCE0允许重写资源限制并配置 ext2 预留空间
CAP_SYS_TTY_CONFIG0允许配置 tty 设备
CAP_AUDIT_CONTROL0允许配置内核审计系统
CAP_MAC_OVERRIDE0允许忽略 MAC 策略
CAP_MAC_ADMIN0允许配置 MAC 策略
CAP_SYSLOG0允许执行特权 syslog 操作
CAP_DAC_READ_SEARCH0绕过文件读取和目录搜索的权限检查
CAP_LINUX_IMMUTABLE0允许修改文件的不可变和仅追加属性
CAP_IPC_LOCK0允许锁定内存(mlock等)
CAP_IPC_OWNER0绕过 IPC 对象的权限检查
CAP_SYS_PTRACE0允许跟踪任何进程(ptrace)
CAP_SYS_BOOT0允许重启系统或加载新内核
CAP_LEASE0允许在任意文件上建立租约
CAP_WAKE_ALARM0允许触发能唤醒系统的定时器
CAP_BLOCK_SUSPEND0允许阻止系统挂起
runc 源码解读(七):OCI Spec 核心数据结构解析 (上) —— 基础配置与 Process
runc 源码解读(四):从 SPEC.md 看 Intel RDT 微架构资源隔离
由 Valaxy v1.2.2 驱动|主题-Yunv1.2.2