디버그 도구 (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) | KFENCE | Kmemleak |
| **탐지 대상** | OOB, UAF, invalid-free | OOB, 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 다이어그램
도구 계층 구조
호출 흐름 비교