디버깅 랩 01_Use_After_Free

JM·2026년 9월 18일

디버깅 랩

목록 보기
1/2
post-thumbnail

디버깅 랩 1번 문제 해결하다가 발견한 것들.


함수 포인터

도움이 되는 글

구조체의 render과 on_event는 함수다.
Vtable->on_event이런 식으로 함수를 호출할 수 있다.

screen_dispatch함수에서는 on_event함수를, screen_render에서는 render함수를 호출하고 있다.
함수의 주소가 저장된 Widget에 저장된 VTable의 종류에 따라 호출하는 함수가 달라진다.

VTable은 이렇게 정의되어 있고, widget_noop_event는 아무 일도 하지 않는 더미 함수다.

이 예제로 설명하면,

  • 10번 위젯은 LABET_VT를 가짐.
  • 11번과 13번 위젯은 BUTTON_VT를 가짐.
  • 12번 위젯은 DIALOG_VT를 가짐.

각각의 위젯은 가지고 있는 VT 종류에 따라 screen_dispatch와 screen_render함수가 호출될 때 같은 코드에서 각각 다른 함수를 호출한다.
이를 다형성이라고 한다.


구조체의 특이한 초기화?

각 위젯은 Screen구조체에 포인터 배열로 저장된다.
예제에서 main함수 내부에서 Screen을 다음과 같이 초기화하고 있다.

int main(){
	Screen s = { .count = 0 };
    
    .
    .
    .
}

뭔가 정말 C답지 않은 코드인데...
C99부터 지원되는 Designated Initializer(지정 초기자)문법이라고 한다.
구조체 멤버를 초기화할 때 사용할 수 있다.

위 코드처럼 초기화하면

s.items[0] = NULL
s.items[1] = NULL
s.items[2] = NULL
...
s.items[7] = NULL

s.count = 0

같은 모양이 된다.
초기화하지 않은 다른 멤버 또한 0에 해당하는 값으로 초기화하는 aggregate initialization규칙이 있어서 배열의 멤버들도 NULL로 초기화되는 모양이다.


그래서 문제는?

160번째 줄 screen_dispatch(&s, 1)을 실행한 후 12번 위젯이 free되는데, 해당 위젯을 가르키는 포인터를 그대로 두면서 댕글링 포인터가 됨. app_build_status함수로 인위적인 UAF상황을 만들었고, 168번째 줄에서 screens_render을 다시 호출하고 12번 위젯에 접근하면서 SEGFAULT발생.

Solution

  • Free의 발생은 DIALOG_VT 와 screen_dispatch의 두 번째 매개변수가 1일 때.
    • 문제에서는 12번 위젯이 해당됨.
  • screen_dispatch함수는 위젯 배열을 순회하면서 on_event함수를 각각 호출함. (기능 다 다름)
    • screen_dispatch → dialog_on_event → widget_destroy → free 순서
  • Screen쪽에서 Free하는 위젯을 가르키는 포인터를 제거하지 않은 게 원인임.
static void screen_dispatch(Screen *s, int code) {
    for (int i = 0; i < s->count; i++) {
        Widget *w = s->items[i];
        if(((w->vtbl) == &DIALOG_VT) && (code == 1)) s->items[i] = NULL;  // here
        w->vtbl->on_event(w, code);
    }
}

위젯 포인터 w의 vtbl이 DIALOG_VT와 같고 (& 위치 중요함) code가 1일 때 (1이어야 free함) 위젯을 가르키는 원본 포인터를 NULL로 만든다.

(w->vtbl) == &DIALOG_VT
이렇게 비교하는 이유는 위젯을 생성할 때 함수 포인터 구조체의 주소를 전달받았고, Widget의 vtbl도 포인터 변수여서 서로 주소를 비교해야 된다고 생각했음.

그럼 이제 첫 번 째 dispatch함수 호출에서 정상적으로 Free & NULL되었으니, 다음 Render순회에서 NULL인 위젯 슬롯을 건너뛰게 하면 된다,'

static void screen_render(Screen *s) {
    for (int i = 0; i < s->count; i++) {
        Widget *w = s->items[i];
        if(w == NULL) continue; // here
        w->vtbl->render(w);      
    }
}
profile
개발자 지망생

0개의 댓글