介紹
seccomp按照字面意思可以理解為security computing,最早是作用于網格計算,主要通過限制行程的系統呼叫來完成部分沙箱隔離功能,
通過man prctl可以發現,seccomp的引入是在內核2.6.23,早期的seccomp主要限制read、write、exit以及sigreturn四個系統呼叫,內核3.5版本引入filter模式,將seccomp分成strict和filter兩種,其中strict依舊限制四種系統呼叫,而filter模式則借助了Berkeley Pakcet Filter來進行更細致的指令和引數控制,
STRICT模式
我們可以查閱2.6.32內核代碼來了解strict模式下的系統呼叫控制是如何實作的,
設定
seccomp是通過prctl系統呼叫來進行設定的,
具體使用: prctl(PR_SET_SECCOMP, 1, 0, 0, 0),第二個引數設定為1即可,
通過sys_prctl進行追蹤,在prctl_set_seccomp函式中,將當前行程結構(struct task_struct結構)中的seccomp中的mode標記設定為1,并將thread_info中flag設定為TIF_SECCOMP,
我們可以由此猜測,只要將對應的行程task_struct結構中的對應標記mode置位,則可以進行行程系統呼叫限制,程式可通過prctl給自己做限制,如果想限制其他程式,則需要在自己制作的測驗驅動中,通過對不同的測驗程式設定mode,來測驗該功能,參考如下:
read_lock(&tasklck);
for_each_process(p) {
if (p->pid == seccomp_pid) {
p_pid_address = &(p->seccomp.mode);
ori_seccomp_mode = *p_pid_address;
*p_pid_address = 1;
break;
}
}
read_unlock(&tasklck);
攔截
每個系統呼叫都會陷入系統呼叫控制函式syscall_trace_enter中,該函式中呼叫了secure_computing,系統呼叫號作為引數傳遞,
在__secure_computing函式中,通過current->seccomp.mode提取標志位mode,如果mode置1,則說明當前行程已經設定了seccomp限制,通過系統呼叫號與__NR_read、__NR_write 、__NR_exit和__NR_sigreturn等四個系統呼叫值進行比較,判斷當前系統呼叫是否被允許,如果系統呼叫是被允許的,則直接reurn回傳,如果是其他的系統呼叫則呼叫do_exit(SIGKILL)退出,
FILTER模式
我們可以查閱3.10版本的內核代碼來了解filter模式下系統呼叫是如何進行控制的,
設定
seccomp是通過prctl系統呼叫來進行設定的,其中第一個引數是PR_SET_SECCOMP,第二個引數為非0值,從SECCOMP_MODE_STRICT(嚴格模式,值為1)、SECCOMP_MODE_FILTER(指令過濾,值為2),第三個引數為struct sock_fprog結構指標型別的資料,
在sys_prctl中,呼叫了prctl_set_seccomp函式,在該函式中給current->seccomp.mode賦值為1或者2,既行程對應的task_struct結構中的mode置位了,則說明該行程所呼叫的系呼叫需要受到seccomp的限制,
用戶態傳遞的第三個引數是struct sock_fprog結構指標,結構如下:
struct sock_filter
{
__u16 code;
__u8 jt;
__u8 jf;
__u32 k;
}
struct sock_fprog
{
unsigned short len;
struct sock_filter *filter;
}
其中len為filter指令過濾塊的個數,filter為指令過濾塊指標,多個過濾塊記憶體連續,內核在seccomp_attach_filter函式中,將第三個引數的值賦值到了filter中(struct seccomp_filter結構體指標型別),并將filter的prev指標指向原有的current->seccomp.filter,將current->seccomp.filter更新為新的filter指標,此時新的指令過濾結構就串聯到了對應的task_struct單向鏈表上,
struct seccomp_filter
{
struct seccomp_filter *prev; //通過prev節點將seccomp_filter結構串聯成鏈表
unsigned short len; // 當前seccomp_filter的指令的數量
struct sock_filter insns[];
}
在seccomp_attach_filter中,分別呼叫了sk_chk_filter和seccomp_check_filter,
此處只針對seccomp的業務邏輯進行簡單描述,在sk_chk_filter中做了指令碼的轉換,如將BPF_LD + BPF_W + BPF_ABS轉換成BPF_S_LD_W_ABS,
在seccomp_check_filter函式中,也對一些seccomp模塊需要用到的指令做了轉換,用來區seccomp所用到的指令,比如在使用prctl的filter模式時,構建的指令碼為BPF_LD + BPF_W + BPF_ABS,而在sk_chk_filter中可以轉換為BPF_S_LD_W_ABS,此時為了區分seccomp和網路資料包的指令,則將code修改為BPF_S_ANC_SECCOMP_LD_W,作為seccomp的特有指令,
從內核代碼中發現一個需要注意的地方,prctl生效有個前提條件是,當前行程的no_new_privs已經設定了或者當前行程在自己的命名空間中擁有CAP_SYS_ADMIN能力,
攔截
每個系統呼叫都會陷入系統呼叫控制函式syscall_trace_enter中,該函式中呼叫了secure_computing,系統呼叫號作為引數傳遞,
在__secure_computing函式中,通過current->seccomp.mode提取標志位mode,如果mode為1或者2,則說明當前行程已經設定了seccomp限制,
如果為1使用STRICT模式,通過系統呼叫號與__NR_read、__NR_write 、__NR_exit和__NR_sigreturn等四個系統呼叫值進行比較,判斷當前系統呼叫是否被允許,如果系統呼叫是被允許的,則直接reurn回傳,如果是其他的系統呼叫則呼叫do_exit(SIGKILL)退出(向當前行程發送SIGKILL信號),
如果值為2則使用了FILTER模式,則呼叫seccomp_run_filters函式來進行所有指令判斷過濾,系統呼叫號作為引數傳遞,根據回傳值來進行后續處理,
- 當回傳值為SECCOMP_RET_ALLOW時允許系統呼叫執行,
- 當回傳值為SECCOMP_RET_KILL時程式會收到SIGSYS信號,并立即退出,
- 當回傳值是SECCOMP_RET_ERRNO時會回傳系統呼叫錯誤資訊,并賦值errno,
- 當回傳值是SECCOMP_RET_TRAP時,當前運行行程也會收到SIGSYS信號,不過可以在行程中捕獲信號,
- 當回傳值是SECCOMP_RET_TRACE時,可以給ptrace等除錯程式一個控制機會,
在seccomp_run_filters函式中,通過遍歷current->seccomp.filter鏈表,并呼叫sk_run_filter函式處理每一個節點中的指令陣列insns,函式sk_run_filter中有針對不同的指令的處理流程,可根據具體的業務需求再做仔細查看,
接下來以禁用write系統呼叫為例,通過一個應用層例子程式來做個簡單的指令解說,
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4 #include <sys/prctl.h>
5 #include <linux/seccomp.h>
6 #include <linux/filter.h>
7
8 int main(int argc, char **argv)
9 {
10 struct sock_filter filter[] = {
11 // 提取系統呼叫號,賦值給A
12 {BPF_LD+BPF_ABS+BPF_W, 0, 0, 0},
13 // 將A與k進行比較,讓k等于__NR_write,既對__NR_write進行過濾
14 // 通過查看/usr/include/asm/unistd_64.h,返現__NR_write值為1
15 // fentry += (A == K) ? fentry->jt : fentry->jf;
16 // 讓jt等于0,則命中write系統呼叫時不跳轉,既回傳KILL.
17 // 讓jf等于1,則沒有命中時進行跳轉,既回傳ALLOW.
18 {BPF_JMP+BPF_JEQ, 0, 1, 1},
19 // 回傳k,讓k等于SECCOMP_RET_KILL,既回傳KILL
20 {BPF_RET+BPF_K, 0, 0, SECCOMP_RET_KILL},
21 // 回傳k,讓k等于SECCOMP_RET_ALLOW,既回傳允許
22 {BPF_RET+BPF_K, 0, 0, SECCOMP_RET_ALLOW},
23 };
24 struct sock_fprog prog = {
25 .len = (sizeof(filter)/sizeof(struct sock_filter)),
26 .filter = filter
27 };
28 prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
29 write(1, "hello world\n", sizeof("hello world\n") - 1);
30
31 return 0;
32 }
上述代碼中,提取系統呼叫號使用的指令是 BPF_LD + BPF_W + BPF_ABS組合,在seccomp_check_filter中該指令碼修改為BPF_S_ANC_SECCOMP_LD_W,當發現BPF_S_ANC_SECCOMP_LD_W指令時,會呼叫seccomp_bpf_load函式,在函式中根據傳遞的k值來提取不同的資訊,提取系統呼叫號時給k賦的值為struct seccomp_data資料結構中對應元素的偏移值,
struct seccomp_data
{
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
}
其中nr代表系統呼叫號,偏移值為0,則k應該賦值為0,
上述測驗的例子程式,是在root用戶下運行的,既擁有CAP_SYS_ADMIN能力,否則還需要先進行prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)設定,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276747.html
標籤:其他
