Jump to...
redirecting...

Log for Ubuntu 台灣社群

Linux 內部機制: /proc/self/mem 如何寫入不可寫記憶體(2021) (★ 102 分)

這篇文章解析 Linux 的 /proc/<pid>/mem(例如 /proc/self/mem)一個不太為人知、但刻意保留的「穿透式寫入」行為:透過這個偽檔案寫入目標位址時,即使該虛擬記憶體頁面被標記為不可寫(例如唯讀,甚至是 libc 的可執行程式碼頁),寫入仍可能成功。作者用範例程式示範:先 mmap 一頁 PROT_READ 的唯讀記憶體,再用 /proc/self/mem 以「檔案位移=位址」的方式寫入資料,最後更直接把 libc 的 getchar(C 標準函式庫)程式碼區段改寫成 0xcc(x86-64 常見的軟體斷點指令),讓後續呼叫 getchar 時觸發 SIGTRAP(除錯陷阱訊號),證明連不可寫的可執行頁也能被「打補丁」。作者也提到這種行為被 Julia 的 JIT(Just-In-Time,即時編譯器)與 rr(Linux 錄製/重播除錯器)等專案實際利用。

接著文章把問題拉回硬體層:在 x86-64 上,CPU 透過 MMU(Memory Management Unit,記憶體管理單元)與控制位元影響核心對記憶體的存取,其中包含 CR0.WP(Write Protect,寫入保護;決定特權模式能否寫入唯讀頁)與 CR4.SMAP(Supervisor Mode Access Prevention,核心模式存取防護;用來阻止核心直接讀寫使用者空間以降低攻擊面)。Linux 通常在開機就啟用 CR0.WP,因此核心若「直接」寫唯讀頁會引發頁面錯誤;但作者強調,這類限制多半只是路障,因為核心掌控虛擬記憶體機制,本來就有其他路徑能達到同樣效果。

關鍵在 /proc/*/mem 的實作:寫入流程會走到 mem_rw(),再呼叫 access_remote_vm();它先用 get_user_pages_remote() 走訪頁表,把目標虛擬位址對應到實際的實體頁框,並透過 FOLL_FORCE(強制忽略寫入權限檢查的旗標)讓「不可寫」的 VMA(virtual memory area,虛擬記憶體區段)也能被釘住並繼續處理。同時它還會模擬頁面錯誤來處理 Copy-on-Write(CoW,寫入時複製):若寫入的是私有映射(像行程內的 libc),就會先複製頁面,確保改寫只影響該行程。之後核心用 kmap() 把該實體頁框映射進核心自己的線性映射區,套用 PAGE_KERNEL 的可讀寫權限,最後 copy_to_user_page() 幾乎就是對這個「核心端可寫映射」做 memcpy。作者藉此總結:記憶體權限是綁在「用哪個虛擬位址去存取」而非綁在實體頁框;核心只要換個映射方式,就能繞開使用者空間那套不可寫限制。

在 Hacker News 的討論裡,有人用一句話濃縮成「核心維護自己的線性映射,所以看起來像繞過保護」,也有人提醒這不只是「核心想寫就寫」的老生常談,更值得注意的是:使用者空間程式若能使用 /proc/self/mem,就可能藉此改寫原本應該維持 R^X(Read XOR Execute,只讀與可執行不可同時可寫)性質的頁面,影響安全模型。也有留言補充作者文中提到的硬體機制其實不只兩個:像 MPK(Memory Protection Keys,記憶體保護金鑰)、PKS(Protection Keys for Supervisor,核心用保護金鑰)、IOMMU(I/O Memory Management Unit,I/O 記憶體管理單元)、虛擬化下的 EPT/NPT(Extended/Nested Page Tables,第二層頁表),以及 AMD SEV(Secure Encrypted Virtualization)、Intel SGX(Software Guard Extensions)與 TDX(Trust Domain Extensions)等,都可能在特定威脅模型下限制「連核心都不該碰」的記憶體;但也有人反駁雲端的「主機被攻破也碰不到 VM 記憶體」承諾仍常受重開機與實體存取影響。另有技術勘誤指出:程式碼仍會檢查 VM_MAYWRITE(允許成為可寫的權限),也就是映射必須是「理論上可改成可寫」的那種,並非任何頁面都能無條件被打穿。

👥 22 則討論、評論 💬
https://news.ycombinator.com/item?id=47302463