內存檢測工具KASAN:精準定位內存越界的“幽靈”
在Linux 內核這片廣袤而復雜的 “代碼叢林” 中,內存管理無疑是最為關鍵且棘手的領域之一。內存越界錯誤,就像隱匿其中的 “幽靈”,神出鬼沒,難以捉摸。它可能在系統運行的任何時刻悄然現身,一個不經意間,就可能引發系統崩潰、數據損壞等嚴重后果,令無數開發人員和運維工程師頭疼不已。
以往,開發人員面對內存越界錯誤時,常常陷入困境。傳統的檢測手段猶如在黑暗中摸索,效率低下且準確性欠佳。例如,一些簡單的內存檢測工具,雖能發現部分明顯的錯誤,但對于那些隱藏在復雜代碼邏輯深處、間歇性出現的內存越界問題,往往無能為力。而人工排查,不僅耗費大量時間和精力,還極易遺漏關鍵線索。
直到 KASAN(Kernel Address Sanitizer,內核地址清理器)的出現,為這一難題帶來了曙光。它憑借強大的功能和獨特的機制,成為精準定位內存越界 “幽靈” 的得力武器。那么,KASAN 究竟是如何做到在復雜的內核環境中,敏銳地捕捉到內存越界錯誤,并將其精準定位的呢?接下來,讓我們一同深入探尋 KASAN 的奧秘。
一、KASAN概述
KASAN,即 Kernel Address Sanitizer,是 Linux內核中用于檢測內存錯誤的重要工具。它如同一位嚴謹的 “內存管家”,時刻監督著內核內存的使用情況,主要負責檢查內存越界訪問和使用已釋放的內存等問題。
內存越界訪問,就像是你在自己的房子里隨意擴建,超出了原本合法的土地邊界;而使用已釋放的內存,則類似于你把房子賣了之后,還想著回去住。這些錯誤在程序運行中可能會導致難以預測的后果,如程序崩潰、數據損壞,甚至可能引發安全漏洞,讓惡意攻擊者有機可乘。
在 Linux 內核開發的龐大體系中,KASAN 占據著舉足輕重的地位。隨著內核代碼的不斷增長和功能的日益復雜,內存管理變得愈發困難,內存錯誤也更容易隱藏其中。KASAN 的出現,為內核開發者提供了一雙 “火眼金睛”,能夠快速、準確地發現這些內存錯誤,大大提高了內核的穩定性和安全性。它就像是在黑暗中為開發者照亮前行道路的明燈,讓他們在復雜的內核代碼中能夠及時發現并修復內存問題,保障整個 Linux 系統的穩定運行。
二、KASAN的強大功能
KASAN 之所以能在 Linux 內核開發中備受青睞,源于其強大且實用的功能,特別是在內存越界檢測、釋放后使用檢測以及未初始化內存檢測等方面表現卓越。
2.1內存越界檢測
內存越界是內核開發中常見的 “陷阱”。當程序訪問的內存地址超出了已分配內存塊的邊界,就會發生內存越界,這可能導致數據被破壞,甚至引發系統崩潰。KASAN 利用 “影子內存” 技術來實現對內存越界的精準檢測。它為每 8 字節的內存分配 1 字節的影子內存,通過影子內存中的標記來記錄對應內存區域的訪問權限。當程序嘗試訪問內存時,KASAN 會檢查影子內存中的標記,判斷該訪問是否合法。
舉個例子,在一個網絡驅動程序中,假設需要接收網絡數據包并存儲到已分配的內存緩沖區中。如果代碼中對數據包長度的檢查不嚴格,就可能導致寫入操作超出緩沖區的邊界,從而引發內存越界。使用 KASAN 后,一旦發生這種情況,它會立即捕獲到異常,并輸出詳細的錯誤信息,包括越界的地址、訪問的大小以及相關的調用棧信息。這使得開發者能夠快速定位到問題代碼所在,及時進行修復。就好像在一個倉庫中,KASAN 為每個存儲區域都設置了明確的邊界標識,一旦有人試圖超出邊界存放物品,它就能馬上發出警報。
2.2釋放后使用檢測
釋放后使用錯誤也是內存管理中的一大 “頑疾”。當內存塊被釋放后,程序卻仍然嘗試訪問它,這就如同使用已經歸還的物品一樣,會導致不可預測的后果。KASAN 通過維護內存的分配和釋放狀態來檢測這類錯誤。當內存被釋放時,KASAN 會將其對應的影子內存標記為不可訪問狀態。如果程序后續嘗試訪問已釋放的內存,KASAN 便能及時發現并報告錯誤。
例如,在一個設備驅動程序中,當設備關閉時,相關的內存資源會被釋放。但如果在釋放后,代碼中還有部分邏輯試圖訪問這些已釋放的內存,KASAN 就能敏銳地察覺到這個問題,并提供詳細的錯誤報告,幫助開發者找出錯誤的根源。這一功能極大地提高了系統的穩定性,避免了因釋放后使用錯誤而導致的系統崩潰或數據丟失問題,就像一個嚴謹的管家,時刻確保歸還的物品不會被再次使用。
2.3未初始化內存檢測
未初始化內存的使用同樣會給程序帶來潛在風險。當程序使用未初始化的內存時,可能會讀取到隨機值,從而導致程序行為異常。KASAN 通過特殊的機制來檢測這類問題。它會在內存分配時對其進行初始化標記,當程序訪問內存時,KASAN 會檢查該內存是否已經被正確初始化。
比如,在一個文件系統驅動程序中,如果在讀取文件數據時,使用了未初始化的緩沖區來存儲數據,KASAN 就能夠檢測到這一錯誤,并給出相應的提示。通過這種方式,開發者可以及時發現并解決未初始化內存的使用問題,確保程序的正確性和穩定性,如同為程序的內存使用加上了一把 “安全鎖”,防止因未初始化內存而引發的各種問題。
三、KASAN的工作原理
Kasan 的原理是利用“額外”的內存來標記那些可以被使用的內存的狀態。這些做標記的區域被稱為影子區域(shadow region)。了解 Linux 內存管理的讀者知道,內存中的每個物理頁在內存中都會有一個 struct page 這樣的結構體來表示,即每 4KB 的頁需要 40B 的結構體,大約 1% 的內存用來表示內存本身。Kasan 與其類似但“浪費”更為嚴重,影子區域的比例是 1:8,即總內存的九分之一會被“浪費”。用官方文檔中的例子,如果有 128TB 的可用內存,需要有額外 16TB 的內存用來做標記。
做標記的方法比較簡單,將可用內存按照 8 子節的大小分組,如果每組中所有 8 個字節都可以訪問,則影子內存中相應的地方用全零(0x00)表示;如果可用內存的前 N(1 到 7 范圍之間)個字節可用,則影子內存中響應的位置用 N 表示;其它情況影子內存用負數表示該內存不可用。
填充值定義:
#define KASAN_FREE_PAGE 0xFF /* page was freed */
#define KASAN_PAGE_REDZONE 0xFE /* redzone for kmalloc_large allocations */
#define KASAN_KMALLOC_REDZONE 0xFC /* redzone inside slub object */
#define KASAN_KMALLOC_FREE 0xFB /* object was freed (kmem_cache_free/kfree) */
#define KASAN_GLOBAL_REDZONE 0xFA /* redzone for global variable */
內核配置選項:
CONFIG_HAVE_ARCH_KASAN=y
CONFIG_KASAN=y
3.1影子內存機制
KASAN的核心技術之一是影子內存(Shadow Memory)機制。影子內存就像是實際內存的 “影子分身”,它與實際內存存在著緊密的對應關系。具體來說,KASAN 會為每 8 字節的實際內存分配1字節的影子內存。這 1 字節的影子內存就像是一個 “小賬本”,用來記錄對應的 8 字節實際內存的訪問權限和狀態信息 。
當實際內存中的某個區域被分配使用時,對應的影子內存會被標記為特定的值,表示該區域是可訪問的。比如,如果 8 字節的實際內存都可以正常訪問,那么對應的影子內存值就會被設置為 0x00。相反,如果實際內存中的某個區域已經被釋放或者是非法訪問區域,影子內存會被標記為其他特定的值,如 0xFC 表示這是一個紅區(Redzone),0xFB 表示該內存已經被釋放。通過這種方式,KASAN 在程序運行過程中,每當進行內存訪問操作時,都會先查看影子內存的標記,以此來判斷此次內存訪問是否合法。
3.2紅區(Redzone)的作用
紅區是 KASAN 內存管理中的一個重要概念。在內存分配時,紅區會被放置在已分配內存塊的邊界之外,它就像是在內存區域周圍設置的一圈 “警戒線”。
當程序申請內存時,內核會在分配的內存塊前后額外劃出一些空間作為紅區。這些紅區在正常情況下是不應該被程序訪問到的。如果程序不小心訪問到了紅區,就說明可能發生了內存越界的情況。例如,當一個程序申請了一塊大小為 100 字節的內存,內核可能會在這 100 字節的前后各劃出幾個字節作為紅區。假設紅區大小為 4 字節,那么實際分配的內存空間就變成了 108 字節,而程序只能合法訪問中間的 100 字節。如果程序試圖訪問第 101 字節到第 104 字節,或者第 0 字節到第 - 4 字節(在內存地址上表現為低地址方向),就會觸發 KASAN 的檢測機制,因為這些區域屬于紅區。
紅區的存在大大提高了 KASAN 檢測內存越界錯誤的能力,它就像一個敏感的 “報警器”,一旦被觸發,就能及時發現內存訪問異常,幫助開發者快速定位問題。
3.3檢測流程詳解
KASAN 的檢測流程是一個嚴謹而高效的過程,它貫穿于程序運行的每一個內存訪問操作中。
當程序執行內存訪問指令時,無論是讀取數據(load)還是寫入數據(store),編譯器都會自動在指令前后插入 KASAN 的檢測代碼。這些檢測代碼首先會根據訪問的內存地址計算出對應的影子內存地址。由于影子內存與實際內存是按照 8:1 的比例映射,所以計算過程就是將實際內存地址右移 3 位(因為 2^3 = 8),再加上一個固定的偏移量,從而得到影子內存地址。
接著,檢測代碼會讀取影子內存中對應位置的值。根據這個值,檢測代碼判斷當前的內存訪問是否合法。如果影子內存的值表明該內存區域是可訪問的,程序會繼續正常執行內存訪問操作;但如果影子內存的值顯示該區域不可訪問,例如是紅區標記或者已釋放內存的標記,KASAN 就會立即捕獲這個錯誤,并生成詳細的錯誤報告。
錯誤報告中會包含豐富的信息,如發生錯誤的內存地址、訪問的類型(讀或寫)、訪問的大小以及相關的調用棧信息。調用棧信息就像是一個 “錯誤路徑導航圖”,它展示了從程序入口開始到發生錯誤時的函數調用順序,開發者可以根據這些信息快速定位到出錯的代碼位置,進而進行修復。
四、如何使用KASAN?
4.1編譯內核啟用 KASAN
要使用 KASAN,首先需要在編譯內核時啟用它。這一過程需要一些特定的配置選項和命令。
①準備編譯環境:確保系統中安裝了必要的編譯工具,如 GCC、make、flex、bison 等。如果是在 Ubuntu 系統中,可以使用以下命令安裝:
sudo apt-get update
sudo apt-get install build-essential libncurses-dev bison flex libssl-dev libelf-dev
②獲取內核源碼:從官方 Linux 內核網站(https://www.kernel.org/ )下載所需版本的內核源碼,然后解壓到指定目錄。例如,下載并解壓 Linux 5.10 內核源碼:
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.xz
tar -xf linux-5.10.tar.xz
cd linux-5.10
③配置內核選項:進入內核源碼目錄后,執行以下命令進入內核配置界面:
make menuconfig
在配置界面中,找到 “Kernel Hacking” -> “Memory Debugging”,然后啟用 “Kernel Address Sanitizer (KASAN)” 選項。此外,還可以根據需求選擇 “KASAN: Inline Allocation”(CONFIG_KASAN_INLINE)選項,它能提供更高效的檢測性能,但可能會增加內核二進制文件的大??;或者選擇 “KASAN: Outlined Allocation”(CONFIG_KASAN_OUTLINE)選項,它生成的二進制文件較小,但檢測速度相對較慢 。
④ 編譯內核:完成配置后,退出配置界面并執行編譯命令。為了加快編譯速度,可以利用多核處理器并行編譯,命令如下:
make -j$(nproc)
這里$(nproc)會自動獲取系統的 CPU 核心數,從而確定并行編譯的任務數。
⑤ 安裝內核:編譯完成后,安裝新編譯的內核和內核模塊:
sudo make modules_install
sudo make install
安裝完成后,重啟系統,新的內核就會生效,此時 KASAN 已經被成功啟用。
4.2運行時檢測與錯誤報告
當系統運行在啟用了 KASAN 的內核上時,KASAN 會自動檢測內存錯誤。一旦檢測到錯誤,它會生成詳細的錯誤報告。
- 錯誤檢測:KASAN 在程序運行過程中,對每一次內存訪問進行檢查。當發現內存越界訪問、使用已釋放內存等錯誤時,KASAN 會立即捕獲到這些異常。
- 查看錯誤報告:KASAN 將錯誤信息輸出到系統日志中,通常可以通過dmesg命令查看。例如,執行以下命令查看最近的系統日志:
dmesg | grep -i kasan
這個命令會過濾出包含 “kasan” 關鍵字的日志信息,這些信息就是 KASAN 生成的錯誤報告。
- 分析錯誤報告:錯誤報告中包含了豐富的信息,如錯誤類型、發生錯誤的內存地址、訪問的大小、相關的調用棧信息等。
以一個內存越界的錯誤報告為例:
BUG: KASAN: slab-out-of-bounds in some_function+0x58/0x88 at addr ffffffdabc0a2a96
Write of size 1 by task some_task/1234
CPU: 3 PID: 1234 Comm: some_task Tainted: G W O 4.19.0-10-amd64 #1
Hardware name: Some Hardware (DT)
Call trace:
[<ffffffffc088b4e8>] dump_backtrace+0x0/0x358
[<ffffffffc088b8dc>] show_stack+0x14/0x20
[<ffffffffc0cde118>] dump_stack+0xa8/0xd0
[<ffffffffc06933c4>] kasan_object_err+0x24/0x80
[<ffffffffc0693654>] kasan_report.part.1+0x1dc/0x498
[<ffffffffc0693b98>] qlist_move_cache+0x0/0xc0
[<ffffffffc0691fe4>] __asan_store1+0x4c/0x58
[<ffffffffc0ec13a8>] some_function+0x58/0x88
從這個報告中,可以得知錯誤類型是 “slab-out-of-bounds”(內存越界),發生錯誤的函數是 “some_function”,錯誤發生的地址是 “fffffdabc0a2a96”,寫入的大小是 1 字節,以及詳細的調用棧信息。通過分析這些信息,開發者可以快速定位到出錯的代碼位置,進而進行修復。
五、KASAN實例源碼分析
KASAN實例分析,在內存訪問中有很多種錯誤類型:
- 越界訪問(out-of-bounds)。
- 訪問已經釋放的內存(use-after-free)。
- 重復釋放。(double-free)。
5.1越界訪問(out-of-bounds)
實例源碼(實例包括):
- kmalloc_oob_right:右側數組越界訪問。
- kmalloc_oob_left:左側數組越界訪問。
- kmalloc_node_oob_right:右側節點越界訪問。
- kmalloc_pagealloc_oob_right:右側頁越界訪問。
- kmalloc_large_oob_right:右側kmalloc最大分配越界訪問。
- kmalloc_oob_krealloc_more:krealloc擴大后越界訪問。
- kmalloc_oob_krealloc_less:krealloc縮小后越界訪問。
- kmalloc_oob_16:申請小于賦值內存塊導致越界訪問。
- kmalloc_oob_memset_2:memset越界訪問。
- kmalloc_oob_memset_4:memset越界訪問。
- kmalloc_oob_memset_8:memset越界訪問。
- kmalloc_oob_memset_16:memset越界訪問。
- kmalloc_oob_in_memset:memset越界訪問。
- kmem_cache_oob:其他cache域賦值導致越界訪問。
- kasan_global_oob:全局變量越界訪問。
- kasan_stack_oob:堆棧越界訪問。
- ksize_unpoisons_memory:ksize后右側越界訪問。
- copy_user_test:內核態與用戶態傳遞類越界訪問。
- kasan_alloca_oob_left:左側alloca越界訪問。
- kasan_alloca_oob_right:右側alloca越界訪問。
#define pr_fmt(fmt) "kasan test: %s " fmt, __func__
#include <linux/kernel.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/kasan.h>
static noinline void __init kmalloc_oob_right(void)
{
char *ptr;
size_t size = 123;
pr_info("out-of-bounds to right\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
ptr[size] = 'x';
kfree(ptr);
}
static noinline void __init kmalloc_oob_left(void)
{
char *ptr;
size_t size = 15;
pr_info("out-of-bounds to left\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
*ptr = *(ptr - 1);
kfree(ptr);
}
static noinline void __init kmalloc_node_oob_right(void)
{
char *ptr;
size_t size = 4096;
pr_info("kmalloc_node(): out-of-bounds to right\n");
ptr = kmalloc_node(size, GFP_KERNEL, 0);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
ptr[size] = 0;
kfree(ptr);
}
#ifdef CONFIG_SLUB
static noinline void __init kmalloc_pagealloc_oob_right(void)
{
char *ptr;
size_t size = KMALLOC_MAX_CACHE_SIZE + 10;
/* Allocate a chunk that does not fit into a SLUB cache to trigger
* the page allocator fallback.
*/
pr_info("kmalloc pagealloc allocation: out-of-bounds to right\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
ptr[size] = 0;
kfree(ptr);
}
#endif
static noinline void __init kmalloc_large_oob_right(void)
{
char *ptr;
size_t size = KMALLOC_MAX_CACHE_SIZE - 256;
/* Allocate a chunk that is large enough, but still fits into a slab
* and does not trigger the page allocator fallback in SLUB.
*/
pr_info("kmalloc large allocation: out-of-bounds to right\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
ptr[size] = 0;
kfree(ptr);
}
static noinline void __init kmalloc_oob_krealloc_more(void)
{
char *ptr1, *ptr2;
size_t size1 = 17;
size_t size2 = 19;
pr_info("out-of-bounds after krealloc more\n");
ptr1 = kmalloc(size1, GFP_KERNEL);
ptr2 = krealloc(ptr1, size2, GFP_KERNEL);
if (!ptr1 || !ptr2) {
pr_err("Allocation failed\n");
kfree(ptr1);
return;
}
ptr2[size2] = 'x';
kfree(ptr2);
}
static noinline void __init kmalloc_oob_krealloc_less(void)
{
char *ptr1, *ptr2;
size_t size1 = 17;
size_t size2 = 15;
pr_info("out-of-bounds after krealloc less\n");
ptr1 = kmalloc(size1, GFP_KERNEL);
ptr2 = krealloc(ptr1, size2, GFP_KERNEL);
if (!ptr1 || !ptr2) {
pr_err("Allocation failed\n");
kfree(ptr1);
return;
}
ptr2[size2] = 'x';
kfree(ptr2);
}
static noinline void __init kmalloc_oob_16(void)
{
struct {
u64 words[2];
} *ptr1, *ptr2;
pr_info("kmalloc out-of-bounds for 16-bytes access\n");
ptr1 = kmalloc(sizeof(*ptr1) - 3, GFP_KERNEL);
ptr2 = kmalloc(sizeof(*ptr2), GFP_KERNEL);
if (!ptr1 || !ptr2) {
pr_err("Allocation failed\n");
kfree(ptr1);
kfree(ptr2);
return;
}
*ptr1 = *ptr2;
kfree(ptr1);
kfree(ptr2);
}
static noinline void __init kmalloc_oob_memset_2(void)
{
char *ptr;
size_t size = 8;
pr_info("out-of-bounds in memset2\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
memset(ptr+7, 0, 2);
kfree(ptr);
}
static noinline void __init kmalloc_oob_memset_4(void)
{
char *ptr;
size_t size = 8;
pr_info("out-of-bounds in memset4\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
memset(ptr+5, 0, 4);
kfree(ptr);
}
static noinline void __init kmalloc_oob_memset_8(void)
{
char *ptr;
size_t size = 8;
pr_info("out-of-bounds in memset8\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
memset(ptr+1, 0, 8);
kfree(ptr);
}
static noinline void __init kmalloc_oob_memset_16(void)
{
char *ptr;
size_t size = 16;
pr_info("out-of-bounds in memset16\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
memset(ptr+1, 0, 16);
kfree(ptr);
}
static noinline void __init kmalloc_oob_in_memset(void)
{
char *ptr;
size_t size = 666;
pr_info("out-of-bounds in memset\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
memset(ptr, 0, size+5);
kfree(ptr);
}
static int __init kmalloc_tests_init(void)
{
/*
* Temporarily enable multi-shot mode. Otherwise, we'd only get a
* report for the first case.
*/
bool multishot = kasan_save_enable_multi_shot();
kmalloc_oob_right();
kmalloc_oob_left();
kmalloc_node_oob_right();
#ifdef CONFIG_SLUB
kmalloc_pagealloc_oob_right();
#endif
kmalloc_large_oob_right();
kmalloc_oob_krealloc_more();
kmalloc_oob_krealloc_less();
kmalloc_oob_16();
kmalloc_oob_in_memset();
kmalloc_oob_memset_2();
kmalloc_oob_memset_4();
kmalloc_oob_memset_8();
kmalloc_oob_memset_16();
kasan_restore_multi_shot(multishot);
return -EAGAIN;
}
module_init(kmalloc_tests_init);
MODULE_LICENSE("GPL");
5.2訪問已經釋放的內存(use-after-free)
實例源碼(實例包括)
- kmalloc_uaf:向釋放已內存賦值。
- kmalloc_uaf_memset:用memset向釋放已內存賦值。
- kmalloc_uaf2:向釋放已內存賦值。
#define pr_fmt(fmt) "kasan test: %s " fmt, __func__
#include <linux/kernel.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/kasan.h>
static noinline void __init kmalloc_uaf(void)
{
char *ptr;
size_t size = 10;
pr_info("use-after-free\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
kfree(ptr);
*(ptr + 8) = 'x';
}
static noinline void __init kmalloc_uaf_memset(void)
{
char *ptr;
size_t size = 33;
pr_info("use-after-free in memset\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
kfree(ptr);
memset(ptr, 0, size);
}
static noinline void __init kmalloc_uaf2(void)
{
char *ptr1, *ptr2;
size_t size = 43;
pr_info("use-after-free after another kmalloc\n");
ptr1 = kmalloc(size, GFP_KERNEL);
if (!ptr1) {
pr_err("Allocation failed\n");
return;
}
kfree(ptr1);
ptr2 = kmalloc(size, GFP_KERNEL);
if (!ptr2) {
pr_err("Allocation failed\n");
return;
}
ptr1[40] = 'x';
if (ptr1 == ptr2)
pr_err("Could not detect use-after-free: ptr1 == ptr2\n");
kfree(ptr2);
}
static int __init kmalloc_tests_init(void)
{
/*
* Temporarily enable multi-shot mode. Otherwise, we'd only get a
* report for the first case.
*/
bool multishot = kasan_save_enable_multi_shot();
kmalloc_uaf();
kmalloc_uaf_memset();
kmalloc_uaf2();
kasan_restore_multi_shot(multishot);
return -EAGAIN;
}
module_init(kmalloc_tests_init);
MODULE_LICENSE("GPL");
輸出log
/ # insmod lib/modules/4.9.191/test_kasan.ko
[ 110.109605] kasan test: kmalloc_uaf use-after-free
[ 110.124350] ==================================================================
[ 110.132481] BUG: KASAN: use-after-free in kmalloc_uaf+0x84/0xb8 [test_kasan]
[ 110.140365] Write of size 1 at addr ffffffc0336d9208 by task insmod/1204
[ 110.147856]
[ 110.149540] CPU: 0 PID: 1204 Comm: insmod Tainted: G B 4.9.191 #4
[ 110.157613] Hardware name: sun50iw10 (DT)
[ 110.162098] Call trace:
[ 110.164853] [<ffffff900808ba74>] dump_backtrace+0x0/0x318
[ 110.170900] [<ffffff900808bda0>] show_stack+0x14/0x1c
[ 110.176563] [<ffffff90083ae758>] dump_stack+0xb0/0xe8
[ 110.182225] [<ffffff90081f7ad4>] print_address_description+0x60/0x228
[ 110.189436] [<ffffff90081f8140>] kasan_report+0x250/0x27c
[ 110.195485] [<ffffff90081f695c>] __asan_store1+0x44/0x4c
[ 110.201473] [<ffffff90007b87d4>] kmalloc_uaf+0x84/0xb8 [test_kasan]
[ 110.208528] [<ffffff90007b955c>] kmalloc_tests_init+0x4c/0x90 [test_kasan]
[ 110.216227] [<ffffff9008083a8c>] do_one_initcall+0xe4/0x1b0
[ 110.222471] [<ffffff9008190ce4>] do_init_module+0xf0/0x2ac
[ 110.228615] [<ffffff900815bd40>] load_module+0x2690/0x2dcc
[ 110.234762] [<ffffff900815c614>] SyS_init_module+0x198/0x224
[ 110.241095] [<ffffff9008083200>] el0_svc_naked+0x34/0x38
[ 110.247032]
[ 110.248704] Allocated by task 1204:
[ 110.252619] save_stack_trace_tsk+0x0/0x1f4
[ 110.257307] save_stack_trace+0x18/0x20
[ 110.261607] kasan_kmalloc.part.1+0x44/0xec
[ 110.266295] kasan_kmalloc+0x88/0x9c
[ 110.270338] kmalloc_uaf+0x60/0xb8 [test_kasan]
[ 110.275450] kmalloc_tests_init+0x4c/0x90 [test_kasan]
[ 110.281204] do_one_initcall+0xe4/0x1b0
[ 110.285504] do_init_module+0xf0/0x2ac
[ 110.289701] load_module+0x2690/0x2dcc
[ 110.293902] SyS_init_module+0x198/0x224
[ 110.298294] el0_svc_naked+0x34/0x38
[ 110.302293]
[ 110.303962] Freed by task 1204:
[ 110.307486] save_stack_trace_tsk+0x0/0x1f4
[ 110.312173] save_stack_trace+0x18/0x20
[ 110.316470] kasan_slab_free+0x90/0x15c
[ 110.320767] kfree+0xc8/0x258
[ 110.324133] kmalloc_uaf+0x7c/0xb8 [test_kasan]
[ 110.329245] kmalloc_tests_init+0x4c/0x90 [test_kasan]
[ 110.334999] do_one_initcall+0xe4/0x1b0
[ 110.339304] do_init_module+0xf0/0x2ac
[ 110.343503] load_module+0x2690/0x2dcc
[ 110.347702] SyS_init_module+0x198/0x224
[ 110.352098] el0_svc_naked+0x34/0x38
[ 110.356096]
[ 110.357773] The buggy address belongs to the object at ffffffc0336d9200
[ 110.357773] which belongs to the cache kmalloc-128 of size 128
[ 110.371780] The buggy address is located 8 bytes inside of
[ 110.371780] 128-byte region [ffffffc0336d9200, ffffffc0336d9280)
[ 110.384708] The buggy address belongs to the page:
[ 110.390073] page:ffffffbf00cdb600 count:1 mapcount:0 mapping: (null) index:0xffffffc0336dab00 compound_mapcount: 0
[ 110.402539] flags: 0x10200(slab|head)
[ 110.406635] page dumped because: kasan: bad access detected
[ 110.412860]
[ 110.414530] Memory state around the buggy address:
[ 110.419901] ffffffc0336d9100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.427984] ffffffc0336d9180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.436064] >ffffffc0336d9200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 110.444138] ^
[ 110.448045] ffffffc0336d9280: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.456125] ffffffc0336d9300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.464202] ==================================================================
[ 110.489110] kasan test: kmalloc_uaf_memset use-after-free in memset
[ 110.496258] ==================================================================
[ 110.504390] BUG: KASAN: use-after-free in kmalloc_uaf_memset+0x8c/0xb8 [test_kasan]
[ 110.512953] Write of size 33 at addr ffffffc0336dab00 by task insmod/1204
[ 110.520540]
[ 110.522221] CPU: 0 PID: 1204 Comm: insmod Tainted: G B 4.9.191 #4
[ 110.530298] Hardware name: sun50iw10 (DT)
[ 110.534783] Call trace:
[ 110.537541] [<ffffff900808ba74>] dump_backtrace+0x0/0x318
[ 110.543590] [<ffffff900808bda0>] show_stack+0x14/0x1c
[ 110.549252] [<ffffff90083ae758>] dump_stack+0xb0/0xe8
[ 110.554917] [<ffffff90081f7ad4>] print_address_description+0x60/0x228
[ 110.562131] [<ffffff90081f8140>] kasan_report+0x250/0x27c
[ 110.568182] [<ffffff90081f6d8c>] check_memory_region+0x20/0x144
[ 110.574812] [<ffffff90081f72f0>] memset+0x2c/0x4c
[ 110.580118] [<ffffff90007b8ef4>] kmalloc_uaf_memset+0x8c/0xb8 [test_kasan]
[ 110.587853] [<ffffff90007b9560>] kmalloc_tests_init+0x50/0x90 [test_kasan]
[ 110.595550] [<ffffff9008083a8c>] do_one_initcall+0xe4/0x1b0
[ 110.601794] [<ffffff9008190ce4>] do_init_module+0xf0/0x2ac
[ 110.607937] [<ffffff900815bd40>] load_module+0x2690/0x2dcc
[ 110.614080] [<ffffff900815c614>] SyS_init_module+0x198/0x224
[ 110.620417] [<ffffff9008083200>] el0_svc_naked+0x34/0x38
[ 110.626354]
[ 110.628027] Allocated by task 1204:
[ 110.631940] save_stack_trace_tsk+0x0/0x1f4
[ 110.636628] save_stack_trace+0x18/0x20
[ 110.640930] kasan_kmalloc.part.1+0x44/0xec
[ 110.645617] kasan_kmalloc+0x88/0x9c
[ 110.649661] kmalloc_uaf_memset+0x60/0xb8 [test_kasan]
[ 110.655454] kmalloc_tests_init+0x50/0x90 [test_kasan]
[ 110.661209] do_one_initcall+0xe4/0x1b0
[ 110.665506] do_init_module+0xf0/0x2ac
[ 110.669708] load_module+0x2690/0x2dcc
[ 110.673909] SyS_init_module+0x198/0x224
[ 110.678304] el0_svc_naked+0x34/0x38
[ 110.682303]
[ 110.683975] Freed by task 1204:
[ 110.687500] save_stack_trace_tsk+0x0/0x1f4
[ 110.692189] save_stack_trace+0x18/0x20
[ 110.696489] kasan_slab_free+0x90/0x15c
[ 110.700784] kfree+0xc8/0x258
[ 110.704152] kmalloc_uaf_memset+0x7c/0xb8 [test_kasan]
[ 110.709943] kmalloc_tests_init+0x50/0x90 [test_kasan]
[ 110.715694] do_one_initcall+0xe4/0x1b0
[ 110.719995] do_init_module+0xf0/0x2ac
[ 110.724198] load_module+0x2690/0x2dcc
[ 110.728398] SyS_init_module+0x198/0x224
[ 110.732793] el0_svc_naked+0x34/0x38
[ 110.736791]
[ 110.738468] The buggy address belongs to the object at ffffffc0336dab00
[ 110.738468] which belongs to the cache kmalloc-128 of size 128
[ 110.752478] The buggy address is located 0 bytes inside of
[ 110.752478] 128-byte region [ffffffc0336dab00, ffffffc0336dab80)
[ 110.765407] The buggy address belongs to the page:
[ 110.770772] page:ffffffbf00cdb600 count:1 mapcount:0 mapping: (null) index:0xffffffc0336dbc80 compound_mapcount: 0
[ 110.783238] flags: 0x10200(slab|head)
[ 110.787334] page dumped because: kasan: bad access detected
[ 110.793561]
[ 110.795231] Memory state around the buggy address:
[ 110.800602] ffffffc0336daa00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.808687] ffffffc0336daa80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.816769] >ffffffc0336dab00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 110.824845] ^
[ 110.828463] ffffffc0336dab80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.836546] ffffffc0336dac00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 110.844621] ==================================================================
[ 110.861989] kasan test: kmalloc_uaf2 use-after-free after another kmalloc
[ 110.889739] ==================================================================
[ 110.897890] BUG: KASAN: use-after-free in kmalloc_uaf2+0xc8/0x118 [test_kasan]
[ 110.905972] Write of size 1 at addr ffffffc02fc52da8 by task insmod/1204
[ 110.913462]
[ 110.915144] CPU: 0 PID: 1204 Comm: insmod Tainted: G B 4.9.191 #4
[ 110.923218] Hardware name: sun50iw10 (DT)
[ 110.927702] Call trace:
[ 110.930462] [<ffffff900808ba74>] dump_backtrace+0x0/0x318
[ 110.936512] [<ffffff900808bda0>] show_stack+0x14/0x1c
[ 110.942174] [<ffffff90083ae758>] dump_stack+0xb0/0xe8
[ 110.947840] [<ffffff90081f7ad4>] print_address_description+0x60/0x228
[ 110.955057] [<ffffff90081f8140>] kasan_report+0x250/0x27c
[ 110.961103] [<ffffff90081f695c>] __asan_store1+0x44/0x4c
[ 110.967092] [<ffffff90007b88d0>] kmalloc_uaf2+0xc8/0x118 [test_kasan]
[ 110.974341] [<ffffff90007b9564>] kmalloc_tests_init+0x54/0x90 [test_kasan]
[ 110.982040] [<ffffff9008083a8c>] do_one_initcall+0xe4/0x1b0
[ 110.988286] [<ffffff9008190ce4>] do_init_module+0xf0/0x2ac
[ 110.994435] [<ffffff900815bd40>] load_module+0x2690/0x2dcc
[ 111.000578] [<ffffff900815c614>] SyS_init_module+0x198/0x224
[ 111.006916] [<ffffff9008083200>] el0_svc_naked+0x34/0x38
[ 111.012854]
[ 111.014524] Allocated by task 1204:
[ 111.018433] save_stack_trace_tsk+0x0/0x1f4
[ 111.023119] save_stack_trace+0x18/0x20
[ 111.027419] kasan_kmalloc.part.1+0x44/0xec
[ 111.032106] kasan_kmalloc+0x88/0x9c
[ 111.036152] kmalloc_uaf2+0x68/0x118 [test_kasan]
[ 111.041463] kmalloc_tests_init+0x54/0x90 [test_kasan]
[ 111.047214] do_one_initcall+0xe4/0x1b0
[ 111.051516] do_init_module+0xf0/0x2ac
[ 111.055717] load_module+0x2690/0x2dcc
[ 111.059917] SyS_init_module+0x198/0x224
[ 111.064312] el0_svc_naked+0x34/0x38
[ 111.068309]
[ 111.069981] Freed by task 1204:
[ 111.073505] save_stack_trace_tsk+0x0/0x1f4
[ 111.078191] save_stack_trace+0x18/0x20
[ 111.082490] kasan_slab_free+0x90/0x15c
[ 111.086790] kfree+0xc8/0x258
[ 111.090157] kmalloc_uaf2+0x84/0x118 [test_kasan]
[ 111.095465] kmalloc_tests_init+0x54/0x90 [test_kasan]
[ 111.101217] do_one_initcall+0xe4/0x1b0
[ 111.105518] do_init_module+0xf0/0x2ac
[ 111.109716] load_module+0x2690/0x2dcc
[ 111.113918] SyS_init_module+0x198/0x224
[ 111.118311] el0_svc_naked+0x34/0x38
[ 111.122307]
[ 111.123984] The buggy address belongs to the object at ffffffc02fc52d80
[ 111.123984] which belongs to the cache kmalloc-128 of size 128
[ 111.137989] The buggy address is located 40 bytes inside of
[ 111.137989] 128-byte region [ffffffc02fc52d80, ffffffc02fc52e00)
[ 111.151016] The buggy address belongs to the page:
[ 111.156383] page:ffffffbf00bf1400 count:1 mapcount:0 mapping: (null) index:0xffffffc02fc53c80 compound_mapcount: 0
[ 111.168852] flags: 0x10200(slab|head)
[ 111.172950] page dumped because: kasan: bad access detected
[ 111.179177]
[ 111.180848] Memory state around the buggy address:
[ 111.186217] ffffffc02fc52c80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 111.194301] ffffffc02fc52d00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 111.202384] >ffffffc02fc52d80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 111.210459] ^
[ 111.215532] ffffffc02fc52e00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 111.223610] ffffffc02fc52e80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 111.231683] ==================================================================
分析
從上述log,kasan提示這時一個訪問已釋放內存的錯誤類型(BUG: KASAN: use-after-free xxx),并顯示出錯的函數名稱,出錯信息提示,出錯附近內存狀況和出錯位置。
可以利用gdb+符號表查看源碼,以kmalloc_uaf為例:
$ ./out/gcc-linaro-5.3.1-2016.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gdb ~/link/kernel/linux-4.9/lib/test_kasan.ko
GNU gdb (Linaro_GDB-2016.05) 7.11.1.20160702-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-unknown-linux-gnu --target=aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /home1/weidonghui/link/kernel/linux-4.9/lib/test_kasan.ko...done.
(gdb) list *kmalloc_uaf+0x84
0x7d4 is in kmalloc_uaf (lib/test_kasan.c:267).
warning: Source file is more recent than executable.
262 pr_err("Allocation failed\n");
263 return;
264 }
265
266 kfree(ptr);
267 *(ptr + 8) = 'x';
268 }
269
270 static noinline void __init kmalloc_uaf_memset(void)
271 {
(gdb)
5.3重復釋放(double-free)
實例源碼(實例包括)
- kmalloc_double_free_test:重復釋放內存。
#define pr_fmt(fmt) "kasan test: %s " fmt, __func__
#include <linux/kernel.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/kasan.h>
static noinline void __init kmalloc_double_free_test(void)
{
char *ptr;
size_t size = 123;
pr_info("double-free\n");
ptr = kmalloc(size, GFP_KERNEL);
if (!ptr) {
pr_err("Allocation failed\n");
return;
}
kfree(ptr);
kfree(ptr);
}
static int __init kmalloc_tests_init(void)
{
/*
* Temporarily enable multi-shot mode. Otherwise, we'd only get a
* report for the first case.
*/
bool multishot = kasan_save_enable_multi_shot();
kmalloc_double_free_test();
kasan_restore_multi_shot(multishot);
return -EAGAIN;
}
module_init(kmalloc_tests_init);
MODULE_LICENSE("GPL");
輸出log
[159003.489404] kasan test: kmalloc_double_free_test double-free
[159003.496364] ==================================================================
[159003.504599] BUG: KASAN: double-free or invalid-free in (null)
[159003.512152]
[159003.513943] CPU: 0 PID: 17631 Comm: insmod Tainted: G B 4.9.191 #4
[159003.522265] Hardware name: sun50iw10 (DT)
[159003.526886] Call trace:
[159003.529769] [<ffffff900808ba74>] dump_backtrace+0x0/0x318
[159003.535965] [<ffffff900808bda0>] show_stack+0x14/0x1c
[159003.541771] [<ffffff90083ae758>] dump_stack+0xb0/0xe8
[159003.547579] [<ffffff90081f7ad4>] print_address_description+0x60/0x228
[159003.554950] [<ffffff90081f7ec8>] kasan_report_double_free+0x64/0x8c
[159003.562124] [<ffffff90081f7694>] kasan_slab_free+0x44/0x15c
[159003.568511] [<ffffff90081f4b50>] kfree+0xc8/0x258
[159003.573964] [<ffffff90008289a4>] kmalloc_double_free_test+0x84/0xb0 [test_kasan]
[159003.582443] [<ffffff9000829640>] kmalloc_tests_init+0x78/0x90 [test_kasan]
[159003.590302] [<ffffff9008083a8c>] do_one_initcall+0xe4/0x1b0
[159003.596695] [<ffffff9008190ce4>] do_init_module+0xf0/0x2ac
[159003.602987] [<ffffff900815bd40>] load_module+0x2690/0x2dcc
[159003.609277] [<ffffff900815c614>] SyS_init_module+0x198/0x224
[159003.615761] [<ffffff9008083200>] el0_svc_naked+0x34/0x38
[159003.621846]
[159003.623630] Allocated by task 17631:
[159003.627772] save_stack_trace_tsk+0x0/0x1f4
[159003.632596] save_stack_trace+0x18/0x20
[159003.637035] kasan_kmalloc.part.1+0x44/0xec
[159003.641856] kasan_kmalloc+0x88/0x9c
[159003.646030] kmalloc_double_free_test+0x60/0xb0 [test_kasan]
[159003.652552] kmalloc_tests_init+0x78/0x90 [test_kasan]
[159003.658446] do_one_initcall+0xe4/0x1b0
[159003.662882] do_init_module+0xf0/0x2ac
[159003.667210] load_module+0x2690/0x2dcc
[159003.671544] SyS_init_module+0x198/0x224
[159003.676073] el0_svc_naked+0x34/0x38
[159003.680197]
[159003.681980] Freed by task 17631:
[159003.685733] save_stack_trace_tsk+0x0/0x1f4
[159003.690558] save_stack_trace+0x18/0x20
[159003.694987] kasan_slab_free+0x90/0x15c
[159003.699417] kfree+0xc8/0x258
[159003.702911] kmalloc_double_free_test+0x7c/0xb0 [test_kasan]
[159003.709435] kmalloc_tests_init+0x78/0x90 [test_kasan]
[159003.715328] do_one_initcall+0xe4/0x1b0
[159003.719759] do_init_module+0xf0/0x2ac
[159003.724089] load_module+0x2690/0x2dcc
[159003.728418] SyS_init_module+0x198/0x224
[159003.732945] el0_svc_naked+0x34/0x38
[159003.737071]
[159003.738858] The buggy address belongs to the object at ffffffc031810d00
[159003.738858] which belongs to the cache kmalloc-128 of size 128
[159003.753164] The buggy address is located 0 bytes inside of
[159003.753164] 128-byte region [ffffffc031810d00, ffffffc031810d80)
[159003.766389] The buggy address belongs to the page:
[159003.771893] page:ffffffbf00c60400 count:1 mapcount:0 mapping: (null) index:0xffffffc031813a00 compound_mapcount: 0
[159003.784561] flags: 0x10200(slab|head)
[159003.788786] page dumped because: kasan: bad access detected
[159003.795161]
[159003.796939] Memory state around the buggy address:
[159003.802443] ffffffc031810c00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[159003.810680] ffffffc031810c80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[159003.818920] >ffffffc031810d00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[159003.827150] ^
[159003.830892] ffffffc031810d80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[159003.839142] ffffffc031810e00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[159003.847379] ==================================================================
分析
從上述log,kasan提示這是一個重復釋放內存的錯誤類型(BUG: KASAN: double-free or invalid-free xxx),并顯示出錯的函數名稱,出錯信息提示,出錯附近內存狀況和出錯位置。可以利用gdb+符號表查看源碼,以kmalloc_double_free_test為例:
$ ./out/gcc-linaro-5.3.1-2016.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gdb ~/link/kernel/linux-4.9/lib/test_kasan.ko
GNU gdb (Linaro_GDB-2016.05) 7.11.1.20160702-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-unknown-linux-gnu --target=aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /home1/weidonghui/link/kernel/linux-4.9/lib/test_kasan.ko...done.
(gdb) list *kmalloc_double_free_test+0x84
0x9a4 is in kmalloc_double_free_test (lib/test_kasan.c:477).
472 return;
473 }
474
475 kfree(ptr);
476 kfree(ptr);
477 }
478
479 static int __init kmalloc_tests_init(void)
480 {
481 /*
(gdb)