LKDTM 소개
 
LKDTM은 Linux Kernel Dump Test Module (LKDTM)의 약자로 kdump를 활용해 덤프(vmcore)가 제대로 추출되는지 테스트하는 용도로 작성된 드라이버입니다.
 
관련 자료는 아래 링크에서 확인할 수 있습니다.
 
 
LKDTM 소스는 커널 버전이 업그레이드되면서 (불필요하게) 복잡해 졌는데요. 
전체 구조는 예전 v3.18 소스를 보면 더 빨리 파악할 수 있습니다.
 
 
LKDTM 컨피그를 키는 방법
 
LKDTM는 드라이버 모듈 형식으로 빌드하거나 아예 빌트인으로 커널 이미지에 포함되도록 빌드할 수 있습니다. 이번에는 커널 이미지에 LKDTM을 포함시키는 방법을 소개합니다.
 
아래는 LKDTM 컨피그를 키는 예제 코드입니다. 라즈비안 커널 소스 기준입니다.
 
diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig
index 16d756c..191fb28 100644
--- a/arch/arm64/configs/bcm2711_defconfig
+++ b/arch/arm64/configs/bcm2711_defconfig
@@ -16,6 +16,11 @@ CONFIG_IKCONFIG=m
 CONFIG_IKCONFIG_PROC=y
 CONFIG_MEMCG=y
 CONFIG_BLK_CGROUP=y
 CONFIG_DEBUG_INFO=y
+CONFIG_LKDTM=y
 CONFIG_CFS_BANDWIDTH=y
 CONFIG_CGROUP_PIDS=y
 CONFIG_CGROUP_FREEZER=y
 
bcm2711_defconfig 파일에 'CONFIG_LKDTM=y' 코드 한 줄이 추가됐음을 확인할 수 있습니다.
위와 같은 커널 컨피그를 적용한 후 빌드를 하면, .config(빌드하면 생성되는)에서 'CONFIG_LKDTM=y'가 확인됩니다.
 
$ vi out/.config
...
CONFIG_FUNCTION_ERROR_INJECTION=y
# CONFIG_FAULT_INJECTION is not set
CONFIG_ARCH_HAS_KCOV=y
CONFIG_RUNTIME_TESTING_MENU=y
CONFIG_LKDTM=y
# CONFIG_TEST_MIN_HEAP is not set
 
'CONFIG_LKDTM=y'을 반영하고 커널을 빌드합니다. 빌드한 다음에 생성되는 커널 이미지를 타겟에 다운로드(플래시)합니다.
 
LKDTM 실행 하기 전 체크 포인트
 
LKDTM 컨피그가 플레시된 타겟 디바이스에서, LKDTM 실행 하기 전에 먼저 아래 파일이 제대로 보이는지 확인해야 합니다.
 
/sys/kernel/debug/provoke-crash
 
만약 위 파일이 보이지 않으면 'CONFIG_LKDTM=y' 컨피그에 제대로 적용되지 않고 빌드됐으니 다시 빌드를 해야 합니다.
 
LKDTM 간략한 코드 리뷰
 
LKDTM을 실행하는 방법을 설명드리기 전에 간단히 LKDTM에 대한 코드를 리뷰합니다.
 
LKDTM은 강제로 크래시를 유발할 수 있는 코드로 구성돼 있으며 각각 종류는 아래 코드에서 확인할 수 있습니다. (v5.4 기준)
 
/* Define the possible types of crashes that can be triggered. */
static const struct crashtype crashtypes[] = {
CRASHTYPE(PANIC),
CRASHTYPE(BUG),
CRASHTYPE(WARNING),
CRASHTYPE(WARNING_MESSAGE),
CRASHTYPE(EXCEPTION),
CRASHTYPE(LOOP),
CRASHTYPE(EXHAUST_STACK),
CRASHTYPE(CORRUPT_STACK),
CRASHTYPE(CORRUPT_STACK_STRONG),
CRASHTYPE(CORRUPT_LIST_ADD),
CRASHTYPE(CORRUPT_LIST_DEL),
CRASHTYPE(CORRUPT_USER_DS),
CRASHTYPE(STACK_GUARD_PAGE_LEADING),
CRASHTYPE(STACK_GUARD_PAGE_TRAILING),
CRASHTYPE(UNSET_SMEP),
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
CRASHTYPE(OVERWRITE_ALLOCATION),
CRASHTYPE(WRITE_AFTER_FREE),
CRASHTYPE(READ_AFTER_FREE),
CRASHTYPE(WRITE_BUDDY_AFTER_FREE),
CRASHTYPE(READ_BUDDY_AFTER_FREE),
CRASHTYPE(SLAB_FREE_DOUBLE),
CRASHTYPE(SLAB_FREE_CROSS),
CRASHTYPE(SLAB_FREE_PAGE),
CRASHTYPE(SOFTLOCKUP),
CRASHTYPE(HARDLOCKUP),
CRASHTYPE(SPINLOCKUP),
CRASHTYPE(HUNG_TASK),
CRASHTYPE(EXEC_DATA),
CRASHTYPE(EXEC_STACK),
CRASHTYPE(EXEC_KMALLOC),
CRASHTYPE(EXEC_VMALLOC),
CRASHTYPE(EXEC_RODATA),
CRASHTYPE(EXEC_USERSPACE),
CRASHTYPE(EXEC_NULL),
CRASHTYPE(ACCESS_USERSPACE),
CRASHTYPE(ACCESS_NULL),
CRASHTYPE(WRITE_RO),
CRASHTYPE(WRITE_RO_AFTER_INIT),
CRASHTYPE(WRITE_KERN),
CRASHTYPE(REFCOUNT_INC_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_OVERFLOW),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_DEC_ZERO),
CRASHTYPE(REFCOUNT_DEC_NEGATIVE),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_INC_ZERO),
CRASHTYPE(REFCOUNT_ADD_ZERO),
CRASHTYPE(REFCOUNT_INC_SATURATED),
CRASHTYPE(REFCOUNT_DEC_SATURATED),
CRASHTYPE(REFCOUNT_ADD_SATURATED),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_TIMING),
CRASHTYPE(ATOMIC_TIMING),
CRASHTYPE(USERCOPY_HEAP_SIZE_TO),
CRASHTYPE(USERCOPY_HEAP_SIZE_FROM),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_TO),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_FROM),
CRASHTYPE(USERCOPY_STACK_FRAME_TO),
CRASHTYPE(USERCOPY_STACK_FRAME_FROM),
CRASHTYPE(USERCOPY_STACK_BEYOND),
CRASHTYPE(USERCOPY_KERNEL),
CRASHTYPE(USERCOPY_KERNEL_DS),
CRASHTYPE(STACKLEAK_ERASING),
CRASHTYPE(CFI_FORWARD_PROTO),
}; 
 
크래시 타입 별로 크래시를 유발하는 코드를 확인하는게 중요한데요.
CRASHTYPE(BUG)은 다음 코드에 매핑됩니다.
 
void lkdtm_BUG(void)
{
BUG();
}
 
CRASHTYPE(EXCEPTION)에 매핑되는 루틴은 다음과 같습니다.
 
void lkdtm_EXCEPTION(void)
{
*((volatile int *) 0) = 0;
}
 
제가 위에서 정의된 크래시 타입을 전부 실행했는데, 대부분 크래시가 유발되나 크래시가 유발되지 
않는 경우도 확인했습니다.
 
LKDTM 실행하기
 
(LKDTM 실행하기 전에 먼저 ftrace 이벤트를 활성화하면 좋습니다. vmcore에서 ftrace를 함께 보면 더 많은 디버깅 정보를 확인할 수 있거든요. e.g: sched_switch, irq_handle_entry, irq_handle_exit)
 
LKDTM을 실행하는 방법은 간단합니다. 셸을 하나 열고 아래 포멧으로 명령어를 입력하면 됩니다.
 
$ echo '크래시 타입' > /sys/kernel/debug/provoke-crash/DIRECT
 
몇 가지 예를 들겠습니다. CRASHTYPE(BUG)을 유발하려면 다음과 같은 명령어를 입력하면 됩니다.
 
$ echo BUG > /sys/kernel/debug/provoke-crash/DIRECT
 
'/sys/kernel/debug/provoke-crash/DIRECT'에 BUG 문자열을 전달하면 아래 코드가 실행됩니다.
 
void lkdtm_BUG(void)
{
BUG();
}
 
만약 스택을 오염(Corrupt) 시켜서 크래시를 유발하고 싶으면 아래 명령어를 입력하면 되겠네요.
 
$ echo CORRUPT_STACK > /sys/kernel/debug/provoke-crash/DIRECT
 
위 명령어를 실행하면 lkdtm_CORRUPT_STACK() 함수가 실행되면서 스택을 깹니다.
 
static noinline void __lkdtm_CORRUPT_STACK(void *stack)
{
memset(stack, '\xff', 64);
}
noinline void lkdtm_CORRUPT_STACK(void)
{
/* Use default char array length that triggers stack protection. */
char data[8] __aligned(sizeof(void *));
 
__lkdtm_CORRUPT_STACK(&data);
 
pr_info("Corrupted stack containing char array ...\n");
}
 
data를 char 타입인 배열(8)로 잡았는데, __lkdtm_CORRUPT_STACK() 함수에서 64바이트 사이즈로 메모리 카피를 하니 스택에 '\xff'를 덮어 씁니다. 
 
권장 크래시 타입
 
사실 LKDTM으로 지원되는 크래시 타입을 모두 테스트하면 좋은데요.
실전 프로젝트에서 잘 발생하는 순서로 테스트를 하면 좋을 것 같아요.
 
주석을 달았으니 참고하시면 좋겠네요.
 
static const struct crashtype crashtypes[] = {
CRASHTYPE(PANIC),  /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(BUG), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(WARNING), /* 테스트 요망 | 발생 가능 | 크래시 유발안됨 *'/
CRASHTYPE(WARNING_MESSAGE),
CRASHTYPE(EXCEPTION), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(LOOP),
CRASHTYPE(EXHAUST_STACK), /* 테스트 요망 | 종종 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_STACK), /* 테스트 요망 | 종종 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_STACK_STRONG),
CRASHTYPE(CORRUPT_LIST_ADD), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_LIST_DEL),  /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_USER_DS),  
CRASHTYPE(STACK_GUARD_PAGE_LEADING),
CRASHTYPE(STACK_GUARD_PAGE_TRAILING),
CRASHTYPE(UNSET_SMEP),
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
CRASHTYPE(OVERWRITE_ALLOCATION),
CRASHTYPE(WRITE_AFTER_FREE), /* 테스트 요망 | 종종 발생(메모리 Corruption) | 100% 유발 *'/
CRASHTYPE(READ_AFTER_FREE),  /* 테스트 요망 | 종종 발생(메모리 Corruption) | 100% 유발 *'/
CRASHTYPE(WRITE_BUDDY_AFTER_FREE),
CRASHTYPE(READ_BUDDY_AFTER_FREE),
CRASHTYPE(SLAB_FREE_DOUBLE),
CRASHTYPE(SLAB_FREE_CROSS),
CRASHTYPE(SLAB_FREE_PAGE),
CRASHTYPE(SOFTLOCKUP), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(HARDLOCKUP), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(SPINLOCKUP), /* 테스트 요망 | 종종 발생 | 100% 유발 *'/
CRASHTYPE(HUNG_TASK),
CRASHTYPE(EXEC_DATA),
CRASHTYPE(EXEC_STACK),
CRASHTYPE(EXEC_KMALLOC),
CRASHTYPE(EXEC_VMALLOC),
CRASHTYPE(EXEC_RODATA),
CRASHTYPE(EXEC_USERSPACE),
CRASHTYPE(EXEC_NULL),
CRASHTYPE(ACCESS_USERSPACE),
CRASHTYPE(ACCESS_NULL),
CRASHTYPE(WRITE_RO),
CRASHTYPE(WRITE_RO_AFTER_INIT),
CRASHTYPE(WRITE_KERN),
CRASHTYPE(REFCOUNT_INC_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_OVERFLOW),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_DEC_ZERO),
CRASHTYPE(REFCOUNT_DEC_NEGATIVE),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_INC_ZERO),
CRASHTYPE(REFCOUNT_ADD_ZERO),
CRASHTYPE(REFCOUNT_INC_SATURATED),
CRASHTYPE(REFCOUNT_DEC_SATURATED),
CRASHTYPE(REFCOUNT_ADD_SATURATED),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_TIMING),
CRASHTYPE(ATOMIC_TIMING),
CRASHTYPE(USERCOPY_HEAP_SIZE_TO),
CRASHTYPE(USERCOPY_HEAP_SIZE_FROM),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_TO),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_FROM),
CRASHTYPE(USERCOPY_STACK_FRAME_TO),
CRASHTYPE(USERCOPY_STACK_FRAME_FROM),
CRASHTYPE(USERCOPY_STACK_BEYOND),
CRASHTYPE(USERCOPY_KERNEL),
CRASHTYPE(USERCOPY_KERNEL_DS),
CRASHTYPE(STACKLEAK_ERASING),
CRASHTYPE(CFI_FORWARD_PROTO),
}; 
 
시그니처
 
LKDTM을 실행한 후 덤프(vmcore)에서 받은 커널 로그를 소개합니다.
 
아래는 'echo EXCEPTION > /sys/kernel/debug/provoke-crash/DIRECT' 명령어를 실행후 받은 덤프에서 확인한 커널 로그입니다.
 
[   57.750675] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[   57.757757] pgd = ffffffc045138000
[   57.761957] [00000000] *pgd=0000000000000000
[   57.765404] Internal error: Oops: 96000045 [#1] PREEMPT SMP
[   57.770949] Modules linked in:
[   57.773996] CPU: 7 PID: 1897 Comm: sh Tainted: G    B   W    3.18.49-g9381d81-00083-gb8bb6a4-dirty #2
[   57.783215] task: ffffffc04e980000 ti: ffffffc0450ec000 task.ti: ffffffc0450ec000
[   57.790665] PC is at lkdtm_do_action+0x44/0x130
[   57.795191] LR is at direct_entry+0x100/0x128
[   57.799511] pc : [<ffffffc0006019e8>] lr : [<ffffffc000601bd4>] pstate: 80000145
[   57.806883] sp : ffffffc0450efdb0
[   57.810213] x29: ffffffc0450efdb0 x28: ffffffc0450ec000 
[   57.815480] x27: ffffffc00163b000 x26: 0000000000000040 
[   57.820774] x25: 0000000000000116 x24: 0000000000000015 
[   57.826068] x23: 0000000000000000 x22: 0000000000000003 
[   57.831370] x21: ffffffc0450efec8 x20: ffffffc02f240000 
[   57.836661] x19: 000000000000000a x18: 00000000ffffffff 
[   57.841956] x17: 0000007fa1e89344 x16: ffffffc0002ffb30 
[   57.847252] x15: 0000007fa1d1c120 x14: 0fffffffffffffff 
[   57.852549] x13: 0000000000000038 x12: 0101010101010101 
[   57.857842] x11: 7f7f7f7f7f7f7f7f x10: ff4d4e48534f4442 
[   57.863146] x9 : 7f7fffffffffffff x8 : 5045435845207972 
[   57.868430] x7 : 746e652074636572 x6 : ffffffc00186ee1f 
[   57.873727] x5 : 000000000000000a x4 : 0000000000000004 
[   57.879023] x3 : 0000000000000000 x2 : ffffffc0450ec000 
[   57.884316] x1 : ffffffc0006019d4 x0 : 0000000000000000 [   57.889316] mdss_mdp_video_wait4comp: vsync wait timeout 0, fallback to poll mode
 
이번에는 'echo WRITE_AFTER_FREE > /sys/kernel/debug/provoke-crash/DIRECT' 명령어를 실행한 다음에 받은 덤프에서 확인한 커널 로그입니다.
 
[   84.609150] lkdtm: Performing direct entry WRITE_AFTER_FREE
[   84.127146] BUG kmalloc-1024 (Tainted: G    B   W   ): Poison overwritten
[   84.133909] -----------------------------------------------------------------------------
[   84.143551] INFO: 0xffffffc00dd31600-0xffffffc00dd319fe. First byte 0x78 instead of 0x6b
[   84.151630] INFO: Allocated in lkdtm_do_action+0xd4/0x130 age=8 cpu=4 pid=4509
[   84.158827] +alloc_debug_processing+0x114/0x16c
[   84.163340] +__slab_alloc.isra.52.constprop.58+0x3f4/0x504
[   84.168805] +kmem_cache_alloc_trace+0x9c/0x1e8
[   84.173234] +lkdtm_do_action+0xd0/0x130
[   84.177053] +direct_entry+0xfc/0x128
[   84.180617] +vfs_write+0xcc/0x178
[   84.183912] +SyS_write+0x44/0x74
[   84.187132] +cpu_switch_to+0x48/0x4c
[   84.190689] INFO: Freed in lkdtm_do_action+0xdc/0x130 age=13 cpu=4 pid=4509
[   84.197629] +free_debug_processing+0x22c/0x2cc
[   84.202054] +__slab_free+0x44/0x2b0
[   84.205525] +kfree+0x1f8/0x218
[   84.208566] +lkdtm_do_action+0xd8/0x130
[   84.212387] +direct_entry+0xfc/0x128
[   84.215943] +vfs_write+0xcc/0x178
[   84.219240] +SyS_write+0x44/0x74
[   84.222454] +cpu_switch_to+0x48/0x4c
[   84.226016] INFO: Slab 0xffffffbc05d12780 objects=23 used=23 fp=0x          (null) flags=0x4080
[   84.243379] Bytes b4 ffffffc00dd315f0: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[   84.837991] Object ffffffc00dd319f0: 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
[   84.847280] Redzone ffffffc00dd31a00: bb bb bb bb bb bb bb bb                          ........
[   84.855961] Padding ffffffc00dd31b40: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[   84.884090] Padding ffffffc00dd31b70: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[   84.893467] CPU: 7 PID: 2510 Comm: Binder_10 Tainted: G    B   W    3.10.49-g9381d81-00083-gb8bb6a4-dirty #

+ Recent posts