LOADING

加载过慢请开启缓存 浏览器默认开启

glibc2.35-malloc.c源码阅读

2025/6/29 heap pwn

malloc_state

当我们在 pwndbg 中 p main_arena,打印的东西好像就是这个。

struct malloc_state
{
  /* Serialize access.  */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  int have_fastchunks;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

其中最常用的大概是 top,因为在打 house of orange 的时候,pwndbg 的 heap 指令会看不到 top_chunk,不过改 heap 显示的最后一个 chunk 的 size 也能解决。

__libc_lock_define (, mutex);

这个函数是给 arena 加锁,保护多线程下的并发访问。对 pwn 来说不重要;

flags 存储 arena 的状态,对 pwn 来说不重要;

have_fastchunks 表示 fastbin 内 是否有 free chunk;

fastbinsY 数组中,不同的索引表示不同的大小,fastbin[i] 表示索引 i 对应的链表表头。

top 存储 top_chunk 所在地址。

last_remainder 指向最近一次小块分割后的剩余部分的首地址。

bins 存储各种大小的空闲 chunk。

Small bins (2-63):固定大小的块
Large bins (64-126):按大小范围分组的块
Unsorted bin (1):临时存放刚释放的块

binmap 标识哪些 bin 非空。

剩下的大概都是和线程有关的,对 pwn 应该不重要。

接下来正式开始 malloc。

__libc_malloc

#if IS_IN (libc)  /*当为 libc.so 编译时,IS_IN (libc) 为真*/
void *
__libc_malloc (size_t bytes) /*bytes 就是我们的输入*/
{
  mstate ar_ptr; /*mstate 等同于之前提的 malloc_state,应该可以当做 main_arena 理解。*/
  void *victim; /*受害者,也就是最后返回的地址*/

  _Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
                  "PTRDIFF_MAX is not more than half of SIZE_MAX"); /*确保系统的数据类型满足要求,对 pwn 不重要。*/

  if (!__malloc_initialized)
    ptmalloc_init ();   /*如果未初始化就进行初始化,对 pwn 不重要。*/
#if USE_TCACHE
  /* int_free also calls request2size, be careful to not pad twice.  */
  size_t tbytes;
  if (!checked_request2size (bytes, &tbytes)) /*检查请求大小是否超过PTRDIFF_MAX,事实上即使是'0xff'*16也不会超过。对 pwn 不重要。*/
  /**/
    {
      __set_errno (ENOMEM);
      return NULL;
    }
  size_t tc_idx = csize2tidx (tbytes); /*将申请的大小转化为索引,下面会对索引进行检查,太大就不会使用tcache。*/

  MAYBE_INIT_TCACHE ();     /*尝试初始化tcache*/

  DIAG_PUSH_NEEDS_COMMENT;
  if (tc_idx < mp_.tcache_bins  /*检查大小范围*/
      && tcache     /*检查tcache是否已经初始化*/
      && tcache->counts[tc_idx] > 0)    /*这里检查对应大小的tcache的计数是否大于0。*/
    {
      victim = tcache_get (tc_idx); /*进入 tcache_get 函数获取 victim*/
      return tag_new_usable (victim); /*第一处返回点*/
    }
  DIAG_POP_NEEDS_COMMENT;
#endif

  if (SINGLE_THREAD_P)
    {
      victim = tag_new_usable (_int_malloc (&main_arena, bytes));
      assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
          &main_arena == arena_for_chunk (mem2chunk (victim)));
      return victim;
    }

  arena_get (ar_ptr, bytes);

  victim = _int_malloc (ar_ptr, bytes);
  /* Retry with another arena only if we were able to find a usable arena
     before.  */
  if (!victim && ar_ptr != NULL)
    {
      LIBC_PROBE (memory_malloc_retry, 1, bytes);
      ar_ptr = arena_get_retry (ar_ptr, bytes);
      victim = _int_malloc (ar_ptr, bytes);
    }

  if (ar_ptr != NULL)
    __libc_lock_unlock (ar_ptr->mutex);

  victim = tag_new_usable (victim);

  assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
          ar_ptr == arena_for_chunk (mem2chunk (victim)));
  return victim;
}
libc_hidden_def (__libc_malloc)

先跳转阅读 tcache_get 函数:

tcache_get

tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  if (__glibc_unlikely (!aligned_OK (e)))
    malloc_printerr ("malloc(): unaligned tcache chunk detected");
  tcache->entries[tc_idx] = REVEAL_PTR (e->next);
  --(tcache->counts[tc_idx]);   /*可以得知,就是这个计数,导致只含一个 tcache chunk 时,改写其 next 不会起作用。处理方法也很简单:再给 tcache 塞一个 chunk 即可。*/
  e->key = 0;
  return (void *) e;
}