Kernel Address Sanitizer(KASAN) 사용법

커널 개발에서 메모리 버그는 예기치 않은 동작을 일으키고, 보안 취약점으로 이어질 수 있다. 이를 해결하기 위해 Linux 커널에서는 Kernel Address Sanitizer(KASAN)라는 강력한 도구를 제공한다. 이 기능을 커널에서 활성화 하면 메모리 오류를 탐지하고 디버깅하는 데에 유용하다. 이 글에서는 KASAN을 사용하는 방법에 대해 알아본다.

KASAN?

KASAN은 Linux 커널에서 메모리 버그를 탐지하고 예방하기 위한 도구이다. AddressSanitizer(ASAN)의 커널 버전으로, 주로 메모리 오버플로우, 언더플로우, 사용 후 해제 등의 버그를 찾는 데 사용된다.

KASAN의 동작 원리

KASAN은 커널의 메모리 할당 및 해제 작업을 추적하여 메모리 버그를 탐지한다. 이를 위해 KASAN은 메모리를 여러 영역으로 나누고, 각 영역에 특수한 매직 바이트를 삽입한다. 메모리 할당 및 해제 작업이 발생하면 KASAN은 해당 영역의 매직 바이트를 확인하여 오류를 탐지한다. 오류가 발생하면 KASAN은 해당 위치를 로그에 기록하여 디버깅을 용이하게 한다.

KASAN의 사용 방법

커널 설정

먼저, 커널을 새로 빌드하여 KASAN을 활성화해야 한다. 이를 위해 CONFIG_KASAN 옵션을 활성화하고, 적절한 KASAN 옵션을 설정한다.

$ ./scripts/config --enable KASAN
$ cat .config| grep KASAN
CONFIG_HAVE_ARCH_KASAN=y
CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
CONFIG_CC_HAS_KASAN_GENERIC=y
CONFIG_KASAN=y

CONFIG_KASAN을 확인했으면, 빌드 및 설치 후 재부팅 한다.

$ make
$ sudo make modules_install install
$ sudo reboot

필자의 경우 아래와 같이 frame size 관련 에러가 발생했다.

  CC [M]  arch/x86/kvm/x86.o
arch/x86/kvm/x86.c: In function ‘kvm_arch_vm_ioctl’:
arch/x86/kvm/x86.c:6302:1: error: the frame size of 1176 bytes is larger than 1024 bytes [-Werror=frame-larger-than=]
 6302 | }
      | ^
cc1: all warnings being treated as errors
make[2]: *** [scripts/Makefile.build:297: arch/x86/kvm/x86.o] Error 1
make[1]: *** [scripts/Makefile.build:560: arch/x86/kvm] Error 2
make: *** [Makefile:1911: arch/x86] Error 2

아래와 같이 CONFIG_FRAME_WARN=1024CONFIG_FRAME_WARN=2048 로 변경하고 빌드를 이어서 진행한다.

$ ./scripts/config --set-val FRAME_WARN 2048
$ cat .config | grep FRAME_WARN
CONFIG_FRAME_WARN=2048
$ make

빌드가 완료되었으면, 아래와 같이 모듈 설치 및 커널 이미지 설치를 진행한다.

$ sudo make modules_install
$ sudo make install

커널 빌드에 대한 자세한 부분은 아래 글을 참고한다.

KASAN의 예제

아래 예제는 메모리 오버플로우를 발생시키는 간단한 코드이다. KASAN을 활성화한 상태에서 이 코드를 실행하면 메모리 오류가 감지되어 시스템 로그에 기록된다.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/slab.h>

void kasan_example(void)
{
    char *buffer = kmalloc(10, GFP_KERNEL);
    buffer[11] = 'A'; // Buffer overflow
    kfree(buffer);
}

static int __init kasan_example_init(void)
{
        pr_info("linux-modules: kasan_example_init(). \n");
        kasan_example();
        return 0;
}

static void __exit kasan_example_exit(void)
{
        pr_info("linux-modules: kasan_example_exit().\n");
}

module_init(kasan_example_init);
module_exit(kasan_example_exit);

MODULE_LICENSE("GPL");

drivers/staging/Makefile 에 아래 줄을 추가한다.

obj-m += kasan_example.o 
 
all: 
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 
 
clean: 
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

아래와 같이 빌드 한다.

$ make
  CALL    scripts/checksyscalls.sh
  CALL    scripts/atomic/check-atomics.sh
  DESCEND objtool
  DESCEND bpf/resolve_btfids
  CHK     include/generated/compile.h
  CHK     kernel/kheaders_data.tar.xz
  CC      drivers/staging/kasan_example.o
  AR      drivers/staging/built-in.a
  AR      drivers/built-in.a
  GEN     .version
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
...

아래는 빌드된 kasan_example.ko를 라즈베리파이4용으로 빌드해서 insmod 한 결과이다. kmalloc()으로 slab에서 할당 받은 영역에 대해 out-of-boundns 액세스했다는 것을 BUG()로 잡았다.

[ 5042.511729] linux-modules: kasan_example_init().
[ 5042.511785] ==================================================================
[ 5042.511807] BUG: KASAN: slab-out-of-bounds in kasan_example_init+0x54/0xff8 [kasan_example]
[ 5042.511922] Write of size 1 at addr ffffff8102f1bb0b by task insmod/2082

[ 5042.511974] CPU: 2 PID: 2082 Comm: insmod Tainted: G         C O       6.6.23-v8_kasan+ #3
[ 5042.512013] Hardware name: Raspberry Pi 4 Model B Rev 1.5 (DT)
[ 5042.512036] Call trace:
[ 5042.512053]  dump_backtrace+0x9c/0x100
[ 5042.512095]  show_stack+0x20/0x38
[ 5042.512126]  dump_stack_lvl+0x48/0x60
[ 5042.512162]  print_report+0xf4/0x5b0
[ 5042.512201]  kasan_report+0xa8/0xf8
[ 5042.512237]  __asan_store1+0x60/0x70
[ 5042.512274]  kasan_example_init+0x54/0xff8 [kasan_example]
[ 5042.512365]  do_one_initcall+0xa4/0x3d8
[ 5042.512397]  do_init_module+0x110/0x358
[ 5042.512434]  load_module+0x2958/0x2ab0
[ 5042.512470]  init_module_from_file+0xdc/0x138
[ 5042.512507]  __arm64_sys_finit_module+0x29c/0x398
[ 5042.512546]  invoke_syscall+0x68/0x198
[ 5042.512585]  el0_svc_common.constprop.0+0x80/0x150
[ 5042.512624]  do_el0_svc+0x3c/0x58
[ 5042.512659]  el0_svc+0x38/0x70
[ 5042.512693]  el0t_64_sync_handler+0x13c/0x158
[ 5042.512729]  el0t_64_sync+0x190/0x198

[ 5042.512774] Allocated by task 2082:
[ 5042.512795]  kasan_save_stack+0x2c/0x58
[ 5042.512833]  kasan_set_track+0x2c/0x40
[ 5042.512867]  kasan_save_alloc_info+0x24/0x38
[ 5042.512897]  __kasan_kmalloc+0xa0/0xb8
[ 5042.512930]  kmalloc_trace+0x68/0x130
[ 5042.512967]  kasan_example_init+0x48/0xff8 [kasan_example]
[ 5042.513049]  do_one_initcall+0xa4/0x3d8
[ 5042.513078]  do_init_module+0x110/0x358
[ 5042.513113]  load_module+0x2958/0x2ab0
[ 5042.513147]  init_module_from_file+0xdc/0x138
[ 5042.513183]  __arm64_sys_finit_module+0x29c/0x398
[ 5042.513220]  invoke_syscall+0x68/0x198
[ 5042.513255]  el0_svc_common.constprop.0+0x80/0x150
[ 5042.513294]  do_el0_svc+0x3c/0x58
[ 5042.513328]  el0_svc+0x38/0x70
[ 5042.513359]  el0t_64_sync_handler+0x13c/0x158
[ 5042.513394]  el0t_64_sync+0x190/0x198

[ 5042.513434] The buggy address belongs to the object at ffffff8102f1bb00
                which belongs to the cache kmalloc-64 of size 64
[ 5042.513464] The buggy address is located 1 bytes to the right of
                allocated 10-byte region [ffffff8102f1bb00, ffffff8102f1bb0a)

[ 5042.513512] The buggy address belongs to the physical page:
[ 5042.513531] page:00000000132eb91c refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x102f1b
[ 5042.513566] flags: 0x8000000000000800(slab|zone=2)
[ 5042.513599] page_type: 0xffffffff()
[ 5042.513635] raw: 8000000000000800 ffffff8100002280 fffffffe040bc780 0000000000000002
[ 5042.513668] raw: 0000000000000000 0000000080200020 00000001ffffffff 0000000000000000
[ 5042.513691] page dumped because: kasan: bad access detected

[ 5042.513722] Memory state around the buggy address:
[ 5042.513744]  ffffff8102f1ba00: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc
[ 5042.513772]  ffffff8102f1ba80: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[ 5042.513799] >ffffff8102f1bb00: 00 02 fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 5042.513820]                       ^
[ 5042.513841]  ffffff8102f1bb80: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
[ 5042.513868]  ffffff8102f1bc00: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 5042.513889] ==================================================================
[ 5042.514221] Disabling lock debugging due to kernel taint
[ 5043.472962] hwmon hwmon1: Undervoltage detected!

참고 사이트

답글 남기기