传统的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集合中
当文件执行后,这个集合会与进程的Inheritable
集合做位与操作(&),以确定进程在执行execve
函数后哪些capabilites
可以被继承
这不是一个集合,而是一个位(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集合:
这个集合定义了线程所能够拥有的特权的上限,是Inheritable
和Effective
集合的的超集
包含了当执行execve
函数时,能够被新的可执行文件继承的capabilities
(执行execve
函数后会被添加到Permitted
集合中)
内核检查特权操作时,实际检查的集合(可以通过执行操作前增/删Effective
中的capabilities
,以达到临时开/关权限的功能)
这个集合是 Inheritable
集合的超集,如果某个capability
不在Bounding
集合中,即使它在Permitted
集合中,该线程也不能将该capability
添加到它的Inheritable
集合中,该集合在execve
后不可再添加capabilities
这个集合是Permitted
和Inheritable
的子集,当Permitted
和Inheritable
删除某个capability
时,也会自动删除该集合中对应的capability
,子进程会自动继承这个集合中的capabilities
,子进程的Permitted
、Effective
和Ambient
都会拥有这些capabilities
原型:
int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
文档:https://linux.die.net/man/2/capset
原型:
int capget(cap_user_header_t hdrp , cap_user_data_t datap);
文档:https://linux.die.net/man/2/capget
typedef struct __user_cap_header_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。
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 入门教程:概念篇