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


9

我目前在一个项目中,我有一个父进程来设置套接字对,派生然后使用此套接字对进行通信。子级如果要打开文件(或任何其他基于文件描述符的资源),应始终转到父级,请求资源并fd通过套接字对获取发送。此外,我想防止孩子自己打开任何文件描述符。

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

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


1
您可能对seccomp感兴趣。
user253751

Answers:


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)进行syscall ,则可以用来终止进程,使syscall无法返回指定errno值或action手册页中定义的任何其他值。
  3. 使用加载上下文seccomp_load(3)以使其生效。

在继续之前,请注意,像这样的黑名单方法通常比白名单方法要弱。它允许任何未明确禁止的系统调用,并且可能导致filter的绕过。如果您认为要执行的子进程可能是恶意地试图避开过滤器,或者如果您已经知道子进程将需要哪些系统调用,则白名单方法会更好,您应该执行与上述相反的操作:使用默认动作创建过滤器,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);
}

现在,在您的程序中,您可以调用上面的函数,在之后紧接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...
}

关于seccomp的一些重要说明:

  • 一旦应用seccomp过滤器,该过程将无法删除或更改它。
  • 如果fork(2)还是clone(2)由过滤器允许的,所有子进程将使用相同的过滤器来限制。
  • 如果execve(2)允许,则在调用时将保留现有过滤器execve(2)
  • 如果prctl(2)允许syscall,则该过程能够应用其他过滤器。

2
将沙箱列入黑名单?通常,这是一个坏主意,您希望将其列入白名单。
重复数据删除器

我知道@Deduplicator,但是白名单方法不适用于OP的情况,因为他们只想禁止打开新的文件描述符。我将在末尾添加注释。
Marco Bonelli

感谢您的回答,这就是我所需要的。对于我最初打算的应用程序,白名单确实更好。我只是没有想到,除了打开文件描述符之外,还有更多应该限制的事情。
jklmnn

@jklmnn是的,完全是。我确实只是忘了也socket(2)可以创建一个fd,因此也应该将其阻止。如果您知道子进程,则白名单方法会更好。
Marco Bonelli

@MarcoBonelli白名单肯定更好。随口说说,creat()dup(),并dup2()是所有的Linux系统调用返回的文件描述符。黑名单周围有很多方法……
Andrew Henle
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.