Double Free Bug

이동화·2025년 7월 6일

free 함수로 heap chunk를 해제하면, glibc의 heap 메모리 관리자인 ptmalloc2는 이를 tcache, bins라는 list에 추가하여 관리한다. 이후에 malloc으로 비슷한 크기의 동적 할당이 발생하면 이 연결 리스트를 탐색하여 적당한 크기의 청크를 찾아 재할당한다. 이 과정에서 중요한 점은 free 된 chunk 메모리 공간은 더 이상 사용자 데이터 저장이 아니라 ptmalloc2 내부의 linked list 관리를 위한 포인터를 저장하는 공간으로 사용된다는 것이다. malloc에 의해 heap 용도로 chunk가 재할당되면 메모리가 사용자의 데이터를 저장하는 용도로 다시 바뀐다. 같은 메모리 블록이 상황에 따라 관리용 포인터 저장과 사용자 데이터 저장이라는 역할을 번갈아 수행하게 된다.
해당 메커니즘에서 free로 해제한 chunk를 다시 free로 해제한다면 동일한 chunk를 free list (tcache, bins)에 중복하여 추가할 수 있다. 이런 상태를 chunk가 duplicated 되었다고 표현하는데, 이로 인해 공격자는 원하는 메모리 주소를 free list에 끼워넣을 수 있고 다음 할당에서 특정 주소가 반환되도록 만들 수 있다. 같은 메모리 블록이 list에 두 번 link 되기에 ptmalloc2의 linked list가 비정상 상태에 빠지게 되고 이를 통해 임의의 메모리를 읽거나 임의의 메모리에 쓰는 공격이 가능해진다. 해당 취약점을 Double Free Bug (DFB) 이라고 한다.

DFB를 사용하면 duplicated free list를 생성할 수 있다. ptmalloc2의 경우, free list의 각 chunk는 fd에 자신보다 이후에 해제된 chunk를, bk에는 이전에 해제된 chunk를 가리키게 한다. heap이 해제되면 해당 영역의 fd와 bk는 list의 내부 포인터로 쓰이고, 재할당되면 데이터 저장용으로 쓰이는 heap 메모리 위의 공간이다. 그런데 해제된 chunk에서 fd와 bk 값을 저장하는 공간은 다시 동적 할당되면 chunk에서 데이터를 저장하는데 사용되는데, fd, bk 포인터 자리에 데이터를 써 넣을 수 있게 된다. 즉, 어떤 청크가 만약 free list에 중복되서 포함된다면 첫 번째 재할당에서 fd와 bk를 조작하여 free list에 임의 주소를 포함시킬 수 있게 된다. 이는 ptmalloc2는 이 포인터들을 신뢰하여 list를 관리하기 때문에 가능하다. 이후 다시 해당 chunk를 free하면 조작한 fd, bk 값이 free list에 올라가. 이후 공격자는 다시 동적 할당을 통해 chunk를 할당 받으면, 메모리 상 원하는 위치의 정보를 그대로 탈취할 수 있다.


dangling pointer

DFB를 유발하는 원인 중 하나로, 적절한 타입의 유효한 객체를 가리키고 있지 않은 포인터를 말한다. 해제된 메모리 영역을 가리키고 있는 포인터 역시 Dangling Pointer라고 하며, 메모리 접근 시 예측 불가능한 동작을 유발하며 메모리 접근이 불가능한 경우 segmentation fault를 유발한다.
free 호출 이후에 포인터를 초기화하지 않으면 , 메모리 해제 후 해제된 메모리에 접근할 수 있게 되어 발생하게 되고, 함수 호출에서 자동 변수를 가리키는 포인터를 반환하는 경우 생긴다.
반드시 보안 취약점을 발생시키지는 않지만 예상치 못한 동작을 발생시킬 수 있고 공격 수단이 될 수도 있다.

Use After Free

UAF는 dangling pointer에 의해, 해제된 메모리에 접근할 수 있을 때 발생하는 취약점을 말한다. chunk를 프로그래머가 명시적으로 초기화하지 않으면 메모리에 남아있던 데이터가 유출되거나 사용될 수 있다.
새로운 할당 요청이 들어왔을 때 요청된 크기와 비슷한 chunk가 bin이나 tcache에 존재한다면 해당 chunk를 다시 재사용하는데, 이전에 있었던 값의 일부가 남아있게 되어 공격자로 하여금 초기화되지 않은 메모리의 값을 읽거나 새로운 객체가 악의적인 값을 사용하도록 유도하여 프로그램의 정상적인 실행을 방해할 수 있다.


Mitigation

현재의 glibc는 DFB에 대한 방어책이 구현되어 있다. tcache의 같은 chunk를 두 번 해제하게 되면 이를 감지하여 프로그램을 즉시 종료한다.
double free를 탐지하기 위해 *key 포인터를 추가하여 tcache_entry 구조체 위에서 chunk를 관리한다. LIFO 형식으로 관리하기에 bk 값은 사용하지 않고 fd에 대응하는 next만이 존재한다.

typedef struct tcache_entry {
  struct tcache_entry *next;
// double free detect 하는 곳
// ...
  struct tcache_perthread_struct *key;
} tcache_entry;

tcache에는 tcache_put 함수를 통해 해제되는 chunk의 key에 값을 대입하고, tcache_get 함수를 통해 재사용하는 chunk의 key에 NULL을 대입하여 chunk를 재사용하게 한다.
_int_free 함수를 통해 chunk를 해제하는데, 만약 key 값이 NULL이 아니면 double free를 탐지하여 프로그램을 abort 시킨다.
반대로 말하면 해당 조건문만 우회할 수 있을 경우 tcache chunk를 double free 시킬 수 있다.

profile
notion이 나은듯

0개의 댓글