介绍
传统的Linux权限控制粒度过粗passwd以命令为例,需要修改用户密码root权限,但普通用户应该能够修改自己的密码Linux就使用了SUID、EUID机制,使passwd过程以其所有者为基础root这样,权限运行就可以了root修改密码的权限
SUID机制存在安全隐患,passwd过程只需修改密码,但在整个运行周期中获得root权限一旦出现漏洞,很可能会被利用
所以,Linux内核在2.2后引入了Capabilities机制,细粒度化权限控制,可按需授权
这是文档:https://man7.org/linux/man-pages/man7/capabilities.7.html
如何使用
首先,Capabilities有一个集合的概念,即一个过程或可执行文件,它能拥有什么特权?
可执行文件
有三种可执行文件Capabilities集合:
Permitted
当文件执行时,该集合的内容将添加到过程中Permitted集合中
Inheritable
当文件执行后,这个集合会与进程的Inheritable集合位置和操作(&),确定执行过程execve函数后哪些capabilites可以被继承
Effective
这不是一个集合,而是一个位置(bit),如果此bit设为1,则Permitted集中新增capabilites会在执行execve将函数添加到过程中Effective集合中
命令
- 设置
capabilites
setcap [capability,capability,...] [ep] [文件] # or setcap [capability ep capability ep ...] [文件] capability是特权值, ep代表加入Effective和Permitted集合中
- 获取
capabilites
getcap [文件]
线程(进程)
有五种线程(进程)Capabilities集合:
Permitted
这一集定义了线程所能拥有的特权的上限Inheritable和Effective集合超集
Inheritable
包括当执行execve 函数可以由新的可执行文件继承capabilities(执行execve 添加函数后Permitted集合中)
Effective
内核检查特权操作时,实际检查的集合(可在执行操作前增加/删除)Effective中的capabilities,实现临时开/关权限的功能)
Bounding (内核2.6.25以后)
这个集合是Inheritable如果某个集合的超集,capability不在Bounding即使它在集合中Permitted集线程不能集中capability添加到它的Inheritable集合,集合execve以后不能添加capabilities
Ambient (内核4.3以后)
这个集合是Permitted和Inheritable的子集,当Permitted和Inheritable删除某个capability当集合中对应的对应对应也会自动删除capability,子过程将自动继承该集合capabilities,子进程的Permitted、Effective和Ambient都会有这些capabilities
函数
capset
原型:
int capset(cap_user_header_t hdrp, const cap_user_data_t datap); 文档:https://linux.die.net/man/2/capset
capget
原型:
int capget(cap_user_header_t hdrp , cap_user_data_t datap); 文档:https://linux.die.net/man/2/capget
结构体
typedef struct __user_cap_heaer_struct {
__u32 version;
int pid;
} *cap_user_header_t;
typedef struct __user_cap_data_struct {
__u32 effective;
__u32 permitted;
__u32 inheritable;
} *cap_user_data_t;
计算公式
我们用 P 代表执行 execve() 前线程的 capabilities,P' 代表执行 execve() 后线程的 capabilities,F 代表可执行文件的 capabilities,那么:
P’(ambient) = (file is privileged) ? 0 : P(ambient)
P’(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding))) | P’(ambient)
P’(effective) = F(effective) ? P’(permitted) : P’(ambient)
P’(inheritable) = P(inheritable) [i.e., unchanged]
P’(bounding) = P(bounding) [i.e., unchanged]
我们一条一条来解释:
-
如果用户是 root 用户,那么执行
execve()后线程的Ambient集合是空集;如果是普通用户,那么执行execve()后线程的Ambient集合将会继承执行execve()前线程的Ambient集合。 -
执行
execve()前线程的Inheritable集合与可执行文件的Inheritable集合取交集,会被添加到执行execve()后线程的Permitted集合;可执行文件的 capability bounding 集合与可执行文件的Permitted集合取交集,也会被添加到执行execve()后线程的Permitted集合;同时执行execve()后线程的Ambient集合中的 capabilities 会被自动添加到该线程的Permitted集合中。 -
如果可执行文件开启了 Effective 标志位,那么在执行完
execve()后,线程Permitted集合中的 capabilities 会自动添加到它的Effective集合中。 -
执行
execve()前线程的Inheritable集合会继承给执行execve()后线程的Inheritable集合。
这里有几点需要着重强调:
-
上面的公式是针对系统调用
execve()的,如果是fork(),那么子线程的 capabilities 信息完全复制父进程的 capabilities 信息。 -
可执行文件的
Inheritable集合与线程的Inheritable集合并没有什么关系,可执行文件Inheritable集合中的 capabilities 不会被添加到执行execve()后线程的Inheritable集合中。如果想让新线程的Inheritable集合包含某个 capability,只能通过capset()将该 capability 添加到当前线程的Inheritable集合中(因为 P’(inheritable) = P(inheritable))。 -
如果想让当前线程
Inheritable集合中的 capabilities 传递给新的可执行文件,该文件的Inheritable集合中也必须包含这些 capabilities(因为 P’(permitted) = (P(inheritable) & F(inheritable))|…)。 -
将当前线程的 capabilities 传递给新的可执行文件时,仅仅只是传递给新线程的
Permitted集合。如果想让其生效,新线程必须通过capset()将 capabilities 添加到Effective集合中。或者开启新的可执行文件的 Effective 标志位(因为 P’(effective) = F(effective) ? P’(permitted) : P’(ambient))。 -
在没有
Ambient集合之前,如果某个脚本不能调用capset(),但想让脚本中的线程都能获得该脚本的Permitted集合中的 capabilities,只能将Permitted集合中的 capabilities 添加到Inheritable集合中(P’(permitted) = P(inheritable) & F(inheritable)|…),同时开启 Effective 标志位(P’(effective) = F(effective) ? P’(permitted) : P’(ambient))。有 有Ambient集合之后,事情就变得简单多了,后续的文章会详细解释。 -
如果某个 UID 非零(普通用户)的线程执行了
execve(),那么Permitted和Effective集合中的 capabilities 都会被清空。 -
从 root 用户切换到普通用户,那么
Permitted和Effective集合中的 capabilities 都会被清空,除非设置了 SECBIT_KEEP_CAPS 或者更宽泛的 SECBIT_NO_SETUID_FIXUP。
附录
Capabilities表
| Capability | 描述 |
|---|---|
| CAP_AUDIT_CONTROL | 启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则 |
| CAP_AUDIT_READ | 允许通过 multicast netlink 套接字读取审计日志 |
| CAP_AUDIT_WRITE | 将记录写入内核审计日志 |
| CAP_BLOCK_SUSPEND | 使用可以阻止系统挂起的特性 |
| CAP_BPF (5.8) | 从CAP_SYS_ADMIN分离一部分BFP功能,控制了一些BPF特定的操作,包括创建BPF maps、使用一些高级的BPF程序功能、访问BPF type format(BTF)数据等 |
| CAP_CHECKPOINT_RESTORE (5.9) | 允许更新/proc/sys/kernel/ns_last_pid,使用set_tid特性,读其他进程的/proc/[pid]/map_files |
| CAP_CHOWN | 修改文件所有者的权限 |
| CAP_DAC_OVERRIDE | 忽略文件的 DAC 访问限制 |
| CAP_DAC_READ_SEARCH | 忽略文件读及目录搜索的 DAC 访问限制 |
| CAP_FOWNER | 忽略文件属主 ID 必须和进程用户 ID 相匹配的限制 |
| CAP_FSETID | 允许设置文件的 setuid 位 |
| CAP_IPC_LOCK | 允许锁定共享内存片段 |
| CAP_IPC_OWNER | 忽略 IPC 所有权检查 |
| CAP_KILL | 允许对不属于自己的进程发送信号 |
| CAP_LEASE | 允许修改文件锁的 FL_LEASE 标志 |
| CAP_LINUX_IMMUTABLE | 允许修改文件的 IMMUTABLE 和 APPEND 属性标志 |
| CAP_MAC_ADMIN | 允许 MAC 配置或状态更改 |
| CAP_MAC_OVERRIDE | 忽略文件的 DAC 访问限制 |
| CAP_MKNOD | 允许使用 mknod() 系统调用 |
| CAP_NET_ADMIN | 允许执行网络管理任务 |
| CAP_NET_BIND_SERVICE | 允许绑定到小于 1024 的端口 |
| CAP_NET_BROADCAST | 允许网络广播和多播访问 |
| CAP_NET_RAW | 允许使用原始套接字 |
| CAP_PERFMON (5.8) | 管理性能监控task |
| CAP_SETGID | 允许改变进程的 GID |
| CAP_SETFCAP | 允许为文件设置任意的 capabilities |
| CAP_SETPCAP | 允许设置其他进程的 capabilities |
| CAP_SETUID | 允许改变进程的 UID |
| CAP_SYS_ADMIN | 允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等 |
| CAP_SYS_BOOT | 允许重新启动系统 |
| CAP_SYS_CHROOT | 允许使用 chroot() 系统调用 |
| CAP_SYS_MODULE | 允许插入和删除内核模块 |
| CAP_SYS_NICE | 允许提升优先级及设置其他进程的优先级 |
| CAP_SYS_PACCT | 允许执行进程的 BSD 式审计 |
| CAP_SYS_PTRACE | 允许跟踪任何进程 |
| CAP_SYS_RAWIO | 允许直接访问 /devport、/dev/mem、/dev/kmem 及原始块设备 |
| CAP_SYS_RESOURCE | 忽略资源限制 |
| CAP_SYS_TIME | 允许改变系统时钟 |
| CAP_SYS_TTY_CONFIG | 允许配置 TTY 设备 |
| CAP_SYSLOG | 允许使用 syslog() 系统调用 |
| CAP_WAKE_ALARM | 允许触发一些能唤醒系统的东西(比如 CLOCK_BOOTTIME_ALARM 计时器) |
参考文献
Linux Capabilities 简介
Linux的capabilities机制
Linux Capabilities 入门教程:概念篇