디버그 도구 (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 다이어그램

    도구 계층 구조

    도구 계층 구조

    호출 흐름 비교

    호출 흐름 비교