--- tags: LINUX KERNEL, LKI --- # Linux 核心原始程式碼巨集: `__randomize_layout` > 資料整理: [jserv](https://wiki.csie.ncku.edu.tw/User/jserv) :::success Linux 核心不僅是個作業系統的核心,其涉及的領域之廣,可說是整個資訊科技領域的縮影。Linux 核心原始程式碼的 `__randomize_layout` 巨集反映出資訊安全和編譯器設計的議題,本文嘗試探討該巨集的緣由及 Linux 核心的 GCC plugin 的整合機制。 ::: ## 資訊安全已是現代作業系統設計的首要挑戰 Linux 核心已在超級電腦、伺服器、消費性電子產品,和工業控制等等領域獲得巨大成功,伴隨著盛名而來的,是各式資訊安全的挑戰。[Kernel Self Protection Project](https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project) (KSPP) 著眼於從本質層面消弭 Linux 核心的資訊安全疑慮,並彙整 [PaX](https://pax.grsecurity.net/) 及 [Grsecurity](https://grsecurity.net/) 團隊提出的強化資訊安全成果。 ASLR 全名是 Address Space Layout Randomization,是種安全防禦機制,由 [PaX](https://pax.grsecurity.net/) 團隊於 2001 年正式提出,並於 2005 年導入到 Linux 核心之中,其他主流作業系統也採納 ASLR 機制。ASLR 能在每次運作可執行檔案時,藉由基底址隨機對應的方式來為其隨機配置地址空間,從而防止需要瞭解記憶體地址,來利用記憶體崩潰漏洞的攻擊行為。 2009 年,Google 安全團隊在 CanSecWest,以〈[Linux ASLR Curiosities](https://www.cr0.org/paper/to-jt-linux-alsr-leak.pdf)〉為題,探討 Linux ASLR 其實沒想像中強健,該演講展示藉由 `/proc/[pid]/stat` 及 `/proc/[pid]/wchan` 來獲取標的行程的 IP/SP 等資訊,從而讓攻擊者得以重建標的行程的定址空間佈局。針對 Linux ALSR 的攻擊手法則持續推陳出新,Linux 核心當然也要有所反制,僅在 Linux 核心原始程式碼中,以 `find -name 'Kconfig*'' | xargs grep -i randomize` 就可找到數十處在各層面強化記憶體地址隨機性的變革,這背後就充斥著 `__randomize_layout` 巨集的身影。 ## `__randomize_layout` 巨集 攻擊者可能會利用結構體屬性的記憶體佈局發起攻擊,考慮以下程式碼: ```c struct foo { int val; int *important_fn(void *args); } global_var; ``` 展示用的攻擊者: ```c extern int *malicious_fn(void *args); memcpy((void *)global_var + 4, malicious_fn); // 此時 global_var 中的 important_fn 指標已被覆蓋, // 指向攻擊者的 malicious_fn; // 系統的後續程式碼: global_var->important_fn(args); // 導致惡意函式被呼叫,遭到攻擊 ``` 為避免這種攻擊,Linux 引入 `__randomize_layout` 巨集,讓編譯器將結構體內屬性的記憶體佈局隨機打亂,至於如結果如何打亂,由 seed 唯一確定。 `__randomize_layout` 巨集定義於 Linux 核心原始程式碼的 [include/linux/compiler_types.h](https://elixir.bootlin.com/linux/v6.4.11/source/include/linux/compiler_types.h#L293) 標頭檔: ```c #if defined(RANDSTRUCT) && !defined(__CHECKER__) # define __randomize_layout __designated_init __attribute__((randomize_layout)) # define __no_randomize_layout __attribute__((no_randomize_layout)) /* This anon struct can add padding, so only enable it under randstruct. */ # define randomized_struct_fields_start struct { # define randomized_struct_fields_end } __randomize_layout; #else # define __randomize_layout __designated_init # define __no_randomize_layout # define randomized_struct_fields_start # define randomized_struct_fields_end #endif ``` 其中 `__CHECKER__` 巨集是搭配 Linux 核心靜態分析器 ([sparse](https://www.kernel.org/doc/html/latest/dev-tools/sparse.html)) 使用,我們聚焦在 `RANDSTRUCT` 巨集,這由 Linux 核心建構系統 [Kbuild](https://docs.kernel.org/kbuild/kbuild.html) 解析 `security/Kconfig.hardening` 的選項,部分內容如下: ``` config CC_HAS_RANDSTRUCT def_bool $(cc-option,-frandomize-layout-seed-file=/dev/null) ... choice prompt "Randomize layout of sensitive kernel structures" default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) default RANDSTRUCT_NONE help If you enable this, the layouts of structures that are entirely function pointers (and have not been manually annotated with __no_randomize_layout), or structures that have been explicitly marked with __randomize_layout, will be randomized at compile-time. This can introduce the requirement of an additional information exposure vulnerability for exploits targeting these structure types. ``` 從描述中,我們可知,這項安全強化機制需要搭配編譯器,特別是 [GCC plugins](https://gcc.gnu.org/wiki/plugins),後者以可載入的 GCC 模組形式,讓 GCC 得以在編譯過程中調整其行為,這樣可施加靜態分析和置入特定的程式碼,示意如下: ![](https://hackmd.io/_uploads/BkQAA_g6n.png) 以 `RANDSTRUCT` 來說,就是藉由 GCC plugins 去變更指定 C 程式結構體的記憶體佈局。最初針對 GCC,後來 [LLVM/Clang 也支援該機制](https://www.phoronix.com/news/Linux-5.19-Hardening),這也是前述 [Kernel Self Protection Project](https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project) 著重從本質上強化 Linux 核心抵禦攻擊的基礎建設。這個 GCC plugin 具備以下三個主要功能: * 任何標記有 `__randomize_layout` (實際是 `__attribute__((randomize_layout))`) 的結構體都將隨機佈局。 * 一旦編譯時啟用 RANDSTRUCT,對於所有成員屬性均為函式指標的結構體,隨機佈局會自動開啟 * 可使用 `__no_randomize_layout` 關閉隨機佈局 你或許會好奇,攻擊者不能用 [offsetof](https://hackmd.io/@sysprog/linux-macro-containerof) 來計算偏移量,從而破解隨機佈局機制嗎?然而 `offsetof` 是編譯時期的機制,但攻擊者針對編譯後的二進位內容,當然無法使用 offsetof。 使用案例 (取自 [include/linux/mount.h](https://elixir.bootlin.com/linux/v6.4.11/source/include/linux/mount.h#L75)): ```c struct vfsmount { struct dentry *mnt_root; /* root of the mounted tree */ struct super_block *mnt_sb; /* pointer to superblock */ int mnt_flags; struct mnt_idmap *mnt_idmap; } __randomize_layout; ``` 考慮以下的結構體: ```c struct foo { u32 a; /* 4-byte hole */ u64 b; u64 c; } ``` 一旦經由 `__randomize_layout` 巨集和 GCC plugins 的處理後,可能會隨機產生以下 6 種記憶體佈局 (為了比較的便利,省去 `struct foo`): * `{ u32 a; /* 4-byte */ u64 b; u64 c; };` (原本樣貌) * `{ u32 a; /* 4-byte */ u64 c; u64 b; };` (交換 b 和 c 的地址) * `{ u64 b; u32 a; /* 4-byte */ u64 c; };` (交換 a 和 b 的地址) * `{ u64 b; u64 c; u32 a; /* 4-byte */ };` * `{ u64 c; u32 a; /* 4-byte */ u64 b; };` * `{ u64 b; u64 c; u32 a; /* 4-byte */ };` ## 採納的狀況 前述 RANDSTRUCT 會隨機變更核心結構體內各個欄位的順序,只對包含函式指標的結構或者那些明確用 `__randomize_layout` 巨集標記過的結構體有效。 `scripts/gcc-plugins/randomize_layout_plugin.c` 對應的 GCC plugins 有以下版本: * 限制的版本只會變更同一 cacheline 中的多個成員之間的順序,這降低性能損耗,但也降低保護的強度 * 完整版本則是儘量提高隨機的程度 由於維護核心模組的額外成本 (發行者要公開自己使用的 seed,允許第三方核心模組在開啟的 `RANDSTRUCT` 核心上運行)、性能和相容性的議題,若干 Linux 系統發行商 (distribution) 不傾向啟用。實際上能夠 `RANDSTRUCT` 受益的,主要是 Google, Meta, Amazon 這類雲端服務提供商。