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