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

问题描述 投票:0回答:1

我目前正在开发一个项目,其中我有一个父进程,它设置一个套接字对、分叉,然后使用该套接字对进行通信。如果子级想要打开文件(或任何其他基于文件描述符的资源),则应始终转到父级,请求资源并获取通过套接字对发送的

fd
。此外,我想防止孩子自己打开任何文件描述符。

我偶然发现了

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

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

c linux system-calls
1个回答
8
投票

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

    seccomp_release(ctx);
}

现在,在您的程序中,您可以调用上述函数在

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)
    系统调用,该进程就能够应用进一步的过滤器。
© www.soinside.com 2019 - 2024. All rights reserved.