在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

宋牧春: Linux內(nèi)核內(nèi)存corruption檢查機(jī)制KASAN實(shí)現(xiàn)原理

Linux閱碼場(chǎng) ? 來(lái)源:Linux閱碼場(chǎng) ? 2023-11-06 16:32 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

1. 前言

KASAN是一個(gè)動(dòng)態(tài)檢測(cè)內(nèi)存錯(cuò)誤的工具。KASAN可以檢測(cè)全局變量、棧、堆分配的內(nèi)存發(fā)生越界訪問(wèn)等問(wèn)題。功能比SLUB DEBUG齊全并且支持實(shí)時(shí)檢測(cè)。越界訪問(wèn)的嚴(yán)重性和危害性通過(guò)我之前的文章(SLUB DEBUG技術(shù))應(yīng)該有所了解。正是由于SLUB DEBUG缺陷,因此我們需要一種更加強(qiáng)大的檢測(cè)工具。難道你不想嗎?KASAN就是其中一種。

KASAN的使用真的很簡(jiǎn)單。但是我是一個(gè)追求刨根問(wèn)底的人。僅僅止步于使用的層面,我是不愿意的,只有更清楚的了解實(shí)現(xiàn)原理才能更加熟練的使用工具。不止是KASAN,其他方面我也是這么認(rèn)為。但是,說(shuō)實(shí)話,寫(xiě)這篇文章是有點(diǎn)底氣不足的。

因?yàn)閺奈也殚喌馁Y料來(lái)說(shuō),國(guó)內(nèi)沒(méi)有一篇文章說(shuō)KASAN的工作原理,國(guó)外也是沒(méi)有什么文章關(guān)注KASAN的原理。大家好像都在說(shuō)How to use。由于本人水平有限,就根據(jù)現(xiàn)有的資料以及自己閱讀代碼揣摩其中的意思。本文章作為拋準(zhǔn)引玉,如果有不合理的地方還請(qǐng)指正。

注:文章代碼分析基于linux-4.15.0-rc3。

2. 簡(jiǎn)介

KernelAddressSANitizer(KASAN)是一個(gè)動(dòng)態(tài)檢測(cè)內(nèi)存錯(cuò)誤的工具。它為找到use-after-free和out-of-bounds問(wèn)題提供了一個(gè)快速和全面的解決方案。KASAN使用編譯時(shí)檢測(cè)每個(gè)內(nèi)存訪問(wèn),因此您需要GCC 4.9.2或更高版本。

檢測(cè)堆?;蛉肿兞康脑浇缭L問(wèn)需要GCC 5.0或更高版本。目前KASAN僅支持x86_64和arm64架構(gòu)(linux 4.4版本合入)。你使用ARM64架構(gòu),那么就需要保證linux版本在4.4以上。當(dāng)然了,如果你使用的linux也有可能打過(guò)KASAN的補(bǔ)丁。

例如,使用高通平臺(tái)做手機(jī)的廠商使用linux 3.18同樣支持KASAN。

3. 如何使用

使用KASAN工具是比較簡(jiǎn)單的,只需要添加kernel以下配置項(xiàng)。

CONFIG_SLUB_DEBUG=y

CONFIG_KASAN=y

為什么這里必須打開(kāi)SLUB_DEBUG呢?是因?yàn)橛卸螘r(shí)間KASAN是依賴SLUBU_DEBUG的,什么意思呢?就是在Kconfig中使用了depends on,明白了吧。

不過(guò)最新的代碼已經(jīng)不需要依賴了,可以看下提交。但是我建議你打開(kāi)該選項(xiàng),因?yàn)閘og可以輸出更多有用的信息。重新編譯kernel即可,編譯之后你會(huì)發(fā)現(xiàn)boot.img(Android環(huán)境)大小大了一倍左右。

所以說(shuō),影響效率不是沒(méi)有道理的。不過(guò)我們可以作為產(chǎn)品發(fā)布前的最后檢查,也可以排查越界訪問(wèn)等問(wèn)題。我們可以查看內(nèi)核日志內(nèi)容是否包含KASAN檢查出的bugs信息。

4. KASAN是如何實(shí)現(xiàn)檢測(cè)的?

KASAN的原理是利用額外的內(nèi)存標(biāo)記可用內(nèi)存的狀態(tài)。這部分額外的內(nèi)存被稱作shadow memory(影子區(qū))。KASAN將1/8的內(nèi)存用作shadow memory。使用特殊的magic num填充shadow memory,在每一次load/store(load/store檢查指令由編譯器插入)內(nèi)存的時(shí)候檢測(cè)對(duì)應(yīng)的shadow memory確定操作是否valid。

連續(xù)8 bytes內(nèi)存(8 bytes align)使用1 byte shadow memory標(biāo)記。如果8 bytes內(nèi)存都可以訪問(wèn),則shadow memory的值為0;如果連續(xù)N(1 =《 N 《= 7) bytes可以訪問(wèn),則shadow memory的值為N;如果8 bytes內(nèi)存訪問(wèn)都是invalid,則shadow memory的值為負(fù)數(shù)。

1ce85e48-7c38-11ee-939d-92fbcf53809c.jpg

在代碼運(yùn)行時(shí),每一次memory access都會(huì)檢測(cè)對(duì)應(yīng)的shawdow memory的值是否valid。這就需要編譯器為我們做些工作。編譯的時(shí)候,在每一次memory access前編譯器會(huì)幫我們插入__asan_load##size()或者_(dá)_asan_store##size()函數(shù)調(diào)用(size是訪問(wèn)內(nèi)存字節(jié)的數(shù)量)。這也是要求更新版本gcc的原因,只有更新的版本才支持自動(dòng)插入。

mov x0, #0x5678

movk x0, #0x1234, lsl #16

movk x0, #0x8000, lsl #32

movk x0, #0xffff, lsl #48

mov w1, #0x5

bl __asan_store1

strb w1, [x0]

上面一段匯編指令是往0xffff800012345678地址寫(xiě)5。在KASAN打開(kāi)的情況下,編譯器會(huì)幫我們自動(dòng)插入bl __asan_store1指令,__asan_store1函數(shù)就是檢測(cè)一個(gè)地址對(duì)應(yīng)的shadow memory的值是否允許寫(xiě)1 byte。

藍(lán)色匯編指令就是真正的內(nèi)存訪問(wèn)。因此KASAN可以在out-of-bounds的時(shí)候及時(shí)檢測(cè)。__asan_load##size()和__asan_store##size()的代碼在mm/kasan/kasan.c文件實(shí)現(xiàn)。

4.1. 如何根據(jù)shadow memory的值判斷內(nèi)存訪問(wèn)操作是否valid?

shadow memory檢測(cè)原理的實(shí)現(xiàn)主要就是__asan_load##size()和__asan_store##size()函數(shù)的實(shí)現(xiàn)。那么KASAN是如何根據(jù)訪問(wèn)的address以及對(duì)應(yīng)的shadow memory的狀態(tài)值來(lái)判斷訪問(wèn)是否合法呢?首先看一種最簡(jiǎn)單的情況。訪問(wèn)8 bytes內(nèi)存。

long *addr = (long *)0xffff800012345678;

*addr = 0;

以上代碼是訪問(wèn)8 bytes情況,檢測(cè)原理如下:

long *addr = (long *)0xffff800012345678;

char *shadow = (char *)(((unsigned long)addr 》》 3) + KASAN_SHADOW_OFFSE);

if (*shadow)

report_bug();

*addr = 0;

紅色區(qū)域類(lèi)似是編譯器插入的指令。既然是訪問(wèn)8 bytes,必須要保證對(duì)應(yīng)的shadow mempry的值必須是0,否則肯定是有問(wèn)題。那么如果訪問(wèn)的是1,2 or 4 bytes該如何檢查呢?也很簡(jiǎn)單,我們只需要修改一下if判斷條件即可。修改如下:

if (*shadow && *shadow 《 ((unsigned long)addr & 7) + N); //N = 1,2,4

如果*shadow的值為0代表8 bytes均可以訪問(wèn),自然就不需要report bug。addr & 7是計(jì)算訪問(wèn)地址相對(duì)于8字節(jié)對(duì)齊地址的偏移。還是使用下圖來(lái)說(shuō)明關(guān)系吧。假設(shè)內(nèi)存是從地址8~15一共8 bytes。對(duì)應(yīng)的shadow memory值為5,現(xiàn)在訪問(wèn)11地址。那么這里的N只要大于2就是invalid。

1cf44cda-7c38-11ee-939d-92fbcf53809c.jpg

4.2. shadow memory內(nèi)存如何分配?

在ARM64中,假設(shè)VA_BITS配置成48。那么kernel space空間大小是256TB,因此shadow memory的內(nèi)存需要32TB。我們需要在虛擬地址空間為KASAN shadow memory分配地址空間。所以我們有必要了解一下ARM64 memory layout。

基于linux-4.15.0-rc3的代碼分析,我繪制了如下memory layout(VA_BITS = 48)。kernel space起始虛擬地址是0xffff_0000_0000_0000,kernel space被分成幾個(gè)部分分別是KASAN、MODULE、VMALLOC、FIXMAP、PCI_IO、VMEMMAP以及linear mapping。其中KASAN的大小是32TB,正好是kernel space大小的1/8。不知道你注意到?jīng)]有,KERNEL的位置相對(duì)以前是不是有所不一樣。

你的印象中,KERNEL是不是位于linear mapping區(qū)域,這里怎么變成了VMALLOC區(qū)域?這里是Ard Biesheuvel提交的修改。主要是為了迎接ARM64世界的KASLR(which allows the kernel image to be located anywhere in the vmalloc area)的到來(lái)。

1d065772-7c38-11ee-939d-92fbcf53809c.jpg

4.3. 如何建立shadow memory的映射關(guān)系?

當(dāng)打開(kāi)KASAN的時(shí)候,KASAN區(qū)域位于kernel space首地址處,從0xffff_0000_0000_0000地址開(kāi)始,大小是32TB。shadow memory和kernel address轉(zhuǎn)換關(guān)系是:shadow_addr = (kaddr 》》 3) + KASAN_SHADOW_OFFSE。

為了將[0xffff_0000_0000_0000, 0xffff_ffff_ffff_ffff]和[0xffff_0000_0000_0000, 0xffff_1fff_ffff_ffff]對(duì)應(yīng)起來(lái),因此計(jì)算KASAN_SHADOW_OFFSE的值為0xdfff_2000_0000_0000。我們將KASAN區(qū)域放大,如下圖所示。

1d12c1e2-7c38-11ee-939d-92fbcf53809c.jpg

KASAN區(qū)域僅僅是分配的虛擬地址,在訪問(wèn)的時(shí)候必須建立和物理地址的映射才可以訪問(wèn)。上圖就是KASAN建立的映射布局。左邊是系統(tǒng)啟動(dòng)初期建立的映射。在kasan_early_init()函數(shù)中,將所有的KASAN區(qū)域映射到kasan_zero_page物理頁(yè)面。因此系統(tǒng)啟動(dòng)初期,KASAN并不能工作。

右側(cè)是在kasan_init()函數(shù)中建立的映射關(guān)系,kasan_init()函數(shù)執(zhí)行結(jié)束就預(yù)示著KASAN的正常工作。我們將不需要address sanitizer功能的區(qū)域同樣還是映射到kasan_zero_page物理頁(yè)面,并且是readonly。

我們主要是檢測(cè)kernel和物理內(nèi)存是否存在UAF或者OOB問(wèn)題。所以建立KERNEL和linear mapping(僅僅是所有的物理地址建立的映射區(qū)域)區(qū)域?qū)?yīng)的shadow memory建立真實(shí)的映射關(guān)系。

MOUDLE區(qū)域?qū)?yīng)的shadow memory的映射關(guān)系也是需要?jiǎng)?chuàng)建的,但是映射關(guān)系建立是動(dòng)態(tài)的,他在module加載的時(shí)候才會(huì)去創(chuàng)建映射關(guān)系。

4.4. 伙伴系統(tǒng)分配的內(nèi)存的shadow memory值如何填充?

既然shadow memory已經(jīng)建立映射,接下來(lái)的事情就是探究各種內(nèi)存分配器向shadow memory填充什么數(shù)據(jù)了。首先看一下伙伴系統(tǒng)allocate page(s)函數(shù)填充shadow memory情況。

1d2a3390-7c38-11ee-939d-92fbcf53809c.jpg

假設(shè)我們從buddy system分配4 pages。系統(tǒng)首先從order=2的鏈表中摘下一塊內(nèi)存,然后根據(jù)shadow memory address和memory address之間的對(duì)應(yīng)的關(guān)系找對(duì)應(yīng)的shadow memory。

這里shadow memory的大小將會(huì)是2KB,系統(tǒng)會(huì)全部填充0代表內(nèi)存可以訪問(wèn)。

我們對(duì)分配的內(nèi)存的任意地址內(nèi)存進(jìn)行訪問(wèn)的時(shí)候,首先都會(huì)找到對(duì)應(yīng)的shadow memory,然后根據(jù)shadow memory value判斷訪問(wèn)內(nèi)存操作是否valid。

如果釋放pages,情況又是如何呢?

1d613e12-7c38-11ee-939d-92fbcf53809c.jpg

同樣的,當(dāng)釋放pages的時(shí)候,會(huì)填充shadow memory的值為0xFF。如果釋放之后,依然訪問(wèn)內(nèi)存的話,此時(shí)KASAN根據(jù)shadow memory的值是0xFF就可以斷,這是一個(gè)use-after-free問(wèn)題。

4.5. SLUB分配對(duì)象的內(nèi)存的shadow memory值如何填充?

當(dāng)我們打開(kāi)KASAN的時(shí)候,SLUB Allocator管理的object layout將會(huì)放生一定的變化。如下圖所示。

1d6fbe56-7c38-11ee-939d-92fbcf53809c.jpg

在打開(kāi)SLUB_DEBUG的時(shí)候,object就增加很多內(nèi)存,KASAN打開(kāi)之后,在此基礎(chǔ)上又加了一截。為什么這里必須打開(kāi)SLUB_DEBUG呢?是因?yàn)橛卸螘r(shí)間KASAN是依賴SLUBU_DEBUG的,什么意思呢?就是在Kconfig中使用了depends on,明白了吧。不過(guò)最新的代碼已經(jīng)不需要依賴了,可以看下提交。

當(dāng)我們第一次創(chuàng)建slab緩存池的時(shí)候,系統(tǒng)會(huì)調(diào)用kasan_poison_slab()函數(shù)初始化shadow memory為下圖的模樣。整個(gè)slab對(duì)應(yīng)的shadow memory都填充0xFC。

1d865cec-7c38-11ee-939d-92fbcf53809c.jpg

上述步驟雖然填充了0xFC,但是接下來(lái)初始化object的時(shí)候,會(huì)改變一些shadow memory的值。我們先看一下kmalloc(20)的情況。我們知道kmalloc()就是基于SLUB Allocator實(shí)現(xiàn)的,所以會(huì)從kmalloc-32的kmem_cache中分配一個(gè)32 bytes object。

1daa7028-7c38-11ee-939d-92fbcf53809c.jpg

首先調(diào)用kmalloc(20)函數(shù)會(huì)匹配到kmalloc-32的kmem_cache,因此實(shí)際分配的object大小是32 bytes。KASAN同樣會(huì)標(biāo)記剩下的12 bytes的shadow memory為不可訪問(wèn)狀態(tài)。

根據(jù)object的地址,計(jì)算shadow memory的地址,并開(kāi)始填充數(shù)值。由于kmalloc()返回的object的size是32 bytes,由于kmalloc(20)只申請(qǐng)了20 bytes,剩下的12 bytes不能使用。KASAN必須標(biāo)記shadow memory這種情況。

object對(duì)應(yīng)的4 bytes shadow memory分別填充00 00 04 FC。00代表8個(gè)連續(xù)的字節(jié)可以訪問(wèn)。04代表前4個(gè)字節(jié)可以訪問(wèn)。

作為越界訪問(wèn)的檢測(cè)的方法??偣布釉谝黄鹗钦檬?0 bytes可訪問(wèn)。0xFC是Redzone標(biāo)記。如果訪問(wèn)了Redzone區(qū)域KASAN就會(huì)檢測(cè)out-of-bounds的發(fā)生。

當(dāng)申請(qǐng)使用之后,現(xiàn)在調(diào)用kfree()釋放之后的shadow memory情況是怎樣的呢?看下圖。

1dbfa0ce-7c38-11ee-939d-92fbcf53809c.jpg

根據(jù)object首地址找到對(duì)應(yīng)的shadow memory,32 bytes object對(duì)應(yīng)4 bytes的shadow memory,現(xiàn)在填充0xFB標(biāo)記內(nèi)存是釋放的狀態(tài)。此時(shí)如果繼續(xù)訪問(wèn)object,那么根據(jù)shadow memory的狀態(tài)值既可以確定是use-after-free問(wèn)題。

4.6. 全局變量的shadow memory值如何填充?

前面的分析都是基于內(nèi)存分配器的,Redzone都會(huì)隨著內(nèi)存分配器一起分配。那么global variables如何檢測(cè)呢?global variable的Redzone在哪里呢?這就需要編譯器下手了。編譯器會(huì)幫我們填充Redzone區(qū)域。例如我們定義一個(gè)全局變量a,編譯器會(huì)幫我們填充成下面的樣子。

char a[4];

轉(zhuǎn)換

struct {

char original[4];

char redzone[60];

} a; //32 bytes aligned

如果這里你問(wèn)我為什么填充60 bytes。其實(shí)我也不知道。這個(gè)轉(zhuǎn)換例子也是從KASAN作者的PPT中拿過(guò)來(lái)的。估計(jì)要涉及編譯器相關(guān)的知識(shí),我無(wú)能為力了,但是下面做實(shí)驗(yàn)來(lái)猜吧。當(dāng)然了,PPT的內(nèi)容也需要驗(yàn)證才具有說(shuō)服力。

盡信書(shū)則不如無(wú)書(shū)。我特地寫(xiě)三個(gè)全局變量來(lái)驗(yàn)證。發(fā)現(xiàn)System.map分配地址之間的差值正好是0x40。因此這里的確是填充60 bytes。 另外從我的測(cè)試發(fā)現(xiàn),如果上述的數(shù)組a的大小是33的時(shí)候,填充的redzone就是63 bytes。

所以我推測(cè),填充的原理是這樣的。全局變量實(shí)際占用內(nèi)存總數(shù)S(以byte為單位)按照每塊32 bytes平均分成N塊。假設(shè)最后一塊內(nèi)存距離目標(biāo)32 bytes還差y bytes(if S%32 == 0,y = 0),那么redzone填充的大小就是(y + 32) bytes。畫(huà)圖示意如下(S%32 != 0)。

因此總結(jié)的規(guī)律是:redzone = 63 – (S - 1) % 32

1dd8a722-7c38-11ee-939d-92fbcf53809c.jpg

全局變量redzone區(qū)域?qū)?yīng)的shadow memory是在什么填充的呢?又是如何調(diào)用的呢?這部分是由編譯器幫我們完成的。編譯器會(huì)為每一個(gè)全局變量創(chuàng)建一個(gè)函數(shù),函數(shù)名稱是:_GLOBAL__sub_I_65535_1_##global_variable_name。

這個(gè)函數(shù)中通過(guò)調(diào)用__asan_register_globals()函數(shù)完成shadow memory標(biāo)記。并且將自動(dòng)生成的這個(gè)函數(shù)的首地址放在.init_array段。

在kernel啟動(dòng)階段,通過(guò)以下代調(diào)用關(guān)系最終調(diào)用所有全局變量的構(gòu)造函數(shù)。kernel_init_freeable()-》do_basic_setup() -》do_ctors()。do_ctors()代碼實(shí)現(xiàn)如下:

static void __init do_ctors(void)

{

ctor_fn_t *fn = (ctor_fn_t *) __ctors_start;

for (; fn 《 (ctor_fn_t *) __ctors_end; fn++)

(*fn)();

}

這里的代碼意思對(duì)于輕車(chē)熟路的你再熟悉不過(guò)了吧。因?yàn)閮?nèi)核中這么搞的太多了。便利__ctors_start和__ctors_end之間的所有數(shù)據(jù),作為函數(shù)地址進(jìn)行調(diào)用,即完成了所有的global variables的shadow memory初始化。

我們可以從鏈接腳本中知道__ctors_start和__ctors_end的意思。 #define KERNEL_CTORS() 。 = ALIGN(8);

VMLINUX_SYMBOL(__ctors_start) = 。;

KEEP(*(.ctors))

KEEP(*(SORT(.init_array.*)))

KEEP(*(.init_array))

VMLINUX_SYMBOL(__ctors_end) = 。; 上面說(shuō)了這么多,不知道你是否產(chǎn)生了疑心?怎么都是猜啊!猜的能準(zhǔn)確嗎?

是的,我也這么覺(jué)得。是騾子是馬,拉出來(lái)溜溜唄!現(xiàn)在用事實(shí)說(shuō)話。首先我創(chuàng)建一個(gè)c文件drivers/input/smc.c。在smc.c文件中創(chuàng)建3個(gè)全局變量如下:

1dec3fc6-7c38-11ee-939d-92fbcf53809c.jpg

然后就隨便使用吧!編譯kernel,我們先看看System.map文件中,3個(gè)全局變量分配的地址。 ffff200009f540e0 B smc_num1 ffff200009f54120 B smc_num2 ffff200009f54160 B smc_num3 還記得上面說(shuō)會(huì)有一個(gè)形如_GLOBAL__sub_I_65535_1_##global_variable_name的函數(shù)嗎?

在System.map文件文件中,我看到了_GLOBAL__sub_I_65535_1_smc_num1符號(hào)。但是沒(méi)有smc_num2和smc_num3的構(gòu)造函數(shù)。你是不是很奇怪,不是每一個(gè)全局變量都會(huì)創(chuàng)建一個(gè)類(lèi)似的構(gòu)造函數(shù)嗎?

馬上為你揭曉。我們先執(zhí)行aarch64-linux-gnu-objdump –s –x –d vmlinux 》 vmlinux.txt命令得到反編譯文件。

現(xiàn)在好多重要的信息在vmlinux.txt?,F(xiàn)在主要就是查看vmlinux.txt文件。先看一下_GLOBAL__sub_I_65535_1_smc_num1函數(shù)的實(shí)現(xiàn)。

1dfe78bc-7c38-11ee-939d-92fbcf53809c.jpg

匯編和C語(yǔ)言傳遞參數(shù)在ARM64平臺(tái)使用的是x0~x7。通過(guò)上面的匯編計(jì)算一下,x0=0xffff200009682c50,x1=3。然后調(diào)用__asan_register_globals()函數(shù),x0和x1就是傳遞的參數(shù)。我們看一下__asan_register_globals()函數(shù)實(shí)現(xiàn)。

void __asan_register_globals(struct kasan_global *globals, size_t size)

{

int i;

for (i = 0; i 《 size; i++)

register_global(&globals[i]);

}

size是3就是要初始化全局變量的個(gè)數(shù),所以這里只需要一個(gè)構(gòu)造函數(shù)即可。一次性將3個(gè)全局變量全部搞定。這里再說(shuō)一點(diǎn)猜測(cè)吧!我猜測(cè)是以文件為單位編譯器創(chuàng)建一個(gè)構(gòu)造函數(shù)即可,將本文件全局變量一次性全部打包初始化。第一個(gè)參數(shù)globals是0xffff200009682c50,繼續(xù)從vmlinux.txt中查看該地址處的數(shù)據(jù)。struct kasan_global是編譯器幫我們自動(dòng)創(chuàng)建的結(jié)構(gòu)體,每一個(gè)全局變量對(duì)應(yīng)一個(gè)struct kasan_global結(jié)構(gòu)體。struct kasan_global結(jié)構(gòu)體存放的位置是.data段,因此我們可以從.data段查找當(dāng)前地址對(duì)應(yīng)的數(shù)據(jù)。數(shù)據(jù)如下:

1e148d14-7c38-11ee-939d-92fbcf53809c.jpg

首先f(wàn)fff200009682c50對(duì)應(yīng)的第一個(gè)數(shù)據(jù)6041f509 0020ffff,這是個(gè)啥?其實(shí)是一個(gè)地址數(shù)據(jù),你是不是又疑問(wèn)了,ARM64的kernel space地址不是ffff開(kāi)頭嗎?這個(gè)怎么60開(kāi)頭?其實(shí)這個(gè)地址數(shù)據(jù)是反過(guò)來(lái)的,你應(yīng)該從右向左看。這個(gè)地址其實(shí)是ffff200009f54160。這不正是smc_num3的地址嘛!解析這段數(shù)據(jù)之前需要了解一下struct kasan_global結(jié)構(gòu)體。

/* The layout of struct dictated by compiler */

struct kasan_global {

const void *beg; /* Address of the beginning of the global variable. */

size_t size; /* Size of the global variable. */

size_t size_with_redzone; /* Size of the variable + size of the red zone. 32 bytes aligned */

const void *name;

const void *module_name; /* Name of the module where the global variable is declared. */

unsigned long has_dynamic_init; /* This needed for C++ */

#if KASAN_ABI_VERSION 》= 4

struct kasan_source_location *location;

#endif

};

第一個(gè)成員beg就是全局變量的首地址。跟上面的分析一致。第二個(gè)成員size從上面數(shù)據(jù)看出是7,正好對(duì)應(yīng)我們定義的smc_num3[7],正好7 bytes。

size_with_redzone的值是0x40,正好是64。根據(jù)上面猜測(cè)redzone=63-(7-1)%32=57。加上size正好是64,說(shuō)明之前猜測(cè)的redzone計(jì)算方法沒(méi)錯(cuò)。name成員對(duì)應(yīng)的地址是ffff2000092bd6d0??聪耭fff2000092bd6d0存儲(chǔ)的是什么。

1e2b2394-7c38-11ee-939d-92fbcf53809c.png

所以name就是全局變量的名稱轉(zhuǎn)換成字符串。同樣的方式得到module_name的地址是ffff2000092bd6b8。繼續(xù)看看這段地址存儲(chǔ)的數(shù)據(jù)。

1e414ff2-7c38-11ee-939d-92fbcf53809c.png

一目了然,module_name是文件的路徑。has_dynamic_init的值就是0,這是C++需要的。我用的GCC版本是5.0左右,所以這里的KASAN_ABI_VERSION=4。

這里location成員的地址是ffff200009682c20,繼續(xù)追蹤該地址的數(shù)據(jù)。 ffff200009682c20 b8d62b09 0020ffff 0e000000 0f000000 解析這段數(shù)據(jù)之前要先了解struct kasan_source_location結(jié)構(gòu)體。

/* The layout of struct dictated by compiler */

struct kasan_source_location {

const char *filename;

int line_no;

int column_no;

};

第一個(gè)成員filename地址是ffff2000092bd6b8和module_name一樣的數(shù)據(jù)。剩下兩個(gè)數(shù)據(jù)分別是14和15,分別代表全局變量定義地方的行號(hào)和列號(hào)。現(xiàn)在回到上面我定義變量的截圖,仔細(xì)數(shù)數(shù)列號(hào)是不是15,行號(hào)截圖中也有哦!特地截出來(lái)給你看的。

剩下的struct kasan_global數(shù)據(jù)就是smc_num1和smc_num2的數(shù)據(jù)。分析就不說(shuō)了。前面說(shuō)_GLOBAL__sub_I_65535_1_smc_num1函數(shù)會(huì)被自動(dòng)調(diào)用,該地址數(shù)據(jù)填充在__ctors_start和__ctors_end之間。

現(xiàn)在也證明一下觀點(diǎn)。先從System.map得到符號(hào)的地址數(shù)據(jù)。 ffff2000093ac5d8 T __ctors_start ffff2000093ae860 T __ctors_end 然后搜索一下_GLOBAL__sub_I_65535_1_smc_num1的地址ffff200009381df0被存儲(chǔ)在什么位置,記得搜索的關(guān)鍵字是f01d3809 0020ffff。

ffff2000093ae0c0 f01d3809 0020ffff 181e3809 0020ffff 可以看出ffff2000093ae0c0地址處存儲(chǔ)著_GLOBAL__sub_I_65535_1_smc_num1函數(shù)地址。這個(gè)地址不是正好位于__ctors_start和__ctors_end之間嘛!

現(xiàn)在就剩下__asan_register_globals()函數(shù)到底是是怎么初始化shadow memory的呢?以char a[4]為例,如下圖所示

1e5ae44e-7c38-11ee-939d-92fbcf53809c.jpg

a[4]只有4 bytes可以訪問(wèn),所以對(duì)應(yīng)的shadow memory的第一個(gè)byte值是4,后面的redzone就填充0xFA作為越界檢測(cè)。a[4]只有4 bytes可以訪問(wèn),所以對(duì)應(yīng)的shadow memory的第一個(gè)byte值是4,后面的redzone就填充0xFA作為越界檢測(cè)。因?yàn)檫@里是全局變量,因此分配的內(nèi)存區(qū)域位于kernel區(qū)域。

4.7. 棧分配變量的readzone是如何分配的?

從棧中分配的變量同樣和全局變量一樣需要填充一些內(nèi)存作為redzone區(qū)域。下面繼續(xù)舉個(gè)例子說(shuō)明編譯器怎么填充。首先來(lái)一段正常的代碼,沒(méi)有編譯器的插手。

void foo()

{

char a[328];

}

再來(lái)看看編譯器插了哪些東西進(jìn)去。

void foo() {

char rz1[32];

char a[328];

char rz2[56];

int *shadow = (&rz1 》》 3)+ KASAN_SHADOW_OFFSE;

shadow[0] = 0xffffffff;

shadow[11] = 0xffffff00;

shadow[12] = 0xffffffff; ?------------使用完畢--------------?

shadow[0] = shadow[11] = shadow[12] = 0; }

紅色部分是編譯器填充內(nèi)存,rz2是56,可以根據(jù)上一節(jié)全局變量的公式套用計(jì)算得到。但是這里在變量前面竟然還有32 bytes的rz1。這個(gè)是和全局變量的不同,我猜測(cè)這里是為了檢測(cè)棧變量左邊界越界問(wèn)題。

藍(lán)色部分代碼也是編譯器填充,初始化shadow memory。棧的填充就沒(méi)有探究那么深入了,有興趣的讀者可以自己探究。

5. Error log信息包含哪些信息?

從kernel的Documentation文檔找份典型的KASAN bug輸出的log信息如下。

1e71f24c-7c38-11ee-939d-92fbcf53809c.jpg

1e8c91ce-7c38-11ee-939d-92fbcf53809c.jpg

1e9c79b8-7c38-11ee-939d-92fbcf53809c.jpg

輸出的信息很豐富,包含了bug發(fā)生的類(lèi)型、SLUB輸出的object內(nèi)存信息、Call Trace以及shadow memory的狀態(tài)值。其中紅色信息都是比較重要的信息。我沒(méi)有寫(xiě)demo歷程,而是找了一份log信息,不是我想偷懶,而是鍛煉自己。怎么鍛煉呢?我想問(wèn)的是,從這份log中你可以推測(cè)代碼應(yīng)該是怎么樣的?我可以得到一下信息:

1) 程序是通過(guò)kmalloc接口申請(qǐng)內(nèi)存的;

2) 申請(qǐng)的內(nèi)存大小是123 bytes,即p = kamlloc(123);

3) 代碼中類(lèi)似往p[123]中寫(xiě)1 bytes導(dǎo)致越界訪問(wèn)的bug;

4) 在3)步驟發(fā)生前沒(méi)有任何的對(duì)該內(nèi)存的寫(xiě)操作; 如果你也能得到以上4點(diǎn)猜測(cè),我覺(jué)的我寫(xiě)的這幾篇文章你是真的看明白了。

首先輸出信息是有SLUB的信息,所以應(yīng)該是通過(guò)kmalloc()接口;在打印的shadow memory的值中,我們看到連續(xù)的15個(gè)0和一個(gè)3,所以申請(qǐng)的內(nèi)存size就是15x8+3=123;由于是往ffff8800693bc5d3地址寫(xiě)1個(gè)字節(jié),并且object首地址是ffff8800693bc558,所以推測(cè)是往p[123]寫(xiě)1 byte出問(wèn)題;由于log中將object中所有的128 bytes數(shù)據(jù)全部打印出來(lái),一共是127個(gè)0x6b和一個(gè)0xa5(SLUB DEBUG文章介紹的內(nèi)容)。

所以我推測(cè)在3)步驟發(fā)生前沒(méi)有任何的對(duì)該內(nèi)存的寫(xiě)操作。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1413

    瀏覽量

    41205
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11485

    瀏覽量

    213137
  • 堆棧
    +關(guān)注

    關(guān)注

    0

    文章

    183

    瀏覽量

    20073

原文標(biāo)題:宋牧春: Linux內(nèi)核內(nèi)存corruption檢查機(jī)制KASAN實(shí)現(xiàn)原理

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    內(nèi)存管理之KASAN的工作原理

    KASAN的原理是利用額外的內(nèi)存標(biāo)記可用內(nèi)存的狀態(tài)。這部分額外的內(nèi)存被稱作shadow memory(影子區(qū))。KASAN將1/8的
    發(fā)表于 09-19 11:40 ?5022次閱讀
    <b class='flag-5'>內(nèi)存</b>管理之<b class='flag-5'>KASAN</b>的工作原理

    走進(jìn)Linux內(nèi)存系統(tǒng)探尋內(nèi)存管理的機(jī)制和奧秘

    Linux 內(nèi)存是后臺(tái)開(kāi)發(fā)人員,需要深入了解的計(jì)算機(jī)資源。合理的使用內(nèi)存,有助于提升機(jī)器的性能和穩(wěn)定性。本文主要介紹Linux 內(nèi)存組織結(jié)構(gòu)
    的頭像 發(fā)表于 01-05 09:47 ?1915次閱讀

    Linux內(nèi)核內(nèi)存泄漏怎么辦

    Linux內(nèi)核開(kāi)發(fā)中,Kmemleak是一種用于檢測(cè)內(nèi)核內(nèi)存泄漏的工具。
    發(fā)表于 07-04 11:04 ?982次閱讀

    Linux內(nèi)核地址映射模型與Linux內(nèi)核高端內(nèi)存詳解

    Linux 操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,兩者不能簡(jiǎn)單地使用指針傳遞數(shù)據(jù),因?yàn)?b class='flag-5'>Linux使用的虛擬內(nèi)存機(jī)制,用戶
    發(fā)表于 05-08 10:33 ?3588次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>地址映射模型與<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>高端<b class='flag-5'>內(nèi)存</b>詳解

    詳解Linux內(nèi)核搶占實(shí)現(xiàn)機(jī)制

    本文詳解了Linux內(nèi)核搶占實(shí)現(xiàn)機(jī)制。首先介紹了內(nèi)核搶占和用戶搶占的概念和區(qū)別,接著分析了不可搶占內(nèi)核
    發(fā)表于 08-06 06:16

    linux內(nèi)存管理機(jī)制淺析

    本內(nèi)容介紹了arm linux內(nèi)存管理機(jī)制,詳細(xì)說(shuō)明了linux內(nèi)核內(nèi)存管理,
    發(fā)表于 12-19 14:09 ?73次下載
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)存</b>管理<b class='flag-5'>機(jī)制</b>淺析

    基于Linux內(nèi)核2_6的進(jìn)程攔截機(jī)制的研究和實(shí)現(xiàn)_王全民

    基于Linux內(nèi)核2_6的進(jìn)程攔截機(jī)制的研究和實(shí)現(xiàn)_王全民
    發(fā)表于 03-18 09:15 ?3次下載

    linux內(nèi)核rcu機(jī)制詳解

    Linux內(nèi)核源碼當(dāng)中,關(guān)于RCU的文檔比較齊全,你可以在 /Documentation/RCU/ 目錄下找到這些文件。Paul E. McKenney 是內(nèi)核中RCU源碼的主要實(shí)現(xiàn)
    發(fā)表于 11-13 16:47 ?8970次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>rcu<b class='flag-5'>機(jī)制</b>詳解

    linux內(nèi)核oom機(jī)制分析

    Linux 內(nèi)核有個(gè)機(jī)制叫OOM killer(Out-Of-Memory killer),該機(jī)制會(huì)監(jiān)控那些占用內(nèi)存過(guò)大,尤其是瞬間很快消耗
    發(fā)表于 11-13 17:01 ?1543次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>oom<b class='flag-5'>機(jī)制</b>分析

    高端內(nèi)存的詳解:linux用戶空間與內(nèi)核空間

    Linux 操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,兩者不能簡(jiǎn)單地使用指針傳遞數(shù)據(jù),因?yàn)?b class='flag-5'>Linux使用的虛擬內(nèi)存機(jī)制,用戶
    發(fā)表于 04-28 17:33 ?1121次閱讀
    高端<b class='flag-5'>內(nèi)存</b>的詳解:<b class='flag-5'>linux</b>用戶空間與<b class='flag-5'>內(nèi)核</b>空間

    你了解過(guò)Linux內(nèi)核中的Device Mapper 機(jī)制?

    Device mapper 是 Linux 2.6 內(nèi)核中提供的一種從邏輯設(shè)備到物理設(shè)備的映射框架機(jī)制,在該機(jī)制下,用戶可以很方便的根據(jù)自己的需要制定
    發(fā)表于 04-29 15:25 ?836次閱讀

    可以了解并學(xué)習(xí)Linux 內(nèi)核的同步機(jī)制

    Linux內(nèi)核同步機(jī)制,挺復(fù)雜的一個(gè)東西,常用的有自旋鎖,信號(hào)量,互斥體,原子操作,順序鎖,RCU,內(nèi)存屏障等。
    發(fā)表于 05-14 14:10 ?815次閱讀

    Linux內(nèi)核文件Cache機(jī)制

    Linux內(nèi)核文件Cache機(jī)制(開(kāi)關(guān)電源技術(shù)與設(shè)計(jì) 第二版)-Linux內(nèi)核文件Cache機(jī)制
    發(fā)表于 08-31 16:34 ?4次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>文件Cache<b class='flag-5'>機(jī)制</b>

    Linux內(nèi)核實(shí)現(xiàn)內(nèi)存管理的基本概念

    本文概述Linux內(nèi)核實(shí)現(xiàn)內(nèi)存管理的基本概念,在了解基本概念后,逐步展開(kāi)介紹實(shí)現(xiàn)內(nèi)存管理的相關(guān)技術(shù),后面會(huì)分多篇進(jìn)行介紹。
    發(fā)表于 06-23 11:56 ?1094次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核實(shí)現(xiàn)</b><b class='flag-5'>內(nèi)存</b>管理的基本概念

    Linux內(nèi)核KASAN實(shí)現(xiàn)原理詳解

    KernelAddressSANitizer(KASAN)是一個(gè)動(dòng)態(tài)檢測(cè)內(nèi)存錯(cuò)誤的工具。它為找到use-after-free和out-of-bounds問(wèn)題提供了一個(gè)快速和全面的解決方案。KASAN使用編譯時(shí)檢測(cè)每個(gè)
    發(fā)表于 11-06 16:29 ?2444次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>KASAN</b><b class='flag-5'>實(shí)現(xiàn)</b>原理詳解
    主站蜘蛛池模板: 午夜影视啪啪免费体验区深夜 | 黄色免费在线视频 | 久久久综合久久 | 97人人做人人爱 | 日本高清午夜色wwwσ | 啪啪国产视频 | 色播影院性播免费看 | 亚洲视屏一区 | 国产伦理一区二区三区 | 国产一级特黄生活片 | 日韩毛片在线看 | 国产精品资源站 | 在线免费看黄视频 | 色网站免费看 | 天天看夜夜操 | 5252色欧美在线激情 | 91黄色视屏 | 午夜爱爱网站 | 国产特级毛片aaaaaa毛片 | 四虎成人欧美精品在永久在线 | 久久亚洲精品国产亚洲老地址 | 美女扒开尿口给男的桶个爽 | 性感美女毛片 | 成人黄色激情网 | 可以免费看黄色的网站 | 国产性videosgratis| 黄色视奸 | 人人看人人做 | 伊人黄色| 精品国产麻豆免费人成网站 | 欧美日韩精品一区二区在线线 | 天堂网在线播放 | 四虎hu| 日本黄色录象 | 国产男女交性视频播放免费bd | 日本乱妇 | 欧美满足你的丝袜高跟ol | 最近在线观看免费完整视频 | 婷婷中文网 | 久久国产精品视频 | 欧美午夜视频一区二区三区 |