freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

源码分析:fastchunk、smallchunk、largechunk的请求
2021-10-02 14:11:51

我会首先直接上malloc源码,代码量比较大,但是我会按照模块进行讲解,尽量讲解的更清晰一些,使用的是glibc2.23版本。

首先了解一下相关的数据类型

#define INTERNAL_SIZE_T size_t
typedef struct malloc_chunk* mbinptr;

相关变量

INTERNAL_SIZE_T nb;
  unsigned int    idx;
  mbinptr         bin;
  mfastbinptr*    fb;

  mchunkptr       victim;
  INTERNAL_SIZE_T size;
  int             victim_index;

  mchunkptr       remainder;
  unsigned long   remainder_size;

  unsigned int    block;
  unsigned int    bit;
  unsigned int    map;

  mchunkptr       fwd;
  mchunkptr       bck;

这是在int_malloc函数中的一些变量,我们将会在后面的讲解中遇到它们,重要的是它们相关的数据类型

对请求的堆块的检查

变量下面的第一条代码就是checked_request2size(bytes, nb);,这也是一个宏定义,我们看一下这个宏具体做了哪些工作

#define checked_request2size(req, sz)                             \
  if (REQUEST_OUT_OF_RANGE(req)) {                                \
    MALLOC_FAILURE_ACTION;                                        \
    return 0;                                                     \
  }                                                               \
  (sz) = request2size(req);


#define REQUEST_OUT_OF_RANGE(req)                                 \
  ((unsigned long)(req) >=                                        \
   (unsigned long)(INTERNAL_SIZE_T)(-2 * MINSIZE))

#define MALLOC_FAILURE_ACTION \
   errno = ENOMEM;

这就是相关的所有的宏。我们首先看REQUEST_OUT_OF_RANGE(req) ,看一下它的代码会发现明明是无符号类型的变量,却有一个-2的存在,学过机组的同学会知道这会导致它的数值变得特别的大。

结合几个宏我们可以看出,该定义会先对请求的宏进行检查,如果请求的宏特别大,那么就会报错(ENOMEN就是错误号,在errno.c中有定义),如果正常那么就会执行request2size宏,进行请求

根据申请的大小进行chunk分类

if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
    fb = &(av->fastbins[(fastbin_index(nb))]);
    if ( (victim = *fb) != 0) {
      *fb = victim->fd;
      check_remalloced_chunk(av, victim, nb);
      return chunk2mem(victim);
    }
  }

if (in_smallbin_range(nb)) {
    idx = smallbin_index(nb);
    bin = bin_at(av,idx);

    if ( (victim = last(bin)) != bin) {
      if (victim == 0) /* initialization check */
        malloc_consolidate(av);
      else {
        bck = victim->bk;
        set_inuse_bit_at_offset(victim, nb);
        bin->bk = bck;
        bck->fd = bin;

        if (av != &main_arena)
	  victim->size |= NON_MAIN_ARENA;
        check_malloced_chunk(av, victim, nb);
        return chunk2mem(victim);
      }
    }
  }

else {
    idx = largebin_index(nb);
    if (have_fastchunks(av))
      malloc_consolidate(av);
  }

fastbin

我们来看一下,有三个大的if语句,其实就是看我们申请的大小属于哪一类chunk。首先了解一下av指针,这个指针指向一个arena(之前讲过,这里不再过多赘述)。

如果分配的堆块大小属于fastbin,那么就进入第一个if语句。首先有一个fastbin_index宏,我们额外的了解一下#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2),这个很简单,二进制右移三位其实就是除以8(在64位操作系统是右移4位,但是我们先使用这个代码),减去2是因为堆块最小就是四个标准字长,而fastbin的增加步长为两个标准字长,所以说要减去2。

然后我们遇到了fb指针,根据源码,fb指针就是请求的堆块所在fastbin的相关索引的地址。并且后面victim也出现了,victim被赋值为fb地址的内容
image.png
画了一个不太行的图片更好理解一些。
如果victim不等于0,不等于0也就是说该fastbin不为空,那么就把链表尾的chunk移出链表,并且由链表尾的chunk的下一个chunk作为新的链表尾
image.png
后面又有一段我们看不懂的代码,但是大概可以看的懂就是将刚才提取出来的chunk进行检查,但是我们还是要分析一下。

{
  INTERNAL_SIZE_T sz = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);

  if (!chunk_is_mmapped(p)) {
    assert(av == arena_for_chunk(p));
    if (chunk_non_main_arena(p))
      assert(av != &main_arena);
    else
      assert(av == &main_arena);
  }

  do_check_inuse_chunk(av, p);

  /* Legal size ... */
  assert((sz & MALLOC_ALIGN_MASK) == 0);
  assert((unsigned long)(sz) >= MINSIZE);
  /* ... and alignment */
  assert(aligned_OK(chunk2mem(p)));
  /* chunk is less than MINSIZE more than request */
  assert((long)(sz) - (long)(s) >= 0);
  assert((long)(sz) - (long)(s + MINSIZE) < 0);
}

这就是它的代码。首先对sz进行了赋值,sz的值就是p的size字段,但是清除了size字段的prev_inuse和non_main_arena位。然后就是一系列的断言,具体断言的内容可以自己看一下,但是断言一般都是正确的,否则会使程序直接中断。

我们回到之前的代码,最后返回了chunk用户域的指针。

smallbin

最开始同样判断是否属于smallchunk,然后得出smallbin所在的数组的索引,并且获取数组的地址。可以发现最开始和fastbin很像。

然后我们看到了一个last(),这也是一个宏定义,然后我们具体看一下#define last(b) ((b)->bk)。然后将它赋给victim,然后和bin进行对比。如果相等,则表示链表为空。所以在链表不为空的情况下执行if语句里面的内容,判断victim是否等于0。

其实判断victim是否等于0其实就是在判断smallbin是否已经被初始化,如果没有被初始化则执行malloc_consolidate进行初始化。

如果已经被初始化了,那么首先会将victim的bk指向的地址赋给bck,其实bck就是整个链表的第二个chunk(搞清楚什么是链表头和链表尾,链表尾就是bin的fd指针指向的那个chunk,链表头是bin的bk指针指向的chunk),并且设置链表头chunk的物理相邻的nextchunk的inuse位为1,并且将bin中的fd和bk指针进行重新规划。画图大概是这样
image.png
后面还要进行检查,该arena是否是mainarena,如果不是的话设置non_main_arena。并且还会检查申请的chunk(里面也是一系列断言),最后返回用户指针。

总而言之smallbin和fastbin比较像,最大的不同在于smallbin是用双向循环链表进行维护的

largebin

largebin是比较简单的,不属于smallbin和fastbin那么就剩下largebin了。首先将idx设置为相应的largebin的索引,然后使用了一个have_fastchunks宏,它表示判断fastbin中是否有chunk,如果有的话则对fastchunk进行初始化

到这里就是对三个不同的chunk的请求,其中有一个函数malloc_consolidate,这个是比较重要的,所以我将会单独拿出来讲解。

本文参考自Glibc:浅谈 malloc() 函数具体实现libc2.23 malloc部分源码剖析

# linux # 堆管理 # 堆 # pwn # malloc
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录