Android稳定性进程栈bitflip[Android稳定性] 第021篇 [问题篇] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted
zsl
一、问题分析
1.1 dmesg_TZ.txt
1 2 3 4 5 6 7 8 9 10 11
| Kernel log: [41525.700710][T23239] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: mi_binder_wait4_hook$835a4498e5ea78c0e09ecbebc9fc5a14+0x2f8/0x2fc [millet_binder] [41525.700740][T23239] CPU: 5 PID: 23239 Comm: Recents-TaskRes Tainted: G WC O 5.10.198-android12-9-00085-g226a9632f13d-ab11136126 #1 [41525.700753][T23239] Hardware name: Qualcomm Technologies, Inc. Parrot QRD, RUAN based on SM7435 (DT) [41525.700761][T23239] Call trace: [41525.700778][T23239] dump_backtrace.cfi_jt+0x0/0x8 [41525.700788][T23239] dump_stack_lvl+0xdc/0x138 [41525.700800][T23239] panic+0x188/0x46c [41525.700810][T23239] printk_nmi_enter+0x0/0xc4 [41525.700822][T23239] mi_binder_wait4_hook$835a4498e5ea78c0e09ecbebc9fc5a14+0x2f8/0x2fc [millet_binder] [41525.700845][T23239] SMP: stopping secondary CPUs
|
之前以为这边 printk_nmi_enter(),是 panic 流程中的一部分,只是没有打出关键的函数,但是实际上printk_nmi_enter 是完全错误的,不存在打印的不是关键函数的情况
1.2 进程栈解析
mi_binder_wait_for_work()的汇编:
1 2 3 4 5 6 7
| 0xffffffe3f7f6f524 <mi_binder_wait_for_work>: paciasp 0xffffffe3f7f6f528 <mi_binder_wait_for_work+4>: str x30, [x18], #8 0xffffffe3f7f6f52c <mi_binder_wait_for_work+8>: stp x29, x30, [sp, #-64]! 0xffffffe3f7f6f530 <mi_binder_wait_for_work+12>: str x23, [sp, #16] 0xffffffe3f7f6f534 <mi_binder_wait_for_work+16>: stp x22, x21, [sp, #32] 0xffffffe3f7f6f538 <mi_binder_wait_for_work+20>: stp x20, x19, [sp, #48] 0xffffffe3f7f6f53c <mi_binder_wait_for_work+24>: mov x29, sp
|
mi_binder_wait4_hook()的汇编:
1 2 3 4 5 6 7 8 9
| 0xffffffe3f73f5914 <mi_binder_wait4_hook>: paciasp 0xffffffe3f73f5918 <mi_binder_wait4_hook+4>: sub sp, sp, #0xf0 0xffffffe3f73f591c <mi_binder_wait4_hook+8>: str x30, [x18], #8 0xffffffe3f73f5920 <mi_binder_wait4_hook+12>: stp x29, x30, [sp, #160] 0xffffffe3f73f5924 <mi_binder_wait4_hook+16>: str x25, [sp, #176] 0xffffffe3f73f5928 <mi_binder_wait4_hook+20>: stp x24, x23, [sp, #192] 0xffffffe3f73f592c <mi_binder_wait4_hook+24>: stp x22, x21, [sp, #208] 0xffffffe3f73f5930 <mi_binder_wait4_hook+28>: stp x20, x19, [sp, #224] 0xffffffe3f73f5934 <mi_binder_wait4_hook+32>: add x29, sp, #0xa0
|
一般就这两种形式:
- stp x29, x30, [sp, #-64]!
将 x29 存入 sp - 0x40 这个地址,将 x30 存入 sp - 0x40 + 0x8 这个地址,并且 sp = sp - 0x40,这就是为这个函数开辟了 4 行栈空间作为这个函数的栈帧,此时 sp 就指向了这个函数的栈顶。
- sub sp, sp, #0xf0
sp = sp - 0xf0,为此函数开辟 15 行栈空间作为这个函数的栈帧。
寄存器入栈完成后,会有 mov x29, sp 和 add x29, sp, #0xa0 将 fp lr 这行对应的内存地址赋值给 x29,以便跳转到下一个函数时,将 x29 入栈记录为新的 fp。
栈帧中是怎么存放数据的,存放了哪些数据?
1 2
| str x23, [sp, #16] stp x29, x30, [sp, #-64]!
|
str 操作一个寄存器,stp 操作两个寄存器。都是入栈指令,将对应寄存器的值存入对应的地址。
sp 指向栈帧的栈顶,表示一行的内存地址,sp + #16,也就是 sp + 0x10,可以观察到前面的内存地址,每行都差 0x10,所以 sp + 0x10 就是到了 sp 的下一行。
存放的数据需要关注的如下:
x29:栈帧指针 fp 寄存器,记录上一个函数的 fp lr 那一行的内存地址。
x30:lr 寄存器,记录上一个函数跳转指令的下一条指令的内存地址。
x19 - x28:这些寄存器在跳转之前有的会记录传递的参数,因此可以直接读取对应地址的值来看参数值

__stack_chk_fail() 的下一步是 panic(),panic() 栈帧中记录的 lr 应该是 __stack_chk_fail() 中跳转指令的下一条指令的内存地址,也就是 0xffffffe3fd73c6b4 + 0x4 = 0xffffffe3fd73c6b8。

dis 0xffffffe3fd73c6b8 可以看到是函数 printk_nmi_enter() 了,所以这是错误的。

个人认为 __stack_chk_fail() 最后应该填充一句 nop 用来防止这个问题。
1.3 trace32查看进程栈

__stack_chk_fail() 是 mi_binder_wait4_hook() 的下一步,从 mi_binder_wait4_hook() 的汇编看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 0xffffffe3f73f5914 <mi_binder_wait4_hook$835a4498e5ea78c0e09ecbebc9fc5a14>: paciasp ... 0xffffffe3f73f5bc0 <mi_binder_wait4_hook+684>: adrp x9, 0xffffffe3fe2e5000 <llcp_rawsock_ops+8> 0xffffffe3f73f5bc4 <mi_binder_wait4_hook+688>: ldur x8, [x29, #-8] 0xffffffe3f73f5bc8 <mi_binder_wait4_hook+692>: ldr x9, [x9, #3792] 0xffffffe3f73f5bcc <mi_binder_wait4_hook+696>: cmp x9, x8 0xffffffe3f73f5bd0 <mi_binder_wait4_hook+700>: b.ne 0xffffffe3f73f5c08 <mi_binder_wait4_hook$835a4498e5ea78c0e09ecbebc9fc5a14+756> 0xffffffe3f73f5bd4 <mi_binder_wait4_hook+704>: ldp x29, x30, [sp, #160] 0xffffffe3f73f5bd8 <mi_binder_wait4_hook+708>: ldp x20, x19, [sp, #224] 0xffffffe3f73f5bdc <mi_binder_wait4_hook+712>: ldp x22, x21, [sp, #208] 0xffffffe3f73f5be0 <mi_binder_wait4_hook+716>: ldp x24, x23, [sp, #192] 0xffffffe3f73f5be4 <mi_binder_wait4_hook+720>: ldr x25, [sp, #176] 0xffffffe3f73f5be8 <mi_binder_wait4_hook+724>: ldr x30, [x18, #-8]! 0xffffffe3f73f5bec <mi_binder_wait4_hook+728>: add sp, sp, #0xf0 0xffffffe3f73f5bf0 <mi_binder_wait4_hook+732>: autiasp 0xffffffe3f73f5bf4 <mi_binder_wait4_hook+736>: ret 0xffffffe3f73f5bf8 <mi_binder_wait4_hook+740>: mov w1, #0x3 0xffffffe3f73f5bfc <mi_binder_wait4_hook+744>: mov x0, x20 0xffffffe3f73f5c00 <mi_binder_wait4_hook+748>: bl 0xffffffe3fc9dd0b4 <refcount_warn_saturate> 0xffffffe3f73f5c04 <mi_binder_wait4_hook+752>: b 0xffffffe3f73f5bc0 <mi_binder_wait4_hook$835a4498e5ea78c0e09ecbebc9fc5a14+684> 0xffffffe3f73f5c08 <mi_binder_wait4_hook+756>: bl 0xffffffe3fd73c670 <__stack_chk_fail>
|
怎么确定函数汇编最后跑到了哪里?
1.mi_binder_wait4_hook$835a4498e5ea78c0e09ecbebc9fc5a14+0x2f8/0x2fc 最后的 +0x2f8/0x2fc,0x2f8 表示
运行指令的偏移,0x2fc 表示函数汇编的长度。(如果不是最后发生 panic 的函数,这个偏移就是跳转指令的下一行。)
2.通过进程栈记录的 lr,也可以确定。
看 __stack_chk_fail() 函数内容,和报错对应的,panic 是在这个函数中触发的。

1.4 问题地址推导
mi_binder_wait4_hook() 函数首地址 0xffffffe3f73f5914 + 0x2f8 = 0xffffffe3f73f5c0c,也就是运行到了这行:
1
| bl 0xffffffe3fd73c670 <__stack_chk_fail>
|
从汇编看,这行肯定是跳转过来的,可以看到这行:
1 2
| b.ne 0xffffffe3f73f5c08 <mi_binder_wait4_hook$+756>
|
跳转的原因是因为 cmp x9,x8;x9 不等于 x8,所以要看下 x8、x9 的赋值。
x9 的赋值(adrp 指令等于mov):
1 2
| adrp x9, 0xffffffe3fe2e5000 <llcp_rawsock_ops+8> ldr x9, [x9, #3792]
|
x8 的赋值,函数开始开辟15行栈帧,把 x29 赋值为 sp - 0xf0 + 0xa0 的位置,计算出 x8 的值(adrp 指令等于mov)后,存入 x29 - 0x8 的位置,最后 x8 也是从 x29 - 0x8 的位置去拿值:
1 2 3 4 5 6
| sub sp, sp, #0xf0 add x29, sp, #0xa0 adrp x8, 0xffffffe3fe2e5000 <llcp_rawsock_ops+8> ldr x8, [x8, #3792] stur x8, [x29, #-8] ldur x8, [x29, #-8]
|

x9 计算出为 bee1da79a4be8600,rd ffffffc0337eb9b8 也是 bee1da79a4be8600,此时问题出现了,在从 x29 - 0x8 这个位置读值的时候,x29 发生了 bitflip,导致了从错误的地址读值,造成 x8 和 x9 不相等,从而运行到了
__stack_chk_fail(),触发了 panic。
2. 根本原因
函数编译开始时会把 x29 入栈,入栈后会执行 add x29, sp, #0xa0;然后将 sp + 0xa0,也就是 fp lr 这行的地址赋值给 x29,以便执行到下一个函数时入栈 x29,以记录 fp。
因此可以通过下一个函数栈帧记录的 fp 来查看 x29 的值,显然,x29 变心了。

ffffffc0337eb9c0 变成了 ffffffc0334eb9c0
ffffffc0337eb9c0 二进制:
1111 1111 1111 1111 1111 1111 1100 0000 0011 0011 0111 1110 1011 1001 1100 0000
ffffffc0334eb9c0 二进制:
1111 1111 1111 1111 1111 1111 1100 0000 0011 0011 0100 1110 1011 1001 1100 0000
大概率就是这两位发生 bitflip。