您在这里所拥有的正是seccomp 的用例。
使用 seccomp,您可以以不同的方式过滤系统调用。在这种情况下,您想要做的是,在fork() 之后安装一个seccomp 过滤器,该过滤器不允许使用open(2)、openat(2)、socket(2)(以及更多)。
为此,您可以执行以下操作:
- 首先,使用
seccomp_init(3) 创建一个seccomp 上下文,默认行为为SCMP_ACT_ALLOW。
- 然后为要拒绝的每个系统调用使用
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) 系统调用,则该进程能够应用更多过滤器。