防止进程在 Linux 上打开新的文件描述符,但允许通过套接字接收文件描述符

jkl*_*mnn 9 c linux system-calls

我目前正在开发一个项目,在该项目中,我有一个父进程可以设置 socketpair、fork,然后使用这个 socketpair 进行通信。孩子,如果它想打开一个文件(或任何其他基于文件描述符的资源)应该总是去父,请求资源并fd通过套接字对发送。此外,我想防止孩子自己打开任何文件描述符。

我偶然发现setrlimit它成功地阻止了孩子打开新的文件描述符,但它似乎也使通过初始套接字连接发送的任何文件描述符无效。Linux 上是否有任何方法允许单个进程打开任何文件,将其文件描述符发送给其他进程并让它们使用它们,而不允许这些其他进程自己打开任何文件描述符?

对于我的用例,可以是任何内核配置、系统调用等,只要它可以在 fork 之后应用并且只要它适用于所有文件描述符(不仅是文件,还包括套接字、套接字对等)。

Mar*_*lli 6

您在这里拥有的正是seccomp的用例。

使用 seccomp,您可以以不同的方式过滤系统调用。在这种情况下,您想要做的是,紧接着fork()安装一个seccomp过滤器,禁止使用open(2)openat(2)socket(2)(以及更多)。为此,您可以执行以下操作:

  1. 首先,使用seccomp_init(3)的默认行为创建一个 seccomp 上下文SCMP_ACT_ALLOW
  2. 然后将规则添加到上下文中,seccomp_rule_add(3)用于您要拒绝的每个系统调用。SCMP_ACT_KILL如果系统调用被尝试,您可以使用来终止进程,SCMP_ACT_ERRNO(val)使系统调用失败,返回指定的errno值,或action手册页中定义的任何其他值。
  3. 使用加载上下文seccomp_load(3)以使其有效。

在继续之前,请注意,像这样的黑名单方法通常比白名单方法弱。它允许任何未明确禁止的系统调用,并可能导致绕过过滤器。如果您认为您要执行的子进程可能恶意地试图避开过滤器,或者如果您已经知道子进程需要哪些系统调用,那么白名单方法更好,您应该执行与上述相反的操作:使用默认操作创建过滤器SCMP_ACT_KILL并允许使用SCMP_ACT_ALLOW. 在代码方面差异很小(白名单可能更长,但步骤相同)。

这是上面的一个例子(exit(-1)为了简单起见,我这样做是为了防止出错):

#include <stdlib.h>
#include <seccomp.h>

static void secure(void) {
    int err;
    scmp_filter_ctx ctx;

    int blacklist[] = {
        SCMP_SYS(open),
        SCMP_SYS(openat),
        SCMP_SYS(creat),
        SCMP_SYS(socket),
        SCMP_SYS(open_by_handle_at),
        // ... possibly more ...
    };

    // Create a new seccomp context, allowing every syscall by default.
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (ctx == NULL)
        exit(-1);

    /* Now add a filter for each syscall that you want to disallow.
       In this case, we'll use SCMP_ACT_KILL to kill the process if it
       attempts to execute the specified syscall. */

    for (unsigned i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); i++) {
        err = seccomp_rule_add(ctx, SCMP_ACT_KILL, blacklist[i], 0);
        if (err)
            exit(-1);
    }

    // Load the context making it effective.
    err = seccomp_load(ctx);
    if (err)
        exit(-1);
}
Run Code Online (Sandbox Code Playgroud)

现在,在您的程序中,您可以调用上述函数在 之后立即应用 seccomp 过滤器fork(),如下所示:

child_pid = fork();
if (child_pid == -1)
    exit(-1);

if (child_pid == 0) {
    secure();

    // Child code here...

    exit(0);
} else {
    // Parent code here...
}
Run Code Online (Sandbox Code Playgroud)

关于 seccomp 的一些重要说明:

  • seccomp 过滤器一旦应用,就不能被该过程删除或更改。
  • 如果过滤器允许fork(2)clone(2),则任何子进程都将受到同一过滤器的约束。
  • 如果execve(2)允许,现有过滤器将在调用execve(2).
  • 如果prctl(2)允许系统调用,则该进程能够应用更多过滤器。

  • 沙箱黑名单?通常这是一个坏主意,您需要将其列入白名单。 (2认同)