Dangling Pointer는 유효하지 않는 메모리 영역을 갖는 포인터를 말합니다. 메모리의 동적 할당에 사용되는 malloc 함수는 할당한 메모리의 주소를 반환합니다. 메모리를 해제할 때는 free 함수를 호출합니다. 하지만 free 함수는 청크를 ptmalloc 에 반환하기만 할 뿐, 청크의 주소를 담고 있던 포인터를 초기화하지는 않습니다. 따라서 free의 호출 이후에 프로그래머가 포인터를 초기화하지 않으면, 포인터는 해제된 청크를 가리키는 Dangling Pointer이 됩니다.
아래는 Dangling Pointer의 위험성을 보이는 예제입니다.
#include <stdio.h>
#include <stdlib.h>
int main() {
char *ptr = NULL;
int idx;
while (1) {
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
if (ptr) {
printf("Already allocated\n");
break;
}
ptr = malloc(256);
break;
case 2:
if (!ptr) {
printf("Empty\n");
}
free(ptr);
break;
default:
break;
}
}
}
예제에선 청크를 해제한 후에 청크를 가리키던 ptr 변수를 초기화하지 않습니다. 따라서 청크를 할당하고 해제해도 ptr은 이전에 할당한 청크의 주소를 가리키는 Dangling Pointer이 됩니다.
ptr이 해제한 청크를 가리키고 있으므로 이를 다시 해제할 수 있고, 이를 Double Free Bug라고 합니다.
$ ./dangling_ptr
> 1
> 2
> 2
free(): double free detected in tcache 2
Aborted (core dumped)
Use-After-Free(UAF)는 문자 그대로, 해제된 메모리에 접근할 수 있을 때 발생하는 취약점을 말합니다.
malloc과 free 함수는 할당 또는 해제할 메모리의 데이터들을 초기화하지 않습니다. 그래서 새롭게 할당한 청크를 프로그래머가 초기화하지 않으면, 메모리에 남아있던 데이터가 유출되거나 사용될 수 있습니다.
아래 코드는 Use After Free 취약점이 있는 예제 코드입니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct NameTag {
char team_name[16];
char name[32];
void (*func)();
};
struct Secret {
char secret_name[16];
char secret_info[32];
long code;
};
int main() {
int idx;
struct NameTag *nametag;
struct Secret *secret;
secret = malloc(sizeof(struct Secret));
strcpy(secret->secret_name, "ADMIN PASSWORD");
strcpy(secret->secret_info, "P@ssw0rd!@#");
secret->code = 0x1337;
free(secret);
secret = NULL;
nametag = malloc(sizeof(struct NameTag));
strcpy(nametag->team_name, "security team");
memcpy(nametag->name, "S", 1);
printf("Team Name: %s\n", nametag->team_name);
printf("Name: %s\n", nametag->name);
if (nametag->func) {
printf("Nametag function: %p\n", nametag->func);
nametag->func();
}
}
ptmalloc2는 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인하고, 있으면 해당 청크를 꺼내서 재사용합니다. 예제 코드에서 Nametag 와 Secret 은 같은 크기의 구조체이므로 앞서 할당한 secret을 해제하고 nametag를 할당하면 nametag는 secret과 같은 메모리 영역을 사용합니다. free는 메모리의 데이터를 초기화하지 않으므로 이전 데이터가 남아있게 됩니다.