LWN:seccomp用戶空間通知功能以及信號(hào)處理!
關(guān)注了就能看到更多這么棒的文章哦~
Seccomp user-space notification and signals
By Jonathan Corbet
April 9, 2021
DeepL assisted translation
https://lwn.net/Articles/851813/
seccomp()機(jī)制允許增加一個(gè) filter program 過濾程序(以 "classic "BPF 的形式),來對(duì)是否允許進(jìn)程調(diào)用某個(gè)系統(tǒng)調(diào)用做出決定。user-space notification 功能可以把決定權(quán)進(jìn)一步交給另一個(gè)進(jìn)程來做。正如 Sargun Dhillon 最近的 patch set 所示,用戶空間通知仍然有一些沒有處理完美的場景,特別是在涉及到 signal 之時(shí)。這個(gè) patch 進(jìn)行了一個(gè)簡單改動(dòng),希望能解決一個(gè)相當(dāng)復(fù)雜的問題。這個(gè)問題是 Go 語言的搶占模式(preemption model)的改動(dòng)引出的。
通??梢允褂?seccomp() 來作為一種簡單的縮減攻擊面的方式,從而使得大部分系統(tǒng)調(diào)用對(duì)于目標(biāo)進(jìn)程來說都可以不受限制地使用。用戶空間通知這個(gè)功能也可以用在這種情況下,但它的主要目的其實(shí)是另一種情況:允許那個(gè)監(jiān)控進(jìn)程來模擬完成目標(biāo)進(jìn)程的系統(tǒng)調(diào)用。舉例來說,某個(gè)容器管理器(container manager)希望在容器內(nèi)提供 mount() 功能,但需要嚴(yán)格限制可以 mount 哪些東西。這時(shí)就可以利用用戶空間通知功能,來允許(擁有特權(quán)的,也就是 privileged)監(jiān)控進(jìn)程來真正進(jìn)行這個(gè)它同意進(jìn)行的 mount 動(dòng)作,并將結(jié)果返回給目標(biāo)進(jìn)程。
當(dāng)監(jiān)控進(jìn)程在處理一個(gè)攔截下來的系統(tǒng)調(diào)用時(shí),目標(biāo)進(jìn)程會(huì)被阻塞在內(nèi)核中,一直等待返回結(jié)果。不過如果該進(jìn)程收到一個(gè) signal(信號(hào)),那么就會(huì)終止這個(gè)等待狀態(tài),立即對(duì)該 signal 做出響應(yīng)。如果該信號(hào)本身不是一個(gè) fatal signal,那么可能會(huì)導(dǎo)致系統(tǒng)調(diào)用返回一個(gè) EINTR 錯(cuò)誤給此進(jìn)程。并且,監(jiān)控進(jìn)程并不知道這個(gè) signal,于是它后面還是會(huì)試圖給內(nèi)核提供對(duì)原來那個(gè)系統(tǒng)調(diào)用的處理結(jié)果。此時(shí),它會(huì)得到一個(gè) ENOENT 錯(cuò)誤,表明之前等待的進(jìn)程已經(jīng)不存在了。
這會(huì)帶來一些不方便之處,尤其是當(dāng)監(jiān)控進(jìn)程已經(jīng)替目標(biāo)進(jìn)程執(zhí)行了某些耗費(fèi)很多時(shí)間的任務(wù)的情況下。如果該 signal 沒有殺死目標(biāo)進(jìn)程,那么很可能不久之后還會(huì)重試,導(dǎo)致一些額外的工作。不過大多數(shù)時(shí)候,在 seccomp() 監(jiān)控之下運(yùn)行的程序中很少會(huì)有這類 non-fatal signal。
Go signal
準(zhǔn)確地說,這是過去的事實(shí)情況,但 Go 語言的開發(fā)者們也有責(zé)任。Go 語言的 "goroutine" 輕量級(jí)線程模型(lightweight thread model)要求 Go runtime(運(yùn)行時(shí))來處理 schedule 工作,也就是根據(jù)需要在 goroutine 之間切換確保它們都有機(jī)會(huì)運(yùn)行。除此之外,偶爾還會(huì)出現(xiàn) "stop the world" 的情況,也就是所有的 goroutine 都被暫停,從而讓垃圾收集器(garbage collector)完成工作。之前實(shí)現(xiàn)這個(gè)功能的具體方式是讓編譯器在每個(gè)函數(shù)的開頭來增加一個(gè) preemption check 來處理。
但是,如果一個(gè) goroutine 運(yùn)行了很長時(shí)間卻一直沒有調(diào)用任何函數(shù),那么會(huì)發(fā)生什么?如果該 routine 在某個(gè)非常嚴(yán)格的循環(huán)中執(zhí)行的話就可能會(huì)發(fā)生這種情況。最壞情況下,比如這個(gè) goroutine 可能一直在 spin 等待一個(gè)鎖,導(dǎo)致鎖的持有者(另一個(gè) goroutine)都沒有機(jī)會(huì)運(yùn)行,也就無法釋放這個(gè)鎖,這種情況往往會(huì)導(dǎo)致用戶不滿。另一種也會(huì)導(dǎo)致 goroutine 之間的 preemption 的情況,就是執(zhí)行那些耗時(shí)很長的系統(tǒng)調(diào)用。
Go 開發(fā)者們已經(jīng)嘗試了一些方法來解決這個(gè)問題。其中一種就是在代碼向回跳轉(zhuǎn)(backward jump)的地方插入 preemption check(例如在某個(gè)循環(huán)的末尾)。但是哪怕將檢查代碼減少到只有一條指令,這帶來的性能損失也還是太高了。這種方法對(duì)于那些耗時(shí)很長的系統(tǒng)調(diào)用場景也沒有幫助。所以 Go 社區(qū)決定用一種非合作性的搶占機(jī)制(non-cooperative preemption mechanism)來解決這個(gè)問題。簡單來說,任何一個(gè)運(yùn)行了 10ms 而沒有 yield(讓出)過的 goroutine 都會(huì)收到 runtime 發(fā)出的 SIGURG signal,然后 Go runtime 運(yùn)行時(shí)會(huì)重新安排一個(gè)線程來執(zhí)行、啟動(dòng)垃圾回收、或者做其他什么需要在此時(shí)做的工作。
而通過 seccomp()轉(zhuǎn)給另一個(gè)進(jìn)程執(zhí)行的系統(tǒng)調(diào)用往往會(huì)比平時(shí)運(yùn)行的時(shí)間更長長,而監(jiān)控進(jìn)程可能在執(zhí)行的相應(yīng)任務(wù)(例如 mount 文件系統(tǒng))可能需要更加長的時(shí)間。顯然,這導(dǎo)致了 Go 程序中大量的以 seccomp()轉(zhuǎn)發(fā)出去的系統(tǒng)調(diào)用被打斷,人們當(dāng)然希望找到一種方法來避免這些中斷事件。
Masking non-fatal signals
為了解決這個(gè)問題,Dhillon 的 patch set 為 SECCOMP_IOCTL_NOTIF_RECV(監(jiān)控進(jìn)程接受通知會(huì)用這個(gè))ioctl() 增加了一個(gè)新的 flag(SECCOMP_USER_NOTIF_FLAG_WAIT_KILLABLE)。如果在向監(jiān)督進(jìn)程發(fā)出通知時(shí)設(shè)置了這個(gè) flag,目標(biāo)進(jìn)程就會(huì)進(jìn)入 "killable" wait 狀態(tài),這意味著 fatal signal 仍可以被正常傳遞處理,但任何其他 signal 都會(huì)被屏蔽掉,直到監(jiān)控進(jìn)程處理完成之后再說。因此,non-fatal signal 將不會(huì)再打斷系統(tǒng)調(diào)用(此時(shí)監(jiān)控進(jìn)程正在處理這些系統(tǒng)調(diào)用)。
請(qǐng)注意,如果在監(jiān)控進(jìn)程收到通知之前,就出現(xiàn)了一個(gè) non-fatal signal 到達(dá),那么目標(biāo)進(jìn)程的這個(gè)系統(tǒng)調(diào)用仍將像從前一樣被打斷。此時(shí)這個(gè) seccomp 通知將會(huì)被取消掉,如果監(jiān)控進(jìn)程試圖去讀取該通知的話,會(huì)收到一個(gè) error。在這種情況下,最終結(jié)果就像系統(tǒng)調(diào)用一開始就沒有發(fā)生過一樣。不過,一旦通知被送達(dá),系統(tǒng)調(diào)用就會(huì)得到執(zhí)行,并一直運(yùn)行到結(jié)束。這個(gè)改動(dòng)相對(duì)較小,它解決了這個(gè)問題,盡管這個(gè)解決方案是在使用 seccomp()和用戶空間通知功能時(shí)可能會(huì)給 Go 的搶占機(jī)制增加不確定時(shí)長的 delay 為代價(jià)的。delay 其實(shí)本來也正是搶占機(jī)制想要預(yù)防的,但這個(gè) delay 至少是在監(jiān)控進(jìn)程的控制之下,而且應(yīng)該不會(huì)是無限延遲。
截至目前,這組 patch set 已經(jīng)發(fā)布了兩次,它沒有收到多少回應(yīng)。這可能表明,到目前為止很少有人看過它,這對(duì)于用戶空間 API 的安全相關(guān)的改動(dòng)來說,不是一個(gè)好兆頭。在得到更多 review 之前,這項(xiàng)工作不太可能取得進(jìn)展,使用 seccomp()和用戶空間通知功能的 Go 用戶也將繼續(xù)忍受當(dāng)前的困擾。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
