为什么我们需要在调用 SECCOMP_MODE_FILTER 之前设置 no_new_privs ?

Kar*_*yan 5 linux security system-calls

手册页上,我阅读了以下行:

导致尝试使用 setuid(2) 将调用者的用户 ID 设置为非零值,而是返回 0 而不实际进行系统调用

我无法理解他们想说什么。任何人都可以向我解释这一点吗?

谢谢。

Dan*_*ver 6

首先,请注意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;
}
Run Code Online (Sandbox Code Playgroud)

在跟踪执行的同时编译并运行该程序表明,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
Run Code Online (Sandbox Code Playgroud)

好吧,现在我们了解了 seccomp 过滤器的功能。让我们更接近您在问题中引用的段落的要点。思考两件事:

  • 非特权进程可能会使用一些内核机制来提升其特权;
  • 有一些工具使用这种机制来临时授予特权,并且仅用于有限数量的操作。

典型的例子是sudo(8)实用程序,它是root拥有的 SUID 二进制文件。在非特权进程的上下文中执行它会授予调用进程完全的超级用户权力(除了一些特殊情况,如沙箱和容器),然后sudo(8)检查/etc/sudoers文件以了解是否允许调用用户执行请求的操作。如果是,则sudo(8)继续执行,如果不是,则执行被拒绝。此外,sudo(8)允许代表任意用户执行 - 在这种情况下,它将在执行请求的操作之前设置适当的凭据

例如,假设/etc/sudoers文件包含以下记录

testuser ALL=(anotheruser) NOPASSWD: /bin/bash
Run Code Online (Sandbox Code Playgroud)

用户testuser将能够anotheruser使用以下命令代表运行 shell :

sudo -u anotheruser -i /bin/bash
Run Code Online (Sandbox Code Playgroud)

现在,我们更接近这一点:如果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)被过滤器跳过并且没有真正执行!

因此,允许非特权用户安装 seccomp 过滤器也允许此用户通过setuid(2)临时以升级的权限运行时捕获调用来“劫持”特权凭据,因此在联机帮助页中指定了限制:

调用线程必须在其用户命名空间中具有 CAP_SYS_ADMIN 功能,或者线程必须已经设置了 no_new_privs 位。

很明显这句话的第一部分是什么意思:CAP_SYS_ADMIN是一种授予进程许多特权的能力,因此可以安全地假设拥有它的进程已经足够强大,足以在系统中造成严重破坏。第二部分呢?

no_new_privs位是进程的一个属性,如果设置,它会告诉内核不要使用像 SUID 位这样的特权升级机制(因此,调用类似的东西sudo(8)根本不起作用),因此允许无特权的进程使用该位是安全的设置为使用 seccomp 过滤器:这个过程即使是暂时的也不可能提升权限,因此,将无法“劫持”这些权限。