[DESCRIPTION]
踩内存是最难调试的问题之一,kernel里大部分kernel结构体都是从slub分配出去的,因此slub踩内存也是常见的问题。
通常遇到踩内存,我们会切换的eng版本复现,eng版本有开slub debug功能,会对free memory填充0x6b,pad填充0x5a等等,同时还会记录申请和释放的调用栈,可以轻易查出use after free问题。
下面将给出查看slub内存申请/释放的调用栈的方法。
[SOLUTION]
在eng版本,抓到minidump或ramdump情况下,如果有dump到slub memory,那么可以根据slub memory layout找出存放申请/释放的调用栈的位置,推导出申请/释放调用栈。具体位置看对应的代码就知道了:
[C/C++]hide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
struct
track *get_track( struct
kmem_cache *s, void
*object, enum
track_item alloc) { struct
track *p; if
(s->offset) p
= object + s->offset + sizeof ( void
*); else p
= object + s->inuse; return
p + alloc; } |
可以看到是放在后面,已下面的例子来讲:
[C/C++]hide
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
|
_____________________address|________0________4________8________C_0123456789ABCDEF NSD:0000:FFFFFFC08167F790|
6B6B6B6B 6B6B6B6B 6B6B6B6B 6B6B6B6B kkkkkkkkkkkkkkkk ^E__+^Track+ NSD:0000:FFFFFFC08167F7A0|
6B6B6B6B 6B6B6B6B 6B6B6B6B 6B6B6B6B kkkkkkkkkkkkkkkk NSD:0000:FFFFFFC08167F7B0|
6B6B6B6B 6B6B6B6B 6B6B6B6B 6B6B6B6B kkkkkkkkkkkkkkkk NSD:0000:FFFFFFC08167F7C0|
6B6B6B6B 6B6B6B6B 6B6B6B6B 6B6B6B6B kkkkkkkkkkkkkkkk NSD:0000:FFFFFFC08167F7D0|
6B6B6B6B 6B6B6B6B 6B6B6B6B 6B6B6B6B kkkkkkkkkkkkkkkk NSD:0000:FFFFFFC08167F7E0|
6B6B6B6B 6B6B6B6B 6B6B6B6B 6B6B6B6B kkkkkkkkkkkkkkkk NSD:0000:FFFFFFC08167F7F0|
6B6B6B6B 6B6B6B6B 6B6B6B6B A56B6B6B kkkkkkkkkkkkkkk. NSD:0000:FFFFFFC08167F800|
BBBBBBBB BBBBBBBB 00000000 00000000 ................ NSD:0000:FFFFFFC08167F810|
005A0268 FFFFFFC0 041E9A40 041EA47C h.Z.....@...|... NSD:0000:FFFFFFC08167F820|
041EAA58 045A0268 045930E8 04594AB0 X...h.Z..0Y..JY. NSD:0000:FFFFFFC08167F830|
0420B4CC 0420B6DC 00000000 00000242 .. ... .....B... NSD:0000:FFFFFFC08167F840|
00038494 00000001 0059FF2C FFFFFFC0 ........,.Y..... NSD:0000:FFFFFFC08167F850|
041E939C 041E9744 041EB8EC 0459FF2C ....D.......,.Y. NSD:0000:FFFFFFC08167F860|
040C3CDC 04085450 00000000 00000000 .<..PT.......... NSD:0000:FFFFFFC08167F870|
00000000 00000348 00038494 00000001 ....H........... |
0xBB是redzone,那么track起始地址是0xFFFFFFC08167F810,用trace32可以case出2个track结构体内容:
[C/C++]hide
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
(( struct
track*)0xFFFFFFC08167F810)[0..1] = ( ( addr
= 0xFFFFFFC0005A0268, addrs
= ( 0x041E9A40, 0x041EA47C, 0x041EAA58, 0x045A0268, 0x045930E8, 0x04594AB0, 0x0420B4CC, 0x0420B6DC), cpu
= 0x0, pid
= 0x0242, when
= 0x0000000100038494), ( addr
= 0xFFFFFFC00059FF2C, addrs
= ( 0x041E939C, 0x041E9744, 0x041EB8EC, 0x0459FF2C, 0x040C3CDC, 0x04085450, 0x0, 0x0), cpu
= 0x0, pid
= 0x0348, when
= 0x0000000100038494)) |
其中第0个是申请时的信息,addrs是申请调用栈,第1个是释放的信息,addrs是释放调用栈。
如果是64位OS的话,还需要将32位地址转换为64位地址才行。看代码就知道如何转换了:
[C/C++]hide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
void
set_track( struct
kmem_cache *s, void
*object, enum
track_item alloc, unsigned long
addr) { ...... for
(i = 0; i < TRACK_ADDRS_COUNT; i++) { if
(addrs[i]) p->addrs[i]
= addrs[i] - MODULES_VADDR; else p->addrs[i]
= 0; } ...... } |
只要加上MODULES_VADDR就可以。
然后再通过addr2line转换为具体的函数名,就可以看到调用栈了。