ILD

mips kernel panic disassembly analysis
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2023-9-19 站点:Inside Linux Development

路由器开机启动过程中,概率性出现panic,日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CPU 2 Unable to handle kernel paging request at virtual address d0c17558, epc == 8ce01110, ra == 8ce010d4
Oops[#1]:
CPU: 2 PID: 6392 Comm: trafficd Not tainted 4.4.198 #0
task: 8fedc860 task.stack: 8b0ac000
$ 0   : 00000000 7705f098 00000001 d0c17548
$ 4   : 0a1f0337 8ce09c40 00000000 00000000
$ 8   : 00000000 ffffffff 00000000 0000003c
$12   : 00000001 00000000 00000000 00000000
$16   : 8ce10e68 8ce00000 8ce00000 8ce10000
$20   : 8ce10000 8ce00000 00000000 8ce01c20
$24   : 00000000 8105596c                  
$28   : 8b0ac000 8fc0fe88 8184a884 8ce010d4
Hi    : 00000000
Lo    : 0000bc00
epc   : 8ce01110 0x8ce01110
ra    : 8ce010d4 0x8ce010d4
Status: 11007c03 KERNEL EXL IE 
Cause : 40800008 (ExcCode 02)
BadVA : d0c17558
PrId  : 0001992f (MIPS 1004Kc)
Modules linked in: 
Process trafficd (pid: 6392, threadinfo=8b0ac000, task=8fedc860, tls=7707adc0)
Stack : 00000002 8184a884 00000005 8fc4a804 81850000 8fc4a430 81740000 8104d6c0
          304d9b80 81ec2120 30e63200 0000000c 00000001 81c90000 00000100 81850000
          8ce009ec 81ec1940 81ec1a40 81ec1340 00200000 00000200 00100000 8107a9a4
          818591fc 81c22ec0 00000008 00000001 81853c20 8184a884 81ec1320 81850000
          81ec1840 8107ac4c 0000003a 8106c000 00000001 00000001 00000001 81ec1740
          ...
 
Call Trace:
[<8ce01110>] 0x8ce01110
 
 
Code: 00041940  02031821  8cab0008 <8c660010> 8c6c0014  8cad000c  00cb5821  018d6021  0166302b 
---[ end trace eb3bf2a8ccaf6627 ]---


Call trace没有打印出符号,上一篇文章[1],已经根据panic信息中的Code指令定位到了出现问题的内核模块。对其进行反汇编如下:

$ mipsel-openwrt-linux-objdump -S xxx.ko

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    for (i = 0; i < n; i++) {
        if ((table->netmask & node[i].ip) != table->net)
    10d4:   8e070008   lw  a3,8(s0)
    10d8:   8e080004   lw  t0,4(s0)
            continue;
        slot = node[i].ip & ~ table->netmask;
    10dc:   00074827   nor t1,zero,a3
    10e0:   26858020   addiu   a1,s4,-32736
    for (i = 0; i < n; i++) {
    10e4:   00005025   move    t2,zero
    10e8:   0142182a   slt v1,t2,v0
    10ec:   1060fe7f   beqz    v1,aec <stat_timer_cb+0x100>
    10f0:   00000000   nop
        if ((table->netmask & node[i].ip) != table->net)
    10f4:   8ca40000   lw  a0,0(a1)
    10f8:   00e41824   and v1,a3,a0
    10fc:   1468002e   bne v1,t0,11b8 <stat_timer_cb+0x7cc>
    1100:   00892024   and a0,a0,t1
 
        table->hnat_stats[slot].src_bytes += node[i].src_bytes;
    1104:   00041940   sll v1,a0,0x5
    1108:   02031821   addu    v1,s0,v1
    110c:   8cab0008   lw  t3,8(a1)
    1110:   8c660010   lw  a2,16(v1)
    1114:   8c6c0014   lw  t4,20(v1)
    1118:   8cad000c   lw  t5,12(a1)
    111c:   00cb5821   addu    t3,a2,t3
    1120:   018d6021   addu    t4,t4,t5
    1124:   0166302b   sltu    a2,t3,a2
    1128:   00cc3021   addu    a2,a2,t4
    112c:   ac6b0010   sw  t3,16(v1)
    1130:   ac660014   sw  a2,20(v1)


代码及数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct ip_acc_table {
        uint32_t ip, net, netmask;
        /* increase by 1 when timer run
           userspace tool can check this field to know
           that ip account update a rate
         */
        unsigned cursor;
        struct ip_acc_stats hnat_stats[MAX_IP_NUM];
        struct ip_acc ip_acc[MAX_IP_NUM];
        struct ip_acc_mask_24 __percpu *stats;
};
 
struct hnat_speed_node {
        uint32_t ip;
        u64 src_bytes, dst_bytes, src_pkts, dst_pkts;
};
 
struct ip_acc_stats {
        uint64_t src_bytes;
        uint64_t dst_bytes;
        uint64_t src_pkts;
        uint64_t dst_pkts;
};
 
static struct hnat_speed_node node[N];
 
static void mtk_hnat_v1_stat(struct ip_acc_stats *stats)
{
        int i, n;
        unsigned slot;
        struct ip_acc_table *table = &ip_acc_table_default;
 
        n = hnat_sync_speed_table_hook(node);
 
        for (i = 0; i < n; i++) {
                if ((table->netmask & node[i].ip) != table->net)
                        continue;
                slot = node[i].ip & ~ table->netmask;
 
                table->hnat_stats[slot].src_bytes += node[i].src_bytes;
        }
}


node是一个全局数组,table是一个全局变量。寄存器和变量的对应关系得多分析几遍才能慢慢确定。确定好后,就可以顺腾摸瓜,计算出各个变量的值了。非法内存访问,基本上就是内存越界了。


10d4/10d8处的两条指令:

lw a3, 8(s0)
lw t0, 4(s0)

lw是load word指令,读取s0寄存器表示的内存的8和4偏移的内存到寄存器a3和t0。

像这种带偏移的指令,通常是读取结构体的成员。观察上述结构体,table的net和netmask偏移刚好是4和8。和反汇编的源码也对应的上。

因此可以推断:s0是table的地址,a3是读取netmask,t0是读取net。

根据参考[2],s0是16号寄存器,call trace中,其值为:0x8ce10e68。

a3是7号寄存器,其值为0,t0是8号寄存器,其值也为0。根据后续指令,a3和t0在panic之前,没有被覆盖,因此可以推断:

table->net值为0,table->netmask值为0。


10dc处的指令:

nor t1,zero,a3

nor指令是,两个寄存器先或,再取反。这条指令等价于 t1 = ~(0 | a3)

a3是table->netmask。因此t1的值是 ~tables->netamsk,为0xffffffff。

t1是9号寄存器,call trace中的值也是0xffffffff,逻辑关系对应得上。


10e0处的指令:

addiu a1,s4,-32736

搞不太懂,先跳过


10e4处的指令:

move t2,zero

将t2指令设置为0,根据后续指令,可以推断,这条指令对应:i = 0,

t2寄存器存储变量i的值。t2是10号寄存器,call trace中10号寄存器的值是0,也就是说i等于0的时候,就崩溃了。


10e8处的指令:

slt v1,t2,v0

slt指令,是如果第一个源寄存器的值小于第二个源寄存器的值,则将目标寄存器设置为1。

这显然对应for 语句中的条件判断:i < n。

t2是i,n是v0,v0是2号寄存器,call trace中2号寄存器的值是1,也就是说n的值是1。

结果v1的值为1,v1是3号寄存器,call trace中3号寄存器的值不是1,因为后续指令把v1的值覆盖了。


10ec处的指令:

beqz v1,aec <stat_timer_cb+0x100>

b是跳转指令,这个显然是 i < n 失败时,跳转结束循环。


10f4、10f8、10fc处的指令:

lw a0,0(a1)

and v1,a3,a0

bne v1,t0,11b8

a1是在10e0处设置的,猜不到是什么值。但是可以根据下2条指令

v1 = a3 & a0

if (v1 != t0)

    goto 11b8

11b8处指令:

addiu t2,t2,1

t2是i,也就是i = i + 1,因此可以推断上述这3条指令,对应:

if ((table->netmask & node[i].ip) != table->net)

                        continue;

前面已经得到a3是table->netmask,t0是table->net,那a0就是node[i].ip,a1就是&node[i]。

a0是4号寄存器,值是0x0a1f0337,这是一个主机字节序的ip地址值,也就是对应ip:10.31.3.55

这里因为netmask和net都是0,因此条件不成立,不会走continue


1100指令:

and a0,a0,t1

等价于:a0 = a0 & t1,

a0是node[i].ip,t1是~table->netmask。这条指令也就是:

slot = node[i].ip & ~ table->netmask;

因此a0的值,0x0a1f0337,因为t1是全1,所以a0的值不变。到这里其实问题已经明朗了,slot的合法范围是0到255.

这里存在严重的越界。


1104-1110 4条指令:

sll v1,a0,0x5

addu v1,s0,v1

lw t3,8(a1)

lw a2,16(v1)

这4条指令是读取table->hnat_stats[slot].src_bytes和node[i].src_bytes的值

hnat_stats结构体大小是32字节,所以hnat_stats[slot]的偏移是32 * slot也就是 slot << 5

hnat_stats在table中的偏移是16. 所以内存地址是 table + 16 + (slot << 5)

table是s0,slot是a0。


panic发生在1110指令处,读取16(v1)内存的时候,访问非法内存指令:

s0 = 0x8ce10e68

a0 = 0x0a1f0337

计算出非法内存地址:0x8ce10e68  + 16 + (0x0a1f0337 << 5) = d0c17558

和panic报告的内存地址完全对应上了。


结论:

panic是由于开机过程中,table->net和table->netmask还未初始化,值为0,导致统计ip的时候,计算出的slot越界。


参考:

[1] no stack trace kernel panic analysis. https://insidelinuxdev.net/article/a0fbgf.html

[2] mips架构. https://insidelinuxdev.net/article/a06pxn.html

[3] nor instruction. https://www.eecis.udel.edu/~davis/cpeg222/AssemblyTutorial/Chapter-12/ass12_10.html

[4] slt instruction. https://www3.ntu.edu.sg/home/smitha/fyp_gerald/sltInstruction.html

[5] sll instruction. https://people.cs.pitt.edu/~childers/CS0447/lectures/shift-operations.pdf

[6] lui instruction. https://stackoverflow.com/questions/8380135/mips-assembly-lui-t0-4097


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.