libc-2.23——free源码分析
上一篇我们介绍了malloc的分配
我们可以了解到再malloc时候,操作系统会跑到一个叫mstate的结构体里面取寻找满足条件的bin,如果没有满足条件的,就会调用top chunk来获取chunk,还不行,就会调用bk指针来将top chunk变大
这里我们来说一下free函数
源码分析
__libc_free
首先我们调用free函数的时候,会发现其进入了__libc_free里面进行调用
void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; /* chunk corresponding to mem */ void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); if (__builtin_expect (hook != NULL, 0)) { (*hook)(mem, RETURN_ADDRESS (0)); return; } if (mem == 0) /* free(0) has no effect */ return; p = mem2chunk (mem); if (chunk_is_mmapped (p)) /* release mmapped memory. */ { /* see if the dynamic brk/mmap threshold needs adjusting */ if (!mp_.no_dyn_threshold && p->size > mp_.mmap_threshold && p->size <= DEFAULT_MMAP_THRESHOLD_MAX) { 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; } ar_ptr = arena_for_chunk (p); _int_free (ar_ptr, p, 0); } libc_hidden_def (__libc_free)
- 该函数与malloc一样的流程,也是先检查是否有hook函数,有就调用然后退出即可,否则就接着往下调用
- 其次该函数还会检查当前的chunk是否由mmap分配出来的,然后做一些我不知道的操作,最后调用munmap_chunk进行chunk的回收
- 否则就会跑取调用_int_free函数
_int_free
static void _int_free (mstate av, mchunkptr p, int have_lock) { INTERNAL_SIZE_T size; /* its size */ mfastbinptr *fb; /* associated fastbin */ mchunkptr nextchunk; /* next contiguous chunk */ INTERNAL_SIZE_T nextsize; /* its size */ int nextinuse; /* true if nextchunk is used */ INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */ mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */ const char *errstr = NULL; int locked = 0; size = chunksize (p); /* Little security check which won't hurt performance: the allocator never wrapps around at the end of the address space. Therefore we can exclude some size values which might appear here by accident or by "design" from some intruder. */ if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)//安全检查(防止chunk的内存 溢出 因为如果ptr>-size,那么ptr+size就会发生溢出) || __builtin_expect (misaligned_chunk (p), 0)) //检查是否对齐 { errstr = "free(): invalid pointer"; errout: if (!have_lock && locked) (void) mutex_unlock (&av->mutex); malloc_printerr (check_action, errstr, chunk2mem (p), av); return; } /* We know that each chunk is at least MINSIZE bytes in size or a multiple of MALLOC_ALIGNMENT. */ if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))//如果chunk的大小不满足最小chunk,并且未对齐 { errstr = "free(): invalid size"; goto errout; } check_inuse_chunk(av, p); //检查chunk的前一个chunk是否使用 /* If eligible, place chunk on a fastbin so it can be found and used quickly in malloc. */ if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())//如果chunk属于fast bin #if TRIM_FASTBINS /* If TRIM_FASTBINS set, don't place chunks bordering top into fastbins */ && (chunk_at_offset(p, size) != av->top) #endif ) { if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)//查看后面的chunk的size是否大于2*SIZE_SZ || __builtin_expect (chunksize (chunk_at_offset (p, size)) //并且下一个chunk的size不大于sys_mem(一般不大于132K,这个好像是所有的chunk都不准大于那么多) >= av->system_mem, 0)) { /* We might not have a lock at this point and concurrent modifications of system_mem might have let to a false positive. Redo the test after getting the lock. */ if (have_lock || ({ assert (locked == 0); mutex_lock(&av->mutex); locked = 1; chunk_at_offset (p, size)->size <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem; })) { errstr = "free(): invalid next size (fast)"; goto errout; } if (! have_lock) { (void)mutex_unlock(&av->mutex); locked = 0; } } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);//将chunk的mem部分全部设置为perturb_byte set_fastchunks(av); //设置fastchunks标记位 unsigned int idx = fastbin_index(size); fb = &fastbin (av, idx); //取出fastbin的头部 /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */ mchunkptr old = *fb, old2;//使用原子操作,将P插入链表中 unsigned int old_idx = ~0u; do { /* Check that the top of the bin is not the record we are going to add (i.e., double free). */ if (__builtin_expect (old == p, 0)) //查看头部是否与p为同一个chunk,是的话就报错double free,不是的话继续运行 { errstr = "double free or corruption (fasttop)"; goto errout; } /* Check that size of fastbin chunk at the top is the same as size of the chunk that we are adding. We can dereference OLD only if we have the lock, otherwise it might have already been deallocated. See use of OLD_IDX below for the actual check. */ if (have_lock && old != NULL) old_idx = fastbin_index(chunksize(old));//获取对应的下标 p->fd = old2 = old; //让p的fd指针变为原来的头部 } while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);//如果fb等于old2,就将fb赋值为p if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))//如果先前的old_idx不属于当前的idx就报错(说明有人修改了chunk的size) { errstr = "invalid fastbin entry (free)"; goto errout; } } /* Consolidate other non-mmapped chunks as they arrive. */ else if (!chunk_is_mmapped(p)) { //如果当前的chunk不属于mmaped if (! have_lock) { (void)mutex_lock(&av->mutex); locked = 1; } nextchunk = chunk_at_offset(p, size); //获取物理相邻的下一个chunk /* Lightweight tests: check whether the block is already the top block. */ if (__glibc_unlikely (p == av->top)) //当前的chunk不属于top chunk { errstr = "double free or corruption (top)"; goto errout; } /* Or whether the next chunk is beyond the boundaries of the arena. */ if (__builtin_expect (contiguous (av) //如果物理相邻的下一个chunk的size要大于top chunk的边界,则报错 && (char *) nextchunk >= ((char *) av->top + chunksize(av->top)), 0)) { errstr = "double free or corruption (out)"; goto errout; } /* Or whether the block is actually not marked used. */ if (__glibc_unlikely (!prev_inuse(nextchunk))) //如果下一个chunk的inuse位已经是0了,那么就会报错double free { errstr = "double free or corruption (!prev)"; goto errout; } nextsize = chunksize(nextchunk); if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)//下一个chunk的size是否2*SIZE_SZ并且是否不超过system_mem || __builtin_expect (nextsize >= av->system_mem, 0)) { errstr = "free(): invalid next size (normal)"; goto errout; } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); /* consolidate backward */ if (!prev_inuse(p)) { //查看当前的chunk的前一个chunk是否是被释放的(如果是free状态则进行unlink 与consolidate 先后合并) prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); } if (nextchunk != av->top) { //如果下一个chunk部位top chunk /* get and clear inuse bit */ nextinuse = inuse_bit_at_offset(nextchunk, nextsize); /* consolidate forward */ if (!nextinuse) { //查看下一个chunk的inuse位是否位0(为0进行向前合并) unlink(av, nextchunk, bck, fwd); size += nextsize; } else clear_inuse_bit_at_offset(nextchunk, 0);//否则将下一个chunk的inuse位置0 /* Place the chunk in unsorted chunk list. Chunks are not placed into regular bins until after they have been given one chance to be used in malloc. */ bck = unsorted_chunks(av); //验证unsorted bin的bk与fd指针是否被修改(这里只检查了unsorted bin的下一个(fwd)的bk指针是否等于自身) fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "free(): corrupted unsorted chunks"; goto errout; } p->fd = fwd; //更新p的fd与bk指针 p->bk = bck; if (!in_smallbin_range(size))//如果是large bin { p->fd_nextsize = NULL; p->bk_nextsize = NULL; } bck->fd = p; //插入p fwd->bk = p; set_head(p, size | PREV_INUSE); set_foot(p, size); check_free_chunk(av, p); } /* If the chunk borders the current high end of memory, consolidate into top */ else { size += nextsize; //否则直接合并到top chunk set_head(p, size | PREV_INUSE); av->top = p; check_chunk(av, p); } /* If freeing a large space, consolidate possibly-surrounding chunks. Then, if the total unused topmost memory exceeds trim threshold, ask malloc_trim to reduce top. Unless max_fast is 0, we don't know if there are fastbins bordering top, so we cannot tell for sure whether threshold has been reached unless fastbins are consolidated. But we don't want to consolidate on each free. As a compromise, consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD is reached. */ if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) { //检查当前chunk大小是否大于65536 if (have_fastchunks(av)) //如果由fastbin的chunk存在,触发合并 malloc_consolidate(av); if (av == &main_arena) { #ifndef MORECORE_CANNOT_TRIM if ((unsigned long)(chunksize(av->top)) >= (unsigned long)(mp_.trim_threshold)) systrim(mp_.top_pad, av); #endif } else { /* Always try heap_trim(), even if the top chunk is not large, because the corresponding heap might go away. */ heap_info *heap = heap_for_ptr(top(av)); assert(heap->ar_ptr == av); heap_trim(heap, mp_.top_pad); } } if (! have_lock) { assert (locked); (void)mutex_unlock(&av->mutex); } } /* If the chunk was allocated via mmap, release via munmap(). */ else { munmap_chunk (p);//如果是mmap分配出来的chunk 直接进行回收 } }
小总结
- __libc_free里真正进行free的函数是_int_free函数,该函数首先判断当前释放的chunk是否为fast bin如果是的话,会进行检查,最重要的检查是:查看当前的头部与释放的chunk是否一致,然后检查其old chunk(原来的bin头)头部的size是否满足属于该bin的idx下标条件
- 如果释放的chunk不属于fast bin,就回检查其是否满足mmap,不满足,就检查当前chunk是否为top chunk,不是则继续运行
- 接着会检查其释放的chunk的下一个chunk的指针是否会大于top chunk的边界值,同时也会检查下一个chunk的prev_inuse是否为1,不为1则报错(因为当前我们释放的是这个chunk,所以下一个chunk的pre_inuse必须为1)
- 然后还会检查下一个chunk的size是否满足bin的基本条件,满足继续运行,然后开始检查当前chunk的prev_inuse位是否为0,为0进行向后合并,接着检查下一个chunk是否为top chunk,不为top chunk的话,接着检查其下一个的chunk的下一个chunk的prev_inuse位是否为1,不为1则进行向前合并
- 接着就是将我们释放的chunk进行插入链表
- 最后如果chunk是mmap出来的,则直接释放
参考资料
https://ctf-wiki.org/pwn/linux/glibc-heap/implementation/free/