函数描述

Base Glibc 2.41

  ------------------------- malloc_consolidate -------------------------

  malloc_consolidate is a specialized version of free() that tears
  down chunks held in fastbins.  Free itself cannot be used for this
  purpose since, among other things, it might place chunks back onto
  fastbins.  So, instead, we need to use a minor variant of the same
  code.

主要用于将fastbin的堆块整理到unsortedbin中

在malloc过程中有两次机会进入这个函数

  1. 在申请的堆块≥0x400的时候,会先整理堆块,后续再对unsortedbin遍历
  2. 在top_chunk没法满足堆块大小申请的时候,会整理fastbin然后再来一遍

源码分析

static void malloc_consolidate(mstate av)
{
  mfastbinptr*    fb;                 /* current fastbin being consolidated */
  mfastbinptr*    maxfb;              /* last fastbin (for loop control) */
  mchunkptr       p;                  /* current chunk being consolidated */
  mchunkptr       nextp;              /* next chunk to consolidate */
  mchunkptr       unsorted_bin;       /* bin header */
  mchunkptr       first_unsorted;     /* chunk to link to */

  /* These have same use as in free() */
  mchunkptr       nextchunk;
  INTERNAL_SIZE_T size;
  INTERNAL_SIZE_T nextsize;
  INTERNAL_SIZE_T prevsize;
  int             nextinuse;

  atomic_store_relaxed (&av->have_fastchunks, false);

  unsorted_bin = unsorted_chunks(av);

  /*
    Remove each chunk from fast bin and consolidate it, placing it
    then in unsorted bin. Among other reasons for doing this,
    placing in unsorted bin avoids needing to calculate actual bins
    until malloc is sure that chunks aren't immediately going to be
    reused anyway.
  */

  maxfb = &fastbin (av, NFASTBINS - 1);
  fb = &fastbin (av, 0); // 取fastbinY[0]
  do {
    p = atomic_exchange_acquire (fb, NULL);
    if (p != NULL) { // 如果存在fastbin则遍历所有单链表
      do {
	{
	  if (__glibc_unlikely (misaligned_chunk (p))) // 校验堆快是否对齐
	    malloc_printerr ("malloc_consolidate(): "
			     "unaligned fastbin chunk detected");

	  unsigned int idx = fastbin_index (chunksize (p)); // 校验size
	  if ((&fastbin (av, idx)) != fb)
	    malloc_printerr ("malloc_consolidate(): invalid chunk size");
	}

	check_inuse_chunk(av, p); // 检查inuse位
	nextp = REVEAL_PTR (p->fd); // fastbin->fd

	/* Slightly streamlined version of consolidation code in free() */
	size = chunksize (p);
	nextchunk = chunk_at_offset(p, size); // next_chunk
	nextsize = chunksize(nextchunk);

	if (!prev_inuse(p)) { // 如果victim chunk的prev_inuse位为0
	  prevsize = prev_size (p); // 向前合并
	  size += prevsize;
	  p = chunk_at_offset(p, -((long) prevsize));
	  if (__glibc_unlikely (chunksize(p) != prevsize)) // 注意size和prevsize需要一致
	    malloc_printerr ("corrupted size vs. prev_size in fastbins");
	  unlink_chunk (av, p);
	}

	if (nextchunk != av->top) {
	  nextinuse = inuse_bit_at_offset(nextchunk, nextsize); // next_chunk->inuse

	  if (!nextinuse) { // 向后合并
	    size += nextsize;
	    unlink_chunk (av, nextchunk);
	  } else
	    clear_inuse_bit_at_offset(nextchunk, 0);

	  first_unsorted = unsorted_bin->fd; // insert into unsortedbin
	  unsorted_bin->fd = p;
	  first_unsorted->bk = p;

	  if (!in_smallbin_range (size)) { // 如果合并后是largebin范围的chunk
	    p->fd_nextsize = NULL;
	    p->bk_nextsize = NULL;
	  }

	  set_head(p, size | PREV_INUSE);
	  p->bk = unsorted_bin;
	  p->fd = first_unsorted;
	  set_foot(p, size);
	}

	else { // 被top_chunk合并
	  size += nextsize;
	  set_head(p, size | PREV_INUSE);
	  av->top = p;
	}

      } while ( (p = nextp) != NULL);

    }
  } while (fb++ != maxfb);
}

主要逻辑是遍历所有fastbinY数组中的单链表

对每个fastbin节点首先校验堆块是否对齐,以及size是否是这个fastbinY索引的size

之后会校验这个chunk的inuse位

后续就是向前合并和向后合并的过程

通过判断自身chunk的preinuse位来判断前对快是否需要合并,如果需要合并还会去判断presize是否等于前堆块的size

向后合并会判断next_chunk是否是top_chunk,如果不是则会去判断next_chunk的inuse位,这里因为chunk是fastbin范围内的,所以不会存在presize和size的校验,之后将合并后的chunk判断大小初始化fd_nextsize以及bk_nextsize并链入unsortedbin中

如果next_chunk是top_chunk则会被top_chunk合并

可能的攻击面

如果能够挟持fastbin的fd

需满足: