bigywater.tech
Docker · 45

Linux capabilities:--cap-drop 之后能做什么、不能做什么

容器里的 root 到底有多大权限?--cap-drop 和 --cap-add 具体改变什么?为什么非 root 用户也能绑定 80 端口?这篇解码容器的 capability 集合,并逐个操作验证。

14 个
默认容器里 root 的 capability 个数(解码用的名字表里列了 41 个)
EPERM / EACCES
--cap-drop ALL 后 root 的 chown、ICMP raw socket、mknod 返回 EPERM,读别人的 0600 文件返回 EACCES
绑定 80 仍成功
uid 1000 即使 --cap-drop NET_BIND_SERVICE 也能绑定 80,因为 ip_unprivileged_port_start 默认已放开

默认的 14 个

实测 · 本机执行输出
  CapEff(14 个):chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap
  CapBnd(14 个):chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap

解码 /proc/self/status 的 CapEff:chown、dac_override、fowner、fsetid、kill、setgid、setuid、setpcap、net_bind_service、net_raw、sys_chroot、mknod、audit_write、setfcap。没有 sys_admin 这类危险的。

逐项验证

实测 · 本机执行输出
  默认:uid=0  chown 文件给 uid 1234:成功  绑定 80 端口:成功  创建 ICMP raw socket:成功  mknod 设备文件:成功  读别的用户 0600 的文件:成功
  --cap-drop ALL:uid=0  chown 文件给 uid 1234:EPERM  绑定 80 端口:成功  创建 ICMP raw socket:EPERM  mknod 设备文件:EPERM  读别的用户 0600 的文件:EACCES
  --cap-drop ALL --cap-add CHOWN:uid=0  chown 文件给 uid 1234:成功  绑定 80 端口:成功  创建 ICMP raw socket:EPERM  mknod 设备文件:EPERM  读别的用户 0600 的文件:EACCES
  --cap-drop NET_RAW:uid=0  chown 文件给 uid 1234:成功  绑定 80 端口:成功  创建 ICMP raw socket:EPERM  mknod 设备文件:成功  读别的用户 0600 的文件:成功
  --cap-drop MKNOD:uid=0  chown 文件给 uid 1234:成功  绑定 80 端口:成功  创建 ICMP raw socket:成功  mknod 设备文件:EPERM  读别的用户 0600 的文件:成功

每一项 capability 对应一类操作:丢掉 NET_RAW 只影响 ICMP raw socket,丢掉 MKNOD 只影响 mknod,--cap-drop ALL 后 root 连读别的用户的 0600 文件都不行(缺 DAC_OVERRIDE)。--cap-drop ALL 再 --cap-add 需要的那几个,是最小权限的做法;但应用实际要用到哪些,要自己试。

绑定 80 端口

实测 · 本机执行输出
  默认:uid=1000  chown 文件给 uid 1234:EPERM  绑定 80 端口:成功  创建 ICMP raw socket:EPERM  mknod 设备文件:EPERM  读别的用户 0600 的文件:EACCES
  --user 1000 --cap-drop NET_BIND_SERVICE:uid=1000  chown 文件给 uid 1234:EPERM  绑定 80 端口:成功  创建 ICMP raw socket:EPERM  mknod 设备文件:EPERM  读别的用户 0600 的文件:EACCES
  --user 1000 --sysctl net.ipv4.ip_unprivileged_port_start=1024:uid=1000  chown 文件给 uid 1234:EPERM  绑定 80 端口:EACCES  创建 ICMP raw socket:EPERM  mknod 设备文件:EPERM  读别的用户 0600 的文件:EACCES
  uid 1000 的 capability:CapEff(0 个): / CapBnd(14 个):chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap
  --cap-add SYS_ADMIN 后 CapEff 个数:CapEff(15 个)

普通用户默认就能绑定 80,即使丢掉 NET_BIND_SERVICE:Docker 在容器的网络命名空间里把 net.ipv4.ip_unprivileged_port_start 放开了(我没有读出它的默认值,但加 --sysctl net.ipv4.ip_unprivileged_port_start=1024 之后绑定 80 就是 EACCES,说明确实是这个参数在起作用)。

分步演示

Linux capability
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。capability 来自 /proc/self/status 的 CapEff / CapBnd(Python 里按位解码);操作用 Python 的 os.chown、socket.bind、socket.SOCK_RAW、os.mknod、open 实测,errno 取自异常;每种配置运行一次;实验后已删除容器和镜像
    • 没有验证:ambient / inheritable capability、文件 capability(setcap)、user namespace 里的 capability、各个 capability 的完整语义、seccomp 与 capability 同时限制的组合(见 c39)、Docker Desktop 以外的内核
    京ICP备2026067502号-1京公网安备11010502063487号