sysmalloc 분석

Osori·2021년 3월 22일
0

house of orange 기법을 공부하기 이전에 묵혀놓았던 sysmalloc 함수를 분석하고자 한다.
MALLOC_ALIGNMENT 라는 매크로 상수가 있는데, 이 상수는 기본적으로 0x10 이지만 사용자가 정의해서 바꾸는 것이 가능하다. 그런데 보통은 바꾸는 경우는 거의 없을 것이기 때문에 바뀌지 않았다고 가정하고, 바뀌었을 때 처리되는 부분의 분석은 하지 않았다.
사실 포너블을 위해서 분석할만한 가치가 있는지는 잘 모르겠다. 이 sysmalloc 코드가 괴기한 이유의 99%는 예기치 못한 상황에 대한 chunk alginment가 대부분이기 때문에.. 그래도 한번 코드를 눈에 익혀놓으면 나중에 볼일이 있을 때 편하게 볼 수 있으니까..
프로그램 사용시 mmap이 실패하거나 sbrk가 실패하거나 malloc과 sbrk를 동시에 사용하는 프로그램이 있을지 궁금하다.
(아무리 생각해도 malloc과 sbrk를 동시에 쓰는건, 바보같은 짓 같다. malloc으로 동적메모리 관리가 되는데, 이 둘을 동시에 사용할 이유가 있을까)🤔

mmap으로 할당받기

mmap으로 할당하는 사이즈는 mp_.mmap_threshold에 저장되어 있는데, 이 사이즈 이상을 요구하면 기본적으로 mmap을 시도할 것이고, 일반적인 경우에는 mmaped chunk가 반환될 것이다. 이경우가 아니라면 이제 brk syscall을 이용해서 세그먼트를 확장시킬 준비를 한다.

sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
  mchunkptr old_top;              /* 이전 탑청크 */
  INTERNAL_SIZE_T old_size;       /* 이전 사이즈  */
  char *old_end;                  /* 이전 끝 경계 */
  long size;                      /* 처음 MORECORE or mmap call의 인자 */
  char *brk;                      /* MORECORE의 반환값 */
  long correction;                /* MORECORE call의 2번째 호출의 인자 */
  char *snd_brk;                  /* 2nd 반환 변수 */
  INTERNAL_SIZE_T front_misalign; /* 새로운 공간의 초반의 사용불가능한 바이트 */
  INTERNAL_SIZE_T end_misalign;   /* 새로운 공간의 끝에서 남은 부분 페이지 */
  char *aligned_brk;              /* 정렬된 오프셋 */
  mchunkptr p;                    /* 할당/반환된 청크 */
  mchunkptr remainder;            /* 할당후 남은 놈 */
  unsigned long remainder_size;   /* its size */
  size_t pagesize = GLRO (dl_pagesize);
  bool tried_mmap = false;
  /*일단 mmap으로 할당가능하면 mmap으로 할당해주려고 노력한다. 
    요청사이즈>=mmap 경계값 && mmap 가능한 최대값보다 적게 호출했을 시*/
if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
          && (mp_.n_mmaps < mp_.n_mmaps_max))) 
    {
      char *mm;           /* return value from mmap call*/
    try_mmap:
      /*
         mmap을 하기위한 size로 페이지단위인 0x1000에 맞추어 라운딩 해준다. 
         MALLOC_ALIGNMENT는 기본적으로 64bit환경에서 0x10이다. 
       */
      if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) //따라서 이게 호출될 것임. 
        size = ALIGN_UP (nb + SIZE_SZ, pagesize);
      else
        size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
      tried_mmap = true; //mmap 시도하겠다는 flag 
      /* 라운딩된 size의 유효성을 검사한다. */
      if ((unsigned long) (size) > (unsigned long) (nb))
        {
          mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));
          //mmap 성공시
          if (mm != MAP_FAILED)
            {
              /*
                 realloc() 과 munmap() 등을 위해서 시작 오프셋은 
                 할당된 청크의 prev_size영역에 저장된다고 한다. 
                 MMAP_ALIGNMENT가 바뀐 경우만 해당 
               */
              if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) //여기가 실행될 것임 
                {
                  //할당된 주소값이 유효한지 검사한다. 간략하게 16진수 배수인지 검사 
                  assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
                  front_misalign = 0; //잘 정렬되었다. 
                }
              else
              //사용자가 MMAP_ALIGNMENT를 바꾼경우
                front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
              if (front_misalign > 0) 
                {
                  correction = MALLOC_ALIGNMENT - front_misalign;
                  p = (mchunkptr) (mm + correction);
                  set_prev_size (p, correction);
                  set_head (p, (size - correction) | IS_MMAPPED);
                }
              else
                { //일반적인 흐름으로는 이곳으로 간다. 
                  p = (mchunkptr) mm;
                  set_prev_size (p, 0); //prev_size를 0으로 설정 
                  set_head (p, size | IS_MMAPPED); //header에 사이즈와 IS_MMAP 설정 
                }
              /* 통계 업데이트 */ 
              int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
              atomic_max (&mp_.max_n_mmaps, new);
              unsigned long sum;
              sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
              atomic_max (&mp_.max_mmapped_mem, sum); 
              check_chunk (av, p); 
              return chunk2mem (p); //반환해줌 
            }
        }
    }

세그먼트 확장시키기

mmap으로 할당받는 것이 실패하거나, mmap으로 할당받을만한 사이즈가 아니였다면 heap의 끝부분을 확장시키는 과정을 수행한다. 이 부분의 코드들은 상당히 복잡하지만 결국 하는 것은 동일하다. 메모리가 연속적인지 검사를 하고, 외부 sbrk가 이용되었는지 검사를 하는 것이다.
메모리가 비연속적이거나 sbrk가 이용되었을 경우, 우리가 malloc에서 사용하는 주소가 중간에 구멍이 뚫리게되므로 이 부분에 대한 처리를 해주는 것이다. malloc의 주소정렬 단위인 0x10 을 맞추어 주는 과정이 이부분의 대부분이다.
mmap으로 확장을 하든 sbrk로 확장을 하든 결과적으로는 새로운 공간에 topchunk가 할당된다는 건 다를게 없다.

  if (av == NULL) //이제 topchunk가 있어야 하니까 areana는 non null이여야함
    return 0;

  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);
  /*
     여기서 중요한 것은 paging 단위도 검사한다는 것. 
     따라서 우리가 topchunk를 변조한다고 할 때 topchunk+topchunk->size는 페이지 단위에    
     맞게 설정 되야한다. 
   */
  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));== 0)); 
  assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
  if (av != &main_arena) //메인아레나가 아닌경우 
    {
      heap_info *old_heap, *heap;
      size_t old_heap_size;
      /* 현재힙을 확장시켜본다. 실제로 공간의 할당이 이루어지지 않음 */
      old_heap = heap_for_ptr (old_top);
      old_heap_size = old_heap->size;
      if ((long) (MINSIZE + nb - old_size) > 0
          && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
        {
          av->system_mem += old_heap->size - old_heap_size;
          set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
                    | PREV_INUSE);
        }
        //이 경우에는 mmap으로 할당을 받는다. 
      else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
        {
          /* 할당된 힙은 싱글리스트 형태로 prev에 계속 연결된다. */
          heap->ar_ptr = av;
          heap->prev = old_heap; /
          av->system_mem += heap->size;
          /* 각정 설정들을 함  */
          top (av) = chunk_at_offset (heap, sizeof (*heap));
          set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);
          /* 울타리 청크를 설정해준다.. */
          old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
          set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
          if (old_size >= MINSIZE) //이전 topchunk의 크기가 chunk의 최소크기 이상인 경우 
            {
              set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
              set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
              set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
              _int_free (av, old_top, 1); //이전 topchunk를 free
            }
          else
            {
              set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
              set_foot (old_top, (old_size + 2 * SIZE_SZ));
            }
        }
      else if (!tried_mmap)
        /* We can at least try to use to mmap memory.  */
        goto try_mmap;
    }
  else     //메인아레나인 경우 
    { /* 일단 사이즈를 라운딩한다
      size = nb + mp_.top_pad + MINSIZE;
      /*
         일단 주소가 연속적이라면(자세히는 모르겠는데 외부 sbrk사용 여부인듯) 
         이전에 남은 topchunk도 같이 사용이 가능할 것이다. 
       */
      if (contiguous (av))
        size -= old_size; //따라서 그 처리를 해준다. 
      /*
        pagesize로 정렬해준다.
       */
      size = ALIGN_UP (size, pagesize);
      /*
         long size이기 때문에 너무 큰요청의 size에  대해서는 sbrk를 사용하지 않는다.
       */
      if (size > 0)
        {
          brk = (char *) (MORECORE (size));  //MORECORE은 sbrk랑 동일하다
          LIBC_PROBE (memory_sbrk_more, 2, brk, size); 
        }
      if (brk != (char *) (MORECORE_FAILURE)) //sbrk 성공
        {
          /* morecore_hook 을 실행한다.  */
          void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
          if (__builtin_expect (hook != NULL, 0))
            (*hook)();
        }
      else //sbrk가 실패한 경우임. 왠만하면 성공함.
        {
          /*
             mmap을 사용했다면 이걸 backup용으로 사용한다.
             메모리 공간에 구멍이 있다면 유용하다. 
          if (contiguous (av))
            size = ALIGN_UP (size + old_size, pagesize);
          /* If we are relying on mmap as backup, then use larger units */
          if ((unsigned long) (size) < (unsigned long) (MMAP_AS_MORECORE_SIZE))
            size = MMAP_AS_MORECORE_SIZE;
          /* Don't try if size wraps around 0 */
          if ((unsigned long) (size) > (unsigned long) (nb))
            {
              char *mbrk = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));
              if (mbrk != MAP_FAILED)
                {
                  /* We do not need, and cannot use, another sbrk call to find end */
                  brk = mbrk;
                  snd_brk = brk + size;
                  set_noncontiguous (av); //mmap으로 받았기 때문에 비연속적
                }
            }
        }
      if (brk != (char *) (MORECORE_FAILURE))
        {
          if (mp_.sbrk_base == 0)
            mp_.sbrk_base = brk;
          av->system_mem += size;
           //메모리가 연속적인 경우에는 이전 topchunk를 확장시켜준다. 
          if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
            set_head (old_top, (size + old_size) | PREV_INUSE);
           //arena에는 메모리가 연속적이라고 기록됬는데, 누군가 공간을 줄여버린 경우 
          else if (contiguous (av) && old_size && brk < old_end)
           //에러 출력함. 그냥 종료해버림. 메모리 간섭이 일어나서 문제가 될 수 있기 때문
            malloc_printerr ("break adjusted to free malloc space");
          /*
             그렇지 않다면 조정을 실시한다. 
           *만약 noncontiguous이거나 처음이면, 기존 세그먼트 끝을 찾기 위해 sbrk호출
           *우리는 malloc으로 반환되는 청크들이 모두 정렬되게 해야한다.
           * 만약 외부 sbrk가 있었다면 topchunk를 old top과 이어지게 하면 안된다. 
           */
          else //외부 sbrk가 호출된 경우임. house of orange에서 써먹는 곳. 
            {
              front_misalign = 0;
              end_misalign = 0;
              correction = 0;
              aligned_brk = brk;
              /* handle contiguous cases */
              if (contiguous (av)) 
                {
                  /* 외부 sbrk가 메모리를 어디까지 먹었나 검사*/
                  if (old_size)
                    av->system_mem += brk - old_end;
                  front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
                  if (front_misalign > 0)
                    {
                      //메모리 정렬을 위한 루틴 
                      correction = MALLOC_ALIGNMENT - front_misalign;
                      aligned_brk += correction;
                    }
                  correction += old_size;
                  /* Extend the end address to hit a page boundary */
                  end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
                  correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;
                  assert (correction >= 0);
                  snd_brk = (char *) (MORECORE (correction));

                  if (snd_brk == (char *) (MORECORE_FAILURE))
                    {
                      correction = 0;
                      snd_brk = (char *) (MORECORE (0));
                    }
                  else
                    {
                      void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
                      if (__builtin_expect (hook != NULL, 0))
                        (*hook)();
                    }
                }
              /* 연속적이지 않은 경우*/
              else
                {
                  if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
                    /* MORECORE/mmap must correctly align */
                    assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
                  else
                    {
                      front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
                      if (front_misalign > 0)
                        {
                          /*
                             malloc aligned를 만족하기 위해서 몇바이트는 스킵 
                           */
                          aligned_brk += MALLOC_ALIGNMENT - front_misalign;
                        }
                    }
                  /* Find out current end of memory */
                  if (snd_brk == (char *) (MORECORE_FAILURE))
                    {
                      snd_brk = (char *) (MORECORE (0));
                    }
                }
              /* 그 결과를 바탕으로 topchunk 설정 */
              if (snd_brk != (char *) (MORECORE_FAILURE))
                {
                  av->top = (mchunkptr) aligned_brk;
                  set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
                  av->system_mem += correction;
                  /*
                     외부 brk 영역과 구분을 위해서 울타리 청크를 삽입
                   */
                  if (old_size != 0)
                    {
                      /*
                         old topchunk의 공간을 이용해서 할당한다. 
                       */
                      old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
                      set_head (old_top, old_size | PREV_INUSE);
                      /*
                         Note that the following assignments completely overwrite
                         old_top when old_size was previously MINSIZE.  This is
                         intentional. We need the fencepost, even if old_top otherwise gets
                         lost.
                       */
                      set_head (chunk_at_offset (old_top, old_size),
                                (2 * SIZE_SZ) | PREV_INUSE);
                      set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ),
                                (2 * SIZE_SZ) | PREV_INUSE);
                      /* 이전 topchunk free*/
                      if (old_size >= MINSIZE)
                        {
                          _int_free (av, old_top, 1);
                        }
                    }
                }
            }
        }
    } /* if (av !=  &main_arena) */
  if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
    av->max_system_mem = av->system_mem;
  check_malloc_state (av);
  /* finally, do the allocation */
  p = av->top;
  size = chunksize (p);
  /* check that one of the above allocation paths succeeded */
  if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
    {
      remainder_size = size - nb;
      remainder = chunk_at_offset (p, nb);
      av->top = remainder;
      set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
      set_head (remainder, remainder_size | PREV_INUSE);
      check_malloced_chunk (av, p, nb);
      return chunk2mem (p);
    }
  /* catch all failure paths */
  __set_errno (ENOMEM);
  return 0;
}

Reference

https://youngsouk-hack.tistory.com/56
순서도까지 정리되어있음
https://code.woboq.org/userspace/glibc/malloc/morecore.c.html#2increment
libc 코드 참조

profile
해킹, 리버싱, 게임 좋아합니다

0개의 댓글