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

标签 c linux system-calls

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

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

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

最佳答案

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

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

  • 首先,使用 seccomp_init(3) 的默认行为SCMP_ACT_ALLOW创建一个seccomp上下文。
  • 然后,使用 seccomp_rule_add(3) 为要拒绝的每个系统调用向上下文添加规则。如果尝试进行系统调用,则可以使用SCMP_ACT_KILL终止进程,使用SCMP_ACT_ERRNO(val)来使系统调用无法返回指定的errno值,或者使用手册页中定义的任何其他action值。
  • 使用 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) syscall,则该进程可以应用其他过滤器。
  • 关于c - 防止进程在Linux上打开新的文件描述符,但允许通过套接字接收文件描述符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59730828/

    相关文章:

    c - 使用 C 中的 openssl 库进行简单的 AES 加密解密

    linux - 创建一个显示程序输出的假文件

    regex - bash - 如何从 bash 脚本中将换行符传递给 perl?

    linux - Apache Karaf 在 Intel 主板上运行速度较慢

    c - strace 中的 set_thread_area

    C:如何将多位数字分解成单独的变量?

    go - 在 MacOS 上使用 Golang 创建容器 - syscall.CLONE_NEWUTS 不起作用

    c - Waitpid 的行为就像处于非阻塞模式

    c++ - System() 调用返回 255,但随后执行

    c - C中的中止陷阱6错误