Linux Capabilities机制
时间:2022-10-22 09:30:00
介绍
传统的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 入门教程:概念篇