glibc源码分析-malloc-free
字数 2936 2025-08-22 12:22:37

glibc内存管理机制深入解析:malloc与free的实现原理

1. 核心数据结构分析

1.1 malloc_state结构体

malloc_state是ptmalloc分配器的核心管理结构,用于管理内存分配区(arena):

struct malloc_state {
    mutex_t mutex;           // 访问序列化锁
    int flags;               // 标志位
    mfastbinptr fastbinsY[NFASTBINS]; // 快速bin数组
    mchunkptr top;           // 顶部空闲chunk
    mchunkptr last_remainder; // 最近分割剩余的chunk
    mchunkptr bins[NBINS * 2 - 2]; // 常规bin数组
    unsigned int binmap[BINMAPSIZE]; // bin位图
    struct malloc_state *next; // 链接的下一个arena
    struct malloc_state *next_free; // 空闲arena链表
    INTERNAL_SIZE_T attached_threads; // 附加线程数
    INTERNAL_SIZE_T system_mem; // 系统分配的内存总量
    INTERNAL_SIZE_T max_system_mem; // 系统分配的内存最大值
};

1.2 malloc_chunk结构体

malloc_chunk是内存块的基本管理单元:

struct malloc_chunk {
    INTERNAL_SIZE_T mchunk_prev_size; // 前一个chunk的大小(如果空闲)
    INTERNAL_SIZE_T mchunk_size;      // 当前chunk大小(包括元数据)
    struct malloc_chunk *fd;         // 双向链表前向指针(仅空闲时使用)
    struct malloc_chunk *bk;         // 双向链表后向指针(仅空闲时使用)
    struct malloc_chunk *fd_nextsize; // 大块专用:指向下一个不同大小的块
    struct malloc_chunk *bk_nextsize; // 大块专用:指向前一个不同大小的块
};

2. 内存分配流程(malloc)

2.1 初始化过程

首次调用__libc_malloc时的初始化流程:

  1. 检查__malloc_hook,初始时指向malloc_hook_ini
  2. malloc_hook_ini函数将__malloc_hook置为NULL
  3. 调用ptmalloc_init进行初始化
  4. 设置__malloc_initialized > 0表示初始化完成

2.2 分配流程

  1. 请求大小转换checked_request2size(bytes, nb)将用户请求大小转换为实际分配的chunk大小
  2. 获取分配区arena_get(ar_ptr, bytes)获取管理空闲空间的分配区
  3. 分配路径选择
    • fastbin分配:当nb <= get_max_fast()
      • 计算fastbin索引:idx = fastbin_index(nb)
      • 获取对应fastbin链表头:fb = &fastbin(av, idx)
    • smallbin分配:当in_smallbin_range(nb)为真时
      • 计算smallbin索引:idx = smallbin_index(nb)
      • 获取对应smallbin:bin = bin_at(av, idx)
    • largebin分配:其他情况
      • 先合并fastbin:malloc_consolidate(av)
  4. top chunk分割:当其他bin无法满足时
    • 检查top chunk大小:size = chunksize(av->top)
    • 如果足够则分割:
      remainder_size = size - nb;
      remainder = chunk_at_offset(victim, nb);
      av->top = remainder;
      set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
      set_head(remainder, remainder_size | PREV_INUSE);
      
  5. 系统内存申请:当所有方法失败时调用sysmalloc

3. 内存释放流程(free)

3.1 基本释放流程

  1. hook检查:检查__free_hook是否为NULL
  2. 指针转换p = mem2chunk(mem)将用户指针转换为chunk指针
  3. mmap检查chunk_is_mmapped(p)判断是否由mmap分配
  4. 获取分配区ar_ptr = arena_for_chunk(p)
  5. 实际释放:调用_int_free(ar_ptr, p, 0)

3.2 _int_free详细流程

  1. 安全检查

    • 地址有效性检查:(uintptr_t)p > (uintptr_t)-size
    • 对齐检查:aligned_OK(size)
    • 大小检查:size >= MINSIZE
  2. fastbin释放

    • 检查大小:(unsigned long)(size) <= (unsigned long)(get_max_fast())
    • 设置fastbin标志:set_fastchunks(av)
    • 获取fastbin索引:idx = fastbin_index(size)
    • 检查double free:if (__builtin_expect(old == p, 0))
    • 插入fastbin链表:p->fd = old2 = old
  3. 合并与unsorted bin

    • 检查相邻chunk是否空闲
    • 合并操作:unlink将相邻空闲chunk从链表中移除
    • 插入unsorted bin:bck = unsorted_chunks(av)

4. 关键机制与安全防护

4.1 unlink操作

unlink宏用于从双向链表中安全移除一个chunk:

#define unlink(AV, P, BK, FD) {
    // 大小一致性检查
    if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0))
        malloc_printerr("corrupted size vs. prev_size");
    
    FD = P->fd;
    BK = P->bk;
    
    // 双向链表完整性检查
    if (__builtin_expect(FD->bk != P || BK->fd != P, 0))
        malloc_printerr("corrupted double-linked list");
    else {
        FD->bk = BK;
        BK->fd = FD;
        
        // 处理largebin的nextsize链表
        if (!in_smallbin_range(chunksize_nomask(P)) && 
            __builtin_expect(P->fd_nextsize != NULL, 0)) {
            // nextsize链表完整性检查
            if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0) || 
                __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))
                malloc_printerr("corrupted double-linked list (not small)");
            
            // 更新nextsize链表
            if (FD->fd_nextsize == NULL) {
                if (P->fd_nextsize == P)
                    FD->fd_nextsize = FD->bk_nextsize = FD;
                else {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize;
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                }
            } else {
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;
            }
        }
    }
}

4.2 安全防护机制

  1. Double Free检测

    • 检查fastbin链表头是否等于当前释放的chunk
    • 绕过方法:在两次释放之间释放一个同大小的chunk
  2. 大小检查

    • fastbin取出时检查:fastbin_index(chunksize(victim)) != idx
    • 利用find_fake_fast工具伪造size
  3. 链表完整性检查

    • 检查FD->bk == P && BK->fd == P
    • 检查nextsize链表的完整性

5. 利用技术分析

5.1 信息泄露技术

  1. unsorted bin泄露libc地址

    • 原理:释放到unsorted bin的chunk会记录main_arena地址
    • 实现:释放chunk后读取其fd/bk指针
  2. smallbin残留指针

    • 合并两个smallbin时可能残留main_arena指针
    • 申请时不覆盖这些指针可泄露信息

5.2 任意地址写技术

  1. unsorted bin攻击

    unsorted_chunks(av)->bk = bck;
    bck->fd = unsorted_chunks(av);
    
    • 控制bk可将unsorted_chunks(av)写入任意地址-2*SIZE_SZ处
    • 写入值为一个很大的数值
  2. largebin攻击

    • 利用largebin的nextsize链表操作
    • 通过精心构造实现任意地址写

5.3 House of系列攻击

  1. House of Spirit

    • 伪造fastbin chunk并释放
    • 下次分配可获取伪造chunk
  2. House of Einherjar

    • 利用off-by-one等漏洞修改chunk的size
    • 实现chunk overlap
  3. House of Force

    • 修改top chunk的size
    • 控制后续分配位置

6. 关键宏定义解析

6.1 大小相关宏

#define chunksize(p) ((p)->size & ~(SIZE_BITS)) // 获取chunk实际大小
#define in_smallbin_range(sz) ((unsigned long)(sz) < (unsigned long)MIN_LARGE_SIZE)
#define smallbin_index(sz) ((sz) >> SMALLBIN_WIDTH_SHIFT)
#define largebin_index_32(sz) \
    ((((unsigned long)(sz)) >> 6) <= 38)?  56 + (((unsigned long)(sz)) >> 6): \
    ((((unsigned long)(sz)) >> 9) <= 20)?  91 + (((unsigned long)(sz)) >> 9): \
    ((((unsigned long)(sz)) >> 12) <= 10)? 110 + (((unsigned long)(sz)) >> 12): \
    ((((unsigned long)(sz)) >> 15) <= 4)? 119 + (((unsigned long)(sz)) >> 15): \
    ((((unsigned long)(sz)) >> 18) <= 2)? 124 + (((unsigned long)(sz)) >> 18): \
    126

6.2 指针转换宏

#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) // 用户指针转chunk指针
#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ)) // chunk指针转用户指针
#define bin_at(m, i) \
    (mbinptr)(((char*)&((m)->bins[((i) - 1) * 2])) - offsetof(struct malloc_chunk, fd))

6.3 标志位宏

#define PREV_INUSE 0x1 // 前一个chunk在使用中
#define IS_MMAPPED 0x2 // 当前chunk由mmap分配
#define NON_MAIN_ARENA 0x4 // 当前chunk不属于主分配区
#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)

7. 调试与分析方法

  1. gdb调试技巧

    • p main_arena查看主分配区状态
    • heap命令查看堆布局
    • b _int_free在释放函数设断点
  2. 关键函数断点

    • __libc_malloc
    • _int_malloc
    • __libc_free
    • _int_free
    • sysmalloc
  3. 内存布局分析

    • fastbin: FILO(先进后出)单链表
    • smallbin: FIFO(先进先出)双向链表
    • largebin: 大小排序的双向链表+nextsize索引
    • unsortedbin: FIFO双向链表

8. 性能优化策略

  1. fastbin设计

    • 小内存快速分配
    • 单链表操作,无合并操作
  2. binmap设计

    • 快速定位非空bin
    • 减少遍历开销
  3. 多arena设计

    • 减少线程竞争
    • nextnext_free管理arena链表
  4. last_remainder优化

    • 记录最近分割的剩余chunk
    • 提高局部性分配效率

9. 安全编程建议

  1. 分配注意事项

    • 检查malloc返回值是否为NULL
    • 避免整数溢出导致分配异常大小
  2. 释放注意事项

    • 避免double free
    • 释放后及时置空指针
    • 避免use-after-free
  3. 敏感操作防护

    • 关键数据结构设置完整性校验
    • 重要操作前进行安全检查
    • 使用安全版本的分配函数
  4. 内存管理最佳实践

    • 统一分配释放接口
    • 实现内存池管理大块内存
    • 定期检查内存泄漏
glibc内存管理机制深入解析:malloc与free的实现原理 1. 核心数据结构分析 1.1 malloc_ state结构体 malloc_state 是ptmalloc分配器的核心管理结构,用于管理内存分配区(arena): 1.2 malloc_ chunk结构体 malloc_chunk 是内存块的基本管理单元: 2. 内存分配流程(malloc) 2.1 初始化过程 首次调用 __libc_malloc 时的初始化流程: 检查 __malloc_hook ,初始时指向 malloc_hook_ini malloc_hook_ini 函数将 __malloc_hook 置为NULL 调用 ptmalloc_init 进行初始化 设置 __malloc_initialized > 0 表示初始化完成 2.2 分配流程 请求大小转换 : checked_request2size(bytes, nb) 将用户请求大小转换为实际分配的chunk大小 获取分配区 : arena_get(ar_ptr, bytes) 获取管理空闲空间的分配区 分配路径选择 : fastbin分配 :当 nb <= get_max_fast() 时 计算fastbin索引: idx = fastbin_index(nb) 获取对应fastbin链表头: fb = &fastbin(av, idx) smallbin分配 :当 in_smallbin_range(nb) 为真时 计算smallbin索引: idx = smallbin_index(nb) 获取对应smallbin: bin = bin_at(av, idx) largebin分配 :其他情况 先合并fastbin: malloc_consolidate(av) top chunk分割 :当其他bin无法满足时 检查top chunk大小: size = chunksize(av->top) 如果足够则分割: 系统内存申请 :当所有方法失败时调用 sysmalloc 3. 内存释放流程(free) 3.1 基本释放流程 hook检查 :检查 __free_hook 是否为NULL 指针转换 : p = mem2chunk(mem) 将用户指针转换为chunk指针 mmap检查 : chunk_is_mmapped(p) 判断是否由mmap分配 获取分配区 : ar_ptr = arena_for_chunk(p) 实际释放 :调用 _int_free(ar_ptr, p, 0) 3.2 _ int_ free详细流程 安全检查 : 地址有效性检查: (uintptr_t)p > (uintptr_t)-size 对齐检查: aligned_OK(size) 大小检查: size >= MINSIZE fastbin释放 : 检查大小: (unsigned long)(size) <= (unsigned long)(get_max_fast()) 设置fastbin标志: set_fastchunks(av) 获取fastbin索引: idx = fastbin_index(size) 检查double free: if (__builtin_expect(old == p, 0)) 插入fastbin链表: p->fd = old2 = old 合并与unsorted bin : 检查相邻chunk是否空闲 合并操作: unlink 将相邻空闲chunk从链表中移除 插入unsorted bin: bck = unsorted_chunks(av) 4. 关键机制与安全防护 4.1 unlink操作 unlink 宏用于从双向链表中安全移除一个chunk: 4.2 安全防护机制 Double Free检测 : 检查fastbin链表头是否等于当前释放的chunk 绕过方法:在两次释放之间释放一个同大小的chunk 大小检查 : fastbin取出时检查: fastbin_index(chunksize(victim)) != idx 利用 find_fake_fast 工具伪造size 链表完整性检查 : 检查 FD->bk == P && BK->fd == P 检查nextsize链表的完整性 5. 利用技术分析 5.1 信息泄露技术 unsorted bin泄露libc地址 : 原理:释放到unsorted bin的chunk会记录main_ arena地址 实现:释放chunk后读取其fd/bk指针 smallbin残留指针 : 合并两个smallbin时可能残留main_ arena指针 申请时不覆盖这些指针可泄露信息 5.2 任意地址写技术 unsorted bin攻击 : 控制bk可将 unsorted_chunks(av) 写入任意地址-2* SIZE_ SZ处 写入值为一个很大的数值 largebin攻击 : 利用largebin的nextsize链表操作 通过精心构造实现任意地址写 5.3 House of系列攻击 House of Spirit : 伪造fastbin chunk并释放 下次分配可获取伪造chunk House of Einherjar : 利用off-by-one等漏洞修改chunk的size 实现chunk overlap House of Force : 修改top chunk的size 控制后续分配位置 6. 关键宏定义解析 6.1 大小相关宏 6.2 指针转换宏 6.3 标志位宏 7. 调试与分析方法 gdb调试技巧 : p main_arena 查看主分配区状态 heap 命令查看堆布局 b _int_free 在释放函数设断点 关键函数断点 : __libc_malloc _int_malloc __libc_free _int_free sysmalloc 内存布局分析 : fastbin: FILO(先进后出)单链表 smallbin: FIFO(先进先出)双向链表 largebin: 大小排序的双向链表+nextsize索引 unsortedbin: FIFO双向链表 8. 性能优化策略 fastbin设计 : 小内存快速分配 单链表操作,无合并操作 binmap设计 : 快速定位非空bin 减少遍历开销 多arena设计 : 减少线程竞争 next 和 next_free 管理arena链表 last_ remainder优化 : 记录最近分割的剩余chunk 提高局部性分配效率 9. 安全编程建议 分配注意事项 : 检查malloc返回值是否为NULL 避免整数溢出导致分配异常大小 释放注意事项 : 避免double free 释放后及时置空指针 避免use-after-free 敏感操作防护 : 关键数据结构设置完整性校验 重要操作前进行安全检查 使用安全版本的分配函数 内存管理最佳实践 : 统一分配释放接口 实现内存池管理大块内存 定期检查内存泄漏