linux内核堆栈保护浅析

一 启用方式

打开配置CONFIG_CC_STACKPROTECTOR,重新编译内核即可。


二 工作原理

搜索配置CONFIG_CC_STACKPROTECTOR产生的影响,可得到如下结果:

1.include/linux/stackprotector.h

#ifdef CONFIG_CC_STACKPROTECTOR
# include <asm/stackprotector.h>
#else
static inline void boot_init_stack_canary(void)
{
}
#endif

其中asm/stackprotector.h文件内容:

extern unsigned long __stack_chk_guard;


/*
 * Initialize the stackprotector canary value.
 *
 * NOTE: this must only be called from functions that never return,
 * and it must always be inlined.
 */
static __always_inline void boot_init_stack_canary(void)
{
unsigned long canary;


/* Try to get a semi random initial value. */
get_random_bytes(&canary, sizeof(canary));
canary ^= LINUX_VERSION_CODE;


current->stack_canary = canary;
__stack_chk_guard = current->stack_canary;
}


2.kernel/fork.c

#ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = pax_get_random_long();
#endif


3.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)
{
dump_stack();
panic("stack-protector: Kernel stack is corrupted in: %pA\n",
__builtin_return_address(0));
}
EXPORT_SYMBOL(__stack_chk_fail);
#endif


4.arch/mips/kernel/process.c

#ifdef CONFIG_CC_STACKPROTECTOR
#include <linux/stackprotector.h>
unsigned long __stack_chk_guard __read_mostly;
EXPORT_SYMBOL(__stack_chk_guard);
#endif


5.include/linux/sched.h

struct task_struct {

......

#ifdef CONFIG_CC_STACKPROTECTOR
/* Canary value for the -fstack-protector gcc feature */
unsigned long stack_canary;
#endif

....

}

6.arch/mips/kernel/r4k_switch.s

LEAF(resume)

....

#if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
PTR_LA t8, __stack_chk_guard
LONG_L t9, TASK_STACK_CANARY(a1)
LONG_S t9, 0(t8)
#endif


7.arch/mips/makefile

ifdef CONFIG_CC_STACKPROTECTOR
  KBUILD_CFLAGS += -fstack-protector
endif

这些搜索结果可以总结为:定义并设置了变量__stack_chk_guard,定义了函数__stack_chk_fail(),但是发现__stack_chk_guard设置后并没有使用,函数__stack_chk_fail()也没有被调用,顿时线索断了。

但是他们既然被定义了,就一定会被使用到,并且内核不会依赖于其他的库文件,于是打算从obj文件下手,随便找了个init/mounts.o,查看了下重定位表,如下所示:

RELOCATION RECORDS FOR [.init.text]:
OFFSET   TYPE              VALUE 

.......

00000120 R_MIPS_HI16       __stack_chk_guard
00000124 R_MIPS_LO16       __stack_chk_guard

........

00000344 R_MIPS_HI16       __stack_chk_guard
0000034c R_MIPS_LO16       __stack_chk_guard
00000358 R_MIPS_26         __stack_chk_fail
00000388 R_MIPS_HI16       __stack_chk_guard
00000390 R_MIPS_LO16       __stack_chk_guard

从重定位表看__stack_chk_fail和__stack_chk_guard确实是使用到了,查看对应的反汇编结果:

Disassembly of section .init.text

.........

00000120 <mount_block_root>:
     120:       3c020000        lui     v0,0x0
     124:       8c420000        lw      v0,0(v0)
     128:       27bdff98        addiu   sp,sp,-104
     12c:       afa2003c        sw      v0,60(sp)
     130:       3c020000        lui     v0,0x0
     134:       afb70060        sw      s7,96(sp)
     138:       0080b821        move    s7,a0
     13c:       8c440000        lw      a0,0(v0)
     140:       afb2004c        sw      s2,76(sp)

..........

     33c:       0c000000        jal     0 <readonly>
     340:       02002821        move    a1,s0
     344:       3c020000        lui     v0,0x0
     348:       8fa3003c        lw      v1,60(sp)
     34c:       8c420000        lw      v0,0(v0)
     350:       10620003        beq     v1,v0,360 <mount_block_root+0x240>
     354:       8fbf0064        lw      ra,100(sp)
     358:       0c000000        jal     0 <readonly>
     35c:       00000000        nop
     360:       8fb70060        lw      s7,96(sp)
     364:       8fb6005c        lw      s6,92(sp)
     368:       8fb50058        lw      s5,88(sp)
     36c:       8fb40054        lw      s4,84(sp)

打开init/mounts.c文件,找到mount_block_root()函数,发现并没有调用__stack_chk_fail和__stack_chk_guard,顿时就明白了,这个应该跟上面的搜索结果7,编译选项-fstack-protector相关,通过对比着看汇编代码,在函数入口:

     120:       3c020000        lui     v0,0x0
     124:       8c420000        lw      v0,0(v0)
     128:       27bdff98        addiu   sp,sp,-104
     12c:       afa2003c        sw      v0,60(sp)

将__stack_chk_guard的值存入栈顶,然后在函数返回之前:

     344:       3c020000        lui     v0,0x0
     348:       8fa3003c        lw      v1,60(sp)
     34c:       8c420000        lw      v0,0(v0)
     350:       10620003        beq     v1,v0,360 <mount_block_root+0x240>

取出保存在当前栈顶的值与__stack_chk_guard进行比较,如果相等,则跳转到360,顺利返回,否则继续顺序执行:

     354:       8fbf0064        lw      ra,100(sp)
     358:       0c000000        jal     0 <readonly>

通过上面查看到的重定位表,00000358 R_MIPS_26         __stack_chk_fail ,调用到的就是__stack_chk_fail ()这个函数,至此,整个过程应该非常明了了,编译选项-fstack-protector在函数编译时插入一些代码,函数入口在栈顶存储__stack_chk_guard的值,在函数返回之前取出存放在栈顶的值和__stack_chk_guard进行比较,如果内存溢出修改了这个存放的值,则比较的结果不相等,调用__stack_chk_fail,对于内核__stack_chk_fail()函数直接宕机。


三 补充说明

通过上面的推断,发现比较关键的是__stack_chk_guard这个值的安全性,这个值是在内核启动的时候,调用boot_init_stack_canary()函数随机生成的,所以理论上来说这个值是不可猜测的。另外每个进程都对应一个不同的值:tsk->stack_canary = pax_get_random_long();这样进一步增加了安全性,在每次进程切换时设置将要运行的进程的stack_canary到__stack_chk_guard,但是对于多核处理器,由于__stack_chk_guard只存了一份,并不是per cpu变量,所以__stack_chk_guard的值并不会在进程切换时改变,因为假设某个cpu核发生进程切换,重新设置改变了__stack_chk_guard的值,则此时正在内核中运行的其他cpu核可能在函数返回时检查不过二导致系统宕机。至于__stack_chk_guard为啥不搞成per cpu变量,这个就很好理解了,因为gcc只会引用__stack_chk_guard这个变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值