2015-10-15 46 views
17

在嚴格模式seccomp設置後,如何進入EXIT_SUCCESS。是否正確的做法,在主要結束時致電syscall(SYS_exit, EXIT_SUCCESS);seccomp ---如何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 
+0

難道你不能只返回EXIT_SUCCESS? (Woops:沒關係 - 沒有仔細查看你的代碼。) – Steven

+0

我得到了同樣的問題,我的進程被殺死了。 – Lev

+0

非常奇怪'_exit(EXIT_SUCCESS)'不起作用,因爲manpage明確指出,在嚴格的seccomp模式下,「只允許調用線程允許進行的系統調用(2),write(2 ),_exit(2)(但不是exit_group(2))和sigreturn(2)。「 (括號內的數字當然是手動部分)。 – 2016-11-06 20:33:14

回答

10

eigenstate.orgSECCOMP (2)解釋說:

唯一的系統調用調用線程允許 化妝讀取(2),寫(2),_exit(2)( 但不是 exit_group(2)), 和sigreturn(2)。其他系統調用導致SIGKILL信號的交付 。

其結果是,人們所期望的_exit()工作,但它是一個包裝函數調用exit_group(2)未嚴格模式允許([1] [2]),因而,處理就會被殺死。

它甚至報道exit(2) - Linux man page

在glibc的高達2.3版本,的_exit()包裝函數調用同名的內核系統調用。由於glibc 2.3,包裝函數調用exit_group(2),以終止進程中的所有線程。

return聲明相同,聲明最終會以與_exit()非常相似的方式殺死您的進程。

Stracing過程中會提供進一步的確認(允許這種情況出現,你必須集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 "${?}" 
0 

PS

你當然可以自己定義一個filter並使用:

prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter); 

在本徵態鏈接中解釋,允許_exit()(或嚴格來說,exit_group(2)),但只有在您確實需要知道您在做什麼時才這樣做。

+0

此外,'return EXIT_SUCCESS;'也失敗的原因是一樣的:在這種情況下,GNU C庫也會執行'exit_group()'。我確實有一些x86-64 SYSV ABI的獨立C,證明''exit'系統調用在'prctl(PR_SET_SECCOMP,SECCOMP_MODE_STRICT)'調用後正常工作,如果你感興趣的話。 –

+1

運行'strace'下的二進制文件(即'strace。/ example')足以證明C庫使用'exit_group'系統調用而不是'exit'。 –

+0

嘿@NominalAnimal! :)是的,好主意,它也被提到了其中一個鏈接,但沒有顯示,更新!你現在喜歡答案嗎? – gsamaras

6

出現該問題,這是因爲GNU C庫使用exit_group系統調用,如果它是可用的,在Linux中,而不是exit,對於_exit()功能(參見sysdeps/unix/sysv/linux/_exit.c驗證),並且作爲記錄在man 2 prctl,所述exit_group系統調用嚴格的seccomp過濾器是不允許的。因爲_exit()函數調用發生在C庫內部,所以我們不能將它與我們自己的版本(只會執行exit系統調用)進行干預。 (正常的過程清理在別處進行;在Linux中,_exit()功能不僅會終止的過程中最終的系統調用)

我們可以問GNU C庫開發者使用exit_group系統調用在Linux中,只有當有更多的而不是當前進程中的一個線程,但不幸的是,這並不容易,即使現在添加,也需要相當長的時間才能在大多數Linux發行版上使用該功能。

幸運的是,我們可以拋棄默認的嚴格過濾器,而是定義我們自己的過濾器。行爲有一個小的差異:殺死進程的表觀信號將從SIGKILL變爲SIGSYS。 (信號並不實際傳送,因爲內核確實會終止進程;只會導致進程死亡的表觀信號編號發生變化。)

此外,這甚至不是那麼困難。我浪費了一些時間來研究一些GCC宏觀技巧,這將使得管理允許的系統調用列表變得微不足道,但我認爲這不是一個好方法:應該仔細考慮允許的系統調用列表 - 我們只是加入exit_group()相比嚴格的過濾器,在這裏! - 所以使它成爲很困難。

下面的代碼,說example.c,已經驗證到一4.4的內核上工作的x86-64(應在內核3.5或更高的工作)(x86和x86-64的,即,32位和位二進制文​​件)。它應該適用於所有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 ./example 

strict_filter BPF程序是非常微不足道的。第一個操作碼將系統調用號碼加載到累加器中。接下來的五個操作碼將其與可接受的系統調用號碼進行比較,如果找到,則跳轉到允許系統調用的最終操作碼。否則,倒數第二個操作碼將終止進程。

請注意,雖然文檔中提到的sigreturn是允許的系統調用,但Linux系統調用的實際名稱是rt_sigreturn。 (sigreturn已棄用rt_sigreturn年齡。)

此外,安裝過濾器後,操作碼將被複制到內核內存中(請參閱Linux內核源中的kernel/seccomp.c),因此,如果稍後修改數據,它不會以任何方式影響過濾器。換句話說,具有結構static const的安全影響爲零。

我使用了static,因爲不需要在編譯單元之外(或以剝離的二進制)看到符號,並且const將數據放入ELF二進制文件的只讀數據部分。

BPF_JUMP(BPF_JMP | BPF_JEQ, nr, equals, differs)的形式很簡單:將accumulator(系統調用號碼)與nr進行比較。如果它們相等,則跳過下一個equals操作碼。否則,跳過下一個differs操作碼。

由於等號事件會跳轉到最終的操作碼,因此您可以在頂部添加新的操作碼(即緊接在初始操作碼之後),爲每個操作碼遞增等於跳過次數。

注意,安裝過濾器的Seccomp後printf()將無法​​正常工作,因爲在內部,C庫想要做一個系統調用fstat(標準輸出),以及brk系統調用分配一些內存緩衝區。

+1

這是我想要分裂賞金的那些時刻之一。 – 2016-11-10 21:47:44

+2

@ Rhymoid賞金就像一個原子! :)不要擔心,但是Nomimal是一個很酷的傢伙! – gsamaras