内存错误检测工具——kfence工作原理分析
一、功能介紹
Linux 5.13引入一個新的內存錯誤檢測工具:KFENCE(Kernel Electric-Fence,內核電子柵欄)。KFENCE是一個低開銷的、基于采樣的內存錯誤檢測工具。KFENCE檢測越界訪問、釋放后使用和非法釋放(包括重復釋放和釋放的起始地址不是分配的起始地址)這3種錯誤。
KFENCE和KASAN是互補的。KASAN可以檢測KFENCE支持的所有缺陷種類。KASAN依靠編譯器插樁,對每個內存訪問都檢查地址的合法性,更精確,但是導致內核的性能下降,所以KASAN只適合測試環境。KFENCE使用采樣的方法,犧牲了精度,但是性能開銷幾乎為零,它被設計為在產品內核中使用,發現在測試環境中測試用例沒有執行的代碼路徑中的缺陷。
目前只有x86_64和ARM64兩種架構支持KFENCE。
簡要對比kasan,一目了然:
- 敗在屬于概率檢測,不是所有的異常訪問都能抓取。
- 勝在對系統的開銷很小,可以直接在生產環境中使用。
二、使用說明
1. 開啟kfence
- 內核功能宏
- HAVE_ARCH_KFENCE
- KFENCE:kfence總的開關
- 依賴于HAVE_ARCH_KFENCE
- KFENCE_STATIC_KEYS
- KFENCE_SAMPLE_INTERVAL:默認采樣間隔,是指多久之后,guard page可以回收嗎?
- KFENCE_NUM_OBJECTS:kfence obj的個數,kfence obj主要用于guard page
- KFENCE_STRESS_TEST_FAULTS
- 起機內核參數
- kfence.sample_interval:當值為0時,禁用kfence;當值大于0時,啟動kfence
- 檢測結果
2. 技術原理
KFENCE使用一個固定長度的內存池,如圖2.1所示。配置宏CONFIG_KFENCE_NUM_OBJECTS指定對象的數量。每個對象需要2頁,一頁用來存放對象自身,另一頁用作警戒頁(guard page)。對象頁和警戒頁交替出現,每個對象頁被兩個警戒頁包圍。內存池的長度是“(對象數量 + 1)× 2 ×頁長度”。第1頁不是必需的,增加這一頁是因為分配偶數個物理頁可以簡化把對象頁地址轉換為對象索引的計算。
在采樣間隔到期以后,下一次從SLAB分配器(或者SLUB分配器)分配內存的時候,從KFENCE內存池分配一個對象(只支持分配長度不超過一頁),如果內存池用完了,那么返回空指針,由SLAB分配器分配。
周期采樣:
- 因為kfence object個數有限,當前實現采用定時采樣的方式
- 這里所謂的采樣就是每間隔kfence.sample_interval,允許進行分配一個kfence object進行檢測
redzone: - 若實際申請大小小于PAGE_SIZE,那意味著內存頁實際是有部分是未分配的。通過將內存頁未分配部分填充為redzone,來實現單個頁表里的改寫
- 在申請內存時,根據meta->addr和meta->size,將未分配的部分填充為KFENCE_CANARY_PATTERN
- 在釋放內存時,檢測未分配部分的內容是否為KFENCE_CANARY_PATTERN,不是則報錯
如果訪問對象的時候越界訪問到警戒頁,那么觸發頁錯誤異常。在頁錯誤異常處理程序里面,KFENCE攔截頁錯誤異常,報告一個越界訪問,如果開啟了“panic_on_warn”(通過內核啟動參數“panic_on_warn”開啟,或者執行命令“echo 1 > /proc/sys/kernel/panic_on_warn”開啟),那么重啟設備,否則把正在訪問的警戒頁設置為可以訪問,讓出錯的代碼繼續執行。
為了檢測出在對象頁里面的越界寫,KFENCE使用紅色區域。對象頁有2種布局,如下。
(1)如圖2.2所示,對象在對象頁的前半部分,紅色區域在對象頁的后半部分。這種布局有利于檢測左越界,如果向左越界訪問左邊的警戒頁,就會觸發頁錯誤異常。
圖2.2對象在對象頁的前半部分
(2)如圖2.3所示,對象在對象頁的后半部分,紅色區域在對象頁的前半部分。這種布局有利于檢測右越界,如果向右越界訪問右邊的警戒頁,就會觸發頁錯誤異常。
圖2.3 對象在對象頁的后半部分
KFENCE在每次分配對象的時候,隨機選擇一種布局,并且用特定的字符填充紅色區域。釋放對象的時候,檢查紅色區域里面的字符是否變化,如果變化,那么報告錯誤。
釋放一個KFENCE對象的時候,KFENCE把對象頁設置為不可訪問,并且把對象標記為空閑。繼續訪問這個對象就會觸發一個頁錯誤異常,KFENCE報告一個“釋放后使用”錯誤。為了增加檢測出“釋放后使用”的機會,KFENCE把空閑對象插入空閑鏈表的尾部,讓最早釋放的空閑對象先被分配出去。
數據結構
- struct kfence_metadata kfence_metadata[CONFIG_KFENCE_NUM_OBJECTS]:kfence object的維護數據結構
- 采用下標的方式實現metadata與kfence內存頁表間的映射
- 每個metadata指向2個連續的內存頁
- static struct list_head kfence_freelist = LIST_HEAD_INIT(kfence_freelist):空閑的metadata節點鏈表
- static struct delayed_work kfence_timer:
三、實現分析
kfence導入的patch集:
- 框架:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=0ce20dd840897b12ae70869c69f1ba34d6d16965
- x86平臺:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=1dc0da6e9ec0f8d735756374697912cd50f402cf
- arm64平臺:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=840b239863449f27bf7522deb81e6746fbfbfeaf
- slub對接:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=b89fb5ef0ce611b5db8eb9d3a5a7fcaab2cbe9e4
- 異常堆棧打印優化:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=d438fabce7860df3cb9337776be6f90b59ced8ed
- 測試代碼:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=bc8fbc5f305aecf63423da91e5faf4c0ce40bf38
- 文檔說明:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=10efe55f883f2396a0024891ad1d7d5d040364b3
起機初始化:
- void __init kfence_alloc_pool(void):初始化,申請kfence obj pool
- __kfence_pool = memblock_alloc(KFENCE_POOL_SIZE, PAGE_SIZE);
- void __init kfence_init(void):初始化,設置sampling timer等,必須在kfence_alloc_pool()調用
- arch_kfence_init_pool():
申請內存:
- kfence_alloc():
- __kfence_alloc():
- 申請的大小超過PAGE_SIZE,直接返回NULL,不保護了?
- kfence_guarded_alloc():
- __kfence_alloc():
內存頁屬性:
- static bool kfence_protect(unsigned long addr):設置為不可訪問
- kfence_protect_page(addr, bool)
- static bool kfence_unprotect(unsigned long addr):設置為可以訪問
- kfence_protect_page(addr, bool)
設置redzone:
- static inline bool set_canary_byte(u8 *addr)
- static inline bool check_canary_byte(u8 *addr)
- static __always_inline void for_each_canary(const struct kfence_metadata?meta, bool (fn)(u8 *))
定時采樣
- static void toggle_allocation_gate(struct work_struct *work)
- schedule_delayed_work(&kfence_timer, msecs_to_jiffies(kfence_sample_interval));
總結
以上是生活随笔為你收集整理的内存错误检测工具——kfence工作原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android内存检测工具
- 下一篇: 内存检测工具:sanitizer