0%

ptmalloc2_源码详解_part1

ptmalloc2源码详解 Part1

本系列讲解的版本为Glibc2.23

高版本的diff比如tcache bin的增加等会被放在heap exploitation系列中进行分析


在正式开始malloc流程分析的讲解之前,我们先来了解点基础知识

Chunk

在ptmalloc中,无论是申请内存还是释放内存,操作的对象都为堆块(Chunk)

其在源码中的结构体表示为

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

一个chunk结构体中有六个字段,但是对于后两个字段:fd_nextsizebk_nextsize,只有大小符合large bin的堆块才会用到,所以一个普通堆块的最小占用空间为4*INTERNAL_SIZE_T字节

INTERNAL_SIZE_T为机器字长

对于64位来讲,INTERNAL_SIZE_T的大小为8字节,那么堆块最小占用空间就为0x20字节

1
2
3
4
5
6
7
8
9
10
11
12
13
已申请堆块在内存中形如:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if allocated | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

prev_size

这个字段只有当低地址的堆块被释放后才会被使用,指示了前一个堆块的大小

这也就意味着此处空间是可以被复用的,即两个已分配堆块,前一个堆块可以使用后一个堆块的prev_size字段的空间

size

当前堆块的大小

在上图中可以看到size的最低位有两个字符MP,其中:

  • P(PREV_INUSE):该位被设置时,代表上一个堆块是一个正在使用的堆块

  • **M(IS_MMAPED):**该位被设置时,代表堆块内存是通过mmap分配的

这里还有一个上图中未被提到的字段

  • **A(NON_MAIN_ARENA):**记录当前堆块是否属于主进程

这三个字段从高位到低位排序为:A->M->P

fd和bk

双向链表中的前向和后继指针,只有当堆块被释放时才会被使用

在堆块处于被分配状态时,从fd字段开始为用户空间,即上图中mem指向的位置

fd_nextsize和bk_nextsize

作用同fd和bk,但是只有大小处在largebin区间中的被释放的堆块才会使用这两个字段

Chunk相关操作

在ptmalloc中,用户申请的内存大小和实际申请的内存大小是不一样的

request2size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#  define MALLOC_ALIGNMENT       (2 *SIZE_SZ)

/* The corresponding bit mask value */
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

/* The smallest possible chunk */
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))

/* The smallest size we can malloc is an aligned minimal chunk */
#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)


假设在64位下此时用户想要申请0x20大小的内存

ptmalloc会使用request2size宏函数检查0x20(req)+0x8(SIZE_SZ)+0xf(MALLOC_ALIGN_MASK)是否小于0x20(MINSIZE)

如果小于的话,直接返回MINSIZE给用户,否则返回(0x20+0x8+0xf)&~0xf即0x30给用户

这也就意味着:

  1. 当申请小于0x20大小堆内存的时候,系统会直接返回0x20给用户

  2. 当申请大于0x20大小堆内存的时候,系统会给原来申请大小加上0x10(prev_size和size字段所占用空间)并且最终对其到0x10

prev_size和size字段所处空间对于用户来说是透明的,因此对于最终返回给用户的指针是需要进行一些处理的

chunk2mem、mem2chunk

1
2
3
4
/* conversion from malloc headers to user pointers, and back */

#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ))
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))

对于已经分配的 chunk,通过 chunk2mem 宏根据 chunk 地址获得返回给用户的内存地址

反过来通过 mem2chunk 宏根据 mem 地址得到 chunk 地址

chunk 的地址是按 2*SIZE_SZ对齐的,而 chunk 结构体的前两个域刚好也是 2*SIZE_SZ 大小,所以,mem 地址也是 2*SIZE_SZ 对齐的