本文详细介绍了关于seccomp的相关概念,包括seccomp的发展历史、Seccomp BPF的实现原理以及与seccomp相关的一些工具等。此外,通过实例验证了如何使用seccomp bpf 来保护Docker的安全。
seccomp(全称securecomputing mode)是linux kernel支持的一种安全机制。在Linux系统里,大量的系统调用(systemcall)直接暴露给用户态程序。但是,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。通过seccomp,我们限制程序使用某些系统调用,这样可以减少系统的暴露面,同时是程序进入一种“安全”的状态。
2005年,Linux 2.6.12中的引入了第一个版本的seccomp,通过向/proc/PID/seccomp
接口中写入“1”来启用过滤器,最初只有一个模式:严格模式(strict mode),该模式下只允许被限制的进程使用4种系统调用:read() , write() , _exit() , 和 sigreturn() ,需要注意的是,open()
系统调用也是被禁止的,这就意味着在进入严格模式之前必须先打开文件。一旦为程序施加了严格模式的seccomp,对于其他的所有系统调用的调用,都会触发SIGKILL
并立即终止进程。
2007年,Linux 2.6.23 内核使用prctl()
操作代替了/proc/PID/seccomp
接口来施加seccomp,通过Prctl (PR_SET_SECCOMP,arg)
修改调用者的seccomp模式;prctl(PR_GET_SECCOMP)
用来获取seccomp的状态,返回值为0时代表进程没有被施加seccomp,但是如果进程配置了seccomp,则会由于不能调用prctl()
导致进程中止,那就没有其他返回值了??
2012年,Linux 3.5引入了”seccomp mode 2“,为seccomp带来了一种新的模式:过滤模式( filter mode ), 该模式使用 Berkeley 包过滤器 (BPF) 程序过滤任意系统调用及其参数,使用该模式,进程可以使用 prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)
来指定允许哪些系统调用。 现在已经有许多应用使用 seccomp 过滤器来对系统调用进行控制,包括 Chrome/Chromium 浏览器、OpenSSH、vsftpd 和 Firefox OS 。
2013年,Linux 3.8版本,在/proc/PID/status
中添加了一个Seccomp字段, 可以通过读取该文件获取对应进程的 seccomp 模式的状态(0 表示禁用,1 表示严格,2 表示过滤)。
2014年,Linux 3.17 引入了seccomp()
系统调用,seccomp()
在prctl()
的基础上提供了现有功能的超集, 增加了将进程中的所有线程同步到同一组过滤器的能力,这有助于确保即使在施加seccomp过滤器之前创建的线程仍然有效。
seccomp 过滤模式允许开发人员编写 BPF 程序来确定是否允许给定的系统调用,基于系统调用号和参数(寄存器)值进行过滤。当使用seccomp()
或prctl()
对进程施加seccomp 时,需要提前将编写好的BPF程序安装到内核,之后每次系统调用都会经过该过滤器。而且此过程是不可逆的, 因为安装过滤器实际上是声明任何后续执行的代码都不可信。
BPF在1992年的tcpdump程序中首次提出,tcpdump是一个网络数据包的监控工具, 但是由于数据包的数量很大,而且将内核空间捕获到的数据包传输到用户空间会带来很多不必要的性能损耗,所以要对数据包进行过滤,只保留感兴趣的那一部分,而在内核中过滤感兴趣的数据包比在用户空间中进行过滤更有效。BPF 就是提供了一种进行内核过滤的方法,因此用户空间只需要处理经过内核过滤的后感兴趣的数据包 。
BPF定义了一个可以在内核内实现的虚拟机(VM)。该虚拟机有以下特性:
BPF 程序在Linux内核中主要在filter.h
和bpf_common.h
中实现,主要的数据结构包括以下几个:
Linux v5.18.4/include/uapi/linux/filte.h -> sock_fprog
这个结构体记录了过滤规则个数与规则数组起始位置 , 而 filter 域指向了具体的规则,每一条规则的形式如下:
Linux v5.18.4/include/uapi/linux/filte.h -> sock_filter
该规则有四个参数,code:过滤指令;jt:条件真跳转;jf:条件假跳转;k:操作数
BPF的指令集比较简单,主要有以下几个指令:
如何编写BPF程序呢?BPF指令可以手工编写,但是,开发人员定义了符号常量和两个方便的宏BPF_STMT
和BPF_JUMP
可以用来方便的编写BPF规则。
Linux v5.18.4/include/uapi/linux/filte.h -> BPF_STMT&BPF_JUMP
BPF_STMT
有两个参数,操作码(code)和值(k),举个例子:
这里的操作码是由三个指令相或组成的,BPF_LD
: 建一个 BPF 加载操作 ;BPF_W
:操作数大小是一个字,BPF_ABS
: 使用绝对偏移,即使用指令中的值作为数据区的偏移量,该值是体系结构字段与数据区域的偏移量 。offsetof()
生成数据区域中期望字段的偏移量。
该指令的功能是将体系架构数加载到累加器中。
BPF_JUMP
BPF_JUMP
中有四个参数:操作码、值(k)、为真跳转(jt)和为假跳转(jf),举个例子:
BPF_JMP | BPF JEQ
会创建一个相等跳转指令,它将指令中的值(即第二个参数AUDIT_ARCH_X86_64)与累加器中的值(BPF_K)进行比较。判断是否相等,也就是说,如果架构是 x86-64,则跳过下一条指令(jt=1,代表测试为真跳过一条指令),否则将执行下一条指令(jf=0,代表如果测试为假,则跳过0条指令,也就是继续执行下一条指令)。
上面这两条指令常用作系统架构的验证。
再举个实际例子,该示例用作过滤execve系统调用的过滤规则:
在bpf_common.h 中给出了BPF_STMT
和BPF_JUMP
相关的操作码:
与seccomp相关的定义大多数在seccomp.h
中定义。
一旦为程序配置了seccomp-BPF,每个系统调用都会经过seccomp过滤器,这在一定程度上会影响系统的性能。此外,Seccomp过滤器会向内核返回一个值,指示是否允许该系统调用,该返回值是一个 32 位的数值,其中最重要的 16 位(SECCOMP_RET_ACTION掩码)指定内核应该采取的操作,其他位(SECCOMP_RET_DATA 掩码)用于返回与操作关联的数据 。
每一个seccomp-BPF程序都使用seccomp_data 结构作为输入参数:
/ include /uapi /linux /seccomp.h :
prctl 函数是为进程制定而设计的,该函数原型如下
其中明确指定哪种种操作在于option选项, option有很多,与seccomp有关的option主要有两个: PR_SET_NO_NEW_PRIVS()
和PR_SET_SECCOMP()
。
PR_SET_NO_NEW_PRIVS() :是在Linux 3.5 之后引入的特性,当一个进程或者子进程设置了PR_SET_NO_NEW_PRIVS 属性,则其不能访问一些无法共享的操作,如setuid、chroot等。配置seccomp-BPF的程序必须拥有Capabilities 中 的CAP_SYS_ADMIN
,或者程序已经定义了no_new_privs
属性。 若不这样做 非 root 用户使用该程序时 seccomp
保护将会失效,设置了 PR_SET_NO_NEW_PRIVS
位后能保证 seccomp
对所有用户都能起作用
如果将其第二个参数设置为1,则这个操作能保证seccomp对所有用户都能起作用,并且会使子进程即execve
后的进程依然受到seccomp的限制。
PR_SET_SECCOMP() : 为进程设置seccomp; 通常的形式如下
SECCOMP_MODE_FILTER参数表示设置的seccomp的过滤模式,如果设置为SECCOMP_MODE_STRICT
,则代表严格模式;若为过滤模式,则对应的系统调用限制通过&prog
结构体定义(上面提到过的 struct sock_fprog
)。
严格模式的简单示例
在严格模式下,进程可用的系统调用只有4个,因为open()
也被禁用,所有在进入严格模式前,需要先打开文件,简单的示例如下:
seccomp_strict.c:
代码功能实现简单的文件复制,当seccomp施加严格模式的时候运行时,seccomp 会在执行open(argv[1], O_RDONLY)
函数调用时终止应用程序 。
过滤模式的简单示例
通过上面的介绍和程序流,如果我们想要为一个程序施加seccomp-BPF策略,那可以分为以下几个步骤,首先定义filter数组,之后定义prog参数,最后使用prctl施加策略。
示例一:禁止execve系统调用
seccomp_filter_execv.c:
示例二:
seccomp_filter.c:
在这种情况下, 在这种情况下,seccomp-BPF 程序将允许使用 O_RDONLY
参数打开第一个调用 , 但是在使用 O_WRONLY | O_CREAT
参数调用 open 时终止程序。
项目地址:libseccomp:https://github.com/seccomp/libseccomp
基于prctl()
函数的机制不够灵活,libseccomp
库可以提供一些函数实现prctl类似的效果,库中封装了一些函数,可以不用了解BPF规则而实现过滤。但是在c程序中使用它,需要装一些库文件:
使用示例:
simple_syscall_seccomp.c:
编译运行, 在执行 execve 时程序报错退出 :
解释一下上诉代码:
scmp_filter_ctx : 过滤器的结构体
seccomp_init : 初始化的过滤状态 ,函数原型:
可选的def_action有:
seccomp_rule_add : 添加一条规则,函数原型为:
其中arg_cnt
参数表明是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,如果仅仅需要允许或者禁止所有某个系统调用,arg_cnt
直接传入0即可,如 seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0)
即禁用execve,不管其参数如何。 如果arg_cnt
的参数不为0, 那 arg_cnt
表示后面限制的参数的个数,也就是只有调用 execve,且参数满足要求时,才会拦截 syscall 。如果想要更细粒度的过滤系统调用,把参数也考虑进去,就要设置arg_cnt不为零,然后在利用宏做一些过滤。
举个例子, 拦截 write 函数 参数大于 0x10 时的系统调用 :
seccomp_write_limit.c:
编译执行
其中SCMP_A2
代表为第二个参数指定比较结构,SCMP_CMP_GT
代表 大于(greater than),详细内容如下。
libseccmop/include/seccomp.h.in :
除了seccomp_rule_add之外,还有其他添加规则的函数,如:seccomp_rule_add_array ()、 seccomp_rule_add_exact ()和seccomp_rule_add_exact_array (),详细信息可查看参考链接。
seccomp_load: 将当前的 seccomp 过滤器加载到内核中 ,函数原型:
seccomp_reset : 释放现有的过滤上下文重新初始化之前的状态,并且只能在成功调用seccomp_init () 之后才能使用。
seccomp-bpf.h 是由开发人员编写的一个十分便捷的头文件用于开发seccomp-bpf 。该头文件已经定义好了很多常见的宏,如验证系统架构、允许系统调用等功能,十分便捷,如下所示。
应用示例:
seccomp_policy.c
执行结果
一款用于分析seccomp的开源工具,项目地址:https://github.com/david942j/seccomp-tools
主要功能:
安装
使用
从输出中可知禁用了execve系统调用。
Seccomp技术被用在很多应用程序上以保护系统的安全性,Docker支持使用seccomp来限制容器的系统调用,不过需要启用内核中的CONFIG_SECCOMP
。
当使用docker run 启动一个容器时,Docker会使用默认的seccomp配置文件来对容器施加限制策略,该默认文件是以json
格式编写, 在 300 多个系统调用中禁用了大约 44 个系统调用,可以在Moby项目中找到该源码 。
Docker中默认的配置文件提供了最大限度的包容性,除了默认的选择之外,Docker允许我们自定义该配置文件来灵活的对容器的系统调用进行限制。
示例:以白名单的形式允许特定的系统调用
example.json
这样,在使用 docker run 运行容器时,就可以使用 --security-opt
选项指定该配置文件来对容器进行系统调用定制。
举例,禁止容器创建文件夹,就可以用黑名单的形式禁用mkdir系统调用
seccomp_mkdir.json:
使用该策略启动容器,并在容器中创建文件夹时,就会收到禁止信息,不允许创建文件夹。
当然也可以不适用任何seccomp策略启动容器,只需要在启动选项中加上--security-opt seccomp=unconfined
即可。
zaz seccomp 是一个可以为容器自动生成json格式的seccomp文件的开源工具,项目地址:https://github.com/pjbgf/zaz。
主要用法为
它能够为特定的可执行文件定制系统调用,以只允许特定的操作,禁止其他操作
举个例子:为alpine中的ping命令生成seccomp配置文件
如上所示,zaz
检测到了33个系统调用,使用白名单的形式过滤系统调用。那它以白名单的形式生成的系统调用能否很好的过滤系统系统呢?是否能够满足运行ping命令,而不能运行除了它允许的系统调用之外的命令呢?做个测试,首先用下面Dockerfile构建一个简单的镜像。
Dockerfile
构建成功后,使用默认的seccomp策略启动容器,没有任何问题,可以运行。
接着我们使用上述zaz生成的策略试试,
容器并没有成功启动,在创建OCI的时候就报错了,报错原因是operation not permitted
,这个报错上面似乎提到过,是想要使用的系统调用被禁用的缘故,可能zaz这种白名单的模式鲁棒性还是不够强,而且Docker更新那么多次,zaz
缺乏维护导致捕获的系统调用不足,在容器启动过程中出现了问题。奇怪的是,当我在此运行同样的命令,却引发了panic报错:No error following JSON procError payload
。
这种报错或许是不应该的,我尝试在网上寻找报错的相关信息,类似的情况很少,而且并不是每次运行都是出现这种panic,正常情况下应该是operation not permitted
,这是由于我们的白名单没有完全包括必须的系统调用导致的。目前将此情况汇报给了Moby issue ,或许能够得到一些解答。
类似panic信息:
https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=1714183
无论是哪种报错,看起来都是runc出了问题,尝试解决这个问题,我们就要知道Docker到底是如何在运行时加载seccomp?
当我们要创建一个容器的时候 ,容器守护进程 Dockerd会请求 containerd
来创建一个容器 , containerd
收到请求后,也并不会直接去操作容器,而是创建一个叫做 containerd-shim
的进程,让这个进程去操作容器,之后containerd-shim
会通过OCI去调用容器运行时runc
来启动容器, runc
启动完容器后本身会直接退出,containerd-shim
则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd
, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程 。也就是说调用顺序为
启动一个容器ubuntu,并在容器中再运行一个bash
查看调用栈,containerd-shim
(28051-28129)并没有被施加seccomp,而容器内的两个bash(1 -> 28075;9->28126)被施加了seccomp策略。
也就是说对容器施加seccomp 是在container-shim
启动之后,在调用runc的时候出现了问题,是否我们的seccomp策略也要将runc所必须的系统调用考虑进去呢?Zaz是否考虑了容器启动时候的runc所必须的系统调用?
这就需要捕获容器在启动时,runc所必要的系统调用了。
为了获取容器运行时runc用了哪些系统调用,可以有很多方法,比如ftrace、strace、fanotify等,这里使用sysdig
来监控容器的运行,sisdig
时一款原生支持容器的系统可见性工具,项目地址:https://github.com/draios/sysdig。具体的安装和使用方法可以参考GitHub上给出的详细教程,这里只做简单介绍。
安装完成后,直接在命令行运行sysdig,不加任何参数, sysdig 会捕获所有的事件并将其写入标准输出 :
默认情况下,sysdig 在一行中打印每个事件的信息,格式如下
%evt.num %evt.time %evt.cpu %proc.name (%thread.tid) %evt.dir %evt.type %evt.args
其中
启动一个终端A,输入以下命令进行监控,container.name
指定捕获容器名为ping,proc.name
指定进程名为runc的包,保存为runc.scap.
接着在另一个终端B启动该容器
执行完毕后,在终端A使用ctrl+c停止捕获,并筛选捕获的内容,只留系统调用,将结果保存到runc_syscall.txt
中,这样我们就得到了启动容器时runc使用了哪些系统调用。
可以发现筛选出的系统调用数还是有很多的,其中包含很多重复的系统调用,这里可以简单的写一个脚本,进行过滤,通过过滤后,一共有72个系统调用。
将zaz生成的系统调用与我们捕获的系统调用合二为一,系统调用数到了85个。如下:
通过该文件再次运行容器,发现可以成功运行!
尝试运行其他命令,有些命令由于缺乏必须的系统调用,会出现Operation not permitted
的报错。
本文记录了在学习seccomp过程中的相关内容,其中的一些观点难免会有一些问题,若有错误或者建议,烦请批评指正 。
BPF操作码
https://www3.physnet.uni-hamburg.de/physnet/Tru64-Unix/HTML/MAN/MAN7/0012__ .HTM
seccomp_rule_add
https://man7.org/linux/man-pages/man3/seccomp_rule_add.3.html
seccomp和seccomp bfp
https://ajxchapman.github.io/linux/2016/08/31/seccomp-and-seccomp-bpf.html
seccomp 概述
https://lwn.net/Articles/656307/
seccomp沙箱机制 & 2019ByteCTF VIP
https://bbs.pediy.com/thread-258146.htm
prctl(2) — Linux manual page
https://man7.org/linux/man-pages/man2/prctl.2.html
seccomp-tools
https://github.com/david942j/seccomp-tools
libseccomp
https://github.com/seccomp/libseccomp/blob/3f0e47fe2717b73ccef68ca18f9f7297ee73ebb2/include/seccomp.h.in
docker seccomp
https://docs.docker.com/engine/security/seccomp/
Docker seccomp 与OCI
https://forums.mobyproject.org/t/docker-seccomp-prevents-system-calls-issued-by-oci-runtime/297/9
/
*
Valid values
for
seccomp.mode
and
prctl(PR_SET_SECCOMP, <mode>)
*
/
/
*
Valid values
for
seccomp.mode
and
prctl(PR_SET_SECCOMP, <mode>)
*
/
null@ubuntu:~
/
seccomp$ cat
/
proc
/
1
/
status | grep Seccomp
Seccomp:
0
null@ubuntu:~
/
seccomp$ cat
/
proc
/
1
/
status | grep Seccomp
Seccomp:
0
struct sock_fprog {
/
*
Required
for
SO_ATTACH_FILTER.
*
/
unsigned short
len
;
/
*
BPF指令的数量
*
/
struct sock_filter __user
*
filter
;
/
*
指向BPF数组的指针
*
/
};
struct sock_fprog {
/
*
Required
for
SO_ATTACH_FILTER.
*
/
unsigned short
len
;
/
*
BPF指令的数量
*
/
struct sock_filter __user
*
filter
;
/
*
指向BPF数组的指针
*
/
};
struct sock_filter {
/
*
Filter
block
*
/
__u16 code;
/
*
Actual
filter
code
*
/
__u8 jt;
/
*
Jump true
*
/
__u8 jf;
/
*
Jump false
*
/
__u32 k;
/
*
Generic multiuse field
*
/
};
struct sock_filter {
/
*
Filter
block
*
/
__u16 code;
/
*
Actual
filter
code
*
/
__u8 jt;
/
*
Jump true
*
/
__u8 jf;
/
*
Jump false
*
/
__u32 k;
/
*
Generic multiuse field
*
/
};
/
*
*
Macros
for
filter
block array initializers.
*
/
/
*
*
Macros
for
filter
block array initializers.
*
/
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,(offsetof(struct seccomp_data, arch)))
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,(offsetof(struct seccomp_data, arch)))
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K ,AUDIT_ARCH_X86_64 ,
1
,
0
)
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K ,AUDIT_ARCH_X86_64 ,
1
,
0
)
struct sock_filter
filter
[]
=
{
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS,
0
),
/
/
将帧的偏移
0
处,取
4
个字节数据,也就是系统调用号的值载入累加器
BPF_JUMP(BPF_JMP
+
BPF_JEQ,
59
,
0
,
1
),
/
/
当A
=
=
59
时,顺序执行下一条规则,否则跳过下一条规则,这里的
59
就是x64的execve系统调用号
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_KILL),
/
/
返回KILL
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_ALLOW),
/
/
返回ALLOW
};
struct sock_filter
filter
[]
=
{
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS,
0
),
/
/
将帧的偏移
0
处,取
4
个字节数据,也就是系统调用号的值载入累加器
BPF_JUMP(BPF_JMP
+
BPF_JEQ,
59
,
0
,
1
),
/
/
当A
=
=
59
时,顺序执行下一条规则,否则跳过下一条规则,这里的
59
就是x64的execve系统调用号
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_KILL),
/
/
返回KILL
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_ALLOW),
/
/
返回ALLOW
};
/
*
SPDX
-
License
-
Identifier: GPL
-
2.0
WITH Linux
-
syscall
-
note
*
/
/
*
Instruction classes
*
/
/
*
ld
/
ldx fields
*
/
/
*
eBPF BPF_DW
0x18
64
-
bit
*
/
/
/
双字
/
*
alu
/
jmp fields
*
/
/
/
当操作码是jmp时指定跳转类型
/
*
SPDX
-
License
-
Identifier: GPL
-
2.0
WITH Linux
-
syscall
-
note
*
/
/
*
Instruction classes
*
/
/
*
ld
/
ldx fields
*
/
/
*
eBPF BPF_DW
0x18
64
-
bit
*
/
/
/
双字
/
*
alu
/
jmp fields
*
/
/
/
当操作码是jmp时指定跳转类型
/
*
*
All
BPF programs must
return
a
32
-
bit value.
*
The bottom
16
-
bits are
for
optional
return
data.
*
The upper
16
-
bits are ordered
from
least permissive values to most,
*
as a signed value (so
0x8000000
is
negative).
*
*
The ordering ensures that a min_t() over composed
return
values always
*
selects the least permissive choice.
*
/
/
*
Masks
for
the
return
value sections.
*
/
/
*
*
All
BPF programs must
return
a
32
-
bit value.
*
The bottom
16
-
bits are
for
optional
return
data.
*
The upper
16
-
bits are ordered
from
least permissive values to most,
*
as a signed value (so
0x8000000
is
negative).
*
*
The ordering ensures that a min_t() over composed
return
values always
*
selects the least permissive choice.
*
/
/
*
Masks
for
the
return
value sections.
*
/
struct seccomp_data {
int
nr ;
/
*
系统调用号(依赖于体系架构)
*
/
__u32 arch ;
/
*
架构(如AUDIT_ARCH_X86_64)
*
/
__u64 instruction_pointer ;
/
*
CPU指令指针
*
/
__u64 args [
6
];
/
*
系统调用参数,最多有
6
个参数
*
/
};
struct seccomp_data {
int
nr ;
/
*
系统调用号(依赖于体系架构)
*
/
__u32 arch ;
/
*
架构(如AUDIT_ARCH_X86_64)
*
/
__u64 instruction_pointer ;
/
*
CPU指令指针
*
/
__u64 args [
6
];
/
*
系统调用参数,最多有
6
个参数
*
/
};
int
prctl(
int
option, unsigned
long
arg2, unsigned
long
arg3, unsigned
long
arg4, unsigned
long
arg5);
int
prctl(
int
option, unsigned
long
arg2, unsigned
long
arg3, unsigned
long
arg4, unsigned
long
arg5);
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
void configure_seccomp() {
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
ssize_t read_bytes;
char
buffer
[
1024
];
if
(argc <
3
) {
printf(
"Usage:\n\tdup_file <input path> <output_path>\n"
);
return
-
1
;
}
configure_seccomp();
/
*
配置seccomp
*
/
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
/
*
open
() 被禁用,进程会在此终止
*
/
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return
0
;
}
void configure_seccomp() {
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
ssize_t read_bytes;
char
buffer
[
1024
];
if
(argc <
3
) {
printf(
"Usage:\n\tdup_file <input path> <output_path>\n"
);
return
-
1
;
}
configure_seccomp();
/
*
配置seccomp
*
/
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
/
*
open
() 被禁用,进程会在此终止
*
/
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return
0
;
}
null@ubuntu:~
/
seccomp$ gcc
-
o seccomp_strict seccomp_strict.c
null@ubuntu:~
/
seccomp$ .
/
seccomp_strict
/
etc
/
passwd output
Configuring seccomp
Opening
'/etc/passwd'
for
reading
Killed
null@ubuntu:~
/
seccomp$ gcc
-
o seccomp_strict seccomp_strict.c
null@ubuntu:~
/
seccomp$ .
/
seccomp_strict
/
etc
/
passwd output
Configuring seccomp
Opening
'/etc/passwd'
for
reading
Killed
int
main()
{
struct sock_filter
filter
[]
=
{
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS,
0
),
/
/
将帧的偏移
0
处,取
4
个字节数据,也就是系统调用号的值载入累加器
BPF_JUMP(BPF_JMP
+
BPF_JEQ,
59
,
0
,
1
),
/
/
判断系统调用号是否为
59
,是则顺序执行,否则跳过下一条
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_KILL),
/
/
返回KILL
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_ALLOW),
/
/
返回ALLOW
};
struct sock_fprog prog
=
{
.
len
=
(unsigned short)(sizeof(
filter
)
/
sizeof(
filter
[
0
])),
/
/
规则条数
.
filter
=
filter
,
/
/
结构体数组指针
};
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
/
/
设置NO_NEW_PRIVS
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
write(
0
,
"test\n"
,
5
);
system(
"/bin/sh"
);
return
0
;
}
int
main()
{
struct sock_filter
filter
[]
=
{
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS,
0
),
/
/
将帧的偏移
0
处,取
4
个字节数据,也就是系统调用号的值载入累加器
BPF_JUMP(BPF_JMP
+
BPF_JEQ,
59
,
0
,
1
),
/
/
判断系统调用号是否为
59
,是则顺序执行,否则跳过下一条
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_KILL),
/
/
返回KILL
BPF_STMT(BPF_RET
+
BPF_K,SECCOMP_RET_ALLOW),
/
/
返回ALLOW
};
struct sock_fprog prog
=
{
.
len
=
(unsigned short)(sizeof(
filter
)
/
sizeof(
filter
[
0
])),
/
/
规则条数
.
filter
=
filter
,
/
/
结构体数组指针
};
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
/
/
设置NO_NEW_PRIVS
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
write(
0
,
"test\n"
,
5
);
system(
"/bin/sh"
);
return
0
;
}
void configure_seccomp() {
struct sock_filter
filter
[]
=
{
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
/
/
将系统调用号载入累加器
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write,
0
,
1
),
/
/
测试系统调用号是否匹配
'__NR__write'
,如果是允许其他syscall,如果不是则跳过下一条指令,
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open,
0
,
3
),
/
/
测试是否为
'__NR_open'
,不是直接退出,
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[
1
]))),
/
/
第二个参数送入累加器
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, O_RDONLY,
0
,
1
),
/
/
判断是否是
'O_RDONLY'
的方式,是则允许
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
};
struct sock_fprog prog
=
{
.
len
=
(unsigned short)(sizeof(
filter
)
/
sizeof (
filter
[
0
])),
.
filter
=
filter
,
};
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
ssize_t read_bytes;
char
buffer
[
1024
];
if
(argc <
3
) {
printf(
"Usage:\n\tdup_file <input path> <output_path>\n"
);
return
-
1
;
}
printf(
"Ducplicating file '%s' to '%s'\n"
, argv[
1
], argv[
2
]);
configure_seccomp();
/
/
配置seccomp
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return
0
;
}
void configure_seccomp() {
struct sock_filter
filter
[]
=
{
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
/
/
将系统调用号载入累加器
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write,
0
,
1
),
/
/
测试系统调用号是否匹配
'__NR__write'
,如果是允许其他syscall,如果不是则跳过下一条指令,
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open,
0
,
3
),
/
/
测试是否为
'__NR_open'
,不是直接退出,
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[
1
]))),
/
/
第二个参数送入累加器
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, O_RDONLY,
0
,
1
),
/
/
判断是否是
'O_RDONLY'
的方式,是则允许
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
};
struct sock_fprog prog
=
{
.
len
=
(unsigned short)(sizeof(
filter
)
/
sizeof (
filter
[
0
])),
.
filter
=
filter
,
};
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
ssize_t read_bytes;
char
buffer
[
1024
];
if
(argc <
3
) {
printf(
"Usage:\n\tdup_file <input path> <output_path>\n"
);
return
-
1
;
}
printf(
"Ducplicating file '%s' to '%s'\n"
, argv[
1
], argv[
2
]);
configure_seccomp();
/
/
配置seccomp
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return
0
;
}
$ .
/
seccomp_filter
/
etc
/
passwd output
Ducplicating
file
'/etc/passwd'
to
'output'
Configuring seccomp
Opening
'/etc/passwd'
for
reading
Opening
'output'
for
writing
Bad system call
$ .
/
seccomp_filter
/
etc
/
passwd output
Ducplicating
file
'/etc/passwd'
to
'output'
Configuring seccomp
Opening
'/etc/passwd'
for
reading
Opening
'output'
for
writing
Bad system call
null@ubuntu:~
/
seccomp$ sudo apt install libseccomp
-
dev libseccomp2 seccomp
null@ubuntu:~
/
seccomp$ sudo apt install libseccomp
-
dev libseccomp2 seccomp
/
/
gcc
-
g simple_syscall_seccomp.c
-
o simple_syscall_seccomp
-
lseccomp
int
main(void){
scmp_filter_ctx ctx;
ctx
=
seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),
0
);
seccomp_load(ctx);
char
*
filename
=
"/bin/sh"
;
char
*
argv[]
=
{
"/bin/sh"
,NULL};
char
*
envp[]
=
{NULL};
write(
1
,
"i will give you a shell\n"
,
24
);
syscall(
59
,filename,argv,envp);
/
/
execve
return
0
;
}
/
/
gcc
-
g simple_syscall_seccomp.c
-
o simple_syscall_seccomp
-
lseccomp
int
main(void){
scmp_filter_ctx ctx;
ctx
=
seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),
0
);
seccomp_load(ctx);
char
*
filename
=
"/bin/sh"
;
char
*
argv[]
=
{
"/bin/sh"
,NULL};
char
*
envp[]
=
{NULL};
write(
1
,
"i will give you a shell\n"
,
24
);
syscall(
59
,filename,argv,envp);
/
/
execve
return
0
;
}
null@ubuntu:~
/
seccomp$ gcc
-
g simple_syscall_seccomp.c
-
o simple_syscall_seccomp
-
lseccomp
null@ubuntu:~
/
seccomp$ .
/
simple_syscall_seccomp
i will give you a shell
Bad system call (core dumped)
null@ubuntu:~
/
seccomp$ gcc
-
g simple_syscall_seccomp.c
-
o simple_syscall_seccomp
-
lseccomp
null@ubuntu:~
/
seccomp$ .
/
simple_syscall_seccomp
i will give you a shell
Bad system call (core dumped)
seccomp_init(uint32_t def_action)
seccomp_init(uint32_t def_action)
SCMP_ACT_ALLOW:即初始化为允许所有系统调用,过滤为黑名单模式;
SCMP_ACT_KILL:则为白名单模式过滤。
SCMP_ACT_KILL_PROCESS:整个进程将被内核终止
SCMP_ACT_TRAP:如果所有系统调用都不匹配,则给线程发送一个SIGSYS信号
SCMP_ACT_TRACE(uint16_t msg_num):在使用ptrace根据进程时的相关选项
SCMP_ACT_ERRNO(uint16_t errno):不匹配会收到errno的返回值
SCMP_ACT_LOG:不影响系统调用,但是会被记录;
SCMP_ACT_ALLOW:即初始化为允许所有系统调用,过滤为黑名单模式;
SCMP_ACT_KILL:则为白名单模式过滤。
SCMP_ACT_KILL_PROCESS:整个进程将被内核终止
SCMP_ACT_TRAP:如果所有系统调用都不匹配,则给线程发送一个SIGSYS信号
SCMP_ACT_TRACE(uint16_t msg_num):在使用ptrace根据进程时的相关选项
SCMP_ACT_ERRNO(uint16_t errno):不匹配会收到errno的返回值
SCMP_ACT_LOG:不影响系统调用,但是会被记录;
int
seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action,
int
syscall, unsigned
int
arg_cnt, ...);
int
seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action,
int
syscall, unsigned
int
arg_cnt, ...);
int
main(void){
scmp_filter_ctx ctx;
ctx
=
seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(write),
1
,SCMP_A2(SCMP_CMP_GT,
0x10
));
/
/
第
2
(从
0
)个参数大于
0x10
seccomp_load(ctx);
write(
1
,
"1234567812345678"
,
0x10
);
/
/
不被拦截
write(
1
,
"i will give you a shell\n"
,
24
);
/
/
会拦截
return
0
;
}
int
main(void){
scmp_filter_ctx ctx;
ctx
=
seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(write),
1
,SCMP_A2(SCMP_CMP_GT,
0x10
));
/
/
第
2
(从
0
)个参数大于
0x10
seccomp_load(ctx);
write(
1
,
"1234567812345678"
,
0x10
);
/
/
不被拦截
write(
1
,
"i will give you a shell\n"
,
24
);
/
/
会拦截
return
0
;
}
null@ubuntu:~
/
seccomp$ gcc
-
g seccomp_write_limit.c
-
o seccomp_write_limit
-
lseccomp
null@ubuntu:~
/
seccomp$ .
/
seccomp_write_limit
1234567812345678Bad
system call (core dumped)
null@ubuntu:~
/
seccomp$ gcc
-
g seccomp_write_limit.c
-
o seccomp_write_limit
-
lseccomp
null@ubuntu:~
/
seccomp$ .
/
seccomp_write_limit
1234567812345678Bad
system call (core dumped)
...
...
/
*
*
*
Comparison operators
*
/
enum scmp_compare {
_SCMP_CMP_MIN
=
0
,
SCMP_CMP_NE
=
1
,
/
*
*
<
not
equal
*
/
SCMP_CMP_LT
=
2
,
/
*
*
< less than
*
/
SCMP_CMP_LE
=
3
,
/
*
*
< less than
or
equal
*
/
SCMP_CMP_EQ
=
4
,
/
*
*
< equal
*
/
SCMP_CMP_GE
=
5
,
/
*
*
< greater than
or
equal
*
/
SCMP_CMP_GT
=
6
,
/
*
*
< greater than
*
/
SCMP_CMP_MASKED_EQ
=
7
,
/
*
*
< masked equality
*
/
_SCMP_CMP_MAX,
};
...
struct scmp_arg_cmp {
unsigned
int
arg;
/
*
*
< argument number, starting at
0
*
/
enum scmp_compare op;
/
*
*
< the comparison op, e.g. SCMP_CMP_
*
*
/
scmp_datum_t datum_a;
scmp_datum_t datum_b;
};
....
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
use
in
declaring rules
*
@param arg the argument number, starting at
0
*
@param op the comparison operator, e.g. SCMP_CMP_
*
*
@param datum_a dependent on comparison (
32
-
bits)
*
@param datum_b dependent on comparison, optional (
32
-
bits)
*
/
_SCMP_MACRO_DISPATCHER(_SCMP_CMP32_, __VA_ARGS__)(x, y, __VA_ARGS__)
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
0
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
0
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
1
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
1
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
2
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
2
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
3
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
3
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
4
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
4
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
5
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
5
*
/
...
...
...
...
/
*
*
*
Comparison operators
*
/
enum scmp_compare {
_SCMP_CMP_MIN
=
0
,
SCMP_CMP_NE
=
1
,
/
*
*
<
not
equal
*
/
SCMP_CMP_LT
=
2
,
/
*
*
< less than
*
/
SCMP_CMP_LE
=
3
,
/
*
*
< less than
or
equal
*
/
SCMP_CMP_EQ
=
4
,
/
*
*
< equal
*
/
SCMP_CMP_GE
=
5
,
/
*
*
< greater than
or
equal
*
/
SCMP_CMP_GT
=
6
,
/
*
*
< greater than
*
/
SCMP_CMP_MASKED_EQ
=
7
,
/
*
*
< masked equality
*
/
_SCMP_CMP_MAX,
};
...
struct scmp_arg_cmp {
unsigned
int
arg;
/
*
*
< argument number, starting at
0
*
/
enum scmp_compare op;
/
*
*
< the comparison op, e.g. SCMP_CMP_
*
*
/
scmp_datum_t datum_a;
scmp_datum_t datum_b;
};
....
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
use
in
declaring rules
*
@param arg the argument number, starting at
0
*
@param op the comparison operator, e.g. SCMP_CMP_
*
*
@param datum_a dependent on comparison (
32
-
bits)
*
@param datum_b dependent on comparison, optional (
32
-
bits)
*
/
_SCMP_MACRO_DISPATCHER(_SCMP_CMP32_, __VA_ARGS__)(x, y, __VA_ARGS__)
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
0
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
0
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
1
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
1
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
2
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
2
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
3
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
3
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
4
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
4
*
/
/
*
*
*
Specify a
64
-
bit argument comparison struct
for
argument
5
*
/
/
*
*
*
Specify a
32
-
bit argument comparison struct
for
argument
5
*
/
...
...
int
seccomp_load(scmp_filter_ctx ctx);
int
seccomp_load(scmp_filter_ctx ctx);
int
seccomp_reset(scmp_filter_ctx ctx ,uint32_t def_action )
int
seccomp_reset(scmp_filter_ctx ctx ,uint32_t def_action )
...
define VALIDATE_ARCHITECTURE \
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS, arch_nr), \
BPF_JUMP(BPF_JMP
+
BPF_JEQ
+
BPF_K, ARCH_NR,
1
,
0
), \
BPF_STMT(BPF_RET
+
BPF_K, SECCOMP_RET_KILL)
define EXAMINE_SYSCALL \
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS, syscall_nr)
define ALLOW_SYSCALL(name) \
BPF_JUMP(BPF_JMP
+
BPF_JEQ
+
BPF_K, __NR_
BPF_STMT(BPF_RET
+
BPF_K, SECCOMP_RET_ALLOW)
define KILL_PROCESS \
BPF_STMT(BPF_RET
+
BPF_K, SECCOMP_RET_KILL)
...
...
define VALIDATE_ARCHITECTURE \
BPF_STMT(BPF_LD
+
BPF_W
+
BPF_ABS, arch_nr), \
BPF_JUMP(BPF_JMP
+
BPF_JEQ
+
BPF_K, ARCH_NR,
1
,
0
), \
BPF_STMT(BPF_RET
+
BPF_K, SECCOMP_RET_KILL)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-7-1 14:19
被ZxyNull编辑
,原因: