您在这里拥有的正是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)
进行syscall ,则可以用来终止进程,使syscall无法返回指定errno
值或action
手册页中定义的任何其他值。
- 使用加载上下文
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,则该过程能够应用其他过滤器。