本文簡(jiǎn)介:SLAB內(nèi)存分配器-SLUB的DEBUG功能,如何幫忙檢測(cè)內(nèi)存越界(out-of-bounds)和訪問已經(jīng)釋放的內(nèi)存(use-after-free)。本文目錄:
1. 前言
2. SLUB DEBUG功能
3. object layout
4. SLUB DEBUG原理
5. slabinfo
1. 前言
在工作中,經(jīng)常會(huì)遇到由于越界導(dǎo)致的各種奇怪的問題。為什么越界訪問導(dǎo)致的問題很奇怪呢?在工作差不多半年的時(shí)間里我就遇到了很多越界訪問導(dǎo)致的問題(不得不吐槽下IC廠商提供的driver,總是隱藏著bug)。比如說越界訪問導(dǎo)致的死機(jī)問題,這種問題的出現(xiàn)一般需要長(zhǎng)時(shí)間測(cè)試才能發(fā)現(xiàn),而且發(fā)現(xiàn)的時(shí)候即使有panic log。你也沒什么頭緒。這是為什么呢?假設(shè)驅(qū)動(dòng)A通過kmalloc()申請(qǐng)了一段內(nèi)存,不注意越界改寫了與其相鄰的object的數(shù)據(jù)(經(jīng)過我之前一篇SLUB的文章分析,你應(yīng)該明白kmalloc基于kmem_cache實(shí)現(xiàn)的),假設(shè)被改寫的object是B驅(qū)動(dòng)使用的,巧合B驅(qū)動(dòng)使用object存儲(chǔ)的是地址數(shù)據(jù),如果B驅(qū)動(dòng)訪問這個(gè)地址。那么完了,B驅(qū)動(dòng)死了,panic也是怪B驅(qū)動(dòng)。試想一下,這塊被改寫的object是哪個(gè)驅(qū)動(dòng)使用,是不是哪個(gè)驅(qū)動(dòng)就倒霉了?并且每一次死機(jī)的log中panic極有可能發(fā)生在不同的模塊。但是真正的元兇卻是A驅(qū)動(dòng),他沒事你還不知道,是不是很恐怖?簡(jiǎn)直是借刀殺人??!
當(dāng)然,越界訪問也不一定會(huì)死機(jī)。之前就遇到一個(gè)很奇怪的問題。有兩個(gè)全局?jǐn)?shù)組變量(用作存儲(chǔ)字符串)分別被模塊C和D使用。這兩個(gè)數(shù)組是上層需要顯示的name信息。當(dāng)C和D模塊都工作的時(shí)候,發(fā)現(xiàn)C模塊的name顯示不對(duì),但是D模塊的name顯示正常。將D模塊remove,發(fā)現(xiàn)C模塊的name顯示正確。當(dāng)時(shí)看了下System.map文件,發(fā)現(xiàn)這兩個(gè)全局?jǐn)?shù)組變量分配的內(nèi)存是在一起的,由于D模塊越界寫導(dǎo)致的。而這種情況就不會(huì)死機(jī)。但是當(dāng)你遇到這種情況的時(shí)候,你很驚訝,怎么會(huì)這樣??jī)蓚€(gè)模塊之間根本就沒關(guān)系??!如果完全不借助檢測(cè)工具去查找問題是相當(dāng)費(fèi)時(shí)間的。而且有可能還沒什么頭緒。
這種問題我們?cè)撛趺炊ㄎ??因此我們遇到一種debug的手段,可以檢測(cè)out-of-bounds(oob)問題。剛才的第一種情況就可以SLUB自帶debug功能。針對(duì)第二種情況就需要借助更加強(qiáng)大的KASAN工具(后續(xù)會(huì)有文章介紹)。
因此,我們需要一種debug手段幫助我們定位問題。SLUB DEBUG就是其中的一種。但是SLUB DEBUG僅僅針對(duì)從slub分配器分配的內(nèi)存,如果你需要檢測(cè)從棧中或者數(shù)據(jù)區(qū)分配內(nèi)存的問題,就不行了。當(dāng)然了,你可以選擇KASAN。本文主要關(guān)注SLUB DEBUG的原理,如何定位這些問題的。
SLUB DEBUG檢測(cè)oob問題原理也很簡(jiǎn)單,既然為了發(fā)現(xiàn)是否越界,那么就在分配出去的內(nèi)存尾部添加一段額外的內(nèi)存,填充特殊數(shù)字(magic num)。我們只需要檢測(cè)這塊額外的內(nèi)存的數(shù)據(jù)是否被修改就可以知道是否發(fā)生了oob情況。而這段額外的內(nèi)存就叫做Redzone。直譯過來“紅色區(qū)域”是不是有種神圣不可侵犯的感覺。
說明:slab是最早加入linux的,在那時(shí)只有slab的存在。隨著時(shí)間的推移slub出現(xiàn)了,slub是在slab基礎(chǔ)上進(jìn)行的改進(jìn),在大型機(jī)上表現(xiàn)出色。而slob是針對(duì)小型系統(tǒng)設(shè)計(jì)的。由于slub實(shí)現(xiàn)的接口和slab接口保持一致(雖然你用的是slub分配器,但是很多函數(shù)名稱和數(shù)據(jù)結(jié)構(gòu)還是依然和slab一致),所以有時(shí)候用slab來統(tǒng)稱slab, slub和slob。slab, slub和slob僅僅是分配內(nèi)存策略不同。管理的思想基本一致。本篇文章中說的是slub分配器debug原理。但是針對(duì)分配器管理的內(nèi)存,下文統(tǒng)稱為slab緩存池。所以文章中slub和slab會(huì)混用,表示同一個(gè)意思。
注:文章代碼分析基于linux-4.15.0-rc3。
2.SLUB DEBUG功能
SLUB DEBUG可以檢測(cè)內(nèi)存越界(out-of-bounds)和訪問已經(jīng)釋放的內(nèi)存(use-after-free)等問題。
1.1. 如何打開功能
重新配置kernel選項(xiàng),打開如下選項(xiàng)即可。
CONFIG_SLUB=y
CONFIG_SLUB_DEBUG=y
CONFIG_SLUB_DEBUG_ON=y
1.2. 如何使用
程序中的bug如果想用SLUB DEBUG去檢測(cè),還需要slabinfo命令。因?yàn)?,SLUB內(nèi)存檢測(cè)功能在某些情況下不能立刻檢測(cè)出來,必須主動(dòng)觸發(fā),因此我們需要借助slabinfo命令觸發(fā)SLUB allocator檢測(cè)功能。和KASAN相比較而言,這也是SLUB DEBUG的一個(gè)劣勢(shì)。畢竟KASAN可以做到在越界問題出現(xiàn)時(shí)就報(bào)出問題。
slabinfo工具源碼位于tools/vm目錄??梢允褂萌缦旅罹幾gslabinfo工具(針對(duì)ARM64 architecture)。
aarch64-linux-gnu-gcc -o slabinfo slabinfo.c
當(dāng)系統(tǒng)開機(jī)之后,就可以運(yùn)行slaninfo –v命令觸發(fā)SLUB allocator檢測(cè)所有的object,并將log信息輸出到syslog。接下來的任務(wù)就是查看log信息是否包含SLUB allocator輸出的bug log。其實(shí)有些bug是不需要運(yùn)行slabinfo命令即可捕捉,但是有些卻必須使用slabinfo –v命令才可以。下一節(jié)將會(huì)介紹SLUB DEBUG的原理,為你揭開哪些bug不需要slabinfo命令。
3. object layout
配置kernel選項(xiàng)CONFIG_SLUB_DEBUG_ON后,在創(chuàng)建kmem_cache的時(shí)候會(huì)傳遞很多flags(SLAB_CONSISTENCY_CHECKS、SLAB_RED_ZONE、SLAB_POISON、SLAB_STORE_USER)。針對(duì)這些flags,SLUB allocator管理的object對(duì)象的format將會(huì)發(fā)生變化。如下圖所示。
SLUB DEBUG關(guān)閉的情況下,free pointer是內(nèi)嵌在object之中的,但是SLUB DEBUG打開之后,free pointer是在object之外,并且多了很多其他的內(nèi)存,例如red zone、trace和red_left_pad等。這里之所以將FP后移就是因?yàn)闉榱藱z測(cè)use-after-free問題,當(dāng)free object時(shí)會(huì)在將object填充magic num(0x6b)。如果不后移的話,豈不是破壞了object之間的單鏈表關(guān)系。
3.1. Red zone有什么用
從圖中我們可以看到在object后面緊接著就是Red zone區(qū)域,那么Red zone有什么作用呢?既然緊隨其后,自然是檢測(cè)右邊界越界訪問(right out-of-bounds access)。原理很簡(jiǎn)單,在Red zone區(qū)域填充magic num,檢查Red zone區(qū)域數(shù)據(jù)是否被修改即可知道是否發(fā)生right oob。
可能你會(huì)想到如果越過Redzone,直接改寫了FP,豈不是檢測(cè)不到oob了,并且鏈表結(jié)構(gòu)也被破壞了。其實(shí)在check_object()函數(shù)中會(huì)調(diào)用check_valid_pointer()來檢查FP是否valid,如果invalid,同樣會(huì)print error syslog。
3.2. padding有什么用
padding是sizeof(void *) bytes的填充區(qū)域,在分配slab緩存池時(shí),會(huì)將所有的內(nèi)存填充0x5a。同樣在free/alloc object的時(shí)候作為檢測(cè)的一種途徑。如果padding區(qū)域的數(shù)據(jù)不是0x5a,就代表發(fā)生了“Object padding overwritten”問題。這也是有可能,越界跨度很大。
3.3. red_left_pad有什么用
red_left_pad和Red zone的作用一致。都是為了檢測(cè)oob。區(qū)別就是Red zone檢測(cè)right oob,而red_left_pad是檢測(cè)left oob。如果僅僅看到上面圖片中object layout。你可能會(huì)好奇,如果發(fā)生left oob,那么應(yīng)該是前一個(gè)object的red_left_pad區(qū)域被改寫,而不是當(dāng)前object的red_left_pad。如果你注意到這個(gè)問題,還是很機(jī)智的,這都被你發(fā)現(xiàn)了。為了避免這種情況的發(fā)生,SLUB allocator在初始化slab緩存池的時(shí)候會(huì)做一個(gè)轉(zhuǎn)換。
如果你去追蹤kmem_cache_create(),在calculate_sizes()中布局object。區(qū)域劃分的layout就如同你看到上圖的上半部分。當(dāng)我第一次看到這段代碼的時(shí)候,我也這么認(rèn)為。實(shí)際上卻不是這樣的。在struct page結(jié)構(gòu)中有一個(gè)freelist指針,freelist會(huì)指向第一個(gè)available object。在構(gòu)建object之間的單鏈表的時(shí)候,object首地址實(shí)際上都會(huì)加上一個(gè)red_left_pad的偏移,這樣實(shí)際的layout就如同圖片中轉(zhuǎn)換之后的layout。為什么會(huì)這樣呢?因?yàn)樵谟蠸LUB DEBUG功能的時(shí)候,并沒有檢測(cè)left oob功能。這種轉(zhuǎn)換是后續(xù)一個(gè)補(bǔ)丁的修改。補(bǔ)丁就是為了增加left oob檢測(cè)功能。
做了轉(zhuǎn)換之后的red_left_pad就可以檢測(cè)leftoob。檢測(cè)的方法和Red zone區(qū)域一樣,填充的magic num也一樣,差別只是檢測(cè)的區(qū)域不一樣而已。
4. SLUB DEBUG原理
經(jīng)過上一節(jié)分析應(yīng)該很清楚了大概的原理了。從high level考慮,SLUB就是利用特殊區(qū)域填充特殊的magic num,在每一次alloc/free的時(shí)候檢查magic num是否被意外修改。
4.1. magic num
SLUB 中有哪些magic num呢?所有使用的magic num都宏定義在include/linux/poison.h文件。
SLUB_RED_INACTIVE和SLUB_RED_ACTIVE用來填充Red zone和red_left_pad,目的是檢測(cè)oob。POISON_INUSE用來填充padding區(qū)域,同樣可以用來檢測(cè)oob,只不過是poison overwrite。POISON_FREE作用是檢測(cè)use-after-free問題。POISON_END是object可用區(qū)域的最后一個(gè)字節(jié)填充。
4.2. slab緩存池填充
當(dāng)SLUB allocator申請(qǐng)一塊內(nèi)存作為slab 緩存池的時(shí)候,會(huì)將整塊內(nèi)存填充POISON_INUSE。如下圖所示。
然后通過init_object()函數(shù)將相關(guān)的區(qū)域填充成free object的情況,并且建立單鏈表。注意freelist指針指向的位置,SLUB_DEBUG on和off的情況下是不一樣的。主要就是3.3節(jié)提到的轉(zhuǎn)換關(guān)系。為什么這里填充成freeobject的情況呢?其實(shí)就是為了假裝我這里都是free的object,也是符合情理的。object初始化流程如下。
4.3. free objectlayout
剛分配slab緩存池和free object之后,系統(tǒng)都會(huì)通過調(diào)用init_object()函數(shù)初始化object各個(gè)區(qū)域,主要是填充magic num。free object layout如下圖所示。
red_left_pad和Red zone填充了SLUB_RED_INACTIVE(0xbb);
object填充了POISON_FREE(0x6b),但是最后一個(gè)byte填充POISON_END(0xa5);
padding在allocate_slab的時(shí)候就已經(jīng)被填充POISON_INUSE(0x5a),如果程序意外改變,當(dāng)檢測(cè)到padding被改變的時(shí)候,會(huì)output error syslog并繼續(xù)填充0x5a。
4.4. alloc object layout
當(dāng)從SLUB allocator申請(qǐng)一個(gè)object時(shí),系統(tǒng)同樣會(huì)調(diào)用init_object()初始化成想要的模樣。alloc object layout如下圖所示。
red_left_pad和Red zone填充了SLUB_RED_ACTIVE(0xcc);
object填充了POISON_FREE(0x6b),但是最后一個(gè)byte填充POISON_END(0xa5);
padding在allocate_slab的時(shí)候就已經(jīng)被填充POISON_INUSE(0x5a),如果程序意外改變,當(dāng)檢測(cè)到padding被改變的時(shí)候,會(huì)output error syslog并繼續(xù)填充0x5a。
alloc object layout和free object layout相比較而言,也僅僅是red_left_pad和Red zone的不同。既然該填充的數(shù)據(jù)都搞定了,下面就是如何檢查oob、use-after-free等問題了。
4.5. out-of-bounds bugs detect
下面使用demo例程來說明oob檢測(cè)。我們使用kmalloc分配32 bytes內(nèi)存,然后制造越界訪問第33個(gè)元素,必然會(huì)越界訪問。由于kmalloc是基于SLUB allocator,因此此bug可以檢測(cè)。
運(yùn)行后的object layout如下圖所示。
我們可以看到,Red zone區(qū)域本來應(yīng)該0xcc的地方被修改成了0x88。很明顯這是一個(gè)Redzone overwritten問題。那么系統(tǒng)什么時(shí)候會(huì)檢測(cè)到這個(gè)嚴(yán)重的bug呢?就在你kfree()之后。kfree()中會(huì)去檢測(cè)釋放的object中各個(gè)區(qū)域的值是否valid。Redzone區(qū)域的值全是0xcc就是valid,因此這里會(huì)檢測(cè)0x88不是0xcc,進(jìn)而輸出errorsyslog。kfree()最終會(huì)調(diào)用free_consistency_checks()檢測(cè)object。free_consistency_checks()函數(shù)如下。
check_valid_pointer()負(fù)責(zé)檢測(cè)object的free pointer指針數(shù)據(jù)是否valid。oob是有可能導(dǎo)致這種情況得到發(fā)生。
on_freelist()檢測(cè)object是否已經(jīng)free,可以檢測(cè)多次free的bug。
check_object()會(huì)檢測(cè)Red zone區(qū)域的數(shù)值是否被改變,因此這里就會(huì)報(bào)出bug。
如果是左邊界越界訪問,是否也同樣可以檢測(cè)出呢?可以測(cè)試以下demo例程。
運(yùn)行后的object layout如下圖所示。
檢測(cè)方法大同小異,這里也是最終在free_consistency_checks()函數(shù)中通過檢測(cè)red_left_pad區(qū)域發(fā)現(xiàn)left oob問題。
可能你會(huì)想如果我只申請(qǐng)內(nèi)存不釋放的話,這個(gè)bug還能檢測(cè)到嗎?其實(shí)這里是不行的。我們只能借助slabinfo工具主動(dòng)觸發(fā)檢測(cè)功能。所以,這也是SLUB DEBUG的一個(gè)劣勢(shì),它不能做到動(dòng)態(tài)監(jiān)測(cè)。它的檢測(cè)機(jī)制是被動(dòng)的。
4.6. use-after-free bugs detect
如果是use-after-free問題,我們?cè)撊绾螜z測(cè)呢?首先上demo例程。
運(yùn)行之后object layout如下圖所示。
還記得上面說的嗎?SLUB DEBUG是被動(dòng)的。因此這里就要選擇slabinfo工具了。命令中斷輸入slabinfo –v即可。slabinfo檢測(cè)的原理也很簡(jiǎn)單,便利所有已經(jīng)釋放的object,檢查object區(qū)域是否全是0x6b(最后一個(gè)字節(jié)oxa5)即可,如果不是的話,自然就是use-after-free。
5. slabinfo
我們看一下slabinfo –v命令的實(shí)現(xiàn)方式以及檢查的流程。slabinfo源碼位于tools/vm/slabinfo.c文件。slabinfo –v命令執(zhí)行流程如下圖所示。
針對(duì)系統(tǒng)中每一個(gè)slab都會(huì)執(zhí)行set_obj()函數(shù)。set_obj()代碼如下:
set_obj()參數(shù)name傳遞的是“validate”,n傳遞的是1。作用就是向/sys/kernel/slab/
validate_slab()代碼如下:
check_slab()會(huì)調(diào)用slab_pad_check()檢查slab padding區(qū)域。slab padding和object里面的pading不是一回事。如果說從buddy system分配的頁(yè)按照SLUB規(guī)則平分成很多object,那么有可能不能整除,那么剩下的unused區(qū)域就是slab padding。valid的數(shù)值是0x5a。如下圖所示。
get_map()利用bitmap標(biāo)記所有的available object。例如,slab緩存池一共有10個(gè)對(duì)象,按地址大小排序標(biāo)號(hào)0-9(相當(dāng)于index)。假設(shè)5和8號(hào)object已經(jīng)被分配出去。那么bitmap中除了bit5和bit8以為,其余位為1。
第一個(gè)for循環(huán)遍歷所有的available object是否有oob、use-after-free、object padding overwritten等問題發(fā)生。
第二個(gè)for循環(huán)遍歷所有已經(jīng)分配出去的object是否發(fā)生oob問題。
-
Linux
+關(guān)注
關(guān)注
87文章
11211瀏覽量
208721 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
2976瀏覽量
73815 -
DEBUG
+關(guān)注
關(guān)注
3文章
89瀏覽量
19849
原文標(biāo)題:宋牧春: Linux內(nèi)核slab內(nèi)存的越界檢查——SLUB_DEBUG
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論