沙箱机制

0x00: 简介

沙箱机制,英文sandbox也就是我们常说的沙箱,是计算机领域的虚拟技术,常用于安全方向。一般说来,我们不会将不受信任的软件放在沙箱中运行,一旦该软件有恶意行为,则禁止该程序的进一步运行,不会对真实系统造成任何危害。

在CTF比赛中,pwn题中的沙箱一般都会限制execve的系统调用,这样一来one_gaget和system调用都不好使,只能采取open/read/write的组合方式来读取flag。当然有些题目还会砍掉一个系统调用,进一步限制我们获取flag。

0x01: 开启沙箱的两种方式

在CTF的pwn题中一般有两种函数调用的方式实现沙盒机制,第一种是采用prctl函数调用,第二种是使用seccomp函数。

在具体了解prctl函数之前,我们再了解这样一个概念,沙箱是程序运行过程中的一种隔离机制,其目的是限制不可信进程和不可信代码的访问权限。seccomp是内核中的一种安全机制,seccomp可以在程序中禁用掉一些系统调用来达到保护系统安全的目的,seccomp规则的设置,可以使用prctl函数和seccomp函数。

0x0: prctl函数初探

prctl是基本的进程管理函数,最原始的沙箱规则就是通过prcctl函数来实现的,它可以决定有哪些系统调用函数可以被调用,哪些系统调用函数不能被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数原型
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
// option选项有很多,剩下的参数也由option确定,这里介绍两个主要的option
// PR_SET_NO_NEW_PRIVS(38) 和 PR_SET_SECCOMP(22)
// option为38的情况
// 此时第二个参数设置为1,则禁用execve系统调用,同时这个选项可以通过fork()函数和colne()函数继承给子进程。
prctl(38, 1LL, 0LL, 0LL, 0LL);
// option为22的情况
// 此时第二个参数为1,只允许调用read/write/_exit(not exit_group)/sigreturn这几个syscall
// 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。
// 第二个参数为2,表示允许的系统调用由argv3指向sock_fprog结构体定义,该结构体成员指向的sock_filter可以定义过滤任意系统调用和系统参数。
prctl(22, 2LL, &v1);

如上所示,就是常见的prctl函数调用的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct sock_filter {            /* Filter block */
__u16 code; /* Actual filter code,bpf指令码,后面我们会详细地学习一下 */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
//seccomp-data结构体记录当前正在进行bpf规则检查的系统调用信息
struct seccomp_data{
int nr;//系统调用号
__u32 arch;//调用架构
__u64 instruction_pointer;//CPU指令指针
__u64 argv[6];//寄存器的值,x86下是ebx,exc,edx,edi,ebp;x64下是rdi,rsi,rdx,r10,r8,r9
}
struct sock_fprog {
unsigned short len; /* 指令个数 */
struct sock_filter *filter; /*指向包含struct sock_filter的结构体数组指针*/
}

include/linux/prctl.h里面存储着prctl的所有参数的宏定义,prctl的五个参数中,其中第一个参数是你要做的事情,后面的参数都是对第一个参数的限定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
unsigned int sub_12A9()
{
__int16 v1; // [rsp+0h] [rbp-60h] BYREF
__int16 *v2; // [rsp+8h] [rbp-58h]
__int16 v3; // [rsp+10h] [rbp-50h] BYREF
char v4; // [rsp+12h] [rbp-4Eh]
char v5; // [rsp+13h] [rbp-4Dh]
int v6; // [rsp+14h] [rbp-4Ch]
__int16 v7; // [rsp+18h] [rbp-48h]
char v8; // [rsp+1Ah] [rbp-46h]
char v9; // [rsp+1Bh] [rbp-45h]
int v10; // [rsp+1Ch] [rbp-44h]
__int16 v11; // [rsp+20h] [rbp-40h]
char v12; // [rsp+22h] [rbp-3Eh]
char v13; // [rsp+23h] [rbp-3Dh]
int v14; // [rsp+24h] [rbp-3Ch]
__int16 v15; // [rsp+28h] [rbp-38h]
char v16; // [rsp+2Ah] [rbp-36h]
char v17; // [rsp+2Bh] [rbp-35h]
int v18; // [rsp+2Ch] [rbp-34h]
__int16 v19; // [rsp+30h] [rbp-30h]
char v20; // [rsp+32h] [rbp-2Eh]
char v21; // [rsp+33h] [rbp-2Dh]
int v22; // [rsp+34h] [rbp-2Ch]
__int16 v23; // [rsp+38h] [rbp-28h]
char v24; // [rsp+3Ah] [rbp-26h]
char v25; // [rsp+3Bh] [rbp-25h]
int v26; // [rsp+3Ch] [rbp-24h]
__int16 v27; // [rsp+40h] [rbp-20h]
char v28; // [rsp+42h] [rbp-1Eh]
char v29; // [rsp+43h] [rbp-1Dh]
int v30; // [rsp+44h] [rbp-1Ch]
__int16 v31; // [rsp+48h] [rbp-18h]
char v32; // [rsp+4Ah] [rbp-16h]
char v33; // [rsp+4Bh] [rbp-15h]
int v34; // [rsp+4Ch] [rbp-14h]
unsigned __int64 v35; // [rsp+58h] [rbp-8h]

v35 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
prctl(38, 1LL, 0LL, 0LL, 0LL);
v3 = 32;
v4 = 0;
v5 = 0;
v6 = 4;
v7 = 21;
v8 = 0;
v9 = 5;
v10 = -1073741762;
v11 = 32;
v12 = 0;
v13 = 0;
v14 = 0;
v15 = 21;
v16 = 0;
v17 = 2;
v18 = 0;
v19 = 32;
v20 = 0;
v21 = 0;
v22 = 16;
v23 = 37;
v24 = 1;
v25 = 0;
v26 = 1;
v27 = 6;
v28 = 0;
v29 = 0;
v30 = 2147418112;
v31 = 6;
v32 = 0;
v33 = 0;
v34 = 0;
v1 = 8;
v2 = &v3;
prctl(22, 2LL, &v1);
return alarm(0x20u);
}

上面就是强网拟态的一道真实的pwn题,经过IDA反编译后的结果,这里最关键的代码是两个prctl函数的调用,其他看着零散的变量其实是用来设置沙盒的结构体,这里被IDA解释成了这样而已。

0x1:seccomp库函数

seccomp 是 Linux 内核提供的一种应用程序沙箱机制,seccomp 通过只允许应用程序调用 exit(), sigreturn(), read() 和 write() 四种系统调用来达到沙箱的效果。如果应用程序调用了除了这四种之外的系统调用, kernel 会向进程发送 SIGKILL 信号。

seccomp 很难在实际中得到推广,因为限制实在是太多了,Linus 本人也对它的应用持怀疑的态度,直到出现了 seccomp-bpf。seccomp-bpf 是 seccomp 的一个扩展,它可以通过配置来允许应用程序调用其他的系统调用。chrome 中第一个应用 seccomp-bpf 的场景是把 Flash 放到了沙箱里运行(实在是不放心),后续也把 render 的过程放到了沙箱里。

该函数就比较清晰和简单了,下面的代码也是取材与一道真实的pwn题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
__int64 sandbox()
{

__int64 v1; // [rsp+8h] [rbp-8h]
// 两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U)
// seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式
v1 = seccomp_init(0LL);
if ( !v1 )
{

puts("seccomp error");
exit(0);
}
// seccomp_rule_add添加规则
// v1对应上面初始化的返回值
// 0x7fff0000即对应宏SCMP_ACT_ALLOW
// 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit
// 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);
// seccomp_load - Load the current seccomp filter into the kernel
if ( seccomp_load(v1) < 0 )
{

// seccomp_release - Release the seccomp filter state
// 但对已经load的过滤规则不影响
seccomp_release(v1);
puts("seccomp error");
exit(0);
}
return seccomp_release(v1);
}

0x02: 使用seccomp-tools识别沙箱

0x0:安装

1
2
3
sudo apt install gcc ruby-dev

sudo gem install seccomp-tools

0x1: 使用

1
seccomp-tools dump ./shell

0x03: 自制一个沙箱

在第一章的prctl函数初探中我们介绍了prctl函数的一些参数和用法,现在我们来看看通过定义sock_fprog结构体来实现过滤任意系统调用和系统调用参数。

0x0: BDF过滤规则(伯克利封包过滤)

伯克利包过滤器是指类Unix系统上数据链路层的一种原始接口,提供原始链路层封包的收发。BPF也支持封包过滤,其过滤规则在linux中应用到了很多地方。xt_bpf 对netfilter,cls_bpf在内核的qdisk层,SECCOMP-BPF,以及一系列其他地方例如: team driver ,PTP code等BPF都被用到。

Seccomp Strict Mode

Seccomp 在最初引入的时候只支持了strict mode ,意味着只有readwrite,_exit,_sigreturn四个system call会被允许执行,一旦出现其他的syscall call ,进程便会被立刻终止(SIGJILL)。一个简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <sys/prctl.h>

#include <sys/socket.h>
#include <linux/seccomp.h>


int main(int argc, char* argv[]) {
printf("Install seccomp\n");
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);

printf("Creating socket\n");
int sock = socket(AF_INET, SOCK_STREAM, 0);

return 0;
}

编译并执行

1
2
3
4
5
6
7
8
┌──(kali㉿kali)-[~/…/CTF/competition/2022xiangyuncup/sadbox]
└─$ gcc seccomp.c -o seccomp
┌──(kali㉿kali)-[~/…/CTF/competition/2022xiangyuncup/sadbox]
└─$ ./seccomp
Install seccomp
Creating socket
zsh: killed ./seccomp

上面的程序在seccomp被启动之后会有两个系统调用:write 和 socket ,printf这个函数本质上向stdout写了一些bytes,是被允许的。然而当进程想要创建sockte的时候,程序就被终止了。

Seccomp Filter Mode (Seccomp-BPF)

strict mode 固然很棒,然而实用性却不够高。因为一个复杂的程序根本不可能只用到四个system call 。strict mode 下进程能够完成的任务却非常有限。于是Linux又引入了filter mode ,也就是BPF(Berkley Packet Filter)。BPF提供了更高的灵活度,赋予了开发者对于程序更细颗粒度的控制。

Linux 内核实现了一个能够执行BPF程序的虚拟机。对于每一次system call ,内核都会执行一遍开发者提供的BPF程序,用来确定是否需要过滤system call。BPF程序在c中表示为一个长度固定的指令数组,定义如下:

1
2
3
4
struct sock_fprog {
unsigned short len; /* Number of BPF instructions */
struct sock_filter *filter; /* Pointer to array of BPF instructions */
};

内核的虚拟机会按顺序读取BPF指令(sock_filter),读取完之后会解码(decode)指令并执行指令。指令的定义如下:

1
2
3
4
5
6
struct sock_filter {            /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};

综上以上所述,我们 来看一个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stddef.h>
#include<linux/seccomp.h>
#include<linux/filter.h>
#include<sys/prctl.h>
#include<linux/bpf.h> //off和imm都是有符号类型,编码信息定义在内核头文件linux/bpf.h
#include<sys/types.h>

int main()
{
struct sock_filter filter[]={
BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 0), // 从第0个字节开始,传送4个字节
BPF_JUMP(BPF_JMP|BPF_JEQ, 59, 1, 0), // 比较是否为59(execve 的系统调用号),是就跳过下一行,如果不是,就执行下一行,第三个参数表示执行正确的指令跳转,第四个参数表示执行错误的指令跳转
BPF_JUMP(BPF_JMP|BPF_JGE, 0, 1, 0),
// BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_KILL),
// 杀死一个进程
// BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_TRACE),
// 父进程追踪子进程,具体没太搞清楚
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ERRNO),
// 异常处理
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
// 这里表示系统调用如果正常,允许系统调用
};
struct sock_fprog prog={
.len=sizeof(filter)/sizeof(filter[0]),
.filter=filter,
};
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);//第一个参数是进行什么设置,第二个参数是设置的过滤模式,第三个参数是设置的过滤规则
puts("123");
system("/bin/bash");
return 0;
}

我们之前说过,BPF代码在c中表示成一个指令数组。

还有一点值得注意的是我们一般会设置PR_SET_NO_NEW_PRIVS(禁止execve调用)这个bit ,主要是为了防止untrusted code篡改已有BPF程序,进而导致安全隐患。

0x1: Kafel

如果你是一个硬核的程序员,当然可以直接手写 BPF 代码过滤 system call。然而对于大多数 程序员而言,这个过程可能并不轻松,稍有不慎就会引入 bug,导致一些 system call 没有被blacklist 掉。幸好,开源项目 Kafel 提供了解决方案。

Kafel 规定了一种更便于人理解的 policy file,并且提供了编译器,能把 policy file 编译成 BPF 代码。我这里直接放一个 policy file,不用我多解释,相信大家也能把这个文件的意思猜的差不多了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define CLONE_THREAD  0x00010000

POLICY foo {
ALLOW {
brk,
clone { clone_flags & CLONE_THREAD != 0 },
exit,
exit_group,
futex,
mmap,
mprotect,
read,
write
}
}

USE foo DEFAULT ERRNO(1)

这个 policy 文件比较有意思的地方就clone 的规则,它规定了只允许 argument 中 CLONE_THREAD的 bit 是被开启的。这个 policy 这样设置也很好理解:fork()和 start a new thread 其实都需要用到 clone 这个 system call,只不过 argument 不同。很多时候我们任然希望被 sandbox 的进程能够多线程处理,但是我们却不希望进程 fork 出一个子进程。

接下里我们通过 kafel 生成 bpf 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ dump_policy_bpf -c seccomp.policy 
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xc000003eu, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x39u, 0, 6),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xcau, 0, 3),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xe7u, 0, 1),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xe8u, 11, 12),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xcbu, 10, 11),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x3cu, 0, 9),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x3du, 8, 9),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xbu, 0, 5),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xdu, 0, 3),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x38u, 0, 5),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])),
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x10000u, 4, 3),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0xcu, 3, 2),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x2u, 0, 2),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x9u, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 0x1u),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),

把生成的 bpf 代码放到应用程序中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <sys/prctl.h>
#include <sys/socket.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <thread>
#include <unistd.h>
#include <stdio.h>

void configure_seccomp() {

struct sock_filter filter [] = {
/** 之前生成的bpf代码 此处省略了 */
};

struct sock_fprog prog = {
.len = 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, 0, 0);
}

int main(int argc, char* argv[]) {
configure_seccomp();

std::thread t([]()->void { printf("Hello from thread\n");});
t.join();

pid_t ret = fork();
printf("Return code from fork %d, errno: %d\n", ret, errno);

return 0;
}

编译并执行:

1
2
3
4
5
$ g++82 -o seccomp seccomp.c -pthread
$ ./seccomp
Configuring seccomp
Hello from thread
Return code from fork -1, errno: 1

从上面的例子可以看出:程序可以多线程计算,但是却不能 fork 一个子进程。fork 返回 -1 ,并且把errno 设置成了 1 ,因为在 policy 中我们规定了当 system call violation 的时候会返回EPERM

0x2: Seccomp库函数

这个库可以提供一些函数实现prctl类似的效果,库中封装了一些函数,可以不用了解BPF规则而实现过滤。

但是在C程序中使用它,需要安装一些库文件

1
sudo apt install libseccomp-dev libseccomp2 seccomp

通过使用该库的函数实现禁用execve系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//gcc seccomptest.c -o seccomptest -lseccomp
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>

int main(void){
scmp_filter_ctx ctx;//是过滤器的结构体
ctx = seccomp_init(SCMP_ACT_ALLOW);//对结构体进行初始化,若参数为SCMP_ACT_ALLOW,则过滤为黑名单模式;若为SCMP_ACT_KILL,则为白名单模式,即没有匹配到规则的系统调用都会杀死进程,默认不允许所有的syscall。
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);//添加一条规则,这里是禁止execve系统调用,不管其参数为如何
seccomp_load(ctx);

char * str = "/bin/sh";
write(1,"i will give you a shell\n",24);
syscall(59,str,NULL,NULL);//execve
return 0;
}

scmp_filter_ctx是过滤器的结构体
seccomp_init对结构体进行初始化,若参数为SCMP_ACT_ALLOW,则过滤为黑名单模式;若为SCMP_ACT_KILL,则为白名单模式,即没有匹配到规则的系统调用都会杀死进程,默认不允许所有的syscall。

1
seccomp_init(uint32_t def_action);

def_action为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
* seccomp actions
*/

/**
* Kill the process
*/
#define SCMP_ACT_KILL 0x00000000U
/**
* Throw a SIGSYS signal
*/
#define SCMP_ACT_TRAP 0x00030000U
/**
* Return the specified error code
*/
#define SCMP_ACT_ERRNO(x) (0x00050000U | ((x) & 0x0000ffffU))
/**
* Notify a tracing process with the specified value
*/
#define SCMP_ACT_TRACE(x) (0x7ff00000U | ((x) & 0x0000ffffU))
/**
* Allow the syscall to be executed after the action has been logged
*/
#define SCMP_ACT_LOG 0x7ffc0000U
/**
* Allow the syscall to be executed
*/
#define SCMP_ACT_ALLOW 0x7fff0000U

seccomp_rule_add是添加一条规则

1
2
int seccomp_rule_add(scmp_filter_ctx ctx,
uint32_t action, int syscall, unsigned int arg_cnt, ...);

参数的宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* Specify an 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
* @param datum_b dependent on comparison, optional
*/
#define SCMP_CMP(...) ((struct scmp_arg_cmp){__VA_ARGS__})

/**
* Specify an argument comparison struct for argument 0
*/
#define SCMP_A0(...) SCMP_CMP(0, __VA_ARGS__)

/**
* Specify an argument comparison struct for argument 1
*/
#define SCMP_A1(...) SCMP_CMP(1, __VA_ARGS__)

/**
* Specify an argument comparison struct for argument 2
*/
#define SCMP_A2(...) SCMP_CMP(2, __VA_ARGS__)

/**
* Specify an argument comparison struct for argument 3
*/
#define SCMP_A3(...) SCMP_CMP(3, __VA_ARGS__)

/**
* Specify an argument comparison struct for argument 4
*/
#define SCMP_A4(...) SCMP_CMP(4, __VA_ARGS__)

/**
* Specify an argument comparison struct for argument 5
*/
#define SCMP_A5(...) SCMP_CMP(5, __VA_ARGS__)



/**
* 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,
};

/**
* Argument datum
*/
typedef uint64_t scmp_datum_t;

/**
* Argument / Value comparison definition
*/
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;
};

arg_cnt表明是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,如果仅仅需要允许或者禁止所有某个系统调用,arg_cnt直接传入0即可,seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0)即禁用execve,不管其参数如何。

如果考虑到更高的自定义,需要先去了解一下具体系统调用的参数情况,然后再利用SCMP_AX及SCMP_CMP_XX类的宏定义做一些过滤。以read为例,read函数原型

1
ssize_t read(int fd, void *buf, size_t count);

限制从标准输入stdin读入的字节数不能为100。

1
2
3
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(read), 2,
SCMP_A0(SCMP_CMP_EQ, STDIN_FILENO),
SCMP_A2(SCMP_CMP_EQ, 100))

seccomp_load是应用过滤,seccomp_reset是解除过滤。

1
2
int seccomp_load(const scmp_filter_ctx ctx);
int seccomp_reset(const scmp_filter_ctx ctx);

参考:

https://docs.rs/seccomp-sys/0.1.0/seccomp_sys/fn.seccomp_rule_add.html

https://bbs.pediy.com/thread-258146.htm