内核stack_protector导致异常崩溃

##问题描述:
DMA写完数据后,产生DMA中断,中断中wake_up_interruptible唤醒等待队列(wait_event_interruptible_timeout),唤醒后,中断上下文程序会先执行,但偶尔唤醒的进程上下文会先于中断上下文执行。某次wake_up_interruptible唤醒等待队列后导致内核崩溃。崩溃堆栈打印pc是在__wake_up_common_lock,大致如下:

Unable to handle kernel NULL pointer dereference at virtual address 00000000000005c8
pc : __wake_up_common_lock+0xa0/0xc8   
lr : __wake_up_common_lock+0x90/0xc8  
Call trace:
__wake_up_common_lock+0xa0/0xc8
__wake_up+0x14/0x20
hi3531dv200_dma_irq_handler+0x88/0x210
__handle_irq_event_percpu+0x70/0x180
handle_irq_event_percpu+0x34/0x88
handle_irq_event+0x40/0xa0
handle_fasteoi_irq+0xcc/0x1b0
generic_handle_irq+0x24/0x38
__handle_domain_irq+0x5c/0xb8	

乍一看有点懵逼

##分析问题:
为什么会出现wake_up导致内核崩溃,我只传了一个结构类型为wait_queue_head_t的参数到该API中,并且进程已经被唤醒且调度了。怀疑极大可能是传入的参数wait_queue_head_t数据结构有可能被破坏了。将内核vmlinux反汇编,查看反汇编部分__wake_up_common_lock代码如下:

ffffff80080d9b00 <__wake_up_common_lock>:
ffffff80080d9b00:	a9b87bfd 	stp	x29, x30, [sp, #-128]!
ffffff80080d9b04:	910003fd 	mov	x29, sp
ffffff80080d9b08:	a90153f3 	stp	x19, x20, [sp, #16]
ffffff80080d9b0c:	aa0003f3 	mov	x19, x0
ffffff80080d9b10:	a90363f7 	stp	x23, x24, [sp, #48]
ffffff80080d9b14:	2a0203f4 	mov	w20, w2
ffffff80080d9b18:	a9025bf5 	stp	x21, x22, [sp, #32]
ffffff80080d9b1c:	2a0303f7 	mov	w23, w3
ffffff80080d9b20:	f90023f9 	str	x25, [sp, #64]
ffffff80080d9b24:	f00047f9 	adrp	x25, ffffff80089d8000 <page_wait_table+0x15c0>
ffffff80080d9b28:	91172325 	add	x5, x25, #0x5c8
ffffff80080d9b2c:	2a0103f6 	mov	w22, w1
ffffff80080d9b30:	f94000a6 	ldr	x6, [x5]
ffffff80080d9b34:	f9003fa6 	str	x6, [x29, #120]
ffffff80080d9b38:	d2800006 	mov	x6, #0x0                   	// #0
ffffff80080d9b3c:	aa0403f8 	mov	x24, x4
ffffff80080d9b40:	9101a3a5 	add	x5, x29, #0x68
ffffff80080d9b44:	b90053bf 	str	wzr, [x29, #80]
ffffff80080d9b48:	a905ffbf 	stp	xzr, xzr, [x29, #88]
ffffff80080d9b4c:	f90037a5 	str	x5, [x29, #104]
ffffff80080d9b50:	f9003ba5 	str	x5, [x29, #112]
ffffff80080d9b54:	14000002 	b	ffffff80080d9b5c <__wake_up_common_lock+0x5c>
ffffff80080d9b58:	aa1303e0 	mov	x0, x19
ffffff80080d9b5c:	941b1cab 	bl	ffffff80087a0e08 <_raw_spin_lock_irqsave>
ffffff80080d9b60:	aa0003f5 	mov	x21, x0
ffffff80080d9b64:	2a1403e2 	mov	w2, w20
ffffff80080d9b68:	910143a5 	add	x5, x29, #0x50
ffffff80080d9b6c:	aa1803e4 	mov	x4, x24
ffffff80080d9b70:	2a1703e3 	mov	w3, w23
ffffff80080d9b74:	2a1603e1 	mov	w1, w22
ffffff80080d9b78:	aa1303e0 	mov	x0, x19
ffffff80080d9b7c:	97ffff8b 	bl	ffffff80080d99a8 <__wake_up_common>
ffffff80080d9b80:	2a0003f4 	mov	w20, w0
ffffff80080d9b84:	aa1503e1 	mov	x1, x21
ffffff80080d9b88:	aa1303e0 	mov	x0, x19
ffffff80080d9b8c:	941b1c75 	bl	ffffff80087a0d60 <_raw_spin_unlock_irqrestore>
ffffff80080d9b90:	b94053a0 	ldr	w0, [x29, #80]
ffffff80080d9b94:	3717fe20 	tbnz	w0, #2, ffffff80080d9b58 <__wake_up_common_lock+0x58>
ffffff80080d9b98:	91172339 	add	x25, x25, #0x5c8
ffffff80080d9b9c:	f9403fa1 	ldr	x1, [x29, #120]
ffffff80080d9ba0:	f9400320 	ldr	x0, [x25]
ffffff80080d9ba4:	ca000020 	eor	x0, x1, x0
ffffff80080d9ba8:	b50000e0 	cbnz	x0, ffffff80080d9bc4 <__wake_up_common_lock+0xc4>
ffffff80080d9bac:	a94153f3 	ldp	x19, x20, [sp, #16]
ffffff80080d9bb0:	a9425bf5 	ldp	x21, x22, [sp, #32]
ffffff80080d9bb4:	a94363f7 	ldp	x23, x24, [sp, #48]
ffffff80080d9bb8:	f94023f9 	ldr	x25, [sp, #64]
ffffff80080d9bbc:	a8c87bfd 	ldp	x29, x30, [sp], #128
ffffff80080d9bc0:	d65f03c0 	ret
ffffff80080d9bc4:	97ff0a39 	bl	ffffff800809c4a8 <__stack_chk_fail>

崩溃指令是在__wake_up_common_lock+0xa0/0xc8(ldr x0, [x25])处,我们看汇编代码,代码里面获得自旋锁,唤醒进程,并且释放锁流程已经处理完了,且进程也被唤醒了。崩溃的确实有点懵逼。
咱们联系上下文,看看这条汇编指令是干啥的,这个是把x25里面的值加载到x0寄存器,这也能崩?继续网上寻找x25做了啥。

str	x25, [sp, #64]  	\\将x25值入栈保存
adrp	x25, ffffff80089d8000 <page_wait_table+0x15c0>  \\从page_wait_table+0x15c0获取值存入x25
add	x5, x25, #0x5c8		\\x25+0x5c8存入x5
mov	w22, w1		
ldr	x6, [x5]		\\x5中的值存入x6
str	x6, [x29, #120]	\\x6中的值存入x29+120
```
```
ldr	x1, [x29, #120]	\\将x29+120值加载到x1
ldr	x0, [x25]		\\将x25值加载到x0	
eor	x0, x1, x0		\\x0 = x1^x0
cbnz	x0, ffffff80080d9bc4 <__wake_up_common_lock+0xc4>  \\判断异或结果是否是0,如果不等于0 则跳转__wake_up_common_lock+0xc4()

联系上下文后,再看,豁然开朗。就是进入函数后,将寄存器x25值入栈,然后从page_wait_table+0x15c0获取值存入x25,再经过处理后存入x6,最终入栈x29(sp), #120,然后函数主要流程基本处理完了。最后从x25取值,和入栈时的值做了一个stack_chk_fail判断。
stack_chk_fail这个玩意函数体中根本没有,经查证得知是内核STACK_PROTECTOR校验机制(有具体宏控制)。所以得出结论,wake_up将函数唤醒了,但是做栈校验这部分,出现了异常导致内核崩溃。也就是崩溃在ldr x0, [x25],原因就是x25寄存器中存放的地址被破坏了,根据内核崩溃打印应该是变成了NULL,x25是获得的页基址,__wake_up_common_lock本身并未对x25做异常处理,但做完压栈处理后,为什么取出来后就异常了呢?结合代码分析:

init_waitqueue_head(&new_task->task.dma_write_q);
···
···
wait_event_interruptible_timeout(ptask->task.dma_write_q, 0 != ptask->task.irqraised, HZ);
···

##原因:
结构wait_queue_head_t是定义在栈上的,当初始化完成后,调用等待队列API进入睡眠,wake_up唤醒后即调度该进程,导致唤醒的进程立马跑完了,然后栈空间的wait_queue_head_t结构被释放了,导致后面栈校验的时候就异常了。

##解决方案:
将栈空间的数据结构拷贝到数据结构本身,使得函数走完栈空间释放,不会影响wait_queue_head_t结构的存亡。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值