答案1
首先,请注意seccomp(2)
联机帮助页中的以下段落:
SECCOMP_RET_ERRNO
该值导致过滤器返回值的 SECCOMP_RET_DATA 部分作为 errno 值传递到用户空间,而不执行系统调用。
因此,seccomp 过滤器可能会使内核跳过真正的系统调用执行而不是返回一些值到假装系统调用已执行并产生指定的结果。这也涵盖了成功的结果,如果返回值设置为零。
下面是libseccomp
基于示例的程序 ( sec.c
),它展示了其工作原理(为简洁起见,省略了所有错误检查):
#include <stdio.h>
#include <seccomp.h>
int main() {
scmp_filter_ctx seccomp;
seccomp = seccomp_init(SCMP_ACT_ALLOW);
// Make the `openat(2)` syscall always "succeed".
seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(0), SCMP_SYS(openat), 0);
// Install the filter.
seccomp_load(seccomp);
FILE *file = fopen("/non-existent-file", "r");
// Do something with the file and then perform the cleanup.
// <...>
return 0;
}
编译并运行该程序,同时跟踪执行情况,显示openat(2)
系统调用返回零(即“执行”成功),尽管/non-existent-file
文件系统中不存在该文件:
$ gcc sec.c -lseccomp -o sec
# Run the program showing the openat(2) invocations and their results.
$ strace -e trace=openat ./sec
...
openat(AT_FDCWD, "/non-existent-file", O_RDONLY) = 0
...
# Ensure that the file does not exist.
$ stat /non-existent-file
stat: cannot stat '/non-existent-file': No such file or directory
好了,现在我们了解了 seccomp 过滤器的功能。让我们更接近您在问题中引用的段落的要点。想想两件事:
- 非特权进程可能会使用一些内核机制来提升其特权;
- 有一些工具采用这种机制来授予权限暂时地并且仅适用于有限数量的操作。
典型的例子是sudo(8)
实用程序,它是root
-owned SUID 二进制文件。在非特权进程的上下文中执行它会授予调用进程完全的超级用户权限(除了一些特殊情况,如沙箱和容器),然后sudo(8)
检查/etc/sudoers
文件以了解是否允许调用用户执行请求的操作。如果是,sudo(8)
则继续执行,如果不是,则拒绝执行。还,sudo(8)
允许代表任意用户执行 - 在这种情况下,它将在执行请求的操作之前设置适当的凭据。
例如,假设/etc/sudoers
文件包含以下记录
testuser ALL=(anotheruser) NOPASSWD: /bin/bash
用户testuser
将能够anotheruser
使用以下命令代表运行 shell:
sudo -u anotheruser -i /bin/bash
现在,我们更接近主题了:如果testuser
–owned 进程安装了 seccomp 过滤器,这使得内核在setuid(2)
没有实际执行它的情况下返回,然后运行sudo -u anotheruser -i /bin/bash
?好吧,走着瞧:
- 内核看到
sudo(8)
二进制文件上的 SUID 位并适当提升调用进程的权限; sudo(8)
检查/etc/sudoers
并确保testuser
允许/bin/bash
运行anotheruser
;- 然后
sudo(8)
调用setuid(2)
适当更改进程的凭据并“降级”其权限; - 用户安装的 seccomp 过滤器默默地报告成功,假装“降级”已完成;
sudo(8)
相信该进程现在代表anotheruser
并继续执行/bin/bash
......- 实际上以超级用户权限运行它,因为
setuid(2)
被过滤器跳过并且没有真正执行!
setuid(2)
因此,允许非特权用户安装 seccomp 过滤器还允许该用户通过捕获对while 的调用来“劫持”特权凭据暂时地以升级的权限运行,因此在联机帮助页中指定了限制:
调用线程必须在其用户命名空间中具有 CAP_SYS_ADMIN 功能,或者线程必须已经设置了 no_new_privs 位。
这句话的第一部分的含义很清楚:CAP_SYS_ADMIN
是一种授予进程大量特权的能力,因此可以安全地假设拥有它的进程已经强大到足以对系统造成严重破坏。第二部分呢?
该no_new_privs
位是进程的一个属性,如果设置,则告诉内核不是采用像 SUID 位这样的特权升级机制(因此,调用类似的东西sudo(8)
根本不起作用),因此允许设置此位的非特权进程使用 seccomp 过滤器是安全的:该进程即使暂时也不可能升级特权因此,将无法“劫持”这些特权。