tcache
해당 페이지는 dreamhack.io 사이트의 강의를 참고해 정리하고 이해못한 내용을 추가로 작성해둔것이니 참고사이트의 사이트를 참고하시기바랍니다.
glibc
는 계속해서 업데이트가되고 있다 아래의 내용은 glibc 2.26
버전의 내용이고 glibc 2.29
부터는 달라지게 된다.
tcache
는 glibc 2.26
버전 이상부터 적용된 기술이다. tcache
는 Thread local caching)
의 약자로 멀티 스레드 환경에서 메모리 할당 속도를 높이기 위해 적용되었다. 그렇기 때문에 보안이 취약하다.
우선 malloc
함수를 통해 동적 할당 요청이 들어오면 __libc_malloc
함수가 호출된다.__libc_malloc
함수는 MAYBE_INIT_TCACHE
매크로를 호출하여 tcache_init
함수를 호출한다.
#define MAYBE_INIT_TCACHE() \
if (__glibc_unlikely (tcache == NULL)) \
tcache_init();
void * __libc_malloc (size_t bytes)
{
...
MAYBE_INIT_TCACHE ();
}
tcache_init
함수에서는 tcache_perthread_struct
구조체를 힙영역에 할당하고 초기화하는 역할을 하게 된다. 해당 구조체는 힙 페이지의 맨 첫부분에 할당된다.
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
tcache_perthread_struct
구조체는 tcache_entry
를 관리하기 위해 사용된다. 이전 버전에서 할당된 힙은 main_arena
가 관리했지만 glibc 2.26 버전 이후부터 할당된 tcache의 힙은 tcache_perthread_struct
가 관리하게 된다.
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
tcache_entry
구조체의 멤버 변수인 next
포인터는 tcache->entries
의 연결 리스트를 관리한다.
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;
tcache_put
tcache_put
함수에서는 해제 요청이 들어오면 tcache->entries
에 해제된 힙 청크의 주소를 추가한다. 아래의 코드는 tcache_put
함수가 호출되는 과정이다.
# define TCACHE_MAX_BINS 64
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
#if USE_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 && (pp = *fb) != NULL)
{
REMOVE_FB (fb, tc_victim, pp);
if (tc_victim != 0)
{
tcache_put (tc_victim, tc_idx);
}
}
청크의 크기를 이용해 tc_idx
를 계산한 후 tcache->counts[tcidx] < mp_.tcache_count
조건을 만족한다면 tcache_put
함수를 호출하는 것을 확인할 수 있다.mp_
구조체를 살펴보면 tcache_count
멤버는 .tcache_count = TCACHE_FILL_COUNT
로 인해 7이란 값으로 초기화되어 있다.
해당 코드를 통해 동일한 크기의 tcache->entries
는 7개의 힙 청크만 관리한다는 것을 알 수 있고, fastbin
과 smallbin
크기의 청크가 해제되면 먼저 tcache
가 관리한다.free
함수가 호출되어 해제된 힙 청크의 주소를 tcache->entries
에 삽입할 때에는 해당 힙 청크의 크기만 검증하고, Double Free에 대한 검증은 존재하지 않는다.
static 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]);
}
tcache_get
tcache_get
함수에서는 저장되어 있는 tcache->entries
에서 힙 청크를 가져온다. 이후 가져온 힙 청크의 next
포인터를 tcache->entries
에 삽입하고 힙 청크의 주소를 반환한다. 다음은 tcache_get
함수가 호출되는 과정이다.
size_t tbytes = request2size (bytes);
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
요청이 들어온 사이즈에 맞는 tcache_entry
가 존재한다면 tcache_get
함수가 호출된다.
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;
}