Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

디버그 도구 (KASAN / KFENCE / Kmemleak)

개요 (Overview)

Linux 커널 메모리 디버깅은 세 가지 핵심 도구로 구성됩니다. KASAN (Kernel Address Sanitizer)은 메모리 접근 오류를 실시간으로 감지하며, KFENCE (Kernel Electric-Fence)는 경량 샘플링 기반으로 OOB/UAF를 탐지합니다. Kmemleak은 메모리 누수를 주기적 스캔으로 발견합니다. 이 세 도구는 상호 보완적으로 동작하며, 각각 다른 오버헤드와 탐지 범위를 제공합니다.

각 도구는 독립적으로 활성화할 수 있지만, 함께 사용하면 메모리 안전성을 극대화할 수 있습니다. KASAN은 컴파일 시 코드를 변환하여 모든 메모리 접근을 검사하고, KFENCE는 런타임에 샘플링된 할당만 보호하며, Kmemleak은 주기적으로 커널 힙을 스캔하여 참조되지 않는 객체를 찾습니다.

소스 파일 경로:
├── mm/kasan/           ← KASAN 코어 (generic.c, common.c, report.c, kasan.h)
├── mm/kfence/          ← KFENCE 코어 (core.c, kfence.h)
└── mm/kmemleak.c       ← Kmemleak 코어

빠른 점검 명령

# KASAN 활성화 확인
dmesg | grep -i "kasan"
cat /proc/cmdline | grep -o "kasan=on\|kasan.fault=panic"

# KFENCE 상태 확인
cat /sys/kernel/debug/kfence/stats
dmesg | grep -i "kfence"

# Kmemleak 상태 확인
cat /sys/kernel/debug/kmemleak
echo scan > /sys/kernel/debug/kmemleak

# 메모리 할당 디버그 옵션 확인
cat /proc/cmdline | grep -o "kasan_multi_shot\|kfence_sample_interval=\|kmemleak=on"

# KASAN 리포트 확인
dmesg | grep -A 20 "BUG: KASAN"

# KFENCE 리포트 확인
dmesg | grep -A 20 "kfence:"

# Kmemleak 리포트 확인
dmesg | grep -A 10 "unreferenced object"

핵심 자료구조

1. KASAN — `struct kasan_track` (mm/kasan/kasan.h:193)

struct kasan_track {
    u32 pid;                          // 프로세스 ID
    depot_stack_handle_t stack;       // 스택 트레이스 핸들
#ifdef CONFIG_KASAN_EXTRA_INFO
    u64 cpu:20;                       // CPU 번호 (추가 정보)
    u64 timestamp:44;                 // 타임스탬프 (추가 정보)
#endif
};

2. KASAN — Shadow Memory 값 (mm/kasan/kasan.h:143-170)

#define KASAN_PAGE_FREE      0xFF  // 해제된 페이지
#define KASAN_PAGE_REDZONE   0xFE  // kmalloc_large redzone
#define KASAN_SLAB_REDZONE   0xFC  // slab 객체 redzone
#define KASAN_SLAB_FREE      0xFB  // 해제된 slab 객체
#define KASAN_VMALLOC_INVALID 0xF8 // vmap 영역 내 접근 불가

// 스택 redzone 값 (컴파일러 ABI)
#define KASAN_STACK_LEFT     0xF1
#define KASAN_STACK_MID      0xF2
#define KASAN_STACK_RIGHT    0xF3
#define KASAN_STACK_PARTIAL  0xF4

// alloca redzone 값
#define KASAN_ALLOCA_LEFT    0xCA
#define KASAN_ALLOCA_RIGHT   0xCB

3. KFENCE — `struct kfence_metadata` (mm/kfence/kfence.h:57)

struct kfence_metadata {
    struct list_head list;            // freelist 노드
    struct rcu_head rcu_head;         // RCU 지연 해제용
    raw_spinlock_t lock;              // 메타데이터 보호 락
    enum kfence_object_state state;   // 객체 상태
    unsigned long addr;               // 할당된 객체 주소
    size_t size;                      // 원래 할당 크기
    struct kmem_cache *cache;         // slab 캐시 포인터
    unsigned long unprotected_page;   // 접근 불가 페이지 (OOB 감지 시)
    struct kfence_track alloc_track;  // 할당 스택 정보
    struct kfence_track free_track;   // 해제 스택 정보
    u32 alloc_stack_hash;             // 할당 스택 해시
};

4. KFENCE — `struct kfence_track` (mm/kfence/kfence.h:48)

struct kfence_track {
    pid_t pid;                        // 프로세스 ID
    int cpu;                          // CPU 번호
    u64 ts_nsec;                      // 타임스탬프 (나노초)
    int num_stack_entries;            // 스택 엔트리 수
    unsigned long stack_entries[KFENCE_STACK_DEPTH]; // 스택 트레이스
};

5. Kmemleak — `struct kmemleak_object` (mm/kmemleak.c:135)

struct kmemleak_object {
    raw_spinlock_t lock;              // 객체 보호 락
    unsigned int flags;               // 객체 상태 플래그
    struct list_head object_list;     // 전체 객체 리스트
    struct list_head gray_list;       // gray 객체 리스트 (스캔 대상)
    struct rb_node rb_node;           // 레드블랙 트리 노드
    struct rcu_head rcu;              // RCU 해제용
    atomic_t use_count;               // 참조 카운트
    unsigned int del_state;           // 삭제 상태
    unsigned long pointer;            // 할당된 메모리 주소
    size_t size;                      // 할당 크기
    unsigned long excess_ref;         // 초과 참조 전달 포인터
    int min_count;                    // 최소 참조 수 (이 값 미만이면 leak 의심)
    int count;                        // 현재 참조 수
    u32 checksum;                     // 객체 무결성 검증용 체크섬
    depot_stack_handle_t trace_handle; // 할당 스택 트레이스
    struct hlist_head area_list;      // 스캔 영역 리스트
    unsigned long jiffies;            // 생성 타임스탬프
    pid_t pid;                        // 할당 프로세스 ID
    char comm[TASK_COMM_LEN];         // 프로세스 이름
};

6. Kmemleak — 색상 상태 (mm/kmemleak.c:326-336)

// white — 고아 객체, 참조 부족 (count < min_count)
static bool color_white(const struct kmemleak_object *object)
{
    return object->count != KMEMLEAK_BLACK &&
        object->count < object->min_count;
}

// gray — 참조 충분 또는 false positive로 마킹됨
static bool color_gray(const struct kmemleak_object *object)
{
    return object->min_count != KMEMLEAK_BLACK &&
        object->count >= object->min_count;
}

// black — 스캔 무시 (text 섹션 등, min_count == -1)

핵심 함수

1. KASAN — `check_region_inline()` (mm/kasan/generic.c:175)

static __always_inline bool check_region_inline(const void *addr,
                        size_t size, bool write,
                        unsigned long ret_ip)
{
    if (!kasan_enabled())               // KASAN 비활성화 시 통과
        return true;
    if (unlikely(size == 0))            // 크기 0 접근 시 통과
        return true;
    if (unlikely(addr + size < addr))   // 주소 오버플로우
        return !kasan_report(addr, size, write, ret_ip);
    if (unlikely(!addr_has_metadata(addr))) // 메타데이터 없는 영역
        return !kasan_report(addr, size, write, ret_ip);
    if (likely(!memory_is_poisoned(addr, size))) // 정상 영역
        return true;
    return !kasan_report(addr, size, write, ret_ip); // 오염된 영역
}

2. KASAN — `kasan_report()` (mm/kasan/report.c:571)

bool kasan_report(const void *addr, size_t size, bool is_write,
            unsigned long ip)
{
    bool ret = true;
    unsigned long ua_flags = user_access_save();
    unsigned long irq_flags;
    struct kasan_report_info info;

    // 억제 조건 확인 (kasan_disable_current() 구간)
    if (unlikely(report_suppressed_sw()) || unlikely(!report_enabled())) {
        ret = false;
        goto out;
    }

    start_report(&irq_flags);           // 리포트 시작 (락, lockdep_off)
    __memset(&info, 0, sizeof(info));
    info.type = KASAN_REPORT_ACCESS;
    info.access_addr = addr;
    info.access_size = size;
    info.is_write = is_write;
    info.ip = ip;

    complete_report_info(&info);        // 메타데이터로 객체 정보 완성
    print_report(&info);                // 리포트 출력
    end_report(&irq_flags, (void *)addr, is_write); // 패닉 처리

out:
    user_access_restore(ua_flags);
    return ret;
}

3. KFENCE — `kfence_guarded_alloc()` (mm/kfence/core.c:418)

static void *kfence_guarded_alloc(struct kmem_cache *cache, size_t size,
                  gfp_t gfp,
                  unsigned long *stack_entries,
                  size_t num_stack_entries,
                  u32 alloc_stack_hash)
{
    struct kfence_metadata *meta = NULL;
    unsigned long flags;

    // freelist에서 빈 객체 획득
    raw_spin_lock_irqsave(&kfence_freelist_lock, flags);
    if (!list_empty(&kfence_freelist)) {
        meta = list_entry(kfence_freelist.next,
                 struct kfence_metadata, list);
        list_del_init(&meta->list);
    }
    raw_spin_unlock_irqrestore(&kfence_freelist_lock, flags);
    if (!meta) {
        atomic_long_inc(&counters[KFENCE_COUNTER_SKIP_CAPACITY]);
        return NULL;                    // 용량 초과로 스킵
    }

    // 좌/우 랜덤 배치 결정
    meta->addr = metadata_to_pageaddr(meta);
    if (meta->state == KFENCE_OBJECT_FREED)
        kfence_unprotect(meta->addr);   // 재사용 시 보호 해제

    if (random_right_allocate) {
        meta->addr += PAGE_SIZE - size; // 우측 배치
        meta->addr = ALIGN_DOWN(meta->addr, cache->align);
    }

    addr = (void *)meta->addr;
    metadata_update_state(meta, KFENCE_OBJECT_ALLOCATED,
                 stack_entries, num_stack_entries);
    WRITE_ONCE(meta->cache, cache);
    meta->size = size;

    set_canary(meta);                   // 캐너리 바이트 설정
    kfence_protect(meta->addr);         // redzone 보호

    return addr;
}

4. KFENCE — `kfence_guarded_free()` (mm/kfence/core.c:518)

static void kfence_guarded_free(void *addr, struct kfence_metadata *meta,
                bool zombie)
{
    unsigned long flags;
    bool init;

    raw_spin_lock_irqsave(&meta->lock, flags);

    // 유효하지 않은 해제 또는 double-free 검사
    if (!kfence_obj_allocated(meta) || meta->addr != (unsigned long)addr) {
        atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]);
        kfence_report_error((unsigned long)addr, false, NULL, meta,
                    KFENCE_ERROR_INVALID_FREE);
        raw_spin_unlock_irqrestore(&meta->lock, flags);
        return;
    }

    // 캐너리 바이트 검증 (메모리 손상 탐지)
    check_canary(meta);

    metadata_update_state(meta, KFENCE_OBJECT_FREED, NULL, 0);
    raw_spin_unlock_irqrestore(&meta->lock, flags);

    kfence_protect((unsigned long)addr); // use-after-free 감지용 보호

    if (!zombie) {
        // freelist에 추가 (재사용 가능)
        raw_spin_lock_irqsave(&kfence_freelist_lock, flags);
        list_add_tail(&meta->list, &kfence_freelist);
        raw_spin_unlock_irqrestore(&kfence_freelist_lock, flags);
    }
}

5. Kmemleak — `create_object()` (mm/kmemleak.c:791)

static void create_object(unsigned long ptr, size_t size,
              int min_count, gfp_t gfp)
{
    __create_object(ptr, size, min_count, gfp, 0);
}

static void __create_object(unsigned long ptr, size_t size,
                int min_count, gfp_t gfp, unsigned int objflags)
{
    struct kmemleak_object *object;
    unsigned long flags;
    int ret;

    object = __alloc_object(gfp);       // 메모리 할당 (slab 또는 메모리 풀)
    if (!object)
        return;

    raw_spin_lock_irqsave(&kmemleak_lock, flags);
    ret = __link_object(object, ptr, size, min_count, objflags);
    // rb-tree에 삽입, object_list에 추가
    raw_spin_unlock_irqrestore(&kmemleak_lock, flags);
    if (ret)
        mem_pool_free(object);          // 중복 시 메모리 반환
}

호출 흐름

KASAN 접근 검사 흐름

메모리 접근 (kmalloc/kfree/Stack/Global)
    ↓
__asan_loadN/__asan_storeN (컴파일러 자동 삽입)
    ↓
kasan_check_range()
    ↓
check_region_inline()
    ├─ kasan_enabled() → false이면 통과
    ├─ addr_has_metadata() → 메타데이터 영역 확인
    ├─ memory_is_poisoned() → shadow memory 검사
    │   ├─ memory_is_poisoned_1() (1바이트)
    │   ├─ memory_is_poisoned_2_4_8() (2/4/8바이트)
    │   ├─ memory_is_poisoned_16() (16바이트)
    │   └─ memory_is_poisoned_n() (임의 크기)
    └─ kasan_report() → 오염된 경우 리포트 생성

KFENCE 할당/해제 흐름

    slab 할당 (__kfence_alloc 진입)
    ↓
    __kfence_alloc()
    ├─ kfence_sample_interval 확인
    ├─ static_key 동작 ( kfence_allocation_key )
    └─ kfence_guarded_alloc()
        ├─ freelist에서 metadata 획득
        ├─ 좌/우 랜덤 배치
        ├─ 캐너리 설정 (set_canary)
        ├─ redzone 보호 (kfence_protect)
        └─ 객체 주소 반환

    slab 해제 (__kfence_free 진입)
    ↓
    __kfence_free()
    ├─ is_kfence_address() 확인
    └─ kfence_guarded_free()
        ├─ double-free/invalid-free 검사
        ├─ 캐너리 검증 (check_canary)
        ├─ redzone 보호 설정
        └─ freelist에 추가 (재사용 대기)

Kmemleak 스캔 흐름

kmemleak 스캔 시작 (주기적 또는 수동)
    ↓
kmemleak_scan()
    ├─ gray_list 순회 (참조된 객체)
    │   └─ scan_object() → 메모리 내 포인터 검색
    │       └─ 참조 발견 시 → 객체를 gray로 마킹
    ├─ object_list 순회 (전체 객체)
    │   └─ unreferenced_object() → white 객체 식별
    │       ├─ color_white() → count < min_count
    │       ├─ OBJECT_ALLOCATED 플래그 확인
    │       └─ 최소 경과 시간 (jiffies_min_age) 확인
    └─ unreferenced 객체 리포트
        └─ print_unreferenced() → 스택 트레이스 출력

조건별 비교

도구별 특성 비교

특성KASAN (Generic)KASAN (HW Tags)KFENCEKmemleak
**탐지 대상**OOB, UAF, invalid-freeOOB, UAF, MTE 기반OOB, UAF, corruption메모리 누수
**오버헤드**2-3x 메모리, 2x 느림1.5x 메모리, 중간극히 낮음 (샘플링)낮음 (주기적 스캔)
**활성화 시점**부팅 시부팅 시런타임 가능런타임 가능
**컴파일 변환**필수필수불필요불필요
**감지 정확도**높음 (모든 접근)높음 (하드웨어)낮음 (일부만)중간 (간접 탐지)
**적합한 환경**개발/디버깅ARM64 MTE 지원프로덕션 모니터링장기 누수 탐지

KASAN 모드 비교

모드shadow memory태그 기반아키텍처
**Generic**1:8 비율 메타데이터아니오모든 아키텍처
**SW Tags**소프트웨어 태그소프트웨어x86_64, ARM64
**HW Tags**하드웨어 태그 (MTE)하드웨어ARM64만

KFENCE 오류 타입

타입설명리포트 메시지
**OOB**Out-of-bounds 접근`slab-out-of-bounds`
**UAF**Use-after-free`slab-use-after-free`
**Corruption**캐너리 손상`memory-corruption`
**Invalid Free**잘못된 해제`invalid-free`
**Invalid**알 수 없는 접근`kfence: invalid access`

Kmemleak 객체 플래그

플래그설명
**OBJECT_ALLOCATED**`1 << 0`현재 할당됨
**OBJECT_REPORTED**`1 << 1`첫 리포트 후 설정
**OBJECT_NO_SCAN**`1 << 2`스캔 무시
**OBJECT_FULL_SCAN**`1 << 3`전체 객체 스캔
**OBJECT_PHYS**`1 << 4`물리 주소 할당
**OBJECT_PERCPU**`1 << 5`Per-CPU 할당

관련 문서

  • 00-overview.html — 메모리 관리 개요
  • 02-slab.html — SLUB 할당자 (KASAN/KFENCE와 상호작용)
  • 08-oom.html — OOM Killer (Kmemleak와 연관)
  • 41-page_owner.html — Page Owner (추가 디버깅 도구)

  • SVG 다이어그램

    도구 계층 구조

    도구 계층 구조

    호출 흐름 비교

    호출 흐름 비교