LWN: splice() 以及 set_fs() 的殘留印記!
關(guān)注了就能看到更多這么棒的文章哦~
splice() and the ghost of set_fs()
By Jonathan Corbet
May 26, 2022
DeepL assisted translation
https://lwn.net/Articles/896267/
內(nèi)核開發(fā)有一個通常規(guī)則,就是不允許導(dǎo)致用戶空間出現(xiàn) regression。那些會破壞以前能正常工作的應(yīng)用程序的 patch 必須要被 fix 或 revert。不過也有例外,比如?https://git.kernel.org/linus/36e2c7421f02?這個自從合入 5.10 之后就經(jīng)常有報出 regression 問題的 patch。這里背后的故事正好展示了人們在穩(wěn)定性、避免出現(xiàn)安全問題、以及代碼清理這幾個目標(biāo)發(fā)生沖突的時候會導(dǎo)致什么樣的結(jié)果。
set_fs() 函數(shù)在 Linux kernel 很早期已經(jīng)支持了。它不是在最初的 0.01 版本中,而是在 1991 年末的 0.10 版本之前被添加進(jìn)來的。通常,那些希望訪問用戶空間內(nèi)存的內(nèi)核代碼如果嘗試訪問內(nèi)核空間,就會報出 error;比如可以利用這個限制來阻止攻擊者通過系統(tǒng)調(diào)用訪問內(nèi)核內(nèi)存。在確有需要的時候可以調(diào)用 set_fs(KERNEL_DS) 可以解除限制。set_fs() 的一個常見使用場景就是可以用來從內(nèi)核中執(zhí)行文件 I/O。調(diào)用 set_fs(USER_DS) 會重新加以限制。
set_fs() 的問題在于,很容易忘記再次調(diào)用 set_fs() 來恢復(fù)對內(nèi)核空間的保護(hù),這就會馬上出現(xiàn)那種內(nèi)核開發(fā)人員通常會努力避免的“全面被攻破”的場景。多年來人們已經(jīng) fix 了許多這種類型的錯誤,但很明顯,真正的解決方案還是完全擺脫 set_fs(),并在確有需要的時候采用更安全的方式訪問內(nèi)核內(nèi)存。
開發(fā)人員(尤其是 Christoph Hellwig)在 2020 年更加關(guān)注這個目標(biāo)了,他在堅定地推動完全消除 set_fs() 的工作。盡管 set_fs() 基礎(chǔ)架構(gòu)的最后部分是在 5.18 中刪除的,但他的大部分工作在 5.10 的時候就合入了。然而,在 2020 年的時候出現(xiàn)了一個問題,引發(fā)了關(guān)于該如何處理 splice() 的討論。
splice() 系統(tǒng)調(diào)用會將打開的文件描述符跟 pipe (管道)連接起來,然后在數(shù)據(jù)流持續(xù)不斷的情況下在兩者之間移動數(shù)據(jù)。這些搬移動作完全發(fā)生在內(nèi)核中,可以用來消除大量不必要的系統(tǒng)調(diào)用;在某些使用環(huán)境中,可以提供顯著的性能改進(jìn)。就其本質(zhì)而言,splice() 通常必須將數(shù)據(jù)移入和移出內(nèi)核空間中的 buffer。為了實現(xiàn)這個目標(biāo),它就使用了 set_fs()。
Hellwig 為了在沒有 set_fs() 的情況下仍能讓 splice() 正常工作,提出了一個新的實現(xiàn),但 Linus Torvalds 拒絕了它,說他認(rèn)為這個實現(xiàn)“太復(fù)雜和混亂”了。但他也明確表示,他覺得根本不需要保證 splice() 機(jī)制可以繼續(xù)正常使用。他認(rèn)為在大多數(shù)讓 splice() 在大多數(shù)文件類型上默認(rèn)可用就會導(dǎo)致許多安全問題。例如,在 2020 年晚些時候,他說:
我寧愿盡可能地限制 splice(以及 kernel_read, 基于同樣的理由)。原本允許到處使用它就是一個錯誤,現(xiàn)在它又回來給我們造成麻煩了。
所以我寧愿讓人們注意到這些奇怪的 corner case 并逐個 fix 掉,而不是僅僅說 “什么都可以保證”。
因此,進(jìn)入 5.10 的 patch 最終破壞了 splice() 對于沒有明確支持新的處理方式的類型的文件的支持;人們的想法是隨著時間的推移,那些重要的使用場景會被注意到并得以解決。這確實發(fā)生了。如果看一下所有那些專門為了禁用 splice() 支持而進(jìn)行 fix 所提交的 patch 列表,就可以看到針對 AFS 文件系統(tǒng)、9p 文件系統(tǒng)、orangefs 文件系統(tǒng)、/proc/mountinfo、TTY 子系統(tǒng)、kernfs、sendfile()、 nilfs2 文件系統(tǒng)和 JFFS2 文件系統(tǒng)的改動。
最近,Jens Axboe 報告說 splice() 不再適用于 /dev/random 或 /dev/urandom 了。他還提供了一個 patch 來解決這個問題。這些 patch 后來由隨機(jī)數(shù)生成器的維護(hù)者 Jason Donenfeld 重新設(shè)計,并在 5.19 合并窗口期間應(yīng)用于 mainline。在此過程中,Donenfeld 觀察到這個必須要做的改動導(dǎo)致 /dev/urandom 被讀取時的性能下降約 3%。于是他詢問這個 fix 是否是必須的?在一番討論中,Axboe 給了他上了關(guān)于 regression 的一課:
如果您有一個使用 slice 來讀取 /dev/urandom 的應(yīng)用程序,那么確實會合理地期望可以繼續(xù)安全地使用這種機(jī)制。如果我們在內(nèi)核中有一個核心原則,那就是用戶應(yīng)該始終能夠?qū)?nèi)核進(jìn)行升級,而不用擔(dān)心在用戶空間 ABI 方面出現(xiàn)違背情況。顯然,有時會發(fā)生這種情況,但我認(rèn)為這正是不應(yīng)該發(fā)生的導(dǎo)致用戶 ABI 失效的一個典型案例。我們?nèi)∠巳藗兯蕾嚨墓δ堋?br>
確實這是一個會導(dǎo)致出問題的改動,但是這個案例里面,人們知道會導(dǎo)致這種問題的情況下仍然決定做這個修改。Hellwig 在回應(yīng) Axboe 的 patch set 時說,“與我最初的擔(dān)心相比,后來看下來影響實際上并沒有那么糟糕”,但仔細(xì)閱讀上述 fix list 的話可能會有不同結(jié)論。
刪除 set_fs() 是內(nèi)核開發(fā)過程可以做什么的一個典型案例。一個從一開始就深深嵌入內(nèi)核的底層結(jié)構(gòu)的基本功能被替換為更安全的替代方案,而不會打亂 kernel 每 9 或 10 周進(jìn)行一次發(fā)布的穩(wěn)定步伐。然而,這種變化導(dǎo)致的源源不斷的 regression 并不是該項目的初衷??梢钥隙?,這種 regression 后續(xù)還會出現(xiàn)。
基于 splice() 系統(tǒng)調(diào)用的過去歷史,出于對安全問題的恐懼,人們決定了采取這條道路。如果這些擔(dān)心仍然是有道理的(他們很可能是有道理的;比如 splice() 就是今年早些時候報告的“dirty pipe” 漏洞中的一個要素),那么拒絕讓所有現(xiàn)有的 splice() 使用位置在不用 set_fs() 的情況下的正常工作,也許會阻止我們看到一些比當(dāng)前情況更嚴(yán)重的 regression 問題。必須 fix 文件系統(tǒng),這個工作是很煩人,如果不得不為未來某個臭名昭著的漏洞再忍受另一次安全問題導(dǎo)致的混亂,那就更加煩人了。
沒有辦法知道后續(xù)事情是否會如此發(fā)展。但確實這種類型的插曲使得內(nèi)核的“no regressions”規(guī)則看起來更像僅僅是一個指導(dǎo)方針了。不需要太多這種案例,就會對 kernel project 的聲譽(yù)帶來嚴(yán)重破壞,甚至仍人們無法再相信它。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
