[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转换为具体的函数名,就可以看到调用栈了。