轉自:http://bbs.chinaunix.net/thread-4117342-1-1.html
檢測內核的堆棧溢出
http://www.alivepea.me/kernel/kernel-overflow/
“如果建築工人蓋房子的方式跟程序員寫程序一樣,那第一只飛來的啄木鳥就將毀掉人 類文明。” – Gerald Weinberg
內核堆棧溢出通常有兩種情況。一種是函數調用棧超出了內核棧THREAD_SIZE的大小, 這是棧底越界,另一種是棧上緩沖越界訪問,這是棧頂越界。
檢測棧底越界
以arm平台為例,內核棧THREAD_SIZE為8K,當調用棧層次過多或某調用棧上分配過大的 空間,就會導致它越界。越界后struct thread_info結構可能被破壞,輕則內核 panic,重則內核數據被覆蓋仍繼續運行。
檢測這類棧溢出較通用的辦法是在每次中斷到來的時候檢查當前的棧指針sp是否超過了 某個閾值STACK_WARN。以下是在arm支持上DEBUG_STACKOVERFLOW的 arm-debug_stackoverflow.patch.
--- a/arch/arm/include/asm/thread_info.h
+++ b/arch/arm/include/asm/thread_info.h
@@ -19,6 +19,9 @@
#define THREAD_SIZE 8192
#define THREAD_START_SP (THREAD_SIZE -
+#define THREAD_MASK (THREAD_SIZE - 1UL)
+#define STACK_WARN (THREAD_SIZE /
+
#ifndef __ASSEMBLY__
struct task_struct;
diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c
--- a/arch/arm/kernel/irq.c
+++ b/arch/arm/kernel/irq.c
@@ -56,6 +56,24 @@ int arch_show_interrupts(struct seq_file *p, int prec)
return 0;
}
+static inline void check_stack_overflow(void)
+{
+
+ register unsigned long sp asm ("sp";
+
+ sp &= THREAD_MASK;
+
+ /*
+ * Check for stack overflow: is there less than STACK_WARN free?
+ * STACK_WARN is defined as 1/8 of THREAD_SIZE by default.
+ */
+ if (unlikely(sp < (sizeof(struct thread_info) + STACK_WARN))) {
+ printk("do_IRQ: stack overflow: %ld\n",
+ sp - sizeof(struct thread_info));
+ dump_stack();
+ }
+}
+
/*
* handle_IRQ handles all hardware IRQ's. Decoded IRQs should
* not come via this function. Instead, they should provide their
@@ -68,6 +86,8 @@ void handle_IRQ(unsigned int irq, struct pt_regs *regs)
irq_enter();
+ check_stack_overflow();
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
以上的方法基於內核的中斷棧在當前的cpu線程棧上和內核棧8K對齊兩個前提。 這種方法有兩個缺點:
棧已經溢出了,但中斷還沒來得及發生。使用lkdtm驗證如下:
# mount -t debugfs none /d
# echo OVERFLOW > /d//provoke-crash/DIRECT
在中斷中沒有得到警告信息。
arm的中斷很可能只發生在某個core上,並不是均等的,這樣也不能檢測其它的core上 內核棧溢出。這可能也是內核沒有為arm提供DEBUG_STACK_OVERFLOW的原因。
內核的STACK_TRACER是另一種檢測棧底溢出更可靠的方法,選上內核配置 CONFIG_STACK_TRACER后,使用方法如下:
# echo 10 > /sys/module/lkdtm/parameters/recur_count
# echo 1 > /proc/sys/kernel/stack_tracer_enabled
# cat stack_trace
打印出當前系統內核棧占用最多的函數。如果此時內核panic了,那也可以通過 Kdump得到RAMDUMP后,用crash工具查看是否由於棧底溢出導致的。
發現是由於內核棧溢出導致的問題后,不必抱怨內核棧太小。在堆上分配空間減小當前 棧空間的占用,或通過workqueue下半部的方式減小調用棧層次等可以解決這個問題。 不過內核棧真的很小,所以2.6的內核才將task_struct結構沒有放置內核棧中,見 這里。
檢測棧頂越界
對於棧頂越界,gcc提供了支持。打開內核配置CONFIG_CC_STACKPROTECTOR后,會打 開編譯選項-fstack-protector.使用lkdtm驗證如下:
# echo CORRUPT_STACK > /d/provoke-crash/DIRECT
[ 1320.195246] lkdtm: Performing direct entry CORRUPT_STACK
[ 1320.195681] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: c03383dc
內核默認拋出panic.
內核的規模已經從小湖膨脹到大海一樣,讓身處其中的水手們暈頭轉向。如果用好內核 這些配備的雷達聲納等工具,是不是覺得世界稍美好了一點呢?
~EOF~