linux capabilities 是线程级的权限属性,因为内核将其绑定到每个线程的 task_struct 而非整个进程;fork() 复制父线程能力,execve() 则依据文件 capability 和线程五组能力(permitted、effective、inheritable、bounding、ambient)共同决定新线程的能力集。

Linux Capabilities 是线程级的权限属性,不是进程级的——这意味着同一个进程内不同线程可以拥有完全不同的 capabilities。理解并正确管理这种线程粒度的控制,是避免权限泄露、实现最小权限原则的关键。
为什么 capabilities 是线程级的
Linux 内核将 capabilities 绑定到每个线程(task_struct),而非整个进程。fork() 创建子进程时会复制父线程的 capabilities;但 execve() 加载新程序时,capabilities 的继承规则由文件 capability 和线程当前的五个集合(Permitted、Effective、Inheritable、Bounding、Ambient)共同决定。多线程程序中,主线程调用 capset() 清理权限,不会自动影响已存在的工作线程——它们仍保有旧的有效权限,这就构成典型的风险点。
查看和修改线程的 capabilities
使用 capsh 可查看当前 shell 线程的能力集:
$ capsh --print
要查看指定线程(如 tid=1234)的能力,可读取其 proc 接口:
$ cat /proc/1234/status | grep Cap
修改当前线程 capabilities 需通过 libc 调用(如 cap_set_proc()),或在 C 程序中使用 libcap:
- 获取当前线程能力:cap_get_proc()
- 清除 Effective 中某能力:cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_CLEAR)
- 提交变更:cap_set_proc(caps)
注意:非特权线程只能操作自身 Effective 和 Ambient 集合中的已有能力,不能向 Permitted 添加新能力。
多线程场景下的权限安全实践
常见错误是仅在主线程中 drop capabilities,却忽略其他线程。正确做法包括:
- 在创建任何子线程前,先完成所有 capabilities 清理操作
- 若必须动态降权,使用 pthread_atfork() 注册清理函数,确保 fork 出的线程初始即无多余权限
- 对关键服务(如网络服务器),启用 NoNewPrivs 标志(prctl(PR_SET_NO_NEW_PRIVS, 1)),防止 execve 后重新获得权限
- 考虑使用 libpsx 等封装库,它会在 fork 或线程创建时自动同步 capabilities 到所有线程
文件 capabilities 与线程启动时的转换
当执行带文件 capabilities 的程序(如 setcap cap_net_bind_service=+ep /usr/bin/nginx)时,内核在 execve 过程中按规则初始化线程的五组能力:
- Permitted ← 文件 Permitted ∪ 当前线程 Bounding
- Effective ← 文件 Effective 位为 1 时,对应能力加入 Effective
- Inheritable ← 文件 Inheritable & 当前线程 Bounding
- Ambient ← 若文件 Effective=1 且 Inheritable=1,且当前线程 Ambient 允许,则自动加入 Ambient
- Bounding ← 继承自当前线程,execve 后不可扩展
因此,即使文件设置了 +ep,若线程的 Bounding 集合已移除该 capability,它也不会出现在新线程的 Permitted 或 Inheritable 中。











