double free를 막기 위해서 각 bin에는 여러 보호기법들이 마련되어 있다.
이를 우회하기 위해서 하나는 fastbin에, 하나는 unsortedbin에 저장되게 하는 방법이다.
_int_malloc
의 코드 중 일부를 보면,
// large bin 크기의 요청이 들어온 경우
else
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av);
}
malloc_consolidate()
함수가 호출됨을 알 수 있다.
malloc_consolidate()
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);
do {
p = atomic_exchange_acq (fb, NULL);
if (p != 0) {
do {
{
if (__glibc_unlikely (misaligned_chunk (p)))
malloc_printerr ("malloc_consolidate(): "
"unaligned fastbin chunk detected");
unsigned int idx = fastbin_index (chunksize (p));
if ((&fastbin (av, idx)) != fb)
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}
check_inuse_chunk(av, p);
nextp = REVEAL_PTR (p->fd);
/* Slightly streamlined version of consolidation code in free() */
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != 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);
if (!nextinuse) {
size += nextsize;
unlink_chunk (av, nextchunk);
} else
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range (size)) {
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 {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ( (p = nextp) != 0);
}
} while (fb++ != maxfb);
}
fastbin에 존재하는 모든 chunk들을 unsorted bin으로 옮기는 과정이다.
이 때, 인접한 chunk의 INUSE
bit이 unset 되어 있다면, 그 chunk와 합치는 과정을 거친다.
물론, next chunk가 top chunk인 경우 top chunk의 크기가 늘어나게 되겠지..
fastbin의 경우 빠른 처리를 위해 free가 된 경우에도
INUSE
bit가 set되어있다는 점이다. 그렇기 때문에 fastbin에 있는 동안에는 인접한 chunk들과 합쳐지지 않는다.
void *chunk1 = malloc(0x10);
void *chunk2 = malloc(0x10);
free(chunk1); // chunk1은 fastbin으로 들어간다.
void *chunk3 = malloc(0x400); // 이 때 malloc_consolidate() 호출; chunk1은 unsorted bin에 들어가게 된다.
free(chunk1); // fastbin에 chunk1이 존재하지 않으므로 fastbin의 double free 검증에 걸리지 않는다.