# Deep understanding of heap implementation - ở bài viết này sẽ làm rõ thêm nội dung hiểu sâu về triển khai heap - trọng tâm sẽ nằm ở **tcache Data Structure** ## malloc (https://github.com/iromise/glibc/blob/master/malloc/malloc.c#L448) ``` /* malloc(size_t n) Returns a pointer to a newly allocated chunk of at least n bytes, or null if no space is available. Additionally, on failure, errno is set to ENOMEM on ANSI C systems. If n is zero, malloc returns a minumum-sized chunk. (The minimum size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit systems.) On most systems, size_t is an unsigned type, so calls with negative arguments are interpreted as requests for huge amounts of space, which will often fail. The maximum supported value of n differs across systems, but is in all cases less than the maximum representable value of a size_t. */ ``` - source: ```c void *__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)); arena_get(ar_ptr, bytes); victim = _int_malloc(ar_ptr, bytes); /* Retry with another arena only if we were able to find a usable arena before. */ if (!victim && ar_ptr != NULL) { LIBC_PROBE(memory_malloc_retry, 1, bytes); ar_ptr = arena_get_retry(ar_ptr, bytes); victim = _int_malloc(ar_ptr, bytes); } if (ar_ptr != NULL) __libc_lock_unlock(ar_ptr->mutex); assert(!victim || chunk_is_mmapped(mem2chunk(victim)) || ar_ptr == arena_for_chunk(mem2chunk(victim))); return victim; } ``` ## free (https://github.com/iromise/glibc/blob/master/malloc/malloc.c#L465) ``` /* free(void* p) Releases the chunk of memory pointed to by p, that had been previously allocated using malloc or a related routine such as realloc. It has no effect if p is null. It can have arbitrary (i.e., bad!) effects if p has already been freed. Unless disabled (using mallopt), freeing very large spaces will when possible, automatically trigger operations that give back unused memory to the system, thus reducing program footprint. */ ``` - source: ```c void __libc_free(void *mem) { mstate ar_ptr; mchunkptr p; /* chunk corresponding to mem */ / / Determine whether there is a hook function __free_hook void (*hook)(void *, const void *) = atomic_forced_read(__free_hook); if (__builtin_expect(hook != NULL, 0)) { (*hook)(mem, RETURN_ADDRESS(0)); return; } // free NULL has no effect if (mem == 0) /* free(0) has no effect */ return; / / Convert mem to chunk state p = mem2chunk(mem); // If the block memory is obtained by mmap if (chunk_is_mmapped(p)) /* release mmapped memory. */ { /* See if the dynamic brk/mmap threshold needs adjusting. Dumped fake mmapped chunks do not affect the threshold. */ if (!mp_.no_dyn_threshold && chunksize_nomask(p) > mp_.mmap_threshold && chunksize_nomask(p) <= DEFAULT_MMAP_THRESHOLD_MAX && !DUMPED_MAIN_ARENA_CHUNK(p)) { mp_.mmap_threshold = chunksize(p); mp_.trim_threshold = 2 * mp_.mmap_threshold; LIBC_PROBE(memory_mallopt_free_dyn_thresholds, 2, mp_.mmap_threshold, mp_.trim_threshold); } munmap_chunk(p); return; } // Get a pointer to the allocation area according to the chunk ar_ptr = arena_for_chunk(p); // execute release _int_free(ar_ptr, p, 0); } ``` ## tcache protection mechanism ### ptmalloc2 allocator - ở libc 2.26 đến libc 2.29 trở đi, tcache sẽ kiểm tra chunk có **free()** hay chưa bằng cách thêm một giá trị tại ``bk_pointer`` ===> cơ chế này để chống double free ![image](https://hackmd.io/_uploads/BkVfkfXSp.png) - [examble](https://hackmd.io/@trhoanglan04/H1kTJh87h#231) - for practice, use command below: ```bash $ wget https://ftp.gnu.org/gnu/glibc/glibc-2.29.tar.gz ``` ### ptmalloc3 allocator - ở libc > 2.31, có thêm cơ chế xor bảo vệ tcache ```c #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) #define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr) ``` > với **ptr** là chunk trước đó trong tcache còn **pos** là chunk được free - về cơ bản ta chỉ cần đảo ngược phép xor là được ``(ptr >> 12)^addr`` > **ptr** là chunk mà ta sẽ overwrite ``fw_pointer`` > **addr** là địa chỉ cần malloc tới ## tcache Data Structure - tcache là một kỹ thuật được giới thiệu sau glibc 2.26 (ubuntu 17.10) (xem [commit](https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc)), mục đích là để cải thiện ==Hiệu suất quản lý heap== - nhưng trong khi cải thiện hiệu suất, nó đã loại bỏ rất nhiều kiểm tra bảo mật, vì vậy có nhiều cách mới để sử dụng nó. ``` Free tcahce-cached chunks have a pointer to the allocated space of the next chunk and a pointer to the per-thread struct ``` ![](https://hackmd.io/_uploads/HJrtvFVan.png) ### tcache_entry - [source code](https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#tcache_entry) ```c /* 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; } tcache_entry; ``` - ``tcache_entry`` linking cái free chunk structures - cái ``*next`` sẽ trỏ tới chunk tiếp theo cùng size ![](https://hackmd.io/_uploads/S13gTeN5n.png) ![](https://hackmd.io/_uploads/HyDR3xE9h.png) > free(b) sẽ trỏ b trước (*next của b sẽ trỏ NULL) > tiếp tục free(a) sẽ trỏ a, *next của a sẽ trỏ b > tương tự cho những cái sau ![](https://hackmd.io/_uploads/Hk9eZZV52.png) ![](https://hackmd.io/_uploads/Syr_W-Ec3.png) - ngoài ra, ``tcache_entry`` còn ghép dữ liệu user data portion của free chunk ### tcache_perthread_struct - [source code](https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#tcache_perthread_struct) ```c /* 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 { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct; # define TCACHE_MAX_BINS 64 static __thread tcache_perthread_struct *tcache = NULL; ``` - mỗi thread duy trì 1 luồng ``tcache_prethread_struct`` - là 1 cấu trúc quản lí toàn bộ tcache ![](https://hackmd.io/_uploads/ryl9PeE9h.jpg) >``counts[tc_idx]`` ghi lại số freed chunks trong chuỗi ``tcache_entry`` chain, với tối đa 7 chunks trên mỗi chain >``entries[tc_idx]`` linking các freed chunks cùng size trong dslk đơn (tựa fastbins) - example: ![](https://hackmd.io/_uploads/BkOkQcEa3.png) > khi malloc 0x10 ![](https://hackmd.io/_uploads/rkwuLcVp2.png) > khi free > nhận thấy là: > highlight vàng ---> **counts** > highlight lam ---> **entries** (là 1 pointer) ---> trỏ đến chunk highlight lục - more example: ![](https://hackmd.io/_uploads/S1deYc4a2.png) > malloc 0x30 và free nó > lúc này **counts** nó đếm lượt free cho 0x10 và 0x30 mỗi bin là '1' > tương tự như thế cho các freed chunk cùng size (1++) hoặc khác size ### summary ![](https://hackmd.io/_uploads/HkgqoqET3.png) > struct dựa trên 2 thành phần : **counts** và **entries** - **counts** của từng size có dấu hiệu nhận biết là 2 bytes ![](https://hackmd.io/_uploads/BJXL6qVph.png) > ví dụ ở hightlight vàng: > 0x0000 : size 0x50 > -------0001 : size 0x40 > ------------0000 : size 0x30 > -----------------0001 : size 0x20 > max counts là 64 ``(8*8)`` > ![](https://hackmd.io/_uploads/BkG269ET3.png) > số '0x291' ``(0x280 + 0x10)`` là size of allocated chunk > ![](https://hackmd.io/_uploads/SJUxAqV62.png) ### ref - http://tukan.farm/2017/07/08/tcache/ - https://www.youtube.com/watch?v=0jHtqqdVv1Y - https://www.youtube.com/watch?v=dtHIaH6q_1M - https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L2930 - https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/