Linux mem 2.8 Kfence 詳解【轉】

轉自:https://pwl999.blog.csdn.net/article/details/124494958

1. 原理介紹
Kfence (Kernel Electric Fence) 是 Linux 內核引入的一種低開銷的內存錯誤檢測機制,因爲是低開銷的所以它可以在運行的生產環境中開啓,同樣由於是低開銷所以它的功能相比較 KASAN 會偏弱。

Kfence 的基本原理非常簡單,它創建了自己的專有檢測內存池 kfence_pool。在 data page 的兩邊加上了 fence page 電子柵欄,利用 MMU 的特性把 fence page 設置成不可訪問。如果對 data page 的訪問越過了 page 邊界, 就會立刻觸發異常。

 


Kfence 的主要特點如下:

 

item Kfence KASAN
檢測密度 抽樣法,默認每 100ms 提供一個可檢測的內存 對所有內存訪問進行檢測
檢測粒度 核心的檢測粒度爲 page 檢測粒度爲字節
1.1 slub/slab hook
Kfence 把自己 hook 到 slub/slab 的 malloc()/free() 流程當中去。但並不是所有的 slub/slab 內存都會從 kfence_pool 內存池中分配。它規定了兩個條件:

1、默認每隔 100 ms,開放從 kfence_pool 內存池中分配一次數據。分配成功後會把 kfence_allocation_gate 加 1,阻止繼續從 kfence_pool 的分配。kfence_timer 定時到期以後,又會重新開放一次分配。這相當於一種 抽樣法。
2、每次分配都會佔用 kfence_pool 中的一個 data page,所以可分配的內存長度最大爲 1 page。

 

1.2 out-of-bounds (over data page)
從 kfence_pool 中成功分配一個內存對象 obj,不管 obj 的實際大小有多大,都會佔據一個 data page。

 


當原本訪問 obj 的操作溢出到相鄰的 fence page 時,會立即觸發 CPU 異常,通過堆棧回溯揪出異常訪問的元兇。

1.3 out-of-bounds (in data page)
大部分情況下 obj 是小於一個 page 的,對於 data page 剩餘空間系統使用 canary pattern 進行填充。這種操作是爲了檢測超出了 obj 但還在 data page 範圍內的溢出訪問。

 


這種類型的溢出是不能在溢出發生時立刻觸發的,它只能在 obj free 時,通過檢測 canary pattern 被破壞來檢測到有 canary 區域的溢出訪問。但是異常訪問的元兇卻不能直接抓出來。

1.4 use-after-free
在 obj 被 free 以後,對應 data page 也會被設置成不可訪問狀態。

 


這種狀態下,如果有操作繼續訪問 obj 會立即觸發 CPU 異常,通過堆棧回溯揪出異常訪問的元兇。

1.5 invalid-free
在 obj free 時會判斷記錄的 malloc 信息,判斷是不是一次異常的 free。

2. 代碼解析
分析以下關鍵的代碼流程:

2.1 kfence_protect()
把 fence page 設置成不可訪問的核心就是通過 MMU 清除掉 PTE 中的 present 標誌位:

kfence_init_pool() → kfence_protect() → kfence_protect_page():
kfence_free() → __kfence_free() → kfence_guarded_free() → kfence_protect() → kfence_protect_page():

linux-5.16.14\arch\riscv\include\asm\kfence.h:

static inline bool kfence_protect_page(unsigned long addr, bool protect)
{
pte_t *pte = virt_to_kpte(addr);

if (protect)
set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_PRESENT));
else
set_pte(pte, __pte(pte_val(*pte) | _PAGE_PRESENT));

flush_tlb_kernel_range(addr, addr + PAGE_SIZE);

return true;
}
2.2 kfence_alloc_pool()
在系統啓動時保留 Kfence 需要用到的內存 Page,默認保留 255 個 data page:

start_kernel() → mm_init() → kfence_alloc_pool():

void __init kfence_alloc_pool(void)
{
if (!kfence_sample_interval)
return;

__kfence_pool = memblock_alloc(KFENCE_POOL_SIZE, PAGE_SIZE);

if (!__kfence_pool)
pr_err("failed to allocate pool\n");
}

#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE)

config KFENCE_NUM_OBJECTS
int "Number of guarded objects available"
range 1 65535
default 255

2.3 kfence_init()
void __init kfence_init(void)
{
/* Setting kfence_sample_interval to 0 on boot disables KFENCE. */
if (!kfence_sample_interval)
return;

stack_hash_seed = (u32)random_get_entropy();
/* (1) 初始化 kfence pool 內存池 */
if (!kfence_init_pool()) {
pr_err("%s failed\n", __func__);
return;
}

if (!IS_ENABLED(CONFIG_KFENCE_STATIC_KEYS))
static_branch_enable(&kfence_allocation_key);
WRITE_ONCE(kfence_enabled, true);
/* (2) 初始化定時釋放 guard 的 timer */
queue_delayed_work(system_unbound_wq, &kfence_timer, 0);
pr_info("initialized - using %lu bytes for %d objects at 0x%p-0x%p\n", KFENCE_POOL_SIZE,
CONFIG_KFENCE_NUM_OBJECTS, (void *)__kfence_pool,
(void *)(__kfence_pool + KFENCE_POOL_SIZE));
}


2.4 kfence_alloc()
內存分配流程:

kmem_cache_alloc() → slab_alloc() → kfence_alloc() → __kfence_alloc() → kfence_guarded_alloc():
1
2.5 kfence_free()
內存釋放流程:

kfence_free() → __kfence_free() → kfence_guarded_free():
1
參考文檔
1.Linux內存異常檢測工具—kfence
2.Kernel Electric-Fence (KFENCE)
3.Linux Kernel Sanitizers
4.Linux開源動態之一種新的內存非法訪問檢查工具KFence
————————————————
版權聲明:本文爲CSDN博主「pwl999」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/pwl999/article/details/124494958

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章