【问题标题】:Prevent process from opening new file descriptor on Linux but allow receiving file descriptors via sockets阻止进程在 Linux 上打开新文件描述符,但允许通过套接字接收文件描述符
【发布时间】:2020-05-01 00:07:11
【问题描述】:

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

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

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

【问题讨论】:

  • 您可能对 seccomp 感兴趣。

标签: c linux system-calls


【解决方案1】:

您在这里所拥有的正是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);
}

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

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) 系统调用,则该进程能够应用更多过滤器。

【讨论】:

  • 沙盒黑名单?通常是个坏主意,您需要加入白名单。
  • @Deduplicator 我知道,但是白名单方法不适用于 OP 的情况,因为他们只想禁止打开新的文件描述符。我会在最后添加一个注释。
  • 感谢您的回答,这正是我所需要的。对于我最初打算的应用程序,白名单确实更好。我只是没有想到,除了打开文件描述符之外,还有更多的事情需要限制。
  • @jklmnn 是的,没错。我确实忘记了socket(2) 也可以创建fd,所以也应该阻止它。如果您知道子进程,白名单方法会更好。
  • @MarcoBonelli 白名单肯定更好。副手,creat()dup()dup2() 都是返回文件描述符的 Linux 系统调用。有很多方法可以绕过黑名单...
猜你喜欢
  • 1970-01-01
  • 2015-03-16
  • 2014-04-25
  • 2012-11-02
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
  • 2011-03-31
  • 1970-01-01
相关资源
最近更新 更多