LWN: 放開kcmp()!
關(guān)注了就能看到更多這么棒的文章哦~
kcmp() breaks loose
By Jonathan Corbet
February 11, 2021
DeepL assisted translation
https://lwn.net/Articles/845448/
Linux 內(nèi)核實(shí)現(xiàn)了大量的系統(tǒng)調(diào)用,所以對(duì)于大多數(shù)人來(lái)說(shuō)總是會(huì)有一些不熟悉的系統(tǒng)調(diào)用,畢竟不是每個(gè)人都需要知道 setresgid()、modify_ldt()或 lookup_dcookie()的細(xì)節(jié)的。但是,哪怕是對(duì) Linux 系統(tǒng)調(diào)用列表有著非常深入了解的那些開發(fā)者,也會(huì)對(duì) kcmp()感到陌生,因?yàn)樵?kernel 編譯中缺省是關(guān)閉的。不過(guò),現(xiàn)在似乎更多人知道了 kcmp,人們也在努力讓 kcmp() 能更廣泛的使用起來(lái)。
kcmp()系統(tǒng)調(diào)用是在 2012 年添加的,目的是為了解決用戶空間中的 checkpoint/restore (CRIU) 過(guò)程中遇到的問(wèn)題。CRIU 的開發(fā)者們?cè)谂⒁幌盗羞M(jìn)程的完整狀態(tài)記錄到持久性存儲(chǔ)設(shè)備(persistent storage)中,然后在未來(lái)的某個(gè)時(shí)刻,可能在另一臺(tái)機(jī)器上重新啟動(dòng)這些進(jìn)程。這個(gè)努力已經(jīng)有了一些進(jìn)展。不過(guò)這個(gè)功能的難度往小里說(shuō),也是非常有挑戰(zhàn)性的,但 CRIU 的開發(fā)者們又給自己增加了一個(gè)額外難度:需要從用戶空間(user space)來(lái)完成整個(gè)流程。多年來(lái),人們一直在嘗試實(shí)現(xiàn) kernel-based checkpoint 機(jī)制,但沒有哪個(gè)方案是走到快要合入 kernel 的程度。所以,在 user space 實(shí)現(xiàn),似乎是解決 checkpoint/restore 任務(wù)的唯一可行方案了。
CRIU 可能會(huì)被移到到 user space 去,但內(nèi)核社區(qū)仍然需要在一些地方增加支持功能,從而讓其真正可行。例如,userfaultfd() 功能有助于進(jìn)程內(nèi)存的遷移,clone()系統(tǒng)調(diào)用的各種特性有助于重新創(chuàng)建進(jìn)程的動(dòng)作。這些輔助工具使得 checkpoint/restore 的工作真正可行了,同時(shí)還能將內(nèi)核從這個(gè)過(guò)程中的大部分工作中解放出來(lái)。
CRIU 的開發(fā)者在早期遇到的一個(gè)問(wèn)題是怎樣確定兩個(gè)已經(jīng)打開的文件描述符(可能來(lái)自不同的進(jìn)程)是否指向內(nèi)核中的同一個(gè)已打開的文件。創(chuàng)建這樣的文件描述符很容易,可以用 dup()或 clone()來(lái)完成;然后可以通過(guò)進(jìn)程間通訊的 SCM_RIGHTS datagram 來(lái)傳遞給不相關(guān)的進(jìn)程。對(duì)于 CRIU 來(lái)說(shuō),只要查看/proc 中的條目,就可以很容易地確定兩個(gè)文件描述符引用的是同一個(gè)文件,而在還原時(shí)就可以在這兩個(gè)地方按原樣再打開該文件,就能恢復(fù)出 checkpoint 創(chuàng)建時(shí)的情況。
然而,如果兩個(gè)文件描述符指向同一個(gè)打開的文件——換句話說(shuō),如果它們指向內(nèi)核內(nèi)的同一個(gè) file struct——那么在還原時(shí)如果用兩個(gè)完全不相干的描述符來(lái)替換的話就可能會(huì)破壞應(yīng)用程序。CRIU 可以正確地還原這些描述符,但前提是要能在創(chuàng)建 checkpoint 時(shí)就檢測(cè)出它們是相關(guān)的。這種檢測(cè)是當(dāng)時(shí)內(nèi)核尚未支持的。
要查詢文件描述符的出處,主要需要從內(nèi)核的內(nèi)部數(shù)據(jù)結(jié)構(gòu)來(lái)獲取這個(gè)信息,而要把這些信息提供出去就必須非常謹(jǐn)慎。早期討論過(guò)的一個(gè)想法是讓內(nèi)核將每個(gè)文件描述符背后對(duì)應(yīng)的 file struct 地址給 export 出來(lái)。如果兩個(gè)描述符顯示出相同的地址,那么它們就是指向同一個(gè)東西,重新創(chuàng)建時(shí)就要恢復(fù)這個(gè)樣子。但是內(nèi)核為了防護(hù)惡意攻擊,會(huì)將其數(shù)據(jù)結(jié)構(gòu)的地址隱藏起來(lái)。這種做法有時(shí)候很難做完美,但人們都認(rèn)為這樣做是正確的方向。所以大家不會(huì)贊同這個(gè)方案中的暴露地址的方法。
相反,開發(fā)者最終增加了一個(gè)系統(tǒng)調(diào)用來(lái)針對(duì) “這兩個(gè)描述符是一樣的嗎?” 這個(gè)問(wèn)題給出直接回答,這就是 kcmp():
int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1,
unsigned long idx2);
如果 type 是 KCMP_FILE,那么內(nèi)核將檢查 ID 為 pid1 的進(jìn)程中的文件描述符 idx1 是否與 pid2 進(jìn)程中的描述符 idx2 相同。還有其他一些 resource 也可以用同樣的方法來(lái)調(diào)用此 API,都是為了回答同一個(gè)問(wèn)題:這兩個(gè) resource 是同一個(gè)東西嗎?對(duì)于內(nèi)核來(lái)說(shuō),回答這個(gè)問(wèn)題的話比起提供 file struct 結(jié)構(gòu)地址要安全得多,但是仍然有一些限制,特別是,調(diào)用 kcmp 的進(jìn)程必須要有權(quán)限在兩個(gè)目標(biāo)進(jìn)程上使用 ptrace(),而且這幾個(gè)進(jìn)程必須都在同一個(gè) PID namespace 之內(nèi)。
盡管施加了這些限制,可是還是有一些人對(duì) kcmp() 感到不太放心。于是為了盡量避免有人利用這 kcmp() 來(lái)進(jìn)行破壞,人們就將這個(gè)系統(tǒng)調(diào)用改為只有配置了 checkpoint/restore 功能的 kernel 才會(huì)打開。這樣一來(lái)它在大多數(shù)內(nèi)核上都完全不存在,也就不能被用來(lái)攻擊這些內(nèi)核了。
但在現(xiàn)實(shí)世界中,內(nèi)核開發(fā)者對(duì)內(nèi)核配置選項(xiàng)的選擇并不能代表大多數(shù)的情況。大多數(shù)用戶運(yùn)行的內(nèi)核都是由發(fā)行商構(gòu)建的,而發(fā)行商有動(dòng)力去啟用盡可能多的功能,哪怕只有較少用戶需要這些功能。大多數(shù)人不會(huì)對(duì)內(nèi)核中不需要的代碼提出抱怨,畢竟他們可能根本不知道這些代碼在那里,但是如果他們需要的一些功能無(wú)法正常工作的話,他們肯定會(huì)抱怨。所以,雖然 checkpoint/restore 功能的用戶比較少,但發(fā)行商(例如 Fedora 和 Ubuntu)還是因?yàn)橛猩贁?shù)人需要它而將這個(gè)功能打開了。這使得 kcmp()也被廣泛地開放了出來(lái)。
如果你提供了一個(gè)功能,那么就會(huì)有人來(lái)使用它,可能是按照你沒有想到的的某種方式來(lái)使用。這個(gè)定律很準(zhǔn)。Mesa 圖形庫(kù)就為 kcmp()找到了一個(gè)與 checkpoint 無(wú)關(guān)的用法。有時(shí),該庫(kù)會(huì)發(fā)現(xiàn)自己需要處理多個(gè)指向同一底層 DRM device 的文件描述符。在這種情況下,修改其中一個(gè)文件描述符的話會(huì)影響到另一個(gè)文件描述符,而且可能會(huì)導(dǎo)致出錯(cuò)。為了避免這個(gè)問(wèn)題,Mesa 會(huì)在需要時(shí)使用 kcmp() 來(lái)進(jìn)行檢查,從而確保文件描述符沒有跟其他人公用。
當(dāng)然,只有當(dāng) kcmp() 在運(yùn)行中的內(nèi)核中確實(shí)存在時(shí),這種檢查才能正常工作。但是并不是所有的發(fā)行版都打開了這個(gè)選項(xiàng)。如果要求這些發(fā)行版為了 kcmp() 而打開 checkpoint/restore 功能的話,似乎有些矯枉過(guò)正,所以 Chris Wilson 提交了一個(gè) patch,讓 kcmp() 不再依賴 checkpoint/restore 功能就可以獨(dú)立 enable。在描述這個(gè)補(bǔ)丁的必要性時(shí),Daniel Vetter 說(shuō):
也許最開始是一個(gè)錯(cuò)誤的做法,但我們的用戶空間程序已經(jīng)開始依賴 sys_kcomp 進(jìn)行 fd 比較。所以無(wú)論是對(duì)是錯(cuò),如果你想運(yùn)行 mesa3d gl/vk stack 的話,你就需要這個(gè) kcmp。也許這并不是最聰明的想法,但是因?yàn)橛性S多發(fā)行版默認(rèn)啟用了這個(gè)功能,所以之前沒有發(fā)現(xiàn)這個(gè)問(wèn)題,而現(xiàn)在我們已經(jīng)將這個(gè)功能推廣開來(lái)了。
最初實(shí)現(xiàn)這個(gè)功能的 Michel D?nzer 進(jìn)行了辯護(hù),認(rèn)為使用 kcmp() 是個(gè)正確選擇,并對(duì)它沒有被缺省打開而感到驚訝。他也詢問(wèn)大家應(yīng)該選擇什么其他的解決方案,但沒有得到回答。Kees Cook 指出 kcmp() "是一個(gè)非常強(qiáng)大的系統(tǒng)調(diào)用",但它的使用有限制,而且既然已經(jīng)被廣泛使用了,"所以也許可以公開這個(gè)功能了"。
patch 第一個(gè)版直接默認(rèn)啟用了 kcmp(),但哪怕這不會(huì)引入任何已知安全問(wèn)題,這么做也違背了通常的原則。所以,到了第三版就默認(rèn)改為 "no" 了。不過(guò),如果啟用了 checkpoint/restore 或 graphics,那么這個(gè)系統(tǒng)調(diào)用就 enable,這意味著在大多數(shù)內(nèi)核上都缺省可用 kcmp() 了。不出意外的話這個(gè) patch 會(huì)被合并到 5.12 中。如果沒能趕上的話,發(fā)行商也可能會(huì)將它 backport 移植到舊版內(nèi)核上。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長(zhǎng)按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
