Seccomp BPF(帶過濾器的安全計算)¶
介紹¶
每個使用者空間程序都暴露了大量的系統呼叫,其中許多在程序的整個生命週期中都未使用。隨著系統呼叫的變化和成熟,會發現並消除錯誤。透過減少可用系統呼叫的集合,某些使用者空間應用程式會受益。由此產生的集合減少了暴露給應用程式的核心總表面。系統呼叫過濾旨在用於這些應用程式。
Seccomp過濾提供了一種程序指定傳入系統呼叫過濾器的手段。該過濾器表示為伯克利資料包過濾器(BPF)程式,與套接字過濾器一樣,不同之處在於操作的資料與正在進行的系統呼叫有關:系統呼叫號和系統呼叫引數。這允許使用具有悠久的使用者空間暴露歷史以及直接資料集的過濾器程式語言對系統呼叫進行富有表現力的過濾。
此外,BPF使seccomp的使用者不可能成為時間檢查-時間使用(TOCTOU)攻擊的犧牲品,這些攻擊在系統呼叫介入框架中很常見。BPF程式可能不會取消引用指標,這會將所有過濾器限制為僅直接評估系統呼叫引數。
它不是什麼¶
系統呼叫過濾不是沙箱。它提供了一個明確定義的機制來最小化暴露的核心表面。它旨在成為沙箱開發人員使用的工具。除此之外,邏輯行為和資訊流的策略應與其他系統強化技術以及您選擇的LSM的組合來管理。富有表現力的動態過濾器提供了沿著這條道路的更多選擇(避免病態大小或選擇允許socketcall()中的哪個多路複用系統呼叫,例如),這可能會被錯誤地解釋為更完整的沙箱解決方案。
用法¶
添加了一個額外的seccomp模式,並使用與嚴格seccomp相同的prctl(2)呼叫啟用。如果該架構具有CONFIG_HAVE_ARCH_SECCOMP_FILTER,則可以如下新增過濾器
PR_SET_SECCOMP:現在需要一個額外的引數,該引數使用BPF程式指定一個新的過濾器。BPF程式將在struct seccomp_data上執行,該資料反映了系統呼叫號,引數和其他元資料。然後,BPF程式必須返回可接受的值之一,以通知核心應採取的措施。
用法
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog);
“prog”引數是指向struct sock_fprog的指標,其中將包含過濾器程式。如果程式無效,則呼叫將返回-1並將errno設定為
EINVAL。如果@prog允許
fork/clone和execve,則任何子程序都將受到與父程序相同的過濾器和系統呼叫ABI的約束。在使用之前,任務必須呼叫
prctl(PR_SET_NO_NEW_PRIVS, 1)或在其名稱空間中以CAP_SYS_ADMIN許可權執行。如果這些不正確,將返回-EACCES。此要求確保過濾器程式不能應用於具有比安裝它們的任務更高的許可權的子程序。此外,如果附加的過濾器允許
prctl(2),則可以分層附加過濾器,這將增加評估時間,但允許在程序執行期間進一步減少攻擊面。
以上呼叫在成功時返回0,在錯誤時返回非零值。
返回值¶
seccomp過濾器可能會返回以下任何值。如果存在多個過濾器,則給定系統呼叫評估的返回值將始終使用最高優先順序值。(例如,SECCOMP_RET_KILL_PROCESS將始終優先。)
按優先順序順序,它們是
SECCOMP_RET_KILL_PROCESS:導致整個程序立即退出而不執行系統呼叫。任務的退出狀態(
status & 0x7f)將為SIGSYS,而不是SIGKILL。SECCOMP_RET_KILL_THREAD:導致任務立即退出而不執行系統呼叫。任務的退出狀態(
status & 0x7f)將為SIGSYS,而不是SIGKILL。SECCOMP_RET_TRAP:導致核心向觸發任務傳送
SIGSYS訊號而不執行系統呼叫。siginfo->si_call_addr將顯示系統呼叫指令的地址,並且siginfo->si_syscall和siginfo->si_arch將指示嘗試了哪個系統呼叫。程式計數器將好像發生了系統呼叫一樣(即,它不會指向系統呼叫指令)。返回值暫存器將包含一個與架構相關的的值 - 如果恢復執行,請將其設定為合理的值。(架構依賴性是因為用-ENOSYS替換它可能會覆蓋一些有用的資訊。)返回值中的
SECCOMP_RET_DATA部分將作為si_errno傳遞。由seccomp觸發的
SIGSYS的si_code為SYS_SECCOMP。SECCOMP_RET_ERRNO:導致返回值中的較低16位作為errno傳遞給使用者空間而不執行系統呼叫。
SECCOMP_RET_USER_NOTIF:如果在使用者空間通知fd上傳送
struct seccomp_notif訊息,如果沒有,則返回-ENOSYS。有關如何處理使用者通知的討論,請參見下文。SECCOMP_RET_TRACE:返回時,此值將導致核心嘗試在執行系統呼叫之前通知基於
ptrace()的跟蹤器。如果沒有跟蹤器存在,則將-ENOSYS返回到使用者空間,並且不執行系統呼叫。如果跟蹤器使用
ptrace(PTRACE_SETOPTIONS)請求PTRACE_O_TRACESECCOMP,則將通知跟蹤器。將通知跟蹤器PTRACE_EVENT_SECCOMP,並且BPF程式返回值的SECCOMP_RET_DATA部分將透過PTRACE_GETEVENTMSG提供給跟蹤器。跟蹤器可以透過將系統呼叫號更改為-1來跳過系統呼叫。或者,跟蹤器可以透過將系統呼叫更改為有效的系統呼叫號來更改請求的系統呼叫。如果跟蹤器要求跳過系統呼叫,則系統呼叫將顯示為返回跟蹤器放入返回值暫存器中的值。
通知跟蹤器後,seccomp檢查將不會再次執行。(這意味著基於seccomp的沙箱絕不能允許使用ptrace,即使是對其他沙箱程序的ptrace,也要格外小心;ptracer可以使用此機制來逃脫。)
SECCOMP_RET_LOG:導致系統呼叫在記錄後執行。應用程式開發人員應使用它來了解其應用程式需要的哪些系統呼叫,而無需迭代多個測試和開發週期來構建列表。
只有當“log”出現在actions_logged sysctl字串中時,才會記錄此操作。
SECCOMP_RET_ALLOW:導致系統呼叫被執行。
如果存在多個過濾器,則給定系統呼叫評估的返回值將始終使用最高優先順序值。
僅使用SECCOMP_RET_ACTION掩碼確定優先順序。當多個過濾器返回值的優先順序相同時,僅返回最近安裝的過濾器的SECCOMP_RET_DATA。
陷阱¶
使用過程中要避免的最大陷阱是在不檢查體系結構值的情況下過濾系統呼叫號。為什麼?在支援多種系統呼叫呼叫約定的任何體系結構上,系統呼叫號可能會因特定呼叫而異。如果不同調用約定中的數字重疊,則過濾器中的檢查可能會被濫用。始終檢查arch值!
示例¶
samples/seccomp/目錄包含一個特定於x86的示例以及一個用於BPF程式生成的高階宏介面的更通用的示例。
使用者空間通知¶
SECCOMP_RET_USER_NOTIF返回碼使seccomp過濾器可以將特定的系統呼叫傳遞給使用者空間進行處理。這對於容器管理器之類的應用程式可能很有用,這些應用程式希望攔截特定的系統呼叫(mount(),finit_module()等)並更改其行為。
要獲取通知FD,請使用SECCOMP_FILTER_FLAG_NEW_LISTENER引數呼叫seccomp()系統呼叫
fd = seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog);
(成功時)將返回過濾器的偵聽器fd,然後可以透過SCM_RIGHTS或類似的方式傳遞。請注意,過濾器fd對應於特定的過濾器,而不是特定的任務。因此,如果此任務隨後派生,則來自兩個任務的通知將出現在同一過濾器fd上。對過濾器fd的讀寫也是同步的,因此過濾器fd可以安全地擁有許多讀取器。
seccomp通知fd的介面由兩個結構組成
struct seccomp_notif_sizes {
__u16 seccomp_notif;
__u16 seccomp_notif_resp;
__u16 seccomp_data;
};
struct seccomp_notif {
__u64 id;
__u32 pid;
__u32 flags;
struct seccomp_data data;
};
struct seccomp_notif_resp {
__u64 id;
__s64 val;
__s32 error;
__u32 flags;
};
struct seccomp_notif_sizes結構可用於確定seccomp通知中使用的各種結構的大小。 struct seccomp_data的大小將來可能會更改,因此程式碼應使用
struct seccomp_notif_sizes sizes;
seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes);
確定要分配的各種結構的大小。有關示例,請參見samples/seccomp/user-trap.c。
使用者可以透過在seccomp通知fd上使用ioctl(SECCOMP_IOCTL_NOTIF_RECV)(或poll())讀取以接收struct seccomp_notif,其中包含五個成員:結構的輸入長度,每個過濾器唯一的id,觸發此請求的任務的pid(如果任務在偵聽器的pid名稱空間中不可見,則可能為0)。該通知還包含傳遞給seccomp的data和filters標誌。在呼叫ioctl之前,應將結構清零。
然後,使用者空間可以根據此資訊做出關於如何操作的決定,並ioctl(SECCOMP_IOCTL_NOTIF_SEND)一個響應,指示應返回給使用者空間的內容。struct seccomp_notif_resp的id成員應與struct seccomp_notif中的id相同。
使用者空間還可以透過ioctl(SECCOMP_IOCTL_NOTIF_ADDFD)將檔案描述符新增到通知程序。struct seccomp_notif_addfd的id成員應與struct seccomp_notif中的id相同。 newfd_flags標誌可用於在通知程序中設定O_CLOEXEC之類的標誌。如果主管想要注入具有特定編號的檔案描述符,則可以使用SECCOMP_ADDFD_FLAG_SETFD標誌,並將newfd成員設定為要使用的特定編號。如果該檔案描述符已在通知程序中開啟,則將其替換。主管還可以新增FD,並透過使用SECCOMP_ADDFD_FLAG_SEND標誌自動響應,並且返回值將是注入的檔案描述符編號。
可以搶佔通知程序,從而導致通知中止。當試圖代表通知程序採取長期執行且通常可重試的操作(安裝檔案系統)時,這可能會出現問題。或者,在過濾器安裝時,可以設定SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV標誌。此標誌使得當主管收到使用者通知時,通知程序將忽略非致命訊號,直到傳送響應為止。在使用者空間收到通知之前傳送的訊號將正常處理。
值得注意的是,struct seccomp_data包含系統呼叫的暫存器引數的值,但不包含指向記憶體的指標。合適特權的跟蹤器可以透過ptrace()或/proc/pid/mem訪問任務的記憶體。但是,應注意避免本文件中提到的TOCTOU:從tracee的記憶體中讀取的所有引數都應在做出任何策略決定之前讀入tracer的記憶體中。這允許對系統呼叫引數進行原子決策。
Sysctls¶
可以在/proc/sys/kernel/seccomp/目錄中找到Seccomp的sysctl檔案。這是該目錄中每個檔案的描述
actions_avail:一個只讀的seccomp返回值有序列表(請參閱上面的
SECCOMP_RET_*宏),以字串形式表示。從左到右的順序是從最不嚴格的返回值到最嚴格的返回值。該列表表示核心支援的seccomp返回值集合。使用者空間程式可以使用此列表來確定程式構建時在
seccomp.h中找到的操作是否與當前執行核心中實際支援的操作集合不同。actions_logged:允許記錄的seccomp返回值(請參閱上面的
SECCOMP_RET_*宏)的讀寫有序列表。寫入檔案不需要有序形式,但是從檔案讀取將以與actions_avail sysctl相同的方式排序。由於無法記錄
SECCOMP_RET_ALLOW操作,因此actions_loggedsysctl不接受allow字串。嘗試將allow寫入sysctl將導致返回EINVAL。
新增體系結構支援¶
有關權威要求,請參見arch/Kconfig。通常,如果一個體繫結構同時支援ptrace_event和seccomp,它將能夠透過一些小的修復來支援seccomp過濾器:SIGSYS支援和seccomp返回值檢查。然後,它必須僅將CONFIG_HAVE_ARCH_SECCOMP_FILTER新增到其特定於架構的Kconfig中。
注意事項¶
vDSO可能導致某些系統呼叫完全在使用者空間中執行,從而導致在不同的機器上執行程式時會感到驚訝,這些程式會回退到實際的系統呼叫。為了最大程度地減少x86上的這些意外情況,請確保您使用設定為類似acpi_pm的/sys/devices/system/clocksource/clocksource0/current_clocksource進行測試。
在x86-64上,預設情況下啟用vsyscall模擬。(vsyscalls是vDSO呼叫的舊式變體。)當前,模擬的vsyscall將遵守seccomp,但有一些奇怪之處
SECCOMP_RET_TRAP的返回值將設定一個si_call_addr,該地址指向給定呼叫的vsyscall條目,而不是'syscall'指令之後的地址。任何想要重新啟動呼叫的程式碼都應意識到(a)ret指令已被模擬,並且(b)嘗試恢復syscall將再次觸發標準的vsyscall模擬安全檢查,從而使恢復syscall幾乎毫無意義。SECCOMP_RET_TRACE的返回值將像往常一樣向跟蹤器發出訊號,但是不能使用orig_rax暫存器將syscall更改為另一個系統呼叫。它只能更改為-1,以便跳過當前模擬的呼叫。任何其他更改都可能終止該程序。跟蹤器看到的rip值將是syscall入口地址;這與正常行為不同。跟蹤器絕不能修改rip或rsp。(不要依賴於其他更改來終止該程序。它們可能會起作用。例如,在某些核心上,選擇僅在將來核心中存在的syscall將被正確模擬(透過返回-ENOSYS)。
要檢測到這種古怪的行為,請檢查addr & ~0x0C00 == 0xFFFFFFFFFF600000。(對於SECCOMP_RET_TRACE,請使用rip。對於SECCOMP_RET_TRAP,請使用siginfo->si_call_addr。)不要檢查任何其他條件:將來的核心可能會改善vsyscall模擬,並且當前處於vsyscall=native模式的核心的行為會有所不同,但是在這些情況下,0xF...F600{0,4,8,C}00上的指令將不是系統呼叫。
請注意,現代系統不太可能根本使用vsyscalls - 它們是一項舊式功能,並且它們比標準syscall慢得多。新程式碼將使用vDSO,並且vDSO發出的系統呼叫與普通系統呼叫無法區分。