본 게시글은 free함수의 전체적인 루틴을 설명합니다. 자세한 코드분석은 나중에 따로 할 예정입니다.
__libc_free
free함수가 실행되면 먼저 __libc_free함수가 호출되어 __free_hook을 검사하고 값이 존재할 경우 jump 한다.
그리고 인자가 null일시에는 바로 return 을 하여 아무런 동작이 실행되지 않는다. 이후 아래 루틴을 따른다.
mmap으로 할당한 청크 해제
libc_free에는 다음과 같은 루틴이 있다.
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* See if the dynamic brk/mmap threshold needs adjusting.
Dumped fake mmapped chunks do not affect the threshold. */
if (!mp_.no_dyn_threshold
&& chunksize_nomask (p) > mp_.mmap_threshold
&& chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX
&& !DUMPED_MAIN_ARENA_CHUNK (p))
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}
아래는 대강 잡소리
대강 그대로 번역해보면 dynamic threshold 가 아니고 free하는 청크의 사이즈가 기존의 mmap_threshold보다 크면 mmap threshold(임계값) 을 해당청크 사이즈로 설정한다는 내용이다.
저 mmap threshold는 일단 sysmalloc에서 중요하게(?) 사용되고, house of orange 기법에서도 사용이 된다.
일단 기본값은 DEFAULT_MMAP_THRESHOLD_MIN 매크로 상수로 128kb 이다.
아직 sysmalloc 분석을 자세히 안해서 잘 모르겠다.
어쨌든 mmap 임계값을 바꿔주고 munmap_chunk로 청크를 BIN에 집어넣는 과정없이 free해준다.
MAYBE_INIT_TCACHE ();
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
그 뒤 tcache 가 초기화 되지 않았으면 초기화를 해준뒤, 해당청크의 아레나를 가지고와서 _int_free함수의 인자로 넘겨준다.
_int_free
일단 청크의 정렬이 올바른지 확인한다(주소가 0으로 끝나야 함. 64bit 기준).
Tcache 에 공간이 있을때
그리고 tcache를 사용하면서 해당 사이즈에 해당하는 tcache에 공간이 있을 시에는 해당 tcache에 집어넣어준다. 여기서 해당청크->bk=tcache arena 이면 double free 체크를 하기 때문에, bk 변조가 가능하면 double free가 가능하다.
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}
Tcache가 꽉차서 Fastbin 써야 될 때
일단 해당청크 사이즈가 global_max_fast 사이즈 범위이면 다음 루틴이 실행된다.
(TRIM_FASTBINS 가 설정돼있으면 topchunk와 인접한 fastbin은 존재할 수 없게됨)
arena header에 해당 아레나가 fastbin chunk를 가지고 있다고 기록을 하고, 해당 인덱스의 fastbin에 청크를 집어넣어준다. 이 과정에서 size 검사가 진행된다.
싱글스레드가 아닌 멀티스레드 환경일 경우에는 atomic 연산을 이용해 관련된 영역들의 lock된 상태를 보장하고 청크를 집어넣어준다.
마지막으로 기존의 가장 앞에있던 청크가 방금 free된 청크와 동일한지 확인을 한다.
병합을 하고 unsorted bin으로
mmap 으로 할당받은 청크가 아닌 청크에 대해서 병합과정을 수행한다. 이전 청크가 사용중이지 않으면 이전청크와 변합하고, 다음청크가 topchunk인 경우에는 topchunk와 병합한다. 이 과정에서 unlink_chunk 함수가 사용된다.
다음청크가 topchunk가 아닌 경우에는 unsorted bin에 병합된 청크의 시작부분을 집어넣어준다,
그리고 병합된 이후 청크의 size가 0x10000 이상이라면 malloc_consolidate를 통해서 기존에 있던 fastbin 들을 보두 병합하고 unsortedbin에 집어 넣어준다.
(topchunk와 unsorted bin chunk가 병합되면 fastbin 에 들어간 청크도 unsorted로 들어가는 것이 이쪽 루틴)
(그리고 systrim 함수를 통해서 topchunk의 크기를 줄여주는 과정이 있는데, 잘 모르겠다.)
mmap으로 할당됬나 다시 확인
mmap으로 할당됬나 다시 확인하고 맞다면 munmap으로 할당 해제해준다.
malloc에서 large bin이랑 smallbin을 넣어주니까 free는 별게 없다.
'Security > 개인 연구' 카테고리의 다른 글
C++에서 객체 반환시 어떻게 반환될까 (0) | 2021.03.07 |
---|---|
malloc 함수 분석 (0) | 2021.01.09 |
tcache smallbin 취약점 (0) | 2020.12.28 |
malloc.c 분석 [2] _int_malloc (fastbin, smallbin) (2) | 2020.12.28 |
malloc.c 분석 [1] __libc_malloc (feat. Arena) (0) | 2020.12.28 |