LWN: 5.14 中的 memfd_secret()!
關(guān)注了就能看到更多這么棒的文章哦~
memfd_secret() in 5.14
By Jonathan Corbet
August 6, 2021
DeepL assisted translation
https://lwn.net/Articles/865256/
2020 年 2 月之后,memfd_secret() 系統(tǒng)調(diào)用其實已經(jīng)在 LWN 以其他形式報道過。一開始,它是 memfd_create()的一個 flag,但這部分功能后來被移到單獨的系統(tǒng)調(diào)用中了。在這個功能發(fā)展的過程中有許多變化,但核心目的仍然是保持未變的,即允許用戶空間的進程來創(chuàng)建一個內(nèi)存區(qū)域,確保其他人(包括 kernel 本身)都無法訪問。該內(nèi)存區(qū)域可以用來存放加密密鑰,或其他類似的不能暴露給他人的數(shù)據(jù)。這個新的系統(tǒng)調(diào)用最終被合并到了即將發(fā)布的 5.14 版本中。下面我們來看看這個系統(tǒng)調(diào)用最終在 mainline kernel 中是什么樣子的。
memfd_secret()的原型如下:
int memfd_secret(unsigned int flags);
其中唯一可用的 flag 取值是 O_CLOEXEC,用來設(shè)置當(dāng)此進程后續(xù)調(diào)用 execve()時,這部分區(qū)域會被移除掉。不過,在 fork 調(diào)用之后這部分應(yīng)該保密的區(qū)域?qū)ψ舆M程仍是可以訪問的。memfd_secret() 的返回值是一個文件描述符,用來跟這個新創(chuàng)建的秘密內(nèi)存區(qū)域關(guān)聯(lián)起來。
在這個時刻,進程實際上不能訪問這部分內(nèi)存區(qū)域,因為目前甚至連 size 都還不知道。后續(xù)必須調(diào)用 ftruncate() 來設(shè)置該區(qū)域的 size,然后使用 mmap() 將該 "file" map 到屬主進程的地址空間之內(nèi)。針對這個 mapping 而分配的那些內(nèi)存 page 會被從 kernel 的 direct map 中移除掉,并被特別標(biāo)記過,防止它們后續(xù)被錯誤地重新 map 回來。此后,該內(nèi)存區(qū)域是可以被該進程訪問的,但對其他任何進程甚至包括內(nèi)核都不能訪問這一區(qū)域了。因此,這部分內(nèi)存的數(shù)據(jù)就被很好地保護了起來。很好,但這也帶來一些缺點。例如,指向這部分秘密內(nèi)存區(qū)域的指針不能用在系統(tǒng)調(diào)用里了;該內(nèi)存區(qū)域也不能用于 DMA 操作。
這項工作第一次公開發(fā)表是在 2019 年 10 月。在后續(xù)兩年不斷演進這個系統(tǒng)調(diào)用的時間里,它已經(jīng)有了不少改變,不僅僅是變成了一個單獨的系統(tǒng)調(diào)用(發(fā)生于 2020 年 7 月)而已,這是因為人們認(rèn)為此功能與普通 memfds 操作完全沒有什么共同點。在 2020 年 7 月底發(fā)布的第二版中 memfd_secret() 支持了預(yù)留一塊固定區(qū)域的 memory,但在 9 月的第 5 版中就被刪除了。
早期的 patch set 中包括一個 flag,用來要求從內(nèi)核的 direct map 中刪除這部分秘密 page。這樣就可以讓內(nèi)核無法訪問這些內(nèi)存區(qū)域了(任何可能黑入內(nèi)核的人也就無法看到這些數(shù)據(jù)了),但人們擔(dān)心把用來形成 direct mapping 的 1GB page 中間到處挖洞的話會降低性能。不過,這些擔(dān)憂已經(jīng)隨著時間的推移都消退了。實際上 2MB page 的性能表現(xiàn)與 1GB page 的性能并沒有什么差異。因此,這個 flag 在 2020 年 8 月的第 4 版中消失了,變成了必須要從 direct map 中移除。
11 月發(fā)布的第 8 版中也增加了一些改動。會使用 CMA 來專門用作 memfd_secret() 的內(nèi)存來源,而不是隨便分配一塊內(nèi)核管理的內(nèi)存了。另一個改動也一直讓大家很有爭議,它的作用是只要有一個秘密內(nèi)存區(qū)域處于 active 狀態(tài)時,就禁止系統(tǒng)的休眠(hibernation)。這樣做的目的是防止秘密數(shù)據(jù)被寫入持久性存儲設(shè)備中(persistent storage),但如果一些用戶發(fā)現(xiàn)他們的系統(tǒng)不能再休眠的話很可能會感到不滿。盡管如此,這個行為也還是進入了 5.14 內(nèi)核。
從一開始,memfd_secret()就支持一個 flag 專門用來申請 uncached mapping 來作為秘密數(shù)據(jù)區(qū)域,也就是說希望繞過 memory cache。這樣就可以提供更高的安全性,因為在 cache 中都不會再有這些數(shù)據(jù)了(畢竟 Spectre 漏洞就可以竊取 cache 內(nèi)容),但將內(nèi)存設(shè)置為 uncached 的話會大大降低性能。cache 畢竟是非常重要的。Andy Lutomirski 一再反對提供這種 uncached mapping,反對因此引入的性能降低等。RApoport 最終同意把這個 flag 刪除掉了。在第 11 版完成了刪除,此后到現(xiàn)在的最新版本為止都不再具有這個功能了。
在第 17 版(2021 年 2 月)代碼中,memfd_secret() 被默認(rèn)禁用了,并增加了一個 command-line option(secretmem_enable=),用來在系統(tǒng)啟動時啟用這個功能。這個決定是出于擔(dān)心系統(tǒng)性能可能會因為打破了 direct mapping 以及對秘密區(qū)域內(nèi)存進行 locking 操作而受到影響。所以除非系統(tǒng)管理員將其打開,否則該功能就不可用。這個版本也不再使用 CMA 進行內(nèi)存分配了。
基本上這就是最終版本的 memfd_secret() 的效果了。它已經(jīng)來到了第 23 個版本,看起來版本非常多了,但一般來說內(nèi)存管理方面的改動基本上都是這樣的。等 5.14 版本發(fā)布后,我們將能看到這個功能的用戶群體有多大,以及他們還需要哪些進一步的改動。不過目前而言,這項工作似乎最終已經(jīng)成功實現(xiàn)完畢了。有興趣的人可以參見這個 man page 草案(https://lwn.net/ml/linux-mm/[email protected]/)。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
