Original link: Linux Getting Started Tutorial The Capabilities: The concept papers
Linux is a secure operating system, it is all the system privileges are given a single root, leaving only limited rights to ordinary users. the root user has super administrator privileges, you can install software that allows certain services, such as user management.
As an ordinary user, if you want to perform certain operations only administrators have rights, previously only two ways: first, through the sudo
elevation of privilege if a lot of users, configuration management and access control will be very troublesome; the second is through the SUID (the Set the User ID on execution) is implemented, it allows ordinary users to allow a owner
have root privileges when root executable file.
SUID
The concept of relatively obscure, give an example to understand, in the usual passwd
order, for example, modify the user password is required root privileges, but the ordinary user can change the password, but by this command, because this is /bin/passwd
being set up SUID
to identify, so ordinary user executes passwd command, owner passwd process is the owner, which is the root user.
SUID
Although the problem can be solved, but it has security implications. When running set SUID
when the command is usually just a small part of the required privileges, but SUID
gave all authority has its root. These executable files are the main target of hackers, if they found one of the loopholes, it is easy to use that for security attacks. In short, the SUID
mechanism increases the attack surface security system.
For more fine-grained control of root access on demand authorization, Linux introduces another mechanism called capabilities
.
What 1. Linux capabilities that?
Capabilities
Mechanism in the Linux kernel 2.2
introduced later, the principle is very simple, is before the super user root (UID = 0) privileges associated subdivided into different functional groups, Capabilites as a thread ( Linux does not really distinguish between processes and threads ) of property exists, each functional group can be independently enabled and disabled. Its essence is to classify kernel call, the kernel calls with similar functions are assigned to the same group.
As a result, the authority checking process becomes: when performing privileged operations, valid ID if the thread is not root, went to check whether it has the privilege corresponding to the operation capabilities, and as a basis, decide whether perform privileged operations.
Capabilities can be given in the process of implementation can be inherited from the parent process. So in theory, if given an executable file to nginx CAP_NET_BIND_SERVICE
capabilities, it can be running and listening on port 80 as a normal user.
capability name | description |
---|---|
CAP_AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules |
CAP_AUDIT_READ | It allows a socket to read the audit logs through multicast netlink |
CAP_AUDIT_WRITE | The audit log records into the kernel |
CAP_BLOCK_SUSPEND | Use feature can prevent the system hang |
CAP_CHOWN | Permission to modify the file owner |
CAP_DAC_OVERRIDE | DAC ignore file access restrictions |
CAP_DAC_READ_SEARCH | DAC ignore access restrictions and read files directory search |
CAP_FOWNER | Ignore file is a limit primary ID must match the user ID and the process of |
CAP_FSETID | Allowing the setuid bit set file |
CAP_IPC_LOCK | Allows the locking shared memory segments |
CAP_IPC_OWNER | Ignore IPC ownership checks |
CAP_KILL | Process is not allowed to send their own signal |
CAP_LEASE | It allows you to modify the file lock FL_LEASE logo |
CAP_LINUX_IMMUTABLE | It allows you to modify file attribute flags IMMUTABLE and APPEND |
CAP_MAC_ADMIN | Allow MAC configuration or state change |
CAP_MAC_OVERRIDE | DAC ignore file access restrictions |
CAP_MKNOD | Allows the use of mknod () system call |
CAP_NET_ADMIN | Allowed to perform network management tasks |
CAP_NET_BIND_SERVICE | Allowed to bind to the port less than 1024 |
CAP_NET_BROADCAST | Allows the network broadcast and multicast access |
CAP_NET_RAW | Allows the use of raw socket |
CAP_SETGID | GID allowed to change the course of |
CAP_SETFCAP | Allowing any of capabilities for the file |
CAP_SETPCAP | 参考 capabilities man page |
CAP_SETUID | Allow to change the course of UID |
CAP_SYS_ADMIN | Allowed to perform system administration tasks, such as loading or unloading the file system, set disk quotas, etc. |
CAP_SYS_BOOT | To allow a restart system |
CAP_SYS_CHROOT | Allows the use of chroot () system call |
CAP_SYS_MODULE | It allows you to insert and remove kernel modules |
CAP_SYS_NICE | Allow elevation priority and set the priority of other processes |
CAP_SYS_PACCT | Allow the implementation process of BSD audits |
CAP_SYS_PTRACE | Allows tracking of any process |
CAP_SYS_RAWIO | Allow direct access to / devport, / dev / mem, / dev / kmem and raw block devices |
CAP_SYS_RESOURCE | Ignore resource constraints |
CAP_SYS_TIME | Allow changing the system clock |
CAP_SYS_TTY_CONFIG | TTY device allows you to configure |
CAP_SYSLOG | Allows the use of syslog () system call |
CAP_WAKE_ALARM | Some can wake up the system allows you to trigger something (such as CLOCK_BOOTTIME_ALARM timer) |
2. capabilities of giving and inheritance
Linux capabilities 分为进程 capabilities 和文件 capabilities。对于进程来说,capabilities 是细分到线程的,即每个线程可以有自己的capabilities。对于文件来说,capabilities 保存在文件的扩展属性中。
下面分别介绍线程(进程)的 capabilities 和文件的 capabilities。
线程的 capabilities
每一个线程,具有 5 个 capabilities 集合,每一个集合使用 64
位掩码来表示,显示为 16
进制格式。这 5 个 capabilities 集合分别是:
- Permitted
- Effective
- Inheritable
- Bounding
- Ambient
每个集合中都包含零个或多个 capabilities。这5个集合的具体含义如下:
Permitted
定义了线程能够使用的 capabilities 的上限。它并不使能线程的 capabilities,而是作为一个规定。也就是说,线程可以通过系统调用 capset()
来从 Effective
或 Inheritable
集合中添加或删除 capability,前提是添加或删除的 capability 必须包含在 Permitted
集合中(其中 Bounding 集合也会有影响,具体参考下文)。 如果某个线程想向 Inheritable
集合中添加或删除 capability,首先它的 Effective
集合中得包含 CAP_SETPCAP
这个 capabiliy。
Effective
内核检查线程是否可以进行特权操作时,检查的对象便是 Effective
集合。如之前所说,Permitted
集合定义了上限,线程可以删除 Effective 集合中的某 capability,随后在需要时,再从 Permitted 集合中恢复该 capability,以此达到临时禁用 capability 的功能。
Inheritable
当执行exec()
系统调用时,能够被新的可执行文件继承的 capabilities,被包含在 Inheritable
集合中。这里需要说明一下,包含在该集合中的 capabilities 并不会自动继承给新的可执行文件,即不会添加到新线程的 Effective
集合中,它只会影响新线程的 Permitted
集合。
Bounding
Bounding
集合是 Inheritable
集合的超集,如果某个 capability 不在 Bounding
集合中,即使它在 Permitted
集合中,该线程也不能将该 capability 添加到它的 Inheritable
集合中。
Bounding 集合的 capabilities 在执行 fork()
系统调用时会传递给子进程的 Bounding 集合,并且在执行 execve
系统调用后保持不变。
- 当线程运行时,不能向 Bounding 集合中添加 capabilities。
- 一旦某个 capability 被从 Bounding 集合中删除,便不能再添加回来。
- 将某个 capability 从 Bounding 集合中删除后,如果之前
Inherited
集合包含该 capability,将继续保留。但如果后续从Inheritable
集合中删除了该 capability,便不能再添加回来。
Ambient
Linux 4.3
内核新增了一个 capabilities 集合叫 Ambient
,用来弥补 Inheritable
的不足。Ambient
具有如下特性:
Permitted
和Inheritable
未设置的 capabilities,Ambient
也不能设置。- 当
Permitted
和Inheritable
关闭某权限(比如CAP_SYS_BOOT
)后,Ambient
也随之关闭对应权限。这样就确保了降低权限后子进程也会降低权限。 - 非特权用户如果在
Permitted
集合中有一个 capability,那么可以添加到Ambient
集合中,这样它的子进程便可以在Ambient
、Permitted
和Effective
集合中获取这个 capability。现在不知道为什么也没关系,后面会通过具体的公式来告诉你。
Ambient
的好处显而易见,举个例子,如果你将 CAP_NET_ADMIN
添加到当前进程的 Ambient
集合中,它便可以通过 fork()
和 execve()
调用 shell 脚本来执行网络管理任务,因为 CAP_NET_ADMIN
会自动继承下去。
文件的 capabilities
文件的 capabilities 被保存在文件的扩展属性中。如果想修改这些属性,需要具有 CAP_SETFCAP
的 capability。文件与线程的 capabilities 共同决定了通过 execve()
运行该文件后的线程的 capabilities。
文件的 capabilities 功能,需要文件系统的支持。如果文件系统使用了 nouuid
选项进行挂载,那么文件的 capabilities 将会被忽略。
类似于线程的 capabilities,文件的 capabilities 包含了 3 个集合:
- Permitted
- Inheritable
- Effective
这3个集合的具体含义如下:
Permitted
这个集合中包含的 capabilities,在文件被执行时,会与线程的 Bounding 集合计算交集,然后添加到线程的 Permitted
集合中。
Inheritable
这个集合与线程的 Inheritable
集合的交集,会被添加到执行完 execve()
后的线程的 Permitted
集合中。
Effective
这不是一个集合,仅仅是一个标志位。如果设置开启,那么在执行完 execve()
后,线程 Permitted
集合中的 capabilities 会自动添加到它的 Effective
集合中。对于一些旧的可执行文件,由于其不会调用 capabilities 相关函数设置自身的 Effective
集合,所以可以将可执行文件的 Effective bit 开启,从而可以将 Permitted
集合中的 capabilities 自动添加到 Effective
集合中。
详情请参考 Linux capabilities 的 man page。
3. 运行 execve() 后 capabilities 的变化
上面介绍了线程和文件的 capabilities,你们可能会觉得有些抽象难懂。下面通过具体的计算公式,来说明执行 execve()
后 capabilities 是如何被确定的。
我们用 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。
关于上述计算公式的逻辑流程图如下所示(不包括 Ambient
集合):
4. 简单示例
下面我们用一个例子来演示上述公式的计算逻辑,以 ping
文件为例。如果我们将 CAP_NET_RAW
capability添加到 ping 文件的 Permitted
集合中(F(Permitted)),它就会添加到执行后的线程的 Permitted
集合中(P'(Permitted))。由于 ping 文件具有 capabilities 意识,即能够调用 capset()
和 capget()
,它在运行时会调用 capset()
将 CAP_NET_RAW
capability 添加到线程的 Effective
集合中。
换句话说,如果可执行文件不具有 capabilities 意识,我们就必须要开启 Effective 标志位(F(Effective)),这样就会将该 capability 自动添加到线程的 Effective
集合中。具有capabilities 意识的可执行文件更安全,因为它会限制线程使用该 capability 的时间。
我们也可以将 capabilities 添加到文件的 Inheritable
集合中,文件的 Inheritable
集合会与当前线程的 Inheritable
集合取交集,然后添加到新线程的 Permitted
集合中。这样就可以控制可执行文件的运行环境。
看起来很有道理,但有一个问题:如果可执行文件的有效用户是普通用户,且没有 Inheritable
集合,即 F(inheritable) = 0
,那么 P(inheritable)
将会被忽略(P(inheritable) & F(inheritable))。由于绝大多数可执行文件都是这种情况,因此 Inheritable
集合的可用性受到了限制。我们无法让脚本中的线程自动继承该脚本文件中的 capabilities,除非让脚本具有 capabilities 意识。
要想改变这种状况,可以使用 Ambient
集合。Ambient
集合会自动从父线程中继承,同时会自动添加到当前线程的 Permitted
集合中。举个例子,在一个 Bash 环境中(例如某个正在执行的脚本),该环境所在的线程的 Ambient
集合中包含 CAP_NET_RAW
capability,那么在该环境中执行 ping 文件可以正常工作,即使该文件是普通文件(没有任何 capabilities,也没有设置 SUID)。
5. 终极案例
最后拿 docker 举例,如果你使用普通用户来启动官方的 nginx 容器,会出现以下错误:
bind() to 0.0.0.0:80 failed (13: Permission denied)
因为 nginx 进程的 Effective
集合中不包含 CAP_NET_BIND_SERVICE
capability,且不具有 capabilities 意识(普通用户),所以启动失败。要想启动成功,至少需要将该 capability 添加到 nginx 文件的 Inheritable
集合中,同时开启 Effective 标志位,并且在 Kubernetes Pod 的部署清单中的 securityContext --> capabilities 字段下面添加 NET_BIND_SERVICE
(这个 capability 会被添加到 nginx 进程的 Bounding
集合中),最后还要将 capability 添加到 nginx 文件的 Permitted
集合中。如此一来就大功告成了,参考公式:P'(permitted) = ...|(F(permitted) & P(bounding)))|...
和 P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
。
如果容器开启了 securityContext/allowPrivilegeEscalation
,上述设置仍然可以生效。如果 nginx 文件具有 capabilities 意识,那么只需要将 CAP_NET_BIND_SERVICE
capability 添加到它的 Inheritable
集合中就可以正常工作了。
当然了,除了上述使用文件扩展属性的方法外,还可以使用 Ambient
集合来让非 root 容器进程正常工作,但 Kubernetes 目前还不支持这个属性,具体参考 Kubernetes 项目的 issue。
虽然 Kubernetes 官方不支持,但我们可以自己来实现,具体实现方式可以关注我后续的文章。
6. 参考资料
- Linux Capabilities: Why They Exist and How They Work
- Understanding Capabilities in Linux
- Linux Capabilities in a nutshell
- Linux的capabilities机制
微信公众号
扫一扫下面的二维码关注微信公众号,在公众号中回复◉加群◉即可加入我们的云原生交流群,和孙宏亮、张馆长、阳明等大佬一起探讨云原生技术