Try   HackMD

Linux 核心原始程式碼巨集: __randomize_layout

資料整理: jserv

Linux 核心不僅是個作業系統的核心,其涉及的領域之廣,可說是整個資訊科技領域的縮影。Linux 核心原始程式碼的 __randomize_layout 巨集反映出資訊安全和編譯器設計的議題,本文嘗試探討該巨集的緣由及 Linux 核心的 GCC plugin 的整合機制。

資訊安全已是現代作業系統設計的首要挑戰

Linux 核心已在超級電腦、伺服器、消費性電子產品,和工業控制等等領域獲得巨大成功,伴隨著盛名而來的,是各式資訊安全的挑戰。Kernel Self Protection Project (KSPP) 著眼於從本質層面消弭 Linux 核心的資訊安全疑慮,並彙整 PaXGrsecurity 團隊提出的強化資訊安全成果。

ASLR 全名是 Address Space Layout Randomization,是種安全防禦機制,由 PaX 團隊於 2001 年正式提出,並於 2005 年導入到 Linux 核心之中,其他主流作業系統也採納 ASLR 機制。ASLR 能在每次運作可執行檔案時,藉由基底址隨機對應的方式來為其隨機配置地址空間,從而防止需要瞭解記憶體地址,來利用記憶體崩潰漏洞的攻擊行為。

2009 年,Google 安全團隊在 CanSecWest,以〈Linux ASLR Curiosities〉為題,探討 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 巨集

攻擊者可能會利用結構體屬性的記憶體佈局發起攻擊,考慮以下程式碼:

struct foo {
    int val;
     int *important_fn(void *args);
} global_var;

展示用的攻擊者:

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 標頭檔:

#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) 使用,我們聚焦在 RANDSTRUCT 巨集,這由 Linux 核心建構系統 Kbuild 解析 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,後者以可載入的 GCC 模組形式,讓 GCC 得以在編譯過程中調整其行為,這樣可施加靜態分析和置入特定的程式碼,示意如下:

RANDSTRUCT 來說,就是藉由 GCC plugins 去變更指定 C 程式結構體的記憶體佈局。最初針對 GCC,後來 LLVM/Clang 也支援該機制,這也是前述 Kernel Self Protection Project 著重從本質上強化 Linux 核心抵禦攻擊的基礎建設。這個 GCC plugin 具備以下三個主要功能:

  • 任何標記有 __randomize_layout (實際是 __attribute__((randomize_layout))) 的結構體都將隨機佈局。
  • 一旦編譯時啟用 RANDSTRUCT,對於所有成員屬性均為函式指標的結構體,隨機佈局會自動開啟
  • 可使用 __no_randomize_layout 關閉隨機佈局

你或許會好奇,攻擊者不能用 offsetof 來計算偏移量,從而破解隨機佈局機制嗎?然而 offsetof 是編譯時期的機制,但攻擊者針對編譯後的二進位內容,當然無法使用 offsetof。

使用案例 (取自 include/linux/mount.h):

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;

考慮以下的結構體:

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