struct tcache_perthread_struct *key
tcache_entry
中增加了一项struct tcache_perthread_struct *key
,将chunk
放入tcache
后,会将key
修改为tcache
。
typedef struct tcache_entry
{
struct tcache_entry *next;
+ /* This field exists to detect double frees. */
+ struct tcache_perthread_struct *key;
} tcache_entry;
在free
的时候,如果发现key
为tcache
就要进行double free
的检查,这是因为key
位于bk
的位置,而tcache
也不清除chunk
,存在很小的可能碰巧为tcache
。
+ if (__glibc_unlikely (e->key == tcache && 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. */
+ }
因为很少发生,这里的检查也比较彻底,会检查该tc_idx
中的每一项是否和当前要释放的堆块相同。这个check
可以通过改写key
来绕过。
pointer mangling
高版本的libc
引入了pointer mangling
,对tcache
和fastbin
记录的fd
指针进行了加密处理。具体的加密采用了异或操作,首先需要理解,0与任何数异或之后还是任意数本身,任何数异或自身为0,(这里的异或操作即可以是一位也可以是多位),因此当一个被加密的数异或两次另一个数,就会重新得到被加密的数。
具体的,两个重要的宏函数
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
首先是PROTECT_PTR(pos, ptr)
,将pos
右移12位,由于pos
位size_t
类型的,右移高位补0,然后与被加密数ptr
异或。
接下来是解密函数REVEAL_PTR(ptr)
,将ptr
自身的地址与ptr
执行PROTECT_PTR(pos, ptr)
操作。
PROTECT_PTR(pos, ptr)
目前有三次引用,在tcache
中只有一次。e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
在tcache_put
时,e->next
即头插法新entry
需要保存的指针,从原本的tcache->entries[tc_idx]
变成了其与自身地址的异或关系。相应的,在tcache_get
中通过tcache->entries[tc_idx] = REVEAL_PTR (e->next);
恢复之前的tcache->entries[tc_idx]
。
不难可以发现,tcache->entries[tc_idx]
中记录的entry
是未加密的,但链表后续的指针都是加密后的。