在设置了严格模式seccomp后,现在将EXIT_SUCCESS设置为Η。在main末尾调用syscall(SYS_exit, EXIT_SUCCESS);是正确的做法吗?
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/syscall.h>
int main(int argc, char **argv) {
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
//return EXIT_SUCCESS; // does not work
//_exit(EXIT_SUCCESS); // does not work
// syscall(__NR_exit, EXIT_SUCCESS); // (EDIT) This works! Is this the ultimate answer and the right way to exit success from seccomp-ed programs?
syscall(SYS_exit, EXIT_SUCCESS); // (EDIT) works; SYS_exit equals __NR_exit
}
// gcc seccomp.c -o seccomp && ./seccomp; echo "${?}" # I want 0发布于 2016-11-06 23:52:23
正如在eigenstate.org和SECCOMP (2)中所解释的
允许调用线程进行的唯一系统调用是读(2)、写(2)、_exit(2) (而不是 exit_group(2))和σ返回(2)。其他系统调用导致SIGKILL信号的传递。
因此,人们希望_exit()能够工作,但它是一个包装器函数,它调用不允许在严格模式下([1],[2])的exit_group(2),因此进程会被终止。
甚至在退出(2)- Linux手册页上也有报道
在glibc到版本2.3中,_exit()包装函数调用同名的内核系统调用。从glibc2.3开始,包装函数调用exit_group(2),以终止进程中的所有线程。
return语句也会发生同样的情况,它将以与_exit()非常相似的方式终止您的进程。
跟踪流程将提供更多的确认(为了让它显示出来,您必须不设置PR_SET_SECCOMP;只需注释prctl()),而对于这两个非工作情况,我得到了类似的输出:
linux12:/home/users/grad1459>gcc seccomp.c -o seccomp
linux12:/home/users/grad1459>strace ./seccomp
execve("./seccomp", ["./seccomp"], [/* 24 vars */]) = 0
brk(0) = 0x8784000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb775f000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=97472, ...}) = 0
mmap2(NULL, 97472, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7747000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1730024, ...}) = 0
mmap2(NULL, 1739484, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xdd0000
mmap2(0xf73000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a3) = 0xf73000
mmap2(0xf76000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf76000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7746000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7746900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xf73000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0x16e000, 4096, PROT_READ) = 0
munmap(0xb7747000, 97472) = 0
exit_group(0) = ?
linux12:/home/users/grad1459>正如您所看到的,exit_group()被调用,解释一切!
正如您正确指出的,"SYS_exit equals __NR_exit";例如,它是在mit.syscall.h中定义的
#define SYS_exit __NR_exit因此,最后两个调用是等价的,即您可以使用您喜欢的调用,输出应该如下:
linux12:/home/users/grad1459>gcc seccomp.c -o seccomp && ./seccomp ; echo "${?}"
0PS
当然,您可以自己定义一个filter并使用:
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter);正如在本征状态链接中所解释的,允许_exit() (或者严格地说,是exit_group(2)),但是只有当您真正需要并且知道您正在做什么时,才能这样做。
发布于 2016-11-08 22:38:48
出现这个问题是因为GNU库使用exit_group syscall (如果它在Linux中可用,而不是exit )用于_exit()函数(请参阅sysdeps/unix/sysv/linux/_exit.c进行验证),并且正如man 2 prctl中所记录的,严格的seccomp筛选器不允许使用exit_group syscall。
因为_exit()函数调用发生在C库中,所以我们不能用自己的版本(只执行exit syscall)插入它。(正常的进程清理是在其他地方完成的;在Linux中,_exit()函数只执行终止进程的最终syscall。)
我们可以要求GNU库开发人员只有在当前进程中有多个线程时才使用Linux,但不幸的是,这并不容易,即使现在添加,在大多数Linux发行版上使用该特性也需要相当长的时间。
幸运的是,我们可以放弃默认的严格过滤器,而定义自己的过滤器。行为上有一个小的区别:杀死这个过程的明显信号将从SIGKILL转变为SIGSYS。(信号并不是实际传递的,因为内核确实杀死了进程;只有导致进程死亡的明显信号号发生了变化。)
此外,这甚至没有那么困难。我确实花了一点时间研究GCC的一些宏观诡计,这会让管理允许的系统列表变得琐碎,但我认为这不是一个好方法:应该仔细考虑允许的系统列表--我们只在这里添加exit_group(),而不是严格的过滤器!--所以让它变得有点困难是可以的。
下面的代码(比如example.c )已经被验证可以在x8664( x86和x86-64,即32位和64位二进制文件)上运行4.4内核(应该在内核3.5或更高版本上工作)。但是,它应该在所有Linux体系结构上工作,而且它不需要或使用libseccomp库。
#define _GNU_SOURCE
#include <stdlib.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <stdio.h>
static const struct sock_filter strict_filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof (struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_rt_sigreturn, 5, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_read, 4, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_write, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_exit, 2, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_exit_group, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
};
static const struct sock_fprog strict = {
.len = (unsigned short)( sizeof strict_filter / sizeof strict_filter[0] ),
.filter = (struct sock_filter *)strict_filter
};
int main(void)
{
/* To be able to set a custom filter, we need to set the "no new privs" flag.
The Documentation/prctl/no_new_privs.txt file in the Linux kernel
recommends this exact form: */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
fprintf(stderr, "Cannot set no_new_privs: %m.\n");
return EXIT_FAILURE;
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &strict)) {
fprintf(stderr, "Cannot install seccomp filter: %m.\n");
return EXIT_FAILURE;
}
/* The seccomp filter is now active.
It differs from SECCOMP_SET_MODE_STRICT in two ways:
1. exit_group syscall is allowed; it just terminates the
process
2. Parent/reaper sees SIGSYS as the killing signal instead of
SIGKILL, if the process tries to do a syscall not in the
explicitly allowed list
*/
return EXIT_SUCCESS;
}编译使用。
gcc -Wall -O2 example.c -o example并使用
./example或者在strace下查看系统调用和库调用的完成;
strace ./examplestrict_filter BPF程序实际上是微不足道的。第一个操作码将syscall号加载到累加器中。接下来的五个操作码将其与可接受的syscall数字进行比较,如果找到,则跳转到允许syscall的最终操作码。否则,第二到最后的操作码将终止该进程。
请注意,尽管文档中提到sigreturn是允许的syscall,但Linux中syscall的实际名称是rt_sigreturn。(sigreturn在很久以前就被反对,转而支持rt_sigreturn。)
此外,安装过滤器时,操作码将被复制到内核内存(请参阅Linux中的kernel/seccomp.c ),因此如果以后修改数据,它不会以任何方式影响过滤器。换句话说,拥有static const结构的安全性影响是零的。
我使用了static,因为不需要在这个编译单元之外(或者在剥离的二进制文件中)看到符号,也不需要const将数据放入ELF二进制文件的只读数据部分。
BPF_JUMP(BPF_JMP | BPF_JEQ, nr, equals, differs)的形式很简单:将累加器( syscall数)与nr进行比较。如果它们相等,则跳过下一个equals操作码。否则,跳过下一个differs操作码。
由于等号跳转到最后的操作码,所以您可以在顶部添加新的操作码(即,在初始操作代码之后),增加每个操作的相等跳过计数。
请注意,在安装seccomp过滤器后,printf()将无法工作,因为在内部,C库希望执行fstat syscall (标准输出)和brk syscall来为缓冲区分配一些内存。
https://stackoverflow.com/questions/33150281
复制相似问题