Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[Linux][Kernel] 슬랩 Slab Memory 디버깅(Debugging) - 1 [Linux][Kernel] MM

슬랩 디버그 컨피그를 키면 어떤 일을 더 할까요? 정리해볼께요

콜트래이스 저장
슬랩 메모리를 할당 및 해제할 때 콜트래이스를 저장해요. 이 콜트래이스는 struct track 변수에서 쓰이거든요. 우리가 알고 있는 슬랩의 종류는 30여 가지나 되죠. 이런 슬랩 메모리를 할당하고 해제할 때 콜스택을 모두 저장한다는 것은 시스템에 과부하를 주게 되요. 성능이 좋지 않은 CPU가 탑재된 타겟 디바이스에서 심하면 부팅도 제대로 못할 수도 있어요.

아래 Slab corruption으로 커널 패닉이 발생한 로그를 잠깐 봅시다. 아래 커널 로그는 lkdtm feature를 써서 WRITE_AFTER_FREE 버그를 강제로 유발시킨 건데요.

문제 발생 코드는 아래와 같아요.
(CONFIG_LKDTM을 키고 커널 이미지를 생성한 다음 "echo WRITE_AFTER_FREE > /d/provoke-crash/DIRECT" 을 실행하면 되죠.) 
static void lkdtm_do_action(enum ctype which)
{
// .. 생략..
case CT_WRITE_AFTER_FREE: {
size_t len = 1024;
u32 *data = kmalloc(len, GFP_KERNEL);

kfree(data);
schedule();
memset(data, 0x78, len);
break;
}


[  202.292579] lkdtm: Performing direct entry WRITE_AFTER_FREE
[  202.293587] BUG kmalloc-1024 (Not tainted): Poison overwritten
[  202.293620] Disabling lock debugging due to kernel taint
[  202.293634] INFO: 0xd5885a40-0xd5885e3e. First byte 0x78 instead of 0x6b
[  202.293659] INFO: Allocated in lkdtm_do_action+0xd0/0x1a8 age=0 cpu=0 pid=7421
[  202.293674] lkdtm_do_action+0xd0/0x1a8 
[  202.293689] direct_entry+0xe4/0x110
[  202.293705] vfs_write+0xd0/0x180
[  202.293719] SyS_write+0x38/0x68
[  202.293735] __sys_trace_return+0x0/0x18
[  202.293752] INFO: Freed in lkdtm_do_action+0xd8/0x1a8 age=0 cpu=0 pid=7421
[  202.293766] direct_entry+0xe4/0x110   
[  202.293779] vfs_write+0xd0/0x180
[  202.293793] SyS_write+0x38/0x68
[  202.293808] __sys_trace_return+0x0/0x18
[  202.293823] INFO: Slab 0xc3d07200 objects=26 used=26 fp=0x  (null) flags=0x4080
[  202.293837] INFO: Object 0xd5885a40 @offset=23104 fp=0xd5882f80
[  202.293837] 
[  202.293858] Bytes b4 d5885a30: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  202.293874] Object d5885a40: 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx
[  202.293888] Object d5885a50: 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78  xxxxxxxxxxxxxxxx

위 코드를 해석해볼께요.
1>슬랩 타입: kmalloc-1024임을 알 수 있죠.
2>슬랩 메모리 시작 주소: d5885a30라고 유추할 수 있어요.
3>슬랩 메모리를 해제한 정보
아래와 같이 친절하게 콜트래이스를 보여주는데요. pid와 CPU 번호까지 알려주죠.
보통 아래 콜 스트레이스에서 범인을 찾는 경우가 많아요. 논리적으로 free하면 안 되는데, free를 하고 그 슬랩 메모리에 접근하는 거죠.
[  202.293752] INFO: Freed in lkdtm_do_action+0xd8/0x1a8 age=0 cpu=0 pid=7421
[  202.293766] direct_entry+0xe4/0x110   
[  202.293779] vfs_write+0xd0/0x180
[  202.293793] SyS_write+0x38/0x68
[  202.293808] __sys_trace_return+0x0/0x18


유사 샘플 코드를 한번 짜 볼까요?
아래와 같이 현재 프로세스가 ActivityManager일 때 메모리를 Free하고 return문으로 함수에서
퇴장해야 하는데, 다시 strcpy()를 하고 있죠.
// 자 메모리 할당
data = kmalloc(len, GFP_KERNEL);
// .. 생략..

if ( !strcmp("ActivityManager", current->comm) ) {
free(data);
+ return;
             }

schedule();
// .. 조금 후..
strcpy(data, "slab");

위 코드를 보면서 난 절대 저런 바보 같은 실수는 안해! 라고 어떤 분이 말하실 지 모르겠습니다.
음, 그런데 제 생각에는 코드에 논리적 흐름를 구현하는 순간 어떤 코드도 버그를 유발할 수 있다고 생각해요.
심하게 말하면 "코딩 = 버그"라고 말할 수 있죠. 

아무리 코드를 잘 짜도 IRQ가 언제, 어떤 패턴으로 뜰지, 서로 다른 머신(x86/ARM64)에서 Cache Invalidate는 어떻게 될지.
예상할 수 있나요? 아무리 드라이버를 구조에 맞게 잘 짜도 신뢰성 테스트로 검증되지 않은 코드는 별로 신뢰하지 않습니다.

4> 슬랩 메모리를 할당한 정보
아래는 슬랩 메모리 할당 시 정보에요. 위와 동일한 정보죠.
[  202.293659] INFO: Allocated in lkdtm_do_action+0xd0/0x1a8 age=0 cpu=0 pid=7421
[  202.293674] lkdtm_do_action+0xd0/0x1a8 
[  202.293689] direct_entry+0xe4/0x110
[  202.293705] vfs_write+0xd0/0x180
[  202.293719] SyS_write+0x38/0x68
[  202.293735] __sys_trace_return+0x0/0x18

위 로그는 check_bytes_and_report -> print_trailer 순으로 호출되어  아래 코드에서 찍어 줍니다.
static void print_tracking(struct kmem_cache *s, void *object) {
if (!(s->flags & SLAB_STORE_USER))
return;

print_track("Allocated", get_track(s, object, TRACK_ALLOC));
print_track("Freed", get_track(s, object, TRACK_FREE));


static void print_trailer(struct kmem_cache *s, struct page *page, u8 *p)
{
unsigned int off; /* Offset of last byte */
u8 *addr = page_address(page);

print_tracking(s, p);


핑백

덧글

댓글 입력 영역