歡迎光臨
每天分享高質量文章

宋牧春: Linux內核slab記憶體的越界檢查——SLUB_DEBUG

本文簡介:
SLAB記憶體分配器-SLUB的DEBUG功能,如何幫忙檢測記憶體越界(out-of-bounds)和訪問已經釋放的記憶體(use-after-free)。
本文目錄:

1. 前言

2. SLUB DEBUG功能

3. object layout

4. SLUB DEBUG原理

5. slabinfo

作者簡介:

宋牧春,linux內核愛好者,2017年6月本科畢業於江蘇大學。現就職於一家手機研發公司,任職BSP驅動工程師,主要負責TP驅動bringup和除錯。

1. 前言

在工作中,經常會遇到由於越界導致的各種奇怪的問題。為什麼越界訪問導致的問題很奇怪呢?在工作差不多半年的時間里我就遇到了很多越界訪問導致的問題(不得不吐槽下IC廠商提供的driver,總是隱藏著bug)。比如說越界訪問導致的死機問題,這種問題的出現一般需要長時間測試才能發現,而且發現的時候即使有panic log。你也沒什麼頭緒。這是為什麼呢?假設驅動A通過kmalloc()申請了一段記憶體,不註意越界改寫了與其相鄰的object的資料(經過我之前一篇SLUB的文章分析,你應該明白kmalloc基於kmem_cache實現的),假設被改寫的objectB驅動使用的,巧合B驅動使用object儲存的是地址資料,如果B驅動訪問這個地址。那麼完了,B驅動死了,panic也是怪B驅動。試想一下,這塊被改寫的object是哪個驅動使用,是不是哪個驅動就倒霉了?並且每一次死機的logpanic極有可能發生在不同的模塊。但是真正的元凶卻是A驅動,他沒事你還不知道,是不是很恐怖?簡直是借刀殺人啊!

當然,越界訪問也不一定會死機。之前就遇到一個很奇怪的問題。有兩個全域性陣列變數(用作儲存字串)分別被模塊CD使用。這兩個陣列是上層需要顯示的name信息。當CD模塊都工作的時候,發現C模塊的name顯示不對,但是D模塊的name顯示正常。將D模塊remove,發現C模塊的name顯示正確。當時看了下System.map檔案,發現這兩個全域性陣列變數分配的記憶體是在一起的,由於D模塊越界寫導致的。而這種情況就不會死機。但是當你遇到這種情況的時候,你很驚訝,怎麼會這樣?兩個模塊之間根本就沒關係啊!如果完全不借助檢測工具去查找問題是相當費時間的。而且有可能還沒什麼頭緒。

這種問題我們該怎麼定位?因此我們遇到一種debug的手段,可以檢測out-of-boundsoob)問題。剛纔的第一種情況就可以SLUB自帶debug功能。針對第二種情況就需要借助更加強大的KASAN工具(後續會有文章介紹)。

因此,我們需要一種debug手段幫助我們定位問題。SLUB DEBUG就是其中的一種。但是SLUB DEBUG僅僅針對從slub分配器分配的記憶體,如果你需要檢測從棧中或者資料區分配記憶體的問題,就不行了。當然了,你可以選擇KASAN。本文主要關註SLUB DEBUG的原理,如何定位這些問題的。

SLUB DEBUG檢測oob問題原理也很簡單,既然為了發現是否越界,那麼就在分配出去的記憶體尾部添加一段額外的記憶體,填充特殊數字(magic num)。我們只需要檢測這塊額外的記憶體的資料是否被修改就可以知道是否發生了oob情況。而這段額外的記憶體就叫做Redzone。直譯過來“紅色區域”是不是有種神聖不可侵犯的感覺。

說明:slab是最早加入linux,在那時只有slab的存在。隨著時間的推移slub出現了,slub是在slab基礎上進行的改進,在大型機上表現出色。而slob是針對小型系統設計的。由於slub實現的接口和slab接口保持一致(雖然你用的是slub分配器,但是很多函式名稱和資料結構還是依然和slab一致),所以有時候用slab來統稱slab, slubslobslab, slubslob僅僅是分配記憶體策略不同。管理的思想基本一致。本篇文章中說的是slub分配器debug原理。但是針對分配器管理的記憶體,下文統稱為slab快取池。所以文章中slubslab會混用,表示同一個意思。

註:文章代碼分析基於linux-4.15.0-rc3

2.SLUB DEBUG功能


SLUB DEBUG可以檢測記憶體越界(out-of-bounds)和訪問已經釋放的記憶體(use-after-free)等問題。

1.1. 如何打開功能

重新配置kernel選項,打開如下選項即可。

CONFIG_SLUB=y

CONFIG_SLUB_DEBUG=y

CONFIG_SLUB_DEBUG_ON=y

1.2. 如何使用

程式中的bug如果想用SLUB DEBUG去檢測,還需要slabinfo命令。因為,SLUB記憶體檢測功能在某些情況下不能立刻檢測出來,必須主動觸發,因此我們需要借助slabinfo命令觸發SLUB allocator檢測功能。和KASAN相比較而言,這也是SLUB DEBUG的一個劣勢。畢竟KASAN可以做到在越界問題出現時就報出問題。

slabinfo工具原始碼位於tools/vm目錄。可以使用如下命令編譯slabinfo工具(針對ARM64 architecture)。

aarch64-linux-gnu-gcc -o slabinfo slabinfo.c

當系統開機之後,就可以運行slaninfo –v命令觸發SLUB allocator檢測所有的object,並將log信息輸出到syslog。接下來的任務就是查看log信息是否包含SLUB allocator輸出的bug log。其實有些bug是不需要運行slabinfo命令即可捕捉,但是有些卻必須使用slabinfo –v命令才可以。下一節將會介紹SLUB DEBUG的原理,為你揭開哪些bug不需要slabinfo命令。

3. object layout


配置kernel選項CONFIG_SLUB_DEBUG_ON後,在創建kmem_cache的時候會傳遞很多flagsSLAB_CONSISTENCY_CHECKSSLAB_RED_ZONESLAB_POISONSLAB_STORE_USER)。針對這些flagsSLUB allocator管理的object物件的format將會發生變化。如下圖所示。


SLUB DEBUG關閉的情況下,free pointer是內嵌在object之中的,但是SLUB DEBUG打開之後,free pointer是在object之外,並且多了很多其他的記憶體,例如red zonetracered_left_pad等。這裡之所以將FP後移就是因為為了檢測use-after-free問題,free object時會在將object填充magic num(0x6b)。如果不後移的話,豈不是破壞了object之間的單鏈表關係。

3.1. Red zone有什麼用

從圖中我們可以看到在object後面緊接著就是Red zone區域,那麼Red zone有什麼作用呢?既然緊隨其後,自然是檢測右邊界越界訪問(right out-of-bounds access)。原理很簡單,在Red zone區域填充magic num,檢查Red zone區域資料是否被修改即可知道是否發生right oob

可能你會想到如果越過Redzone,直接改寫了FP,豈不是檢測不到oob了,並且鏈表結構也被破壞了。其實在check_object()函式中會呼叫check_valid_pointer()來檢查FP是否valid,如果invalid,同樣會print error syslog

3.2. padding有什麼用

paddingsizeof(void *)  bytes的填充區域,在分配slab快取池時,會將所有的記憶體填充0x5a。同樣在free/alloc object的時候作為檢測的一種途徑。如果padding區域的資料不是0x5a,就代表發生了“Object padding overwritten”問題。這也是有可能,越界跨度很大。

3.3. red_left_pad有什麼用

red_left_padRed zone的作用一致。都是為了檢測oob。區別就是Red zone檢測right oob,而red_left_pad是檢測left oob。如果僅僅看到上面圖片中object layout。你可能會好奇,如果發生left oob,那麼應該是前一個objectred_left_pad區域被改寫,而不是當前objectred_left_pad。如果你註意到這個問題,還是很機智的,這都被你發現了。為了避免這種情況的發生,SLUB allocator在初始化slab快取池的時候會做一個轉換。

如果你去追蹤kmem_cache_create(),在calculate_sizes()中佈局object。區域劃分的layout就如同你看到上圖的上半部分。當我第一次看到這段代碼的時候,我也這麼認為。實際上卻不是這樣的。在struct page結構中有一個freelist指標,freelist會指向第一個available object。在構建object之間的單鏈表的時候,object首地址實際上都會加上一個red_left_pad的偏移,這樣實際的layout就如同圖片中轉換之後的layout。為什麼會這樣呢?因為在有SLUB DEBUG功能的時候,並沒有檢測left oob功能。這種轉換是後續一個補丁的修改。補丁就是為了增加left oob檢測功能。

做了轉換之後的red_left_pad就可以檢測leftoob。檢測的方法和Red zone區域一樣,填充的magic num也一樣,差別隻是檢測的區域不一樣而已。

4. SLUB DEBUG原理

經過上一節分析應該很清楚了大概的原理了。從high level考慮,SLUB就是利用特殊區域填充特殊的magic num,在每一次alloc/free的時候檢查magic num是否被意外修改。

4.1. magic num

SLUB 中有哪些magic num?所有使用的magic num都宏定義在include/linux/poison.h檔案。

SLUB_RED_INACTIVESLUB_RED_ACTIVE用來填充Red zonered_left_pad,目的是檢測oobPOISON_INUSE用來填充padding區域,同樣可以用來檢測oob,只不過是poison overwritePOISON_FREE作用是檢測use-after-free問題。POISON_ENDobject可用區域的最後一個位元組填充。

4.2. slab快取池填充

SLUB allocator申請一塊記憶體作為slab 快取池的時候,會將整塊記憶體填充POISON_INUSE。如下圖所示。

然後通過init_object()函式將相關的區域填充成free object的情況,並且建立單鏈表。註意freelist指標指向的位置,SLUB_DEBUG onoff的情況下是不一樣的。主要就是3.3節提到的轉換關係。為什麼這裡填充成freeobject的情況呢?其實就是為了假裝我這裡都是freeobject,也是符合情理的。object初始化流程如下。

4.3. free objectlayout

剛分配slab快取池和free object之後,系統都會通過呼叫init_object()函式初始化object各個區域,主要是填充magic numfree object layout如下圖所示。

  • red_left_padRed zone填充了SLUB_RED_INACTIVE0xbb);

  • object填充了POISON_FREE0x6b),但是最後一個byte填充POISON_END0xa5);

  • paddingallocate_slab的時候就已經被填充POISON_INUSE0x5a),如果程式意外改變,當檢測到padding被改變的時候,會output error syslog並繼續填充0x5a

4.4. alloc object layout

當從SLUB allocator申請一個object時,系統同樣會呼叫init_object()初始化成想要的模樣。alloc object layout如下圖所示。

  • red_left_padRed zone填充了SLUB_RED_ACTIVE0xcc);

  • object填充了POISON_FREE0x6b),但是最後一個byte填充POISON_END0xa5);

  • paddingallocate_slab的時候就已經被填充POISON_INUSE0x5a),如果程式意外改變,當檢測到padding被改變的時候,會output error syslog並繼續填充0x5a

alloc object layoutfree object layout相比較而言,也僅僅是red_left_padRed zone的不同。既然該填充的資料都搞定了,下麵就是如何檢查oobuse-after-free等問題了。

4.5. out-of-bounds bugs detect

下麵使用demo例程來說明oob檢測。我們使用kmalloc分配32 bytes記憶體,然後製造越界訪問第33個元素,必然會越界訪問。由於kmalloc是基於SLUB allocator,因此此bug可以檢測。

運行後的object layout如下圖所示。

我們可以看到,Red zone區域本來應該0xcc的地方被修改成了0x88。很明顯這是一個Redzone overwritten問題。那麼系統什麼時候會檢測到這個嚴重的bug呢?就在你kfree()之後。kfree()中會去檢測釋放的object中各個區域的值是否validRedzone區域的值全是0xcc就是valid,因此這裡會檢測0x88不是0xcc,進而輸出errorsyslogkfree()最終會呼叫free_consistency_checks()檢測objectfree_consistency_checks()函式如下。

  • check_valid_pointer()負責檢測objectfree pointer指標資料是否validoob是有可能導致這種情況得到發生。

  • on_freelist()檢測object是否已經free,可以檢測多次freebug

  • check_object()會檢測Red zone區域的數值是否被改變,因此這裡就會報出bug

如果是左邊界越界訪問,是否也同樣可以檢測出呢?可以測試以下demo例程。

運行後的object layout如下圖所示。

檢測方法大同小異,這裡也是最終在free_consistency_checks()函式中通過檢測red_left_pad區域發現left oob問題。

可能你會想如果我只申請記憶體不釋放的話,這個bug還能檢測到嗎?其實這裡是不行的。我們只能借助slabinfo工具主動觸發檢測功能。所以,這也是SLUB DEBUG的一個劣勢,它不能做到動態監測。它的檢測機制是被動的。

4.6. use-after-free bugs detect

如果是use-after-free問題,我們該如何檢測呢?首先上demo例程。

運行之後object layout如下圖所示。

還記得上面說的嗎?SLUB DEBUG是被動的。因此這裡就要選擇slabinfo工具了。命令中斷輸入slabinfo –v即可。slabinfo檢測的原理也很簡單,便利所有已經釋放的object,檢查object區域是否全是0x6b(最後一個位元組oxa5)即可,如果不是的話,自然就是use-after-free

5. slabinfo

我們看一下slabinfo –v命令的實現方式以及檢查的流程。slabinfo原始碼位於tools/vm/slabinfo.c檔案。slabinfo –v命令執行流程如下圖所示。

針對系統中每一個slab都會執行set_obj()函式。set_obj()代碼如下:

set_obj()引數name傳遞的是“validate”,n傳遞的是1。作用就是向/sys/kernel/slab// validate節點寫“1”觸發slab檢測功能。根據validate節點找到寫入口函式validate_store()。呼叫函式執行流如下圖所示。

validate_slab()代碼如下:

  • check_slab()會呼叫slab_pad_check()檢查slab padding區域。slab paddingobject裡面的pading不是一回事。如果說從buddy system分配的頁按照SLUB規則平分成很多object,那麼有可能不能整除,那麼剩下的unused區域就是slab paddingvalid的數值是0x5a。如下圖所示。

  • get_map()利用bitmap標記所有的available object。例如,slab快取池一共有10個物件,按地址大小排序標號0-9(相當於index)。假設58object已經被分配出去。那麼bitmap中除了bit5bit8以為,其餘位為1

  • 第一個for迴圈遍歷所有的available object是否有oobuse-after-freeobject padding overwritten等問題發生。

  • 第二個for迴圈遍歷所有已經分配出去的object是否發生oob問題。

記憶體管理在線直播

宋寶華老師《Linux任督二脈》系列繼行程調度後的第二脈——記憶體管理

報名在線課程:Linux的任督二脈之記憶體管理(2018.3.3-3.7五晚)

赞(0)

分享創造快樂