埃里卡·温迪施(Erica Windisch)指出,若真实root用户被移除SYSCAPADMIN能力,但创建用户命名空间(user namespaces)后,该能力会在假root用户中恢复,此前拒绝的mount系统调用在创建后能以有限方式工作,足以让Linux能力被规避。man 7 usernamespaces称,通过clone(2)带CLONENEWUSER标志创建的子进程在新用户命名空间拥有完整能力集。另有文档提及用户命名空间允许有趣的交集。
正文
Erica Windisch 指出,若真实 root 用户被移除 SYSCAPADMIN 能力,但创建了 user namespace,该能力便为伪 root 恢复:创建前 mount 会被拒绝,创建后 mount 系统调用神奇地再次可用,尽管功能受限。man 7 usernamespaces 确认,clone(2) 带 CLONENEWUSER 标志创建的子进程在新 user namespace 中拥有完整能力集。"Understanding and Hardening Linux Containers" 也提到,user namespaces 允许安全模型的有趣交集,新 namespace 被授予完整 root 能力,这使得 CLONENEWUSER 能在暴露的网络 namespace 上有效使用 CAPNET_ADMIN,未特权 user namespace 可触及内核网络子系统这一巨大攻击面。
我们在编译了 user namespaces 的主机上演示:普通用户 lizzie 运行 subvertingnetworking.c(unshare CLONENEWUSER|CLONENEWNET 后 ioctl 创建桥 br0),输出 ++ success!。但其实没那么强大:另一个程序 subvertingsetfcap.c 尝试 capsetfile 设置 capnetadmin 时失败,报 Operation not permitted。
内核配置 USERNS 默认关闭(init/Kconfig 中 default n)。Ubuntu 开启了 CONFIGUSERNS,但打了补丁增加 sysctl unprivilegedusernsclone 可禁用未特权使用;Debian 行为相同;Grsecurity 则完全禁止无 CAPSYSADMIN、CAPSETUID、CAP_SETGID 的用户;Arch Linux 直接关闭。
克隆栈例子取自 man 2 clone:clonestack.c 分配栈,子进程 execve showstack,对比前后栈地址,x86 栈向下增长,execve 后地址更高说明分配了新栈。指针加法指向数组末元素之后是合法的,但解引用是未定义行为。
PID namespace 中,若 init 进程终止,内核向所有子进程发 SIGKILL(man 7 pidnamespaces)。persistentchild.c 验证容器根退出后子进程被杀。由于进程不能自行写自己的 user namespace 映射,父进程需等子进入后写其 uidmap 和 gidmap(man 7 usernamespaces 列出写映射的严格要求)。struct cred 中包含真实/有效/保存 UID/GID 及能力集。proc 写权限检查 testperm 根据当前 euid 移位模式。
文件能力方面:若丢弃 bounding set,文件额外能力不生效。tryregaincap.c 在 contained 中运行显示 don't have CAPMKNOD;若不丢弃则显示 have CAPMKNOD;若设 +ep 则 execve 失败,因为内核将设了有效位的能力文件视为 capability-dumb,若执行时未获得全部允许能力会拒 execve(man 7 capabilities)。
审计能力 CAPAUDITCONTROL 仅初始 pid namespace 可用。CAPBLOCKSUSPEND 用于阻止挂起。CAPDACREADSEARCH 被 shocker.c 利用:容器进程用 openbyhandleat 绕过 rootfs 绑定挂载,按句柄遍历文件系统读 /etc/shadow;在保留该能力时成功,否则拒绝。CAPFSETID:若未丢弃,capfsetid.c 可覆盖 setuid root 文件自身并保留权限,从而提权。CAPIPCLOCK 用于 mlock 绕过 RLIMITMEMLOCK。CAPMKNOD 可创建设备节点挂载磁盘读 /etc/shadow(capmknod.c 演示,需允许该能力)。CAPSETFCAP 设文件能力,但似乎不能设进程自身没有的能力。CAPSYSLOG 读内核日志。CAPSYSBOOT 用于 reboot/kexec。CAPSYSMODULE 加载/删除模块;devload 还允许仅 CAPNETADMIN 加载 netdev-* 模块。CAPSYSNICE 提高优先级可造成单核 VM 中容器占用更多 CPU(nicedos.c 演示 ticks 341 对 52)。CAPSYSRAWIO 访问 /dev/mem、端口等。CAPSYSRESOURCE 覆盖配额限制。CAPWAKEALARM 设闹钟唤醒系统。CAPDACOVERRIDE 配合 CAPDACREADSEARCH 可修改任意文件(shockerwrite.patch 改写 /etc/motd 成功)。CAPIPCOWNER 用于 IPC 对象权限检查。网络 namespace 有效隔离(enumeratenetdevs 仅列出 lo)。CAPSYSPTRACE 通过查找 pid namespace 中的任务,只能影响同 namespace 进程。CAPKILL 信号发送也遵循 user namespace。CAPSETUID/SETGID 操纵进程凭证。CAPSYS_CHROOT 结合写相同文件系统上 suid 二进制可提权;chroot 本身可用 mkdir foo; chroot foo; cd .. 逃逸。Docker 和 LXC 曾出现解包漏洞(CVE-2014-6407 符号链接遍历提权,CVE-2015-3629 重新生成容器时挂载命名空间逃逸,CVE-2015-1331/1334 目录遍历和 fake proc 逃逸)。
seccomp 过滤:Docker 默认策略禁止一系列 syscall,如 sysctl、kexecload、pivotroot、nice、userfaultfd 等。selfsetuid.c 若允许 chmod 可给自己加 suid 位提权。TIOCSTI ioctl 可向终端输入缓冲注入字符逃逸沙箱(CVE-2016-7545)。内核 keyring 按 user namespace 隔离。ptrace 可修改 syscall 号绕过 seccomp,但 4.8 内核已修复为 seccomp 在 ptrace 后运行。userfaultfd 可暂停页错误助利用堆漏洞。perfeventopen 需 CAPSYSADMIN 或 perfeventparanoid<1,4.6 后默认 paranoid=2。PRSETNONEWPRIVS 确保 execve 不授予新特权,seccomp 过滤器跨 exec 继承。若干废弃 syscalls 如 create_module、bdflush 等已不存在或不做任何事。
cgroup namespaces 虚拟化 /proc/cgroup 视图。cpu、pids、blkio 控制器限制资源:pids 可防 forkbomb 无限 fork;blkio 权重限制 IO。oom killer 可能杀主机关键进程,badness 分数偏好长运行进程。同主机容器间可通过共享桥 ARP 欺骗(LXC bug 1548497)。
评论
社区围绕一篇2016年关于运行不可信代码的旧文展开轻量探讨,分歧在于容器是否足以作为安全边界;少数人重申容器甚至虚拟机均非牢靠边界,另有人疑问现代内核特性可能改变结论,同时有读者贡献了实操沙箱经验。
认为容器不应被视为安全边界
- 即便完整虚拟机也有多次逃逸先例,仅用容器隔离不可信代码远不够安全,亟需更根本的隔离思路(少数人)。 > 连完整的虚拟机都曾被多次逃逸,不该把容器当作安全边界,这种事实说明我们需要更好的方案(@ranger_danger)
探讨现代内核特性是否改变了原有结论
- 原文写于2016年,如今 cgroups v2 与更完善的 seccomp 等机制或许已显著提升限制能力,值得重新评估当时观点(少数人)。
有价值的补充
- kragen 以运行 Graphviz 处理不可信输入为例,用 Bubblewrap 编写不到500行沙箱配置,通过只读绑定、取消所有能力、限制地址空间与进程数等,将绘图库庞大且多发漏洞的依赖链(如 Pango、Harfbuzz)关进笼子。
- 提醒 Graphviz 在运行时才动态加载 Pango 插件,静态检查无法察觉,且字体处理已多线程化带来不确定性,沙箱需预留相应资源限制。
- 即便如此仍暴露大部分内核系统调用,且解析输出格式(如 libpng)亦有历史漏洞,需在沙箱内再做格式转换。
- 反直觉观察:绘制仅三个节点的关系图所需虚拟内存已超过早期整台 Linux 机器总内存,凸显依赖膨胀。