ptmalloc2 源码解析
warning
WARNING
本文所有解读仅代表 Y7n05h 的个人见解,不代表 Glibc
的贡献者观点.
受限于 Y7n05h 的能力于水平,Y7n05h 无法保证本文的正确性.
info
INFO
本文文中所有代码均使用 LGLP 2.1 or later 授权.
本文所述内容均基于 Glibc 2.33.Y7n05h 对 malloc.c
按照自己的代码风格偏好进行了重新格式化并添加了注释,修改后的 malloc.c
见本文附录.
malloc
tip
TIP
在初次阅读 ptmalloc2
源码的过程中,笔者建议暂且忽略「多线程环境下的特殊行为」、「调试信息」、「TCACHE」等相关内容.笔者将在了解 malloc
的大致流程后,再次阅读与「多线程环境」、「TCACHE」相关的代码.
有关「调试信息」的内容笔者不做解读.
有关「多线程环境」、「TCACHE」相关的内容将在本文结尾处补充.
略去关于「编译」和「链接」的细节,在此可以认为 malloc()
就是调用了 void *__libc_malloc(size_t bytes)
.
在 __libc_malloc()
源码中:
1 | void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);//使用原子操作读取 __malloc_hook 将其赋值给 hook |
__malloc_hook
是全局变量,定义是:
1 | void *weak_variable (*__malloc_hook)(size_t __size, const void *) = malloc_hook_ini; |
1 | static void * |
在程序运行时,__malloc_hook
被初始化为 malloc_hook_ini
.在程序中调用 __malloc_hook
时,通过原子操作读取至局部变量 hook
.
info
INFO
weak_variable
的定义是:
1 |
__attribute__ ((weak))
是 GCC
提供的语法扩展,可以将一个「符号」定义为「弱符号」.
这是关于「链接」的内容,有兴趣的读者请自行查阅相关资料.
如读者尚未理解 weak_variable
的含义,只需无视 weak_variable
即可,不影响后文的阅读.
info
INFO
1 |
__builtin_expect ((cond), 0)
与 __builtin_expect ((cond), 1)
都是针对「分支预测」的做出的优化.
__builtin_expect ((cond), 0)
表示大多数情况下cond == False
.通常使用__glibc_unlikely(cond)
简化.__builtin_expect ((cond), 1)
表示大多数情况下cond == True
.通常使用__glibc_likely(cond)
简化.
注意:__builtin_expect ((cond), 0) == cond
且__builtin_expect ((cond), 1) == cond
.__builtin_expect
不改变cond
的值!
1 | if (SINGLE_THREAD_P)// 是单线程 |
这段代码看似复杂,实际上很简单,核心的代码只有:
1 | if (SINGLE_THREAD_P)// 是单线程 |
进入 _int_malloc()
1 | /* |
fastbin
检查
1 |
|
此时笔者只关注:
1 | /* 删去了暂不关注的内容 */ |
若 nb
在 fastbin
范围内,则计算 nb
对应的 fastbin
下标 idx
.根据 idx
从 av
中取出对应的链表(其头指针的指针为 fb
,其首节点为 victim
).
- 若
victim == NULL
,则链表为空,完成对fastbin
的检查. - 若
victim != NULL
,则从fb
中移除victim
,返回对应的指针._int_malloc()
返回.
smallbin
检查
1 | /* |
若 nb
在 SmallBin
范围内(也就是不足 LargeBin
的最小值),则尝试使用 SmallBin
:
- 根据
nb
计算对应的 下标idx
,根据idx
定位到链表的头节点bin
- 若
(victim = last(bin)) == bin
,则链表为空,则SmallBin
无法满足nb
的需求,结束SmallBin
检查. - 若
(victim = last(bin)) != bin
,则链表不空,那么从链表中删去尾节点.将尾节点对应的指针返回.
- 若
tip
TIP
对比 FastBin
与 SmallBin
FastBin
使用单链表管理空闲的chunk
.SmallBin
使用双向循环链表管理空闲的chunk
.
FastBin
使用指向「第一个节点的指针」的指针管理单链表.
1 | mfastbinptr *fb = &fastbin(av, idx); |
- 当
*fb == NULL
时,链表为空. - 当
*fb != NULL
时,链表不空.*fb
指向第一个空闲的chunk
.
SmallBin
使用指向「头节点」的指针管理双向循环链表.
1 | bin = bin_at(av, idx);// bin 是头节点的指针 |
bin
指向链表中第头节点,头节点不是有效的空闲 chunk
,头节点只是为了更方便管理双向循环链表人为添加的节点.- 当
bin->bk == bin
时,链表为空(指除了头节点之外没有其他节点). - 当
bin->bk != bin
时,链表不空.
FastBin
与 SmallBin
中的每个链表中的 chunk
的大小相同.
UnsortBin
检查
执行至此说明在 FastBin
和 SamllBin
中未能完成内存分配.
1 | int iters = 0;// 下面的 while 循环的循环变量 |
安全检查用来及时发现相关数据结构被意外损坏或被恶意篡改.
1 | /* |
当 UnsortBin
中仅剩余上次分割的 chunk
(且这个 chunk
大于 nb + MINSIZE
,即满足 nb
且剩余部分能形成一个新的 chunk
)时,使用这个 chunk
,并将剩余部分根据大小插入 SmallBin
或 LargeBin
.
1 | /* remove from unsorted list */ |
将当前节点 victim
从 UnsortBin
中删除.
1 | /* Take now instead of binning if exact fit */ |
若当前节点 victim
大小与 nb
相同,则返回当前节点对应的 chunk
对应的指针.结束分配流程.
1 | /* size != bin */ |
fwd
与 bck
的设置可能为本段代码的阅读带来了一些困扰.在本段代码中,因各个分支均需要实现对 victim
插入链表中,为复用更多的代码,在每种情况中可以只合理的设置 fwd
和 bck
,在本段末尾处通过设置好的 fwd
和 bck
集中完成插入.(在将 victim
插入 LargeBin
的情况中,每个分支均需额外设置 fd_nextsize
和 bk_nextsize
)
LargeBin
检查
1 | /* |
若 nb
不在 SmallBin
范围内,则从 LargeBin
中寻找最合适的chunk
(大于等于 nb
的最小 chunk
).
若最合适的 chunk
的大小大于 nb + MINSIZE
则进行分割,剩余部分放在 UnsortBin
中.之后返回切分出的 chunk
对应的指针.结束分配流程.
其他情况
1 | /* |
看到这里,使笔者困惑的是 idx
的值是什么?回顾一下之前的代码.
1 | if (in_smallbin_range(nb)) {// 准确的表述为:不满足 LargeBin 分配的最小值 |
可以看到:
- 若
nb
在SmallBin
范围内,则idx
为nb
在SmallBin
中对应的下标. - 若
nb
在LargeBin
范围内,则idx
为nb
在LargeBin
中对应的下标.
因为当前 idx
中无法完成内存分配,那么去下一个 idx
中查找.
1 | ++idx; |
那么 idx2block(idx)
有什么含义呢?
1 | /* Conservatively use 32 bits per map word, even if on 64bit system */ |
idx2block()
是为了将 idx
转换成对应的 bitmap
下标.
为什么 idx2block(i)
将 i
右移 5 位呢?注意看这段代码的英文注释,在看看 bitmap
的定义:
1 | /* Bitmap of bins */ |
看到这里,笔者恍然大悟:
bitmap
是unsigned int
类型的变量的数组.unsigned int
大小为 32 bits.- 32 是 $2^5$
i
是无符号数,那么i >> 5
等于 $\frac{i}{2^5}$ (向下取整)
1 | for (;;) { |
还是相同的套路:
选定 chunk
后,检测 chunk
大小
- 若
chunk
大于等于nb + MINSIZE
则分割,剩余部分形成新的块,放入UnsortBin
.返回分割的chunk
对应的指针,结束分配流程. - 若
chunk
小于nb + MINSIZE
则全部作为分配的chunk
,返回对应的指针结束分配流程.
使用 Top chunk
1 | use_top: |
free
终于来到了 free()
的部分.
1 | void __libc_free(void *mem) { |
看到这段代码:
1 | void (*hook)(void *, const void *) = atomic_forced_read(__free_hook); |
看到这段代码,真是熟悉的流程.在
__libc_malloc()
源码中:1 | void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);//使用原子操作读取 __malloc_hook 将其赋值给 hook |
是不是非常的相似啊?笔者也这样想.看看 __free_hook
的定义:
1 | void weak_variable (*__free_hook)(void *__ptr, const void *) = NULL; |
不太一样的地方出现了:
1 | void *weak_variable (*__malloc_hook)(size_t __size, const void *) = malloc_hook_ini; |
__malloc_hook
被初始化为 malloc_hook_ini
,而 __free_hook
被初始化为 NULL
.
1 | if (mem == 0) /* free(0) has no effect */ |
NULL == 0
在大部分环境中都是为真的.也就是说 free(NULL)
是被允许的,并且不会报错.
1 | int err = errno; |
这段代码首先判断内存的来源.
- 若内存通过
mmap
分配,则在一些检查后执行munmap
. - 若内存通过
brk
分配,则执行_int_free()
.
1 | size = chunksize(p); |
常规的安全检查.
1 | if ((unsigned long) (size) <= (unsigned long) (get_max_fast()) |
若 chunk
在 FastBin 范围内,则在检查后将其插入对应的 FastBin 链表的头指针处.
TCACHE 与多线程环境
__libc_malloc
1 |
|
- 根据
bytes
计算添加元数据后符合对齐要求的chunk
大小tbytes
. - 根据
tbytes
计算相应的tcache
下标tc_idx
.
1 | /* Caller must ensure that we know tc_idx is valid and there's available chunks to remove. */ |
根据 tc_idx
定位链表头指针 tcache->entries[tc_idx]
,从链表中删除第一个节点,并将其返回.
看完 tcache
相关的内容,再看看多线程相关的内容:
1 | // 是多线程 |
多线程部分的主要逻辑是:
获取线程的一个 arena
后,并尝试在其中完成内存分配.若失败,换一块 arean
尝试进行内存分配.
进入 _int_malloc()
:
1 |
|
注意本段代码的英文注释:这里给出了一个将 nb
对应的 FastBin
链表中剩余的 chunk
移动到 tcache
的途径.当然, tcache
同样需要受到 tcache_count
的限制,默认情况下 tcache_count
的值为 7.
1 | /* Caller must ensure that we know tc_idx is valid and there's room for more chunks. */ |
可以看到 tcache_put()
就是简单的链表头插入.
1 |
|
与 FastBin
中将 chunk
移动至 tcache
的路径相似,SmallBin
也存在类似的路径.
这段代码与前段代码极其相似,产生差异的原因主要在于 FastBin
使用单向链表管理 chunk
,而 SmallBin
使用双向循环链表管理 chunk
.
1 |
|
在简单的检查后,对变量进行了赋值.
1 | /* Take now instead of binning if exact fit */ |
请注意代码中的两处英文注释,在对 UnsortBin
的检测中,若遇到合适的 chunk
将优先填充 tcache
,而不是立即返回给用户.
这里存在一个将 UnsortBin
转移至 tcache
的路径.
1 |
|
1 |
|
这两段代码是如此的相似,都先检查了 return_cached
(第一段还检查了其他的条件),满足则从 tcache
中取出 nb
对应的 chunk
,并将其返回.
free
在 _int_free()
部分:
1 |
|
再看源码
1 | graph TD |
整体流程
-->_int_malloc
- Init 计算需要分配的 chunk 大小
- sysmalloc:若无合适的 arena 则调用 sysmalloc 通过 mmap 分配
- Fast Bin:相同 chunk 大小、LIFO 顺序,取出头节点
- tcache:若在 FastBin 中分配成功,将同 Fast Bin 中的 chunk 头插入 tcache
- Small Bin:取出尾节点
- tcache:若在 SmallBin 中分配成功,将同 Small Bin 中的 chunk 头插入 tcache
- Unsorted Bin:
- 特殊规则:对于 Small Bin 范围内的请求,当 Unsorted Bin 仅剩唯一 chunk 且该 chunk 来源于 last remainder,进行分配(能切则切)
- 查找大小相同的 chunk,头插入 tcache,直到 tcache 满
- 大小不同的 chunk 按照大小插入 Large Bin(按照大小,相同大小则将当前 chunk 插入在第 2 个) 或者 Small Bin(头插)
- tcache 满了后则从 Unsorted Bin 中拿出相同大小 chunk 返回
- 在 Unsorted Bin 中若缓存过 chunk 则返回
- Large Bin:选择满足大小的 chunk 中最小的,由链表头开始遍历,若有相同大小的 chunk 则用第 2 个,能拆则拆
- 根据 bitmap 在更大的 Bin 中搜索,
- 从 Top chunk 分割
_int_free
- tcache:未满则放入 tcache
- Fast Bin 头插
- 不是 mmap 分配则尝试合并
- mmap 分配则 ummap
-
ptmalloc2 changelog
2.23
2.24
2.25
New
1 | /* Same, except also perform an argument and result check. First, we check |
Old
1 | /* Same, except also perform argument check */ |
2.26
- 加入
tcache
2.27
tcache
Double Free 检测
New1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;
/* 有删节 */
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
/* 有删节 */
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
/* 有删节 */
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)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* 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)))
malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
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. */
}
if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
}
/* 有删节 */
}
Old
1 | /* We overlay this structure on the user-data portion of a chunk when |
malloc_consolidate()
新增 FastBin 相关 check
New
1 | static void malloc_consolidate(mstate av) |
Old
1 | static void malloc_consolidate(mstate av) |
2.28
修复
__libc_malloc
中tcache
错误
New1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->counts[tc_idx] > 0) //改动在此处
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
/* 有删节 */
}Old
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL) //改动在此处
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
/* 有删节 */_int_malloc
加入 checks
New1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);//此处有改动
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)");
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))//此处有改动
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
/* place chunk in bin */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
if (++iters >= MAX_ITERS)
break;
}
2.29
2.30
2.31
2.32
2.34
Old:
1 | void *(*hook) (size_t, const void *) |
New:
1 | if (!__malloc_initialized) |
在 2.34 中 __malloc_hook
不复存在,于此相关联的劫持 __malloc_hook
的攻击手段也随之无效.
事实上,不只是 __malloc_hook
,还有 __free_hook
、__realloc_hook
也不复存在. __free_hook
被直接删去.
Old:
1 | void (*hook) (void *, const void *) |
New:
1 |
tcache 的 double free 检测完成了修复
Old:
1 | if (__glibc_unlikely (e->key == tcache)) |
1 | if (__glibc_unlikely (e->key == tcache)) |
New:
1 | if (__glibc_unlikely (e->key == tcache_key)) |
1 | if (__glibc_unlikely (e->key == tcache_key)) |
未完待续
参考资料
1. 俞甲子.程序员的自我修养[M].北京:电子工业出版社. ↩
ptmalloc2 源码解析