堆
什么是堆
-
栈常常用于为函数分配大小固定的局部内存
-
堆是可以根据运行时的需要进行动态分配和释放的内存,大小可变
-
堆由低地址向高地址增长
-
对应接口:
malloc/new(c++)
free/delete
-
-
栈是保证函数递归运行的数据结构,程序调用过程中保存局部变量等等由系统自动开辟的一个内存空间,堆是一个我们自己开辟的一个内存空间(不要把两者混为一谈)
堆在干什么
- 堆的实现重点关注内存块的组织和管理方式,尤其是空闲内存块
- 如何提高分配和释放效率
- 如何降低碎片化,提高空间利用率
- 举例:浏览器的DOM树通常分配在堆上
- 堆的实现算法影响堆分配网页加载和动态效果速度
- 堆的实现算法影响浏览器对内存的使用效率(降低)
常见堆实现
- dlmalloc - 通用分配器
- ptmalloc2 - glibc
- 基于dlmalloc fork
- jemalloc - FreeBSD、Firefox、Android
- tcmalloc - Google Chrome
- libumen - Solaris
- Windows 10 - segment heap
ptmalloc2的多线程支持
- 不同的线程维护不同的堆,称为per thread arena
- 主线程创建的堆,称为main arena
- Arena 数量受CPU核数的限制
- 32位系统:arena数量上限 = 2*核数
- 64位系统:arena数量上限 = 8*核数
glibc的堆管理 - ptmalloc2(glibc 2.26)
- arena
- 指的是堆内存区域本身,并非结构
- 主线程的main arena通过sbrk创建
- 其他线程arena通过mmap创建
- malloc_state
- 管理arena的核心结构,包含堆的状态信息,bins链表等
- main arena对应的malloc_state结构储存在glibc的全局变量中(意味着如果揭露了glibc的位置那么也可以知道了malloc_state的位置)
- 其他线程arena对应的malloc_state储存在arena本身当中
- bins
- bins用来管理空闲的内存块,通常使用链表结构来进行组织
- chunks
- 内存块的结构
arena
-
头部结构:malloc_state
-
malloc_state申请了一个名为main_arena 的全局变量用来存储arena的状态
-
以下三个用来管理堆空间中的空闲位置
fastbinsY(数组)用来管理空闲的bins链表
top chunk:指向自留地初始位置(堆中的空闲内存)
bins:
-
main arena的堆空间大致模型
-
free chunk的大概结构
在64位平台下,free chunk的第一个字段(前八个字节): prev_size储存了前一个chunk的大小,当前一个chunk为free chunk时,这个的数据为前一个chunk中的size中的数据,若前一个chunk为allocated时,这里存放的是date
第二个字段size:存储了当前chunk的大小该字段中有三个bit用作其他用途:
P:代表PREV_INUSE,即代表前一个chunk是否被使用
M:代表IS_MMAPPED,即代表当前chunk是否是mmap出来的
N:代表NON_MAIN_ARENA,代表chunk是否属于非Main Arena。
由于size的大小为0x10字节的整数倍,所以size内第三位会被忽略掉,于是可以在后三位来表示其他信息节省空间
第三四字阶段fd和bk(各8字节)前向指针和后向指针,这两个字段用于bin链表当中,用来链接大小相同或是相近的free chunk,便于后续分配时查找。(非空闲堆块中没有这个两个数据)
32位平台下,chunk大小一定是8字节的整数倍,64位平台下chunk的大小一定是16字节的整数倍
-
bins结构
用来管理和组织空闲内存块的链表结构,根据chunk的大小和状态,有许多不同的bins结构
- small bins - 用于管理小(fast bins)到中等大小的chunk
- large bins - 用于管理较大的chunk
- unsorted bins - 用于存放未整理的chunk
Fast bins
-
大小
- x86_32平台:16~64字节
- 非平台:32~128字节
chunk的大小
-
相同大小的chunk放在一个bin中
-
单向链表(只用了fd,没有使用bk)
-
后进现出(First in last out,类似栈)
-
相邻的空闲的fast bin chunk不会被合并(整个堆对于fast bin不会对它有过多操作)
-
当chunk被free时,不会改变下一个chunk中的PREV_INUSE(size中的P)标志
small bins
- chunk大小 < 1024 bytes(64bit)
- 相同大小的chunk放到一个bin中
- 双向循环链表
- 先进先出
- 有空闲块相邻时,chunk会被合并成一个更大的chunk
- 在64位下,bins[2],bins[3],…,bins[124],bins[125],共62组small bin,大小范围[0x20,0x3f0]
- 堆块合并的主要目的是减少碎片化和提高堆块效率
large bins
- chunk 大小 >= 1024 bytes (64bit)
- 每组bin表示一组size范围而不是具体的size,例如bins[126],bins[127]的链表中保存长度在[0x400,0x440]的chunk
- 双向循环链表
- 先进先出
- chunk按照大小从大到小排序
- 有空闲块相邻,chunk会被合并
- 64位下,bins[126],bins[127],…,bins[250],bins[251]共63组large bin,大小范围[0x400,X]
unsorted bin
- 64位平台中:chunk大小 > 128字节
- 有且只有一个unsorted bin
- 双向循环链表
- 当一个chunk(非fast bin)被free,它首先被放入unsorted bin,等后续整理出来时才会放入对应的small bin/fast bin
- bin[0],bin[1](仅有这一组)
其他chunk
top chunk
- 不属于任何bin
- 在arena中处于最高地址
- 在没有其他空闲块时,top chunk就会被用于分配
- 分裂时:一块是请求大小的chunk,另一块余下的chunk将成为新的top chunk
last_remainder
- 当请求small chunk 大小的内存时,如发生分裂,则剩余的chunk保存为last_remainder
malloc和free大概流程
malloc()工作流程
1.如果size< max fast,在fast bins中寻找fast chunk,如找到则结束
2.如果size in_smallbin_range,在small bins中寻找small chunk,如找到则结束
3.如果size not in_smallbin_range,合并所有fast bin的chunk
4.循环
- 检查unsorted bin中的last_remainder。如果满足一定条件,则分裂之,将剩余的chunk标记为新的last_remainder
- 在unsorted bin中搜索,同时进行整理。如果遇到精确大小,则返回,否则就把当前chunk整理到small/large bin中去
- 在small bin和large bin中搜索最合适的chunk(不一定是精确大小)
5.使用top chunk
free()工作流程
1.如果size< max fast,放入fast bin,结束
2.如果前一个chunk是free的
- unlink(通过修改fd和bk的数值把chunk从bin中解除出来)前面的chunk
- 合并两个chunk,并放入unsorted bin
3.如果后一个chunk是top chunk,则将当前chunk并入top chunk
4.如果后面一个chunk是free时,
- unlink后面的chunk
- 合并两个chunk,并放入unsorted bin
5.前后chunk都不是free的,放入unsorted bin
free()的实现较为简单,一共有三种可能
符合条件1时放入fast bin中
不符合1且后面为top chunk时并入top chunk
前两者都不符合,先看前后有无free chunk能否合并,后放入unsorted bin
只有在malloc()运行时才会得到large bin或small bin(先申请一个很大的堆,然后free掉,在malloc)
Fastbin attack
-
Fast bin利用技术
- fast bin为单向链表,结构简单,容易伪造
- 为了提高效率,安全检查较少
- 只针对Fast bin大小的chunk,small/large chunk不适用
-
利用思路
- 空闲的Fast chunk如果发生溢出被覆盖,则链表指针fd可以被修改
- 可以通过修改链表指针fd,在Fast bin链表中引入伪造的空闲Fast chunk
- 下次分配时分配出伪造的Fast chunk
- 伪造的Fast chunk可以在.bss全局变量处,也可以在栈上
-
伪造Fast chunk用处
- 在栈上伪造Fast chunk——覆盖返回地址
- 在bss上伪造Fast chunk——修改全局变量
- 在堆上伪造Fast chunk——修改堆上的数据