#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;
}
$ 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(2)
手册页中的以下段落:因此,seccomp 过滤器可能会使内核跳过真正的系统调用执行,而是返回一些值来假装系统调用已执行并产生了指定的结果。如果返回值设置为零,这也包括成功的结果。
这是
libseccomp
基于示例的程序 (sec.c
),它显示了它是如何工作的(为简洁起见,省略了所有错误检查):在跟踪执行的同时编译和运行这个程序表明
openat(2)
系统调用返回零(即“执行”成功),尽管/non-existent-file
文件系统中不存在该文件:好了,现在我们了解了 seccomp 过滤器的功能。让我们更接近您在问题中引用的段落的要点。想两件事:
典型的例子是
sudo(8)
实用程序,它是root
–owned SUID 二进制文件。在非特权进程的上下文中执行它会授予调用进程完全的超级用户权限(除了沙盒和容器等一些特殊情况),然后sudo(8)
检查/etc/sudoers
文件以了解是否允许调用用户执行请求的操作。如果是,则sudo(8)
继续执行,如果不是,则拒绝执行。此外,sudo(8)
允许代表任意用户执行 - 在这种情况下,它将在执行请求的操作之前设置适当的凭据。例如,假设
/etc/sudoers
文件包含以下记录用户
testuser
将能够anotheruser
使用以下命令代表运行 shell:现在,我们更接近正题了:如果
testuser
–owned 进程安装了 seccomp 过滤器,这使得内核在setuid(2)
没有实际执行的情况下返回,然后运行sudo -u anotheruser -i /bin/bash
呢?好吧,走着瞧:sudo(8)
并适当地提升调用进程的权限;sudo(8)
检查/etc/sudoers
并确保testuser
允许运行/bin/bash
为anotheruser
;sudo(8)
调用setuid(2)
适当地更改进程的凭据并“降级”其权限;sudo(8)
相信该过程现在代表……anotheruser
执行并继续执行/bin/bash
……setuid(2)
被过滤器跳过并且没有真正执行!因此,允许非特权用户安装 seccomp 过滤器还允许该用户通过捕获调用来“劫持”特权凭据,
setuid(2)
同时以升级的权限临时运行,因此在手册页中指定了限制:很清楚这句话的第一部分是什么意思:
CAP_SYS_ADMIN
是一种授予进程大量特权的能力,因此可以安全地假设拥有它的进程已经足够强大,可以对系统造成严重破坏。第二部分呢?该
no_new_privs
位是进程的一个属性,如果设置,它会告诉内核不要使用像 SUID 位这样的特权升级机制(因此,调用类似的东西sudo(8)
根本不起作用),因此允许使用该位的非特权进程是安全的设置为使用 seccomp 过滤器:即使是暂时的,这个进程也没有任何可能提升权限,因此,将无法“劫持”这些权限。