커널 패닉 원인 찾는 방법

2025. 12. 9. 22:42Linux/리눅스 커널 모듈

/ # ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
[  291.469783] BUG: kernel NULL pointer dereference, address: 0000000000000000
[  291.472826] #PF: supervisor read access in kernel mode
[  291.473058] #PF: error_code(0x0000) - not-present page
[  291.473268] PGD 5c10067 P4D 5c10067 PUD 5c9e067 PMD 0 
[  291.473883] Oops: Oops: 0000 [#1] PREEMPT SMP NOPTI
[  291.474263] CPU: 0 PID: 85 Comm: ping Not tainted 6.10.8 #1
[  291.474458] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ub4
[  291.474692] RIP: 0010:netif_rx_internal+0xaa/0x140
[  291.475407] Code: 44 24 20 eb a0 48 8d 45 e0 48 89 45 d8 e8 3e 21 42 ff e8 39 21 45
[  291.475800] RSP: 0018:ffffc9000061b6f8 EFLAGS: 00000206
[  291.475914] RAX: 0000000000000000 RBX: 0000000000000054 RCX: 0000000000000000
[  291.476018] RDX: ffffc9000061b6f8 RSI: ffff888004d5e400 RDI: ffff888004ef0000
[  291.476120] RBP: ffffc9000061b720 R08: ffff888004daaf02 R09: 0000000000000000
[  291.476233] R10: ffffc9000061b7d8 R11: 000000003f04bff0 R12: ffff888004d5e400
[  291.476341] R13: ffff888004ef0000 R14: 0000000000000000 R15: ffff888004d5e400
[  291.476491] FS:  000000000c4098c0(0000) GS:ffff888007a00000(0000) knlGS:00000000000
[  291.476623] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  291.476720] CR2: 0000000000000000 CR3: 0000000005c16000 CR4: 00000000000006f0
[  291.476935] Call Trace:
[  291.477278]  <TASK>
[  291.477616]  ? show_regs+0x6b/0x80
[  291.477756]  ? __die_body+0x24/0x70
[  291.477822]  ? __die+0x2f/0x40
[  291.477905]  ? page_fault_oops+0x15c/0x550
[  291.478119]  ? netif_rx_internal+0xaa/0x140
[  291.478207]  ? search_bpf_extables+0x51/0x60
[  291.478301]  ? netif_rx_internal+0xaa/0x140
[  291.478384]  ? search_exception_tables+0x63/0x70
[  291.478507]  ? kernelmode_fixup_or_oops.isra.0+0x61/0x80
[  291.478600]  ? __bad_area_nosemaphore+0x176/0x2a0
[  291.478689]  ? bad_area_nosemaphore+0x16/0x20
[  291.478765]  ? do_user_addr_fault+0x317/0x820
[  291.478851]  ? exc_page_fault+0x7c/0x180
[  291.478926]  ? asm_exc_page_fault+0x2b/0x30
[  291.479027]  ? netif_rx_internal+0xaa/0x140
[  291.479104]  ? netif_rx_internal+0x97/0x140
[  291.479193]  __netif_rx+0x1c/0xc0
[  291.479336]  loopback_xmit+0xe3/0x140
[  291.479410]  dev_hard_start_xmit+0x8c/0x1e0
[  291.479491]  __dev_queue_xmit+0x89e/0xdb0
[  291.479561]  ? _raw_write_unlock_bh+0x1e/0x30
[  291.479644]  ? ___neigh_create+0x6d3/0x8e0
[  291.479729]  neigh_resolve_output+0x116/0x1c0
[  291.479824]  ip_finish_output2+0x1a9/0x5a0
[  291.479900]  __ip_finish_output+0x216/0x310
[  291.479963]  ? sysvec_apic_timer_interrupt+0x5a/0xb0
[  291.480052]  ip_finish_output+0x32/0x110
[  291.480114]  ip_output+0x65/0xf0
[  291.480171]  ? __pfx_ip_finish_output+0x10/0x10
[  291.480267]  ip_local_out+0x62/0x70
[  291.480335]  ip_send_skb+0x1d/0x50
[  291.480391]  ip_push_pending_frames+0x37/0x40
[  291.480474]  raw_sendmsg+0x773/0xf30
[  291.480550]  ? __wake_up+0x17/0x20
[  291.480651]  ? redirected_tty_write+0x88/0xd0
[  291.480919]  ? aa_sk_perm+0x49/0x210
[  291.480992]  inet_sendmsg+0x78/0x80
[  291.481059]  ? __pfx_raw_sendmsg+0x10/0x10
[  291.481119]  ? inet_sendmsg+0x78/0x80
[  291.481188]  __sock_sendmsg+0xa2/0xc0
[  291.481261]  __sys_sendto+0x121/0x1b0
[  291.481345]  ? do_syscall_64+0x7b/0x110
[  291.481424]  ? __pfx_read_tsc+0x10/0x10
[  291.481484]  ? ktime_get_ts64+0x54/0x140
[  291.481558]  __x64_sys_sendto+0x2d/0x40
[  291.481638]  x64_sys_call+0x2029/0x20c0
[  291.481711]  do_syscall_64+0x6f/0x110
[  291.481767]  ? syscall_exit_to_user_mode+0x7e/0x1a0
[  291.481861]  ? do_syscall_64+0x7b/0x110
[  291.481922]  ? do_sock_setsockopt+0xbd/0x180
[  291.481996]  ? __sys_setsockopt+0x78/0xc0
[  291.482059]  ? syscall_exit_to_user_mode+0x7e/0x1a0
[  291.482136]  ? do_syscall_64+0x7b/0x110
[  291.482214]  ? irqentry_exit_to_user_mode+0x5a/0x170
[  291.482303]  ? irqentry_exit+0x3f/0x50
[  291.482364]  ? exc_page_fault+0x8d/0x180
[  291.482455]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
[  291.482609] RIP: 0033:0x4a1c4a
[  291.482906] Code: 48 c7 c0 ff ff ff ff eb bc 0f 1f 80 00 00 00 00 f3 0f 1e fa 41 8c
[  291.483142] RSP: 002b:00007ffc1c8a8478 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
[  291.483251] RAX: ffffffffffffffda RBX: 0000000000000040 RCX: 00000000004a1c4a
[  291.483357] RDX: 0000000000000040 RSI: 000000000c40bcd0 RDI: 0000000000000000
[  291.483453] RBP: 0000000000538a88 R08: 0000000000614b98 R09: 000000000000001c
[  291.483550] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc1c8a8550
[  291.483628] R13: 000000000c40abb4 R14: 0000000000000002 R15: 0000000000000000
[  291.483921]  </TASK>
[  291.484042] Modules linked in:
[  291.484311] CR2: 0000000000000000
[  291.484627] ---[ end trace 0000000000000000 ]---
[  291.484805] RIP: 0010:netif_rx_internal+0xaa/0x140
[  291.484964] Code: 44 24 20 eb a0 48 8d 45 e0 48 89 45 d8 e8 3e 21 42 ff e8 39 21 45
[  291.485418] RSP: 0018:ffffc9000061b6f8 EFLAGS: 00000206
[  291.485518] RAX: 0000000000000000 RBX: 0000000000000054 RCX: 0000000000000000
[  291.485661] RDX: ffffc9000061b6f8 RSI: ffff888004d5e400 RDI: ffff888004ef0000
[  291.485723] RBP: ffffc9000061b720 R08: ffff888004daaf02 R09: 0000000000000000
[  291.485780] R10: ffffc9000061b7d8 R11: 000000003f04bff0 R12: ffff888004d5e400
[  291.485838] R13: ffff888004ef0000 R14: 0000000000000000 R15: ffff888004d5e400
[  291.485897] FS:  000000000c4098c0(0000) GS:ffff888007a00000(0000) knlGS:00000000000
[  291.485960] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  291.486003] CR2: 0000000000000000 CR3: 0000000005c16000 CR4: 00000000000006f0
[  291.486149] Kernel panic - not syncing: Fatal exception in interrupt
[  291.486719] Kernel Offset: disabled
[  291.486890] ---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---

 

 

커널 네트워크 스택(RPS/RFS 등)을 건드리는 연구를 하던 중, 가장 기초적인 루프백 테스트(ping 127.0.0.1)에서 커널 패닉이 발생했다. 커널 로그를 분석하며 원인을 좁혀가는 과정이다.

1. 문제 상황

QEMU 환경에서 부팅 후 로컬 호스트로 핑을 날리자마자 커널이 멈췄다.

 
# ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
[  291.469783] BUG: kernel NULL pointer dereference, address: 0000000000000000
[  291.474263] CPU: 0 PID: 85 Comm: ping Not tainted 6.10.8 #1
[  291.474692] RIP: 0010:netif_rx_internal+0xaa/0x140

2. 로그 분석

막막해 보이지만 커널 로그는 생각보다 친절했다. 다음 4가지를 확인했다.

1) Error

BUG: kernel NULL pointer dereference, address: 0000000000000000

커널 코드 내부에서 NULL(0번지) 주소를 참조하려다 걸렸다. 즉, 초기화되지 않은 포인터나 구조체 멤버에 접근했다는 뜻이다.

2) Location

RIP: 0010:netif_rx_internal+0xaa/0x140

범인은 netif_rx_internal 함수다. 정확히는 함수 시작점에서 0xaa (170바이트) 떨어진 지점이다. 이 함수는 패킷을 수신해서 어떤 CPU 큐에 넣을지 결정(RPS)하고 인큐(Enqueue)하는 역할을 한다.

3) Call Trace

Call Trace를 역추적(Bottom-up)해보면 흐름이 보인다.

[  291.479193]  __netif_rx+0x1c/0xc0            <-- (3) 수신 처리
[  291.479336]  loopback_xmit+0xe3/0x140        <-- (2) Loopback 전송
[  291.479410]  dev_hard_start_xmit+0x8c/0x1e0  <-- (1) 전송 시작
...
[  291.480474]  raw_sendmsg+0x773/0xf30         <-- (0) Ping 메시지 발송

[흐름 재구성]

  1. ping이 소켓을 통해 패킷을 보냄.
  2. 목적지가 127.0.0.1이라 Loopback Driver가 작동.
  3. Loopback의 특성: "보낸 패킷을 즉시 내가 다시 받음". 따라서 xmit(전송) 함수 안에서 바로 netif_rx(수신)를 호출함.
  4. 수신 로직인 netif_rx_internal 내부에서 NULL 포인터를 건드려 사망.

 

3. 정확한 위치 찾기 / faddr2line

vmlinux 이미지가 있는 곳에서 다음 명령어를 실행하면, C코드의 몇 번째 줄인지 알려준다.

./scripts/faddr2line vmlinux netif_rx_internal+0xaa

 

확인해 보니 당연하게도 내가 추가한 RPS 관련 구조체 포인터를 참조하는 라인이었다.

 

 

 

4. 원인 분석

1) 커널 모듈이 로드되지 않았다

내가 작성한 커널 코드는 active_pkt_steer_ops라는 전역 포인터를 사용하여 커스텀 모듈의 함수를 호출하도록 설계되어 있다. 이 포인터는 커스텀 모듈이 insmod 될 때 get_rps_cpu() 함수의 주소값으로 채워진다.

하지만 테스트 당시 모듈을 로드하지 않은 상태에서 ping을 시도했다.

2) 방어 코드의 부재 (The Missing Check)

C언어에서 전역 변수는 초기화하지 않으면 자동으로 NULL(0)이 된다.

// [문제의 코드 흐름]
// 1. 모듈이 없으므로 NULL 상태
struct pkt_steer_ops *ops = rcu_dereference(active_pkt_steer_ops); 

// 2. ops가 NULL인지 검사하지 않고 바로 멤버 함수 호출 시도
// -> 0번지(NULL)에 있는 get_rps_cpu를 찾으려다 커널 패닉 발생!
cpu = ops->get_rps_cpu(skb->dev, skb, &rflow); 

모듈이 없을 수도 있다는 예외 상황을 고려하지 않고, 무작정 ops->를 호출한 것이 문제였다.


5. 해결 방법

두 가지 방법으로 수정할 수 있다.

방법 1. 방어적인 코드 추가

ops 포인터가 NULL일 경우를 대비해 예외 처리를 추가한다. 모듈이 로드되지 않았다면 기존 커널의 기본 함수(get_rps_cpu)를 사용하도록 흐름을 바꾸면 된다.

ops = rcu_dereference(active_pkt_steer_ops);

/* [FIX] ops가 NULL인지 먼저 확인 */
if (!ops) {
    // 모듈이 없으면: 기존 커널 기본 함수 사용 (Fallback)
    cpu = get_rps_cpu(skb->dev, skb, &rflow); 
} else {
    // 모듈이 있으면: 커스텀 함수 사용
    cpu = ops->get_rps_cpu(skb->dev, skb, &rflow);
}

방법 2. 초기화

애초에 포인터가 NULL이 되지 않도록, 선언 시점에 기본값을 지정해 주는 방법이다.

// [수정 전] 초기화 없음 -> NULL
// struct pkt_steer_ops __rcu *active_pkt_steer_ops;

// [수정 후] 기본 동작을 수행하는 구조체 주소로 초기화
struct pkt_steer_ops __rcu *active_pkt_steer_ops = (struct pkt_steer_ops __rcu *)&pkt_standard_ops;

 

최종 결과

코드를 수정하고 커널을 재빌드한 후, 모듈을 로드하지 않은 상태에서도 QEMU 상에서 ping 127.0.0.1이 정상적으로 동작함을 확인했다. 커널 패닉 없이 처리가 이루어졌다.