在不同的glibc下

分为两篇,一篇记录安全检测,一篇记录操作变化

需要注意,一个大版本下的libc还有许多小版本,版本不同libc也会存在差异,有些明明可能是后面版本的检查,可能被反向移植到旧版

这里以libc下载网站下载的为准

有些检查可能是在介绍的两个版本之间更新的,这里以只考虑比较经典的几个版本

glibc2.23

现在看来,2.23已经是一个比较老的版本了,比它更早的版本暂时不做关注,2.23是一个十分经典的版本,以它为基础先概览各类检查

malloc

__libc_malloc

1
2
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));

返回victim前的最后一个检查,需要满足以下三个条件中的至少一个:

  1. victim==NULL
  2. chunk的mmap分配标志位为1
  3. chunk的non_main_arena标志为0

__int_malloc

mglobal-1

1
2
3
4
5
6
#define checked_request2size(req, sz)                             \
if (REQUEST_OUT_OF_RANGE (req)) { \
__set_errno (ENOMEM); \
return 0; \
} \
(sz) = request2size (req);
1
2
3
#define REQUEST_OUT_OF_RANGE(req)                                 \
((unsigned long) (req) >= \
(unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE))

_int_malloc初始会在checked_request2size (bytes, nb)中判断申请大小是否合规

如果在无符号比较中申请大小大于等于-2* MINSIZE,则会报错

另外提一下,虽然在代码中是先判断申请大小是否越界,再将其转为实际申请chunk大小,但在大多数实际情况下,二者的顺序是倒过来的,即先转化再判断

mfastbin-1

1
2
3
4
5
6
7
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}

取出fastbin中的chunk时,通过该chunk计算出的fastbin索引是否与该chunk所在的链的索引相同,即大小是否对应

msmallbin-1

1
2
3
4
5
6
             bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}

取出smallbin中的chunk时,判断链表的完整性

munsortedbin-1

1
2
3
4
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);

unsorted循环遍历时,检查从unsortedbin中取出的chunk的size是否合规

munsorted-2

1
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);

将unsortedbin中取出的chunk放入不为空的largebin中时,largebin链中的最后一个chunk的NON_MAIN_ARENA标志位应该为0

1
assert ((fwd->size & NON_MAIN_ARENA) == 0);

将unsortedbin中取出的chunk放入不为空的largebin中时,chunk的size大于等于最小的size

会对每一个位于nextsize链上大于它的chunk调用检查

mlargebin-1

1
2
3
4
5
6
7
	  bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}

largebin中取出chunk需要切割时,会检查unsorted头部的完整性

mbinmap-1

1
2
3
4
5
6
7
               bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}

largebin-1相同,位于binmap分配中

free

__int_free

fglobal-1

1
2
3
4
5
6
7
8
9
10
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __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;
}
  1. 通过无符号数比较p是否大于-size,判断p是否正常
  2. 判断p是否对齐

fglobal-2

1
2
3
4
5
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
{
errstr = "free(): invalid size";
goto errout;
}

size是否小于MINSIZE或者size不对齐

ffastbin-1

1
2
3
   if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))

判断p的下一个chunk的size是否正常

ffastbin-2

1
2
3
4
5
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}

如果p等于该fastbin链上的第一个chunk,那么判定为double free

ffastbin-3

1
2
3
4
5
   if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}

比较少见的一个检查

fglobal-2

1
2
3
4
5
   if (__glibc_unlikely (p == av->top))
{
errstr = "double free or corruption (top)";
goto errout;
}

如果p为top_chunk,判定为double free

fglobal-3

1
2
3
4
5
6
7
   if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}

判断p的nextchunk是否超越了堆的边界

fglobal-4

1
2
3
4
5
   if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}

判断p的nextchunk的prev_inuse位是否为1,不为1则报错

fglobal-5

1
2
3
4
5
6
7
   nextsize = chunksize(nextchunk);
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
{
errstr = "free(): invalid next size (normal)";
goto errout;
}

判断p的nextchunk的size是否正常

fglobal-6

1
2
3
4
5
     if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}

放入unsortedbin时,判断unsorted的头部完整性

附属

1
2
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
malloc_printerr (check_action, "corrupted double-linked list", P, AV);

验证完整性,操作chunk的上一个的下一个和下一个的上一个是否都等于该chunk

1
2
3
4
5
   if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV);

和unlink-1类似,不过检查的是largebin的nextsize链完整性

glibc2.27

附属

1
2
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
malloc_printerr ("corrupted size vs. prev_size");

检查通过该chunk的size找到的nextchunk的prev_size是否等于该chunk的size,

unlink不会检查通过prev_size找到的chunk的size是否等于那个prev_size

malloc_consolidate

+maco-1

1
2
3
4
5
{
unsigned int idx = fastbin_index (chunksize (p));
if ((&fastbin (av, idx)) != fb)
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}

判断由p的size计算得出的fastbin索引是否与当前fastbin链匹配

tcache

+tcput-1

assert (tc_idx < TCACHE_MAX_BINS);

判断tc_idx是否越界

+tcget-1

1
2
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);

判断tc_idx是否越界以及该tcache链是否为空

glibc2.29

malloc

@munsortedbin-1

1
2
3
4
5
6
7
8
9
10
11
12
13
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

除了之前就存在的对victim的size检查外,新增:

  1. 对nextchunk(victim的物理相邻chunk)的size的检查
  2. 判断该chunk的size是否等于nextchunk的prev_size
  3. 判断链表尾部的完整性
  4. 对victim的nextchunk的prev_inuse位检查,是否合理

+munsortedbin-2

1
2
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");

移除unsorted中chunk时检查链表尾是否完整

+usetop-1

1
2
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

检查topchunk的size是否合规

free

+ftcache-1

1
2
3
4
5
6
7
8
9
10
11
12
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. */
}

free时,如果一个chunk的key字段等于tcache,那么就会遍历该tcache链中的所有chunk来判断是否存在double free

可以通过破坏key绕过

+fglobal-7

1
2
3
4
5
6
7
8
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 while consolidating");
unlink_chunk (av, p);
}

之前向低地址合并都是不检查即将unlink的chunk的size是否等于找到这个chunk的prev_size

现在新增了这个检查,使得unlink的利用受到多一点的限制

向高地址合并则没有变化

宏unlink现在由函数unlink_chunk实现

但内部具体代码并无明显变化

附属

+maco-2

1
2
3
4
5
6
7
8
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);
}

与fglobal-7相同,只不过由malloc_consolidate触发

glibc2.31

malloc

+munsortedbin-3

1
2
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");

当unsortedbin中的chunk放入largebin时,如果要放入的size不小于largebin链中最小的chunk,且size异于largebin链上的所有chunk,则会在插入nextsize链时检测nextsize链完整性

+munsortedbin-4

1
2
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

当unsortedbin中的chunk放入largebin时,如果要放入的size不小于largebin链中最小的chunk,就会触发fd/bk链的完整性检查

这两个检查实际上应该是2.30增加的

附属

-tcput-1

tcache_put中去除

1
assert (tc_idx < TCACHE_MAX_BINS);

-tcget-1

tcache_get中去除

1
2
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);

当然这并不意味着tcache->entries[tc_idx]== 0是可行的,因为这样的话__libc_malloc根本不会进入tcache_get

glibc2.32

malloc

+mfastbin-2

1
2
if (__glibc_unlikely (misaligned_chunk (victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");

fastbin取出chunk时,新增chunk对齐检查

+mfastbin-3

1
2
3
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp)))       \
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
}

在REMOVE_FB宏中,新增对齐检测

+mfastbin-4

1
2
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");

fastbin填充tcachebin新增对齐检测,会检查每一个chunk是否对齐

free

@ftcache-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 = REVEAL_PTR (tmp->next))
{
if (__glibc_unlikely (!aligned_OK (tmp)))
malloc_printerr ("free(): unaligned chunk detected in tcache 2");
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. */
}
}

判断doublefree时,会检查tcache链上的所有chunk是否对齐

附属

+maco-3

1
2
3
if (__glibc_unlikely (misaligned_chunk (p)))
malloc_printerr ("malloc_consolidate(): "
"unaligned fastbin chunk detected");

每一个fastbin取出时会检查对齐

+tcget-2

1
2
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");

tcache_get取出chunk时会有对齐检查

glibc2.33

free

@ftcache-1

1
2
if (cnt >= mp_.tcache_count)
malloc_printerr ("free(): too many chunks detected in tcache")

判断doublefree时,会检查tcache链上的chunk数目是否超过限制

之前虽然也会与mp_.tcache_count比较,但只是作为一些分支的条件,并不会检查错误

除此之外tcache_counts几乎没有其他检查了

glibc2.34

无显著变化

glibc2.35

无显著变化

glibc2.37

无显著变化

glibc2.38

malloc

-munsortedbin-2

1
2
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");

移除该检查,因为其实前面已经做过一次检查了,这个检查有些重复