文章总结: 本文详细解析了glibc2.27中tcache堆管理机制的核心原理与操作。内容涵盖tcache_entry及tcache_perthread_struct结构体定义,阐述了内存分配时tcache优先于fastbin的查找顺序,以及内存释放优先填充tcache的机制。文末附带战队招新信息。 综合评分: 81 文章分类: 二进制安全,CTF,漏洞分析
tcache结构体(上)
原创
zach0ry zach0ry
OnePanda-Sec
2026年1月27日 16:04 陕西
20
25
OnePanda-Sec
tcache结构体(上)
原理与核心操作
招新说明
招新要求
· 热爱网络安全,喜欢CTF
· 拥有CTF比赛经验,有较好比赛成绩的
· 乐于奉献、热爱分享,愿意提升 自己同时帮助他人
· 时间允许参加各类赛事,服从战队管理与安排
· 各类比赛获奖者、能力出众者视情况考量
· 未参与其他高校联队
· 大一同学视情况放宽资历要求
联系方式
发送简历于邮箱
· 简历邮箱:[email protected]
01
tcache结构体
OnePanda-Sec
在 glibc 2.27 里,chunk 被 free 后如果满足大小范围且对应 bin 未满,就会按对齐后的 size class 进入该线程的 tcache:每个 size class 对应一个 tcache bin,bin 里只存同一对齐尺寸的 chunk,并用单链表管理;
具体做法是把 freed chunk 的用户区前 16 字节改写为 tcache_entry,其中前 8 字节存 next 指针、后 8 字节存 key(用于简单的 double free 检测),然后采用头插法把它挂到 tcache->entries[idx] 上,因此整体呈 LIFO(后进先出)顺序供后续同尺寸 malloc 优先取出。
tcache _entry
在 tcache 中新增了两个结构体,分别是 tcache_entry 和 tcache_perthread_struct:
typedef struct tcache_entry{ struct tcache_entry *next;//fd,下一个大小相同的空闲tcache chunk} tcache_entry;
这里的next指针指向的是user data部分,也就是说指向的是chunk head+0x10地址处,而fastbin则指向的是chunk head
tchache struct
typedef struct tcache_perthread_struct{ char counts[TCACHE_MAX_BINS];//每个tcache bin链的chunk个数 tcache_entry *entries[TCACHE_MAX_BINS];} tcache_perthread_struct;static __thread tcache_perthread_struct *tcache = NULL;
收藏了所有的tcache_entry
这个管理结构会在第一次调用malloc时,先malloc一块内存用来存放tcache_perthread_struct。因此做glibc >=2.26的题时,gdb的时候第一个堆块即为tcache_perthread_struct。
tcache_get
static __always_inline void *tcache_get (size_t tc_idx){ tcache_entry *e = tcache->entries[tc_idx];//tc_idx 是要取的 tcache bin 下标(对应某个 size class) assert (tc_idx < TCACHE_MAX_BINS);//malloc的size合理 assert (tcache->entries[tc_idx] > 0);//不是 NULL tcache->entries[tc_idx] = e->next;//取出第一个e,头节点指向往后移 --(tcache->counts[tc_idx]); return (void *) e;}
在 tcache_get 中,仅仅检查了 tc_idx ,此外,我们可以将 tcache 当作一个类似于 fastbin 的单独链表,只是它的 check,并没有 fastbin 那么复杂,仅仅检查 tcache->entries[tc_idx] = e->next;
tcache_put
static void *tcache_get (size_t tc_idx){ tcache_entry *e = tcache->entries[tc_idx]; assert (tc_idx < TCACHE_MAX_BINS); assert (tcache->entries[tc_idx] > 0); tcache->entries[tc_idx] = e->next; --(tcache->counts[tc_idx]); return (void *) e;}
02
tcache bin 使用
OnePanda-Sec
malloc 查找/分配总体流程
1.对请求大小做对齐与最小化
- 用户请求 bytes
- 计算对齐后的 nb(实际 chunk size,含头部、对齐到 0x10,且 ≥ MINSIZE)
1.先查 tcache(线程私有缓存)
-
如果 nb 在 tcache 支持范围内,算 tc_idx
-
若 tcache->counts[tc_idx] > 0
-
直接 tcache_get(tc_idx) 弹出表头并返回
-
tcache miss 才继续往下
1.若是小块,查 fastbin(arena 级)
-
条件:nb <= get_max_fast()
-
算 idx = fastbin_index(nb)
-
若 fastbin[idx] 非空:
-
弹出一个 victim
-
校验 victim 的真实 size 是否属于当前 idx
-
顺便把 fastbin 里剩下的同尺寸 chunk 回填进 tcache(refill)
-
返回 victim
1.查 smallbin(精确大小 bins)
- 如果 nb 属于 smallbin 范围
- 直接看对应 smallbin 是否有空闲 chunk
- 有则取出(FIFO/双向链表)
1.处理 unsorted bin(未分类大杂烩)
-
先遍历 unsorted:
-
若有大小正好/足够的 chunk
-
可能直接切割返回
-
不合适的会被分流进 smallbin/largebin
1.查 largebin(大块,按范围分组)
- 找到最合适的(best-fit)
- 可能切割返回
1.都没有就从 top chunk 切
- top 足够大 → 切一块返回,剩下继续当 top
1.top 也不够就向系统要内存
- sbrk 扩展 heap 或 mmap 直接映射大块
- 生成新 top 或直接返回 mmap 块
一句话版顺序
tcache → fastbin → smallbin → unsorted → largebin → top → sysmalloc(sbrk/mmap)
内存申请
(1)首先,申请的内存块符合 fastbin 大小时并且在 fastbin 内找到可用的空闲块时,会把该 fastbin 链上的其他内存块放入 tcache 中。
(2)其次,申请的内存块符合 smallbin 大小时并且在 smallbin 内找到可用的空闲块时,会把该 smallbin 链上的其他内存块放入 tcache 中。
(3)当在 unsorted bin 链上循环处理时,当找到大小合适的链时,并不直接返回,而是先放到 tcache 中,继续处理。
贴个符合 fastbin 的时候
//tcachebin不满足时 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))//判断是否属于 fastbin 范围 {//nb 是当前 malloc 需要的 chunk size(对齐后的大小)。 idx = fastbin_index (nb); mfastbinptr *fb = &fastbin (av, idx); mchunkptr pp; victim = *fb;//fastbin不为空 if (victim != NULL) { if (SINGLE_THREAD_P) *fb = victim->fd; else REMOVE_FB (fb, pp, victim); //校验size的合理性 if (__glibc_likely (victim != NULL)) { size_t victim_idx = fastbin_index (chunksize (victim)); if (__builtin_expect (victim_idx != idx, 0))//实际size和malloc计算的下标校验 malloc_printerr ("malloc(): memory corruption (fast)"); check_remalloced_chunk (av, victim, nb);#if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ //填补tcache_bin size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins)//有tcache,范围合理 { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks. */ while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = *fb) != NULL) { if (SINGLE_THREAD_P) *fb = tc_victim->fd;//从fastbin弹出 else { REMOVE_FB (fb, pp, tc_victim); if (__glibc_unlikely (tc_victim == NULL)) break; } tcache_put (tc_victim, tc_idx);//放入tcache } }#endif void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } }
unsoreted bin扫描
在 glibc 2.27 的 _int_malloc 遍历 unsorted bin 时,只有一类 chunk 会被“顺手塞进 tcache”:和当前申请大小同尺寸的小块
开启mp_.tcache_unsorted_limit > 0(提前返回)
#if USE_TCACHE /* If we've processed as many chunks as we're allowed while filling the cache, return one of the cached ones. */ ++tcache_unsorted_count;//为了给 tcache 回填,在 unsorted bin 里已经处理了多少个 chunk if (return_cached && mp_.tcache_unsorted_limit > 0&& tcache_unsorted_count > mp_.tcache_unsorted_limit) //有返回的chunk以及扫描到达最大的限度了会停 { return tcache_get (tc_idx); }#endif没有开启#if USE_TCACHE /* If all the small chunks we found ended up cached, return one now. */ if (return_cached) { return tcache_get (tc_idx); }#endif
内存释放
_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 */ 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);#if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache && tc_idx < mp_.tcache_bins && tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return; } }#endif......}
0nePandaSec团队交流群
欢迎网络安全爱好者加入
OnePanda-Sec
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:OnePanda-Sec zach0ry zach0ry《tcache结构体(上)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论