• house of orange 源码分析


    前言

    原来只知道 house of orange 打 unsorted bin ,碰到题目发现还可以打 fast bin ,今天就具体研究一下源码(glibc-2.23)。

    分析

    当所有的 bins 和 top chunk 都不满足分配要求,且 fast bin 合并后,再次循环中也找不到满足分配要求的 bin ,就会调用 sysmalloc 函数来分配空间。

    if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) //使用 top chunk 分配
    {
          ......
    }
    
    else if (have_fastchunks (av)) //合并 fast bin 后再次循环看是否有满足分配要求的 bin
    {
          ......
    }
    
    else
    {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
                alloc_perturb (p, bytes);
          return p;
    }
    

    跟进 sysmalloc 函数看看

    if (av == NULL
          || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
    	  && (mp_.n_mmaps < mp_.n_mmaps_max))) //使用 mmap 分配内存
    {
          ......
    }
    

    首先如果所需分配的大小满足 mmap 的阈值,而且 mmap 分配的内存块数小于最大值,就会使用 mmap() 向操作系统申请内存进行分配。

      old_top = av->top;
      old_size = chunksize (old_top);
      old_end = (char *) (chunk_at_offset (old_top, old_size));
      brk = snd_brk = (char *) (MORECORE_FAILURE);
    

    保存当前 top chunk 的指针,大小和结束地址到临时变量中。

      assert ((old_top == initial_top (av) && old_size == 0) ||
              ((unsigned long) (old_size) >= MINSIZE &&
               prev_inuse (old_top) &&
               ((unsigned long) old_end & (pagesize - 1)) == 0));
    
      /* Precondition: not enough current space to satisfy nb request */
      assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
    

    检查 top chunk 的合法性,如果第一次调用本函数, top chunk 可能没有初始化,可能 old_size 为 0 ,如果 top chunk 已经初始化,则 top chunk 的大小必须大于等于 MINSIZE 。 prev_inuse 位必须置 1 ,top chunk 的结尾地址必须是页对齐的。
    top chunk 的大小需要小于所需分配大小 + MINSIZE ,这是因为如果 top chunk 的大小需要大于等于所需分配大小 + MINSIZE ,那么在 _int_malloc 中就应该已经切割 top chunk 来分配 chunk 了。

    if (av != &main_arena) //当前分配区不是主分配区时,进入这个分支分配内存
    {
          ......
    }
    
    else //当前分配区为主分配区,进入这个分支分配内存
    {
          ......
    
          if (brk != (char *) (MORECORE_FAILURE)) // brk 合法,说明使用 sbrk() 或 mmap() 分配成功。       
          {
              if (mp_.sbrk_base == 0) //如果 sbrk_base 还没有初始化,更新 sbrk_base 和当前分配区的内存分配总量。 
                    mp_.sbrk_base = brk;
              av->system_mem += size;            
          }    
          
          if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE)) //如果 sbrk()分配成功,更新 top chunk 的大小,
                                                                        //并设定 top chunk 的前一个 chunk 处于 inuse 状态。
                 set_head (old_top, (size + old_size) | PREV_INUSE);
          
          else if (contiguous (av) && old_size && brk < old_end) //如果当前分配区可分配连续虚拟内存,原 top chunk 的大小大于 0,
          {                                                      //但新的 brk 值小于原 top chunk 的结束地址,则报错。
                 /* Oops!  Someone else killed our space..  Can't touch anything.  */
                  malloc_printerr (3, "break adjusted to free malloc space", brk,av);    
          }
         
          else //执行到这个分支,意味着 sbrk()返回的 brk 值大于原 top chunk 的结束地址,
          {    //那么新的地址与原 top chunk 的地址不连续。
                ......
    
               if (snd_brk != (char *) (MORECORE_FAILURE) //如果 brk 的结束地址合法  
                {
                      ......
                      
                      if (old_size != 0) //如果原来 top chunk 的大小不为 0
                      {
                            old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
                            set_head (old_top, old_size | PREV_INUSE);
                            chunk_at_offset (old_top, old_size)->size = (2 * SIZE_SZ) | PREV_INUSE;
                            chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size = (2 * SIZE_SZ) | PREV_INUSE;
                            if (old_size >= MINSIZE)
                            {
                                  _int_free (av, old_top, 1);
                            }
                      }
                }           
          }
          
    }
    

    关键代码在于 if (old_size != 0) 分支,将 top chunk 分为空闲 chunk 和 fencepost 两部分, fencepost 为 MINISIZE 大小,设置 fencepost 里的内容,空闲 chunk 部分调用 _int_free 函数释放。

    检查绕过

    orange 的技术就是为了在程序没有 free 功能下实现 free ,所以利用思路很明显,就是让程序最终进入 if (old_size != 0) 分支调用 free 。
    在 _int_malloc 中,要求所需分配的大小不能从任何 bins 中分配,
    然后需要绕过判断:

    if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
    

    top chunk 的 size 要小于所需分配的大小 + MINISIZE 否则程序就会进入其中使用 top chunk 切割出所需大小的 chunk 。

    else if (have_fastchunks (av))
    

    fast bin 合并后也不能有任何满足所需分配大小的 bin ,否则会使用该 bin 分配。
    绕过以上两个判断后,程序进入 sysmalloc

    if (av == NULL
          || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
    	  && (mp_.n_mmaps < mp_.n_mmaps_max)))
    

    所需分配的大小不能超过 mmap 的阈值,否则就使用 mmap 分配。

      assert ((old_top == initial_top (av) && old_size == 0) ||
              ((unsigned long) (old_size) >= MINSIZE &&
               prev_inuse (old_top) &&
               ((unsigned long) old_end & (pagesize - 1)) == 0));
    
      /* Precondition: not enough current space to satisfy nb request */
      assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)
    

    检查 top chunk 的合法性,以及 top chunk 的大小需要小于所需分配大小 + MINSIZE 。所以构造 top chunk 的 fake size 时需要注意,size 大小必须满足 size + top chunk addr % pagesize == 0 ,因为要保证 top chunk 的 end 是页对齐的。同时记得将 prev_inuse 位置 1 。

          if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE)) //如果 sbrk()分配成功,更新 top chunk 的大小,
                                                                    //并设定 top chunk 的前一个 chunk 处于 inuse 状态。
                set_head (old_top, (size + old_size) | PREV_INUSE);
          
          else if (contiguous (av) && old_size && brk < old_end) //如果当前分配区可分配连续虚拟内存,原 top chunk 的大小大于 0,
          {                                                      //但新的 brk 值小于原 top chunk 的结束地址,则报错。
                 /* Oops!  Someone else killed our space..  Can't touch anything.  */
                  malloc_printerr (3, "break adjusted to free malloc space", brk,av);    
          }
         
          else //执行到这个分支,意味着 sbrk()返回的 brk 值大于原 top chunk 的结束地址,
          {    //那么新的地址与原 top chunk 的地址不连续。
                ......
                _int_free (av, old_top, 1);
          }
    

    最终进入 else 分支,需要程序调用 sbrk() 扩展的新 top chunk 与原来的旧 top chunk 不相邻,这步要怎么实现呢?
    我们看看新的 top chunk 是如何分配的。
    在主分配区的情况下:

    size = nb + mp_.top_pad + MINSIZE; //需要分配的 size
    
    if (contiguous (av))
          size -= old_size; //减去原来 top chunk 的大小
    
    size = ALIGN_UP (size, pagesize); //页对齐
    
    if (size > 0)
    {
          brk = (char *) (MORECORE (size)); //调用 sbrk() 扩展 top chunk
          LIBC_PROBE (memory_sbrk_more, 2, brk, size);
    }
    

    mp_.top_pad 默认大小为 128 * 1024 ,也就是 0x20000 。
    然后我们看看 old_end 是如何计算的:

    old_end = (char *) (chunk_at_offset (old_top, old_size));
    

    依据 top chunk 头地址和 top chunk 的 size 位来计算。
    所以,正常情况下

    brk == old_end
    

    是相等的,但是 orange 技术在使用时通常都会把 top chunk size 改小,这样会导致 old_end 变小,从而最终进入 else 分支调用 free 。

    总结以下,需要注意以下几点:

    1. top chunk 的 size 要小于所需分配的大小 + MINISIZE
    2. fast bin 合并后也不能有任何满足所需分配大小的 bin
    3. 所需分配的大小不能超过 mmap 的阈值
    4. top chunk 的大小必须大于等于 MINSIZE ,prev_inuse 位必须置 1 ,top chunk 的结尾地址必须是页对齐的

    满足这些条件后,就会调用 _int_free 函数将原来的 top chunk 扔进 bins 里(会比原来小 0x20 ,因为有 0x20 被拿去用作 fencepost 了)

    总结

    这样看来,orange 技术可以实现在程序无 free 的情况下,将 chunk 扔进 fast bin 与 unsorted bin 。 同时发现,在主分配区中,如果调用 sbrk 分配内存成功后, __after_morecore_hook 存在则调用 __after_morecore_hook 。

       if (brk != (char *) (MORECORE_FAILURE)) // sbrk 分配成功
            {
              /* Call the `morecore' hook if necessary.  */
              void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
              if (__builtin_expect (hook != NULL, 0))
                (*hook)();
            }
    
    

    所以在 _malloc_hook 跟 _free_hook 都无法使用的时候,可以尝试覆盖 __after_morecore_hook ,以后有机会可以尝试一下。(不过这种情况不太可能 2333 )

    内容来源

    《 glibc 内存管理 ptmalloc 源代码分析》

  • 相关阅读:
    Mysql数据库再度使用
    搭建wamp php环境
    phpMyAdmin
    windows激活全系列
    web常见之音乐播放器
    web常见效果之轮播图
    ASP.NET Web Pages
    Eclipse配置中文(汉化)
    以有涯随无涯
    How to get the edited text from itext in fabricjs
  • 原文地址:https://www.cnblogs.com/luoleqi/p/13566793.html
Copyright © 2020-2023  润新知