---
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 得以在編譯過程中調整其行為,這樣可施加靜態分析和置入特定的程式碼,示意如下:

以 `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 這類雲端服務提供商。