Slub Memory Allocator -11- (slub 디버깅)

 

커널 오브젝트 디버깅

SLAB_POISON

slab-poison-1

Slab(Slub) 디버깅을 위해 SLAB_POISON을 사용하는 경우 object의 할당과 해제 시에 특별한 데이터를 object 데이터 영역에 기록하고 이를 통해서 다음의 에러를 찾아낸다.

  • 이중 해제(double free)
    • 해제를 하게되면 object에는 poison free 데이터(0x6b가 연속되고 마지막에 0xa5)가 기록되는데 만일 해제된 object를 다시 한 번 해제하는 순간 poison free 데이터를 발견하면 반복하여 해제하려고 한다는 것을 알게되어 에러를 출력한다.
  • 해제 후 사용(use after free)
    • 엄밀히 이 free object를 사용할 때에는 체크를 하지 못하지만 다시 object를 할당할 때 free 라고 생각했던 object의 poison free 데이터가 파괴된 것을 보고 누군가가 침해를 한 것으로 판단하여 에러를 출력한다.

 

SLAB_RED_ZONE

slab-red-zone-1

Slab(Slub) 디버깅을 위해 RED_ZONE을 사용하는 경우 object의 할당과 해제 시에 특별한 데이터를 red zone 영역에 기록하고 이를 통해서 다음의 에러를 찾아낸다.

  • object 데이터가 자신의 영역을 초과하여 red zone 영역을 침범한 경우 object를 할당 또는 해지하는 순간 알아낸다.

 

SLAB_STORE_USER

Slab(Slub) 디버깅을 위해 SLAB_STORE_USER를 사용하는 경우 object를 할당과 해제를 한 pid, cpu, 호출한 주소(함수 추적). 시각 등을 추적할 수 있다. 만일 CONFIG_STACKTRACE 커널 옵션을 추가 사용하는 경우 16개까지의  function track back을 할 수 있다.

slab-store-user-1

커널 옵션

CONFIG_KMEMCHECK

  • CONFIG_KMEMCHECK 커널 옵션은 현재 x86 아키텍처에만 적용되어 사용된다.

CONFIG_KASAN

  • 런타임 디버깅을 사용한다.
    • Slab(Slub)에서도 shadow 영역을 사용하는데 최대 3배까지 실행 성능이 떨어진다.

 

slab 할당 시의 flag 옵션

  •  SLAB_NOTRACK
    • 초기화 되지 않은 메모리의 사용에 대해 추적을 하지 않게 한다.
  • shadow 페이지를 이용하지 않는 디버그 방법이다.
    • SLAB_DEBUG_FREE
      • slab free 시 디버깅(expensive)
    • SLAB_RED_ZONE
      • red zone 영역을 사용한 디버깅
    • SLAB_POISON
      • object 영역에 poison 데이터를 기록하여 디버깅
    • SLAB_STORE_USER
      • 디버깅을 위해 last owner 관련 정보를 저장한다.

 

setup_object()

mm/slub.c

static void setup_object(struct kmem_cache *s, struct page *page,
                                void *object)
{
        setup_object_debug(s, page, object);
        if (unlikely(s->ctor)) {
                kasan_unpoison_object_data(s, object);
                s->ctor(object);
                kasan_poison_object_data(s, object);
        }
}

커널 옵션에 의한 디버깅과 런타임 메모리 디버깅을 위해 Slab(Slub) object를 초기화한다.

  • setup_object_debug(s, page, object);
    • Slab(Slub) 디버깅을 위해 POISON, RED ZONE, 트래킹 데이터를 설치한다.
  • if (unlikely(s->ctor)) {
    • 적은 확률로 생성자가 준비된 경우
  • kasan_unpoison_object_data(s, object);
    • 런타임 메모리 디버거를 위해 shadow 영역을 0으로 초기화한다.
  • s->ctor(object);
    • object 생성자를 동작시킨다.
  •  kasan_poison_object_data(s, object);
    • 런타임 메모리 디버거를 위해 shadow 영역을 KASAN_KMALLOC_REDZONE(0xfc)으로 초기화한다.

 

setup_object_debug()

mm/slub.c

/* Object debug checks for alloc/free paths */
static void setup_object_debug(struct kmem_cache *s, struct page *page,
                                                                void *object)
{
        if (!(s->flags & (SLAB_STORE_USER|SLAB_RED_ZONE|__OBJECT_POISON)))
                return;

        init_object(s, object, SLUB_RED_INACTIVE);
        init_tracking(s, object);
}

Slab(Slub) 디버깅을 위해 POISON, RED ZONE, 트래킹 데이터를 설치한다.

  • if (!(s->flags & (SLAB_STORE_USER|SLAB_RED_ZONE|__OBJECT_POISON))) return;
    • slab(slub) 디버깅 플래그가 없는 경우 함수를 빠져나간다.
  • init_object(s, object, SLUB_RED_INACTIVE);
    • POISON 데이터와 RED ZONE 데이터를 설치한다.
  • init_tracking(s, object);
    • 트래킹 데이터를 설치한다.

 

init_object()

mm/slub.c

static void init_object(struct kmem_cache *s, void *object, u8 val)
{
        u8 *p = object;

        if (s->flags & __OBJECT_POISON) {
                memset(p, POISON_FREE, s->object_size - 1);
                p[s->object_size - 1] = POISON_END;
        }

        if (s->flags & SLAB_RED_ZONE)
                memset(p + s->object_size, val, s->inuse - s->object_size);
}

Slab(Slub) object를 초기화할 때 __OBJECT_POISON 플래그가 사용된 경우 object를 POISON_FREE(0x6b)로 채우고 마지막에 POISON_END(0x5a)를 저장한다. 그리고 SLAB_RED_ZONE 플래그를 사용하는 경우 인수 val 값을 red zone 영역에 채운다.

 

init_tracking()

mm/slub.c

static void init_tracking(struct kmem_cache *s, void *object)
{
        if (!(s->flags & SLAB_STORE_USER))
                return;

        set_track(s, object, TRACK_FREE, 0UL);
        set_track(s, object, TRACK_ALLOC, 0UL);
}

트래킹을 위한 TRACK_FREE 및 TRACK_ALLOC 등 2개 영역의 데이터를 0으로 초기화한다.

 

set_track()

mm/slub.c

static void set_track(struct kmem_cache *s, void *object,
                        enum track_item alloc, unsigned long addr)
{
        struct track *p = get_track(s, object, alloc);

        if (addr) {
#ifdef CONFIG_STACKTRACE
                struct stack_trace trace;
                int i;

                trace.nr_entries = 0;
                trace.max_entries = TRACK_ADDRS_COUNT;
                trace.entries = p->addrs;
                trace.skip = 3;
                metadata_access_enable();
                save_stack_trace(&trace);
                metadata_access_disable();

                /* See rant in lockdep.c */
                if (trace.nr_entries != 0 &&
                    trace.entries[trace.nr_entries - 1] == ULONG_MAX)
                        trace.nr_entries--;

                for (i = trace.nr_entries; i < TRACK_ADDRS_COUNT; i++)
                        p->addrs[i] = 0;
#endif
                p->addr = addr;
                p->cpu = smp_processor_id();
                p->pid = current->pid;
                p->when = jiffies;
        } else
                memset(p, 0, sizeof(struct track));
}

트래킹을 하기 위한 호출 주소, cpu, pid, 시각에 대한 데이터를 저장한다.

  • CONFIG_STACKTRACE 커널 옵션을 사용하는 경우 slub 에서도 16개까지의 함수 역추적을 할 수 있도록 한다.

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.