Featured image of post tcache bin学习

tcache bin学习

https://hefangyu3313.github.io/2025/11/18/tcache-attack/

tcache介绍

  • 1.每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410,也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中,当申请大于7个就会放入fastbins,但是tcache优先级高于fastbins,tcache先被申请出来
  • 2.对于每个tcache bin单链表,它和fast bin一样都是先进后出,而且prev_inuse标记位都不会被清除,所以tcache bin中的chunk不会被合并,即使和Top chunk相邻。
  • tcache机制出现后,每次产生堆都会先产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址(这些地址指向用户数据部分)以及每个bins中chunk数量。在这个0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。还有一点值得注意的是,tcache bin中的fd指针是指向malloc返回的地址,也就是用户数据部分,而不是像fast bin单链表那样fd指针指向chunk头。

tcache关键代码(2.35)

相关结构和基础操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* 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.  */
  uintptr_t key;
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  uint16_t counts[TCACHE_MAX_BINS];//每条链的chuunk数
  tcache_entry *entries[TCACHE_MAX_BINS];//每条链的入口
} tcache_perthread_struct;

tcache_perthread_struct是tcache的管理结构,counts里存放着每个tcache链上的chunk个数,entries是tcache链表的入口,与fastbinY差不多

tcache_perthread_struct分配在堆的头部,比较容易控制

 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
/* 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); 

  /* Mark this chunk as "in the tcache" so the test in _int_free will
     detect a double free.  */
  e->key = tcache_key; //防止double free的key

  e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); //将当前bin头部chunk的指针加密后赋给next
  tcache->entries[tc_idx] = e; //将这个chunk存进相应索引的bin链表头部(更新bin头部)
  ++(tcache->counts[tc_idx]); 
}
/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx) //从tcachebin取出chunk
{
  tcache_entry *e = tcache->entries[tc_idx]; //根据计算好的索引取出链表头部的chunk
  if (__glibc_unlikely (!aligned_OK (e))) //检查chunk对齐
    malloc_printerr ("malloc(): unaligned tcache chunk detected");
  tcache->entries[tc_idx] = REVEAL_PTR (e->next); //解密并修改头部
  --(tcache->counts[tc_idx]);
  e->key = 0; //key位置置空
  return (void *) e;
}

主要存在两个安全机制,一个是key来检测double free一个是safe linking(就是在tcache_put和tcache_get中对next指针内容的加密和解密操作)

double free check

2.27后增加

tcache_put在放入chunk后,加入了e->key=tcache

在free时会检测key是否等于tcache

tcache_get在拿出chunk后将key置为空

safe linking

glibc2.32新增的防御机制

其核心思想是将指针的地址右移12位再和指针本身异或,所以在修改next指针时,需要知道对应的堆地址

共生产 19 篇文章,约 39.77k 字使用 Hugo 构建
主题 Stack 由 Jimmy 设计