修改.config文件爲CONFIG_CC_STACKPROTECTOR=y來啓用。未來飛天內核可以將這個選項開啓來防止利用內核stack溢出的0day攻擊。
這個補丁的防溢出原理是: 在進程啓動的時候, 在每個buffer的後面放置一個預先設置好的stack canary,你可以
把它理解成一個哨兵, 當buffer發生緩衝區溢出的時候, 肯定會破壞stack canary的值, 當stack canary的值被破壞的時候, 內核就會直接當機。那麼是怎麼判斷stack canary
被覆蓋了呢? 其實這個事情是gcc來做的,內核在編譯的時候給gcc加了個-fstack-protector參數, 我們先來研究下這個參數是做什麼用的。
先寫個簡單的有溢出的程序:
[wzt@localhost csaw]$ cat test.c
#include <stdio.h>
#include <stdlib.h>
void test(void)
{
char buff[64];
memset(buff, 0x41, 128); //向64大小的buffer拷貝128字節, 肯定會發生緩衝區溢出。
}
int main(void)
{
test();
return 0;
}
[wzt@localhost csaw]$ gcc -o test test.c
[wzt@localhost csaw]$ ./test
段錯誤
反彙編看看:
[wzt@localhost csaw]$ objdump -d test > hex
08048384 <test>:
8048384: 55 push %ebp
8048385: 89 e5 mov %esp,%ebp
8048387: 83 ec 58 sub $0x58,%esp
804838a: c7 44 24 08 80 00 00 movl $0x80,0x8(%esp)
8048391: 00
8048392: c7 44 24 04 41 00 00 movl $0x41,0x4(%esp)
8048399: 00
804839a: 8d 45 c0 lea 0xffffffc0(%ebp),%eax
804839d: 89 04 24 mov %eax,(%esp)
80483a0: e8 e3 fe ff ff call 8048288 <memset@plt>
80483a5: c9 leave
80483a6: c3 ret
沒什麼特別的,我們在加上-fstack-protector參數看看:
[wzt@localhost csaw]$ gcc -o test test.c -fstack-protector
[wzt@localhost csaw]$ ./test
*** stack smashing detected ***: ./test terminated
已放棄
這次程序打印了一條堆棧被溢出的信息,然後就自動退出了。
在反彙編看下:
[wzt@localhost csaw]$ objdump -d test > hex1
080483d4 <test>:
80483d4: 55 push %ebp
80483d5: 89 e5 mov %esp,%ebp
80483d7: 83 ec 68 sub $0x68,%esp
80483da: 65 a1 14 00 00 00 mov %gs:0x14,%eax
80483e0: 89 45 fc mov %eax,0xfffffffc(%ebp)
80483e3: 31 c0 xor %eax,%eax
80483e5: c7 44 24 08 80 00 00 movl $0x80,0x8(%esp)
80483ec: 00
80483ed: c7 44 24 04 41 00 00 movl $0x41,0x4(%esp)
80483f4: 00
80483f5: 8d 45 bc lea 0xffffffbc(%ebp),%eax
80483f8: 89 04 24 mov %eax,(%esp)
80483fb: e8 cc fe ff ff call 80482cc <memset@plt>
8048400: 8b 45 fc mov 0xfffffffc(%ebp),%eax
8048403: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
804840a: 74 05 je 8048411 <test+0x3d>
804840c: e8 db fe ff ff call 80482ec <__stack_chk_fail@plt>
8048411: c9 leave
8048412: c3 ret
使用-fstack-protector參數後, gcc在函數的開頭放置了幾條彙編代碼:
80483d7: 83 ec 68 sub $0x68,%esp
80483da: 65 a1 14 00 00 00 mov %gs:0x14,%eax
80483e0: 89 45 fc mov %eax,0xfffffffc(%ebp)
將代碼段gs偏移0x14內存處的值賦值給了ebp-4, 也就是第一個變量值的後面。
在call完memeset後,有如下彙編代碼:
80483fb: e8 cc fe ff ff call 80482cc <memset@plt>
8048400: 8b 45 fc mov 0xfffffffc(%ebp),%eax
8048403: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
804840a: 74 05 je 8048411 <test+0x3d>
804840c: e8 db fe ff ff call 80482ec <__stack_chk_fail@plt>
在memset後,gcc要檢查這個操作是否發生了堆棧溢出, 將保存在ebp-4的這個值與原來的值對比一下,
如果不相同, 說明堆棧發生了溢出,那麼就會執行__stack_chk_fail這個函數, 這個函數是glibc實現的,
打印出上面看到的信息, 然後進程退出。
從這個例子中我們可以看出gcc使用了-fstack-protector參數後,會自動檢查堆棧是否發生了溢出, 但是有一個前提就是
內核要給每個進程提前設置好一個檢測值放置在%gs:0x14位置處, 這個值稱之爲stack canary。所以我們可以看到防止
堆棧溢出是由內核和gcc共同來完成的。
gcc的任務就是放置幾條彙編代碼, 然後和%gs:0x14位置處的值進行對比即可。 主要任務還是內核如何來設置stack canary, 也是
CC_STACKPROTECTOR補丁要實現的目的, 下面我們仔細來看下這個補丁是如何實現的。
既然gcc硬性規定了stack canary必須在%gs的某個偏移位置處, 那麼內核也必須按着這個規定來設置。
對於32位和64位內核, gs寄存器有着不同的功能。
64位內核gcc要求stack canary是放置在gs段的40偏移處, 並且gs寄存器在每cpu變量中是共享的,每cpu變量irq_stack_union的結構如下:
arch/x86/include/asm/processor.h
union irq_stack_union {
char irq_stack[IRQ_STACK_SIZE];
/*
* GCC hardcodes the stack canary as %gs:40. Since the
* irq_stack is the object at %gs:0, we reserve the bottom
* 48 bytes of the irq stack for the canary.
*/
struct {
char gs_base[40];
unsigned long stack_canary;
};
};
DECLARE_PER_CPU_FIRST(union irq_stack_union, irq_stack_union);
gs_base只是一個40字節的站位空間, stack_canary就緊挨其後。
並且在應用程序進出內核的時候,內核會使用swapgs指令自動更換gs寄存器的內容。
32位下就稍微有點複雜了。由於某些處理器在加載不同的段寄存器時很慢, 所以內核使用fs段寄存器替換了
gs寄存器。 但是gcc在使用-fstack-protector的時候, 還要用到gs段寄存器, 所以內核還要管理gs寄存器,
我們要把CONFIG_X86_32_LAZY_GS選項關閉, gs也只在進程切換的時候才改變。 32位用每cpu變量stack_canary保存stack canary。
前面講過當gcc檢測到堆棧溢出的時候, 會調用glibc的__stack_chk_fail函數, 但是當內核堆棧發生溢出的時候,
不能調用glibc的函數,所以內核自己實現了一個__stack_chk_fail函數:
kernel/panic.c
#ifdef CONFIG_CC_STACKPROTECTOR
/*
* Called when gcc's -fstack-protector feature is used, and
* gcc detects corruption of the on-stack canary value
*/
void __stack_chk_fail(void)
{
panic("stack-protector: Kernel stack is corrupted in: %p\n",
__builtin_return_address(0));
}
EXPORT_SYMBOL(__stack_chk_fail);
#endif
當內核堆棧發生溢出的時候,就會執行__stack_chk_fail函數, 內核當機。 這就是這個補丁的原理,不懂的同學請參考:
http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=commitdiff;h=60a5317ff0f42dd313094b88f809f63041568b08