2026년 2월 13일 금요일

syzbot, qemu, gdb를 사용하여 linux kernel의 버그 수정

 공개된 Syzbot 버그를 통해 보고서부터 시작하여 제공된 환경을 사용하여 원래 충돌을 확인하고 영향을 받는 커널 버전을 로컬에서 다시 빌드한 다음, 상위 버전 수정 사항을 적용한 후 문제가 해결되었는지 확인하는 절차를 알아보자.

목표는 근본 원인을 추적하거나 패치하는 것이 아니다. 버그를 로컬 환경에서 실행하여 수정 전후의 상태를 확인하는 것이다.


Linux Kernel 공개 버그

리눅스 커널에서 공개된 버그를 찾는 방법은 여러가지 이다.

  • BugZilla
  • Syzbot
  • Syzkaller-bugs
  • Changelogs
  • Git log


BugZilla

BugZilla는 리눅스 커널의 버그를 보고하는 표준적인 방법이다. subsystem 별로 정리된 흥미로운 취약점(예: IPv4 및 IPv6 네트워킹, ext* 파일 시스템 등)을 확인할 수 있으며 표준 검색 또는 고급 검색을 통해 overflow, heap, UAF 등의 키워드로 검색할 수도 있다. 개인적으로 아쉬운 점은 취약점이 아니거나 시스템 멈춤 현상 등이 많이 섞여 있다는 것이다. 또 검색 옵션이 그닥 강력하진 않다. 그럼에도 불구하고 BugZilla는 여전히 유용한 도구이다.


Syzbot

syzbot은 syzkaller fuzzer 를 기반으로 한 지속적인 fuzzing / reporting system이다. 많은 미해결 및 수정된 커밋, 취약점, 재현코드를 확인할 수 있다. 하지만 검색 기능이 부족하고 미해결(Open section) 섹션에 오탐이 많다는 단점이 있다.


다음은 syzbot bug를 로컬에서 재현하고 패치를 검증하는 전체 워크플로우이다. 

1단계: syzbot 리포트 선택

  • 필요한 파일들: repro.c, bzImage, disk.raw, vmlinux
  • 이미 upstream 패치가 머지된 버그 선택 (검증 가능하도록)

2단계: 원본 이미지로 버그 재현 (QEMU)

  • syzbot이 제공하는 bzImage와 disk.raw로 VM 부팅
  • reproducer 실행
  • syzbot 리포트와 동일한 crash 발생 확인

3단계: 로컬에서 커널 빌드

  • mainline 저장소 clone
  • 버그가 리포트된 커밋으로 checkout
  • syzbot의 .config 사용 + make olddefconfig
  • debug info 포함해서 bzImage 빌드

4단계: 로컬 빌드 커널로 재현 (QEMU)

  • 직접 빌드한 bzImage + 동일한 disk.raw로 부팅
  • 같은 reproducer 실행
  • 동일한 crash 발생 확인 (로컬 빌드가 정상인지 검증)

5단계: 패치 적용 및 재빌드

  • 수정 커밋으로 checkout
  • 동일한 config로 커널 재빌드

6단계: 패치된 커널 테스트

  • 패치된 커널로 부팅
  • reproducer 다시 실행
  • crash가 사라졌는지 확인 ← 패치 검증 완료

7단계: (선택) CLion 디버거

  • QEMU에 -s -S 옵션으로 GDB stub 활성화
  • CLion에서 vmlinux에 연결, 브레이크포인트 설정
  • crash 지점이나 패치 부분의 런타임 흐름 관찰


Syzkaller-bugs(google groups)

syz-bot에 검색 기능이 없다는 점은 syzkaller-bugs로 훌륭하게 보완되었다. 이 그룹에서는 syz-bot에 보고된 버그들을 댓글과 추가 정보와 함께 확인할 수 있다. 


Changelogs

특정 커널 버전의 모든 변경로그를 다운로드 받아 확인하는 방법이있다. 

URL=https://cdn.kernel.org/pub/linux/kernel/v4.x/ && curl $URL | grep "ChangeLog-4.9" | grep -v '.sign' | cut -d "\"" -f 2 | while read line; do wget "$URL/$line"; done

모든 변경로그가 다운로드 되면 grep을 사용하여 UAF, OOB 와 같은 키워드를 검색할 수 있다. 

grep -A5 -B5 UAF *


Git log

Change log와 유사한 접근법으로 Github 저장소를 복제하고 커밋 기록에서 중요한 키워드를 검색하는 것이다. 다음 명령어로 변경로그를 파일에 저장하고 git.log 파일에서 grep으로 아까와 같은 작업을 수행할 수 있다.

git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd linux-stable
git checkout -f <TAG -> # e.g. git checkout -f v4.9.316 (from https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git)
git log > ../git.log



syzbot 버그 리포트 이해하기

업스트림 수정사항이 포함된 syzbot report를 선택한다.

syzbot Fiexed tab에서 이미 수정되고 머지된 버그를 찾을 수 있다. (정렬: Reported date 오름차순 → 최신 버그 우선)


리눅스 커널 구조에서 upstream 커널은 원본 소스 코드 저장소를 의미

Linus Torvalds (mainline)     ← 최상위 upstream
        ↓
    linux-next                ← 다음 버전 통합 테스트
        ↓
  서브시스템 트리들            ← net, mm, fs 등 각 영역별
        ↓
  배포판 커널                  ← Ubuntu, Fedora, RHEL 등
        ↓
  회사/개인 커스텀 커널        ← 최하위 downstream

버그 선택의 세가지 조건이 있다.

  • C reproducer 가 있음
    • 버그를 실제로 트리거할 수 있어야함
  • bzImage, vmlinux, disk, raw 제공
    • VM에서 동일한 환경 재현이 가능해야함
  • 이미 mainline 에 머지됨
    • 패치 전/후가 비교가능해야함


선택한 버그: general protection fault in dev_set_group

rtnl_create_link()에서 NULL 포인터 역참조 버그를 선택했다. (network subsystem)



Fix 커밋은 다음과 같다. feafc73f3e6a - "net: prevent a NULL deref in rtnl_create_link()"


// 수정 전 (버그)
- dev_set_group(dev, nla_get_u32(tb[IFLA_GROUP]));

// 수정 후 (수정됨)
+ netif_set_group(dev, nla_get_u32(tb[IFLA_GROUP]));
  • 변경사항: dev_set_group() → netif_set_group() 으로 교체



그리고 상위 커밋에서 수정이 병합되어 메인라인에 반영되었음을 확인할 수 있었다.



diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index f9a35bdc58ad2a..c57692eb8da9d4 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -3671,7 +3671,7 @@ struct net_device *rtnl_create_link(struct net *net, const char *ifname,
 	if (tb[IFLA_LINKMODE])
 		dev->link_mode = nla_get_u8(tb[IFLA_LINKMODE]);
 	if (tb[IFLA_GROUP])
-		dev_set_group(dev, nla_get_u32(tb[IFLA_GROUP]));
+		netif_set_group(dev, nla_get_u32(tb[IFLA_GROUP]));
 	if (tb[IFLA_GSO_MAX_SIZE])
 		netif_set_gso_max_size(dev, nla_get_u32(tb[IFLA_GSO_MAX_SIZE]));
 	if (tb[IFLA_GSO_MAX_SEGS])


로컬 작업 공간 설정

버그를 선택했으면 로컬 작업 공간에서 syzbot이 제공하는 파일들을 다운받아 crash 가 발생한 환경을 구성한다.

작업에 필요한 파일은 다음과 같다.

# C reproducer
wget -O repro.c https://syzkaller.appspot.com/x/repro.c?x=15947c82580000

# Kernel config
wget -O .config https://syzkaller.appspot.com/x/.config?x=262b2977ef00756b

# Optional: syzkaller binary reproducer
wget -O repro.syz https://syzkaller.appspot.com/x/repro.syz?x=1122780c580000

# bzImage
wget https://storage.googleapis.com/syzbot-assets/001a9016bbc6/bzImage-b56bbaf8.xz

# vmlinux (used for debugging and symbol resolution)
wget https://storage.googleapis.com/syzbot-assets/531e9b9a7cff/vmlinux-b56bbaf8.xz

# Disk image
wget https://storage.googleapis.com/syzbot-assets/dfc603506ef3/disk-b56bbaf8.raw.xz


압축파일을 해제하자

unxz bzImage-b56bbaf8.xz
unxz vmlinux-b56bbaf8.xz
unxz disk-b56bbaf8.raw.xz

압축 해제 후 다음 파일들이 있어야한다.

├── .config
├── bzImage-b56bbaf8
├── disk-b56bbaf8.raw
├── repro.c
├── repro.syz
└── vmlinux-b56bbaf8


QEMU 에서 syzbot kernel을 부팅한다.

syzbot에 보고된 버그를 재현하려면 먼저 syzbot 이 crash를 발생시키는 데 사용한 것과 동일한 커널 및 디스크 이미지를 실행해야한다. syzbot CI 설정에는 스냅샷, virtio 장치 및 더 많은 자동화 계층이 포함될 수 있지만 최소한의 qemu 구성만으로도 문제를 재현하고 분석하기에 충분한 경우가 많다. 

다음 명령어를 통해 준비해둔 커널 이미지와 디스크이미지를 vm으로 부팅해보자.

qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35,accel=kvm \
  -cpu host \
  -kernel bzImage-b56bbaf8 \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=disk-b56bbaf8.raw,format=raw \
  -nographic \
  -enable-kvm \
  -netdev user,id=net0,hostfwd=tcp::10022-:22 \
  -device e1000,netdev=net0

(참고: .raw 디스크 이미지는 기본적으로 쓰기 가능하다. guest 운영체제 내부에서 변경한 내용은 재부팅 후에도 유지된다. syzkaller 의 상태 비저장 CI 환경을 모방하려면 드라이버 인수에 snapshot=on을 추가하여 스냅샷 모드를 활성화할 수 있다.

-drive file=disk-b56bbaf8.raw,format=raw,snapshot=on

이 옵션은 종료 시 모든 변경 사항을 삭제하는 임시 오버레이를 사용하여 가상 머신을 실행한다. 깨끗하고 재현 가능한 테스트 실행을 위해 이 옵션을 사용하면된다.


개발자는 패치 제출 시 `#syz test:` 줄을 추가하여 syzbot 에게 수정사항의 유효성을 검증하도록 요청할 수도 있다. (참고: syzbot 문서) 하지만 수정 전후에 로컬 환경에서 버그를 검증하는 것은 커널 개발의 기본이다.


가상 머신이 부팅되고 프롬프트가 나타나면 다음 단계는 재현 프로그램을 실행하여 버그가 발생하는지 확인하는 것이다. 


Reproducer 실행 (syzbot image)

수정 사항을 테스트하기 전에 syzbot 이 제공한 원래 환경에서 버그가 재현되는지 확인하는 것이 필수적이다. 

이 단계를 통해 설정이 올바른지 검증하고 syzkaller CI 에 의존하지 않고도 충돌을 관찰할 수 있는지 확인한다. 


syzkaller report는 일반적으로 두가지 유형의 재현도구를 제공한다. 하나는 repro.c 이고 다른 하나는 syz-execprog 권한으로 실행되도록 설계된 바이너리 형식 프로그램 (repro.syz) 이다. 


먼저 호스트에서 c reproducer 코드를 static 으로 컴파일한다.

gcc -static -O2 -o repro repro.c

다음으로 .syz 프로그램을 실행하는데 필요한 바이너리를 준비한다. 

git clone https://github.com/google/syzkaller.git
cd syzkaller
make TARGETOS=linux TARGETARCH=amd64

cp bin/linux_amd64/syz-execprog ~/syz/dev_set_group/assets/
cp bin/linux_amd64/syz-executor ~/syz/dev_set_group/assets/

그 다음 컴파일된 reproducer 바이너리와 .syz 프로그램 파일을 포함한 파일을 게스트 vm으로 전송한다. QEMU 가 포트 포워딩 (호스트: 10022 -> 게스트:22) 으로 실행중일 때 다음 명령어로 복사할 수 있다.

scp -P 10022 \
  -o UserKnownHostsFile=/dev/null \
  -o StrictHostKeyChecking=no \
  ./syz-execprog \
  ./syz-executor \
  ./repro \
  ./repro.syz \
  root@127.0.0.1:/root/

디스크 이미지는 쓰기 가능하므로 디스크 이미지가 재설정되거나 교체되지 않는 한 재부팅이나 시스템 충돌 후에도 이러한 파일은 제자리에 유지된다. 가상 머신을 원래의 .raw.xz 파일의 깨끗한 복사본에서 다시 시작하는 경우에만 파일을 다시 전송해야한다. 


QEMU console이나 SSH를 통해 게스트 운영체제에 접속한 후 다음 명령어로 reproducer 를 실행해본다. 

./repro

또는 다음 명령어를 통해 syzkaller 의 런타임을 사용하여 실행하는 방법도 있다. 이는 syzkaller 가 구성한 것과 동일한 시스템 호출 시퀀스를 실행한다. 

./syz-execprog -enable=all -repeat=0 -procs=6 ./repro.syz

콘솔 출력에서 crash 가 성공적으로 재현되었음을 확인할 수 있다. 주요 세부 정보는 원래 보고서와 일치한다.

  • rtnl_create_link+0x748/0xd10에서 general protection fault 발생
  • 동일 경로에서 NULL 포인터 역참조에 대한 KASAN 경고 발생
  • 일치하는 커널 버전: 6.15.0-syzkaller-07817-gb56bbaf8c9ff
# ./repro
executing program
[23510.795939][ T6164] netlink: 'repro': attribute type 1 has an invalid length.
[23510.798021][ T6164] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000055: 0000 [#1] SMP KASAN NOPTI
[23510.800434][ T6164] KASAN: null-ptr-deref in range [0x00000000000002a8-0x00000000000002af]
[23510.801971][ T6164] CPU: 1 UID: 0 PID: 6164 Comm: repro Not tainted 6.15.0-syzkaller-07817-gb56bbaf8c9ff #0 PREEMPT(full) 
[23510.803990][ T6164] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-9.fc43 06/10/2025
[23510.805562][ T6164] RIP: 0010:dev_set_group+0xc0/0x230
[23510.806568][ T6164] Code: 03 43 80 3c 2f 00 74 08 48 89 df e8 4a 40 d4 f8 41 bd a8 02 00 00 4c 03 2b 4c 89 e8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <80> 3c 08 00 74 08 4c 89 ef e8 22 40 d4 f8 31 c0 49 83 f
[23510.809877][ T6164] RSP: 0018:ffffc90006d4ee60 EFLAGS: 00010206
[23510.810897][ T6164] RAX: 0000000000000055 RBX: ffff88804e600008 RCX: dffffc0000000000
[23510.812230][ T6164] RDX: 0000000000000000 RSI: 0000000055690764 RDI: ffff88804e600000
[23510.813701][ T6164] RBP: ffff88804e600000 R08: 0000000000400dc0 R09: 00000000ffffffff
[23510.815135][ T6164] R10: dffffc0000000000 R11: fffffbfff1f41637 R12: 0000000000000000
[23510.816566][ T6164] R13: 00000000000002a8 R14: 1ffff11009cc0198 R15: 1ffff11009cc0001
[23510.818108][ T6164] FS:  00000000224f93c0(0000) GS:ffff8880ec466000(0000) knlGS:0000000000000000
[23510.819731][ T6164] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[23510.820849][ T6164] CR2: 0000200000001140 CR3: 000000004e6a3000 CR4: 0000000000352ef0
[23510.822474][ T6164] Call Trace:
[23510.823162][ T6164]  <TASK>
[23510.823748][ T6164]  rtnl_create_link+0x748/0xd10
[23510.824805][ T6164]  rtnl_newlink_create+0x25c/0xb00
[23510.825790][ T6164]  ? __pfx_aa_get_newest_label+0x10/0x10
[23510.827214][ T6164]  ? __pfx_rtnl_newlink_create+0x10/0x10
[23510.828318][ T6164]  ? rtnl_newlink+0x8db/0x1c70
[23510.829249][ T6164]  ? __pfx___mutex_lock+0x10/0x10
[23510.830238][ T6164]  ? ns_capable+0x8a/0xf0
[23510.831085][ T6164]  rtnl_newlink+0x16d6/0x1c70
[23510.831999][ T6164]  ? trace_ma_write+0x87/0x1f0
[23510.832923][ T6164]  ? __pfx_rtnl_newlink+0x10/0x10
[23510.833880][ T6164]  ? __lock_acquire+0xab9/0xd20
[23510.834831][ T6164]  ? __lock_acquire+0xab9/0xd20
[23510.835819][ T6164]  ? is_bpf_text_address+0x26/0x2b0
[23510.836888][ T6164]  ? is_bpf_text_address+0x292/0x2b0
[23510.838060][ T6164]  ? is_bpf_text_address+0x26/0x2b0
[23510.839087][ T6164]  ? kernel_text_address+0xa5/0xe0
[23510.840058][ T6164]  ? __lock_acquire+0xab9/0xd20
[23510.840995][ T6164]  ? __pfx_rtnl_newlink+0x10/0x10
[23510.841989][ T6164]  rtnetlink_rcv_msg+0x7cf/0xb70
[23510.842931][ T6164]  ? rtnetlink_rcv_msg+0x1ab/0xb70
[23510.843904][ T6164]  ? __pfx_rtnetlink_rcv_msg+0x10/0x10
[23510.845167][ T6164]  netlink_rcv_skb+0x208/0x470
[23510.846093][ T6164]  ? __pfx_rtnetlink_rcv_msg+0x10/0x10
[23510.847154][ T6164]  ? __pfx_netlink_rcv_skb+0x10/0x10
[23510.848186][ T6164]  ? netlink_deliver_tap+0x2e/0x1b0
[23510.849150][ T6164]  ? netlink_deliver_tap+0x2e/0x1b0
[23510.850168][ T6164]  netlink_unicast+0x75b/0x8d0
[23510.851115][ T6164]  netlink_sendmsg+0x805/0xb30
[23510.852083][ T6164]  ? __pfx_netlink_sendmsg+0x10/0x10
[23510.853099][ T6164]  ? aa_sock_msg_perm+0x94/0x160
[23510.853943][ T6164]  ? bpf_lsm_socket_sendmsg+0x9/0x20
[23510.855337][ T6164]  ? __pfx_netlink_sendmsg+0x10/0x10
[23510.856346][ T6164]  __sock_sendmsg+0x219/0x270
[23510.857360][ T6164]  ____sys_sendmsg+0x505/0x830
[23510.858178][ T6164]  ? __pfx_____sys_sendmsg+0x10/0x10
[23510.859158][ T6164]  ? import_iovec+0x74/0xa0
[23510.860007][ T6164]  ___sys_sendmsg+0x21f/0x2a0
[23510.860909][ T6164]  ? __pfx____sys_sendmsg+0x10/0x10
[23510.861931][ T6164]  ? count_memcg_event_mm+0x92/0x3b0
[23510.862983][ T6164]  ? __pfx_count_memcg_event_mm+0x10/0x10
[23510.864089][ T6164]  __x64_sys_sendmsg+0x19b/0x260
[23510.865017][ T6164]  ? __pfx___x64_sys_sendmsg+0x10/0x10
[23510.866021][ T6164]  ? do_user_addr_fault+0xc8a/0x1390
[23510.867031][ T6164]  ? do_syscall_64+0xbe/0x3b0
[23510.867800][ T6164]  do_syscall_64+0xfa/0x3b0
[23510.868633][ T6164]  ? lockdep_hardirqs_on+0x9c/0x150
[23510.869662][ T6164]  ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
[23510.870865][ T6164]  ? clear_bhb_loop+0x60/0xb0
[23510.871847][ T6164]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[23510.873056][ T6164] RIP: 0033:0x41249d
[23510.873857][ T6164] Code: 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 f0 ff ff ff f7 d8 8
[23510.877455][ T6164] RSP: 002b:00007ffc0d1daa28 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
[23510.879134][ T6164] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 000000000041249d
[23510.880654][ T6164] RDX: 0000000000000000 RSI: 0000200000000280 RDI: 0000000000000003
[23510.882182][ T6164] RBP: 00007ffc0d1daad0 R08: 0000000000000000 R09: 0000000000000000
[23510.883574][ T6164] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc0d1dab48
[23510.885175][ T6164] R13: 00007ffc0d1dab58 R14: 0000000000000002 R15: 00000000004a1e20
[23510.886830][ T6164]  </TASK>
[23510.887493][ T6164] Modules linked in:
[23510.888583][ T6164] ---[ end trace 0000000000000000 ]---
[23510.890683][ T6164] RIP: 0010:dev_set_group+0xc0/0x230
[23510.891880][ T6164] Code: 03 43 80 3c 2f 00 74 08 48 89 df e8 4a 40 d4 f8 41 bd a8 02 00 00 4c 03 2b 4c 89 e8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <80> 3c 08 00 74 08 4c 89 ef e8 22 40 d4 f8 31 c0 49 83 f
[23510.895886][ T6164] RSP: 0018:ffffc90006d4ee60 EFLAGS: 00010206
[23510.897220][ T6164] RAX: 0000000000000055 RBX: ffff88804e600008 RCX: dffffc0000000000
[23510.899535][ T6164] RDX: 0000000000000000 RSI: 0000000055690764 RDI: ffff88804e600000
[23510.901117][ T6164] RBP: ffff88804e600000 R08: 0000000000400dc0 R09: 00000000ffffffff
[23510.902700][ T6164] R10: dffffc0000000000 R11: fffffbfff1f41637 R12: 0000000000000000
[23510.904271][ T6164] R13: 00000000000002a8 R14: 1ffff11009cc0198 R15: 1ffff11009cc0001
[23510.905887][ T6164] FS:  00000000224f93c0(0000) GS:ffff8880ec466000(0000) knlGS:0000000000000000
[23510.907783][ T6164] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[23510.909237][ T6164] CR2: 0000200000001140 CR3: 000000004e6a3000 CR4: 0000000000352ef0
[23510.910927][ T6164] Kernel panic - not syncing: Fatal exception
[23510.912468][ T6164] Kernel Offset: disabled
[23510.913379][ T6164] Rebooting in 86400 seconds..


호출 추적또한 rtnl_newlink_create -> ns_capable -> trace_ma_write 로 동일한 함수 경로를 따라간다. 오류가 원래 보고서와 일치함을 확인할 수 있다.


Reproducer 실행 (커널 코드에서 테스트)

커널 버그를 테스트하거나 검증하려면 문제가 발생했던 것과 동일한 버전의 커널을 사용하여 로컬에서 다시 커널을 다시 빌드해야한다. 먼저 업스트림 linux kernel 소스 코드를 클론한다.

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux


그런 다음 버그를 수정한 커밋을 확인하여 checkout 한다.

git checkout b56bbaf8c9ff


그런 다음 이전에 sybot report 에서 다운로드 받았던 .config 파일을 사용하여 커널을 구성한다.

cp ~/syz/dev_set_group/assets/.config .
make olddefconfig

olddefconfig 대상은 최신 커널 버전에서 누락된 옵션을 채우는 동시에 기존 값은 모두 유지한다. 이렇게 하면 테스트된 설정과의 호환성이 유지된다.

make -j$(nproc)

빌드가 완료되면 arch/x86/boot/bzImage 로 아까와 같이 vm 을 실행하여 재현해보면된다.

qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35,accel=kvm \
  -cpu host \
  -kernel ./arch/x86/boot/bzImage \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=../../syz-reproduce//disk-b56bbaf8.raw,format=raw \
  -nographic \
  -enable-kvm \
  -net user,hostfwd=tcp::10022-:22 -net nic


똑같은 패닉이 발생하는 것을 확인했다.

# ./repro
executing program
[   40.017873][ T5731] netlink: 'repro': attribute type 1 has an invalid length.
[   40.019203][ T5731] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000055: 0000 [#1] SMP KASAN NOPTI
[   40.020934][ T5731] KASAN: null-ptr-deref in range [0x00000000000002a8-0x00000000000002af]
[   40.022055][ T5731] CPU: 0 UID: 0 PID: 5731 Comm: repro Not tainted 6.15.0-07817-gb56bbaf8c9ff #1 PREEMPT(full) 
[   40.023455][ T5731] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-9.fc43 06/10/2025
[   40.024762][ T5731] RIP: 0010:dev_set_group+0x1e1/0x250
[   40.025512][ T5731] Code: 48 89 fa 48 c1 ea 03 80 3c 02 00 75 76 48 b8 00 00 00 00 00 fc ff df 4c 8b 63 08 49 8d bc 24 a8 02 00 00 48 89 fa 48 c1 ea 03 <80> 3c 02 00 75 42 49 83 bc 24 a8 02 00 00 00 0f 84 ac 9
[   40.028091][ T5731] RSP: 0018:ffffc90002d273b8 EFLAGS: 00010206
[   40.028938][ T5731] RAX: dffffc0000000000 RBX: ffff88802606c000 RCX: ffffffff895cad35
[   40.030040][ T5731] RDX: 0000000000000055 RSI: ffffffff895cae89 RDI: 00000000000002a8
[   40.031123][ T5731] RBP: ffff88802606ccc5 R08: ffffffff895cad35 R09: 0000000000000000
[   40.032240][ T5731] R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000000
[   40.033369][ T5731] R13: 0000000055690764 R14: 0000000000000001 R15: ffff88804478a010
[   40.034424][ T5731] FS:  000000002e00b3c0(0000) GS:ffff888097962000(0000) knlGS:0000000000000000
[   40.035478][ T5731] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   40.036223][ T5731] CR2: 0000200000001140 CR3: 0000000044e00000 CR4: 0000000000352ef0
[   40.037340][ T5731] Call Trace:
[   40.037826][ T5731]  <TASK>
[   40.038219][ T5731]  rtnl_create_link+0x69a/0xfe0
[   40.038782][ T5731]  rtnl_newlink+0x1208/0x2080
[   40.039452][ T5731]  ? __pfx_rtnl_newlink+0x10/0x10
[   40.040154][ T5731]  ? __kernel_text_address+0xd/0x30
[   40.040884][ T5731]  ? unwind_get_return_address+0x66/0xd0
[   40.041670][ T5731]  ? arch_stack_walk+0xb2/0x100
[   40.042348][ T5731]  ? __pfx_stack_trace_save+0x10/0x10
[   40.043137][ T5731]  ? rcu_is_watching+0x15/0xe0
[   40.043849][ T5731]  ? trace_cap_capable+0x80/0x1f0
[   40.044567][ T5731]  ? lock_acquire.part.0+0xbc/0x260
[   40.045319][ T5731]  ? rtnetlink_rcv_msg+0x1f4/0xe80
[   40.046066][ T5731]  ? find_held_lock+0x2b/0x80
[   40.046771][ T5731]  ? __pfx_rtnl_newlink+0x10/0x10
[   40.047491][ T5731]  ? __pfx_rtnl_newlink+0x10/0x10
[   40.048226][ T5731]  rtnetlink_rcv_msg+0x951/0xe80
[   40.048959][ T5731]  ? __pfx_rtnetlink_rcv_msg+0x10/0x10
[   40.049688][ T5731]  ? __lock_acquire+0x610/0x1cc0
[   40.050432][ T5731]  netlink_rcv_skb+0x153/0x420
[   40.051129][ T5731]  ? __pfx_rtnetlink_rcv_msg+0x10/0x10
[   40.051940][ T5731]  ? __pfx_netlink_rcv_skb+0x10/0x10
[   40.052705][ T5731]  ? netlink_deliver_tap+0xff/0xd60
[   40.053460][ T5731]  ? netlink_deliver_tap+0x1a7/0xd60
[   40.054263][ T5731]  ? is_vmalloc_addr+0x86/0xa0
[   40.054993][ T5731]  netlink_unicast+0x531/0x7f0
[   40.055727][ T5731]  ? __pfx_netlink_unicast+0x10/0x10
[   40.056535][ T5731]  netlink_sendmsg+0x8a3/0xd90
[   40.057273][ T5731]  ? __pfx_netlink_sendmsg+0x10/0x10
[   40.058080][ T5731]  ? __import_iovec+0x1dc/0x490
[   40.058793][ T5731]  ____sys_sendmsg+0xa80/0xc60
[   40.059503][ T5731]  ? __pfx_____sys_sendmsg+0x10/0x10
[   40.060288][ T5731]  ___sys_sendmsg+0x194/0x1e0
[   40.061010][ T5731]  ? __pfx____sys_sendmsg+0x10/0x10
[   40.061777][ T5731]  ? find_held_lock+0x2b/0x80
[   40.062452][ T5731]  ? __lock_acquire+0x610/0x1cc0
[   40.063192][ T5731]  ? lock_acquire.part.0+0xbc/0x260
[   40.063972][ T5731]  __sys_sendmsg+0x171/0x220
[   40.064667][ T5731]  ? __pfx___sys_sendmsg+0x10/0x10
[   40.065457][ T5731]  ? do_user_addr_fault+0x386/0xde0
[   40.066271][ T5731]  do_syscall_64+0xcb/0x490
[   40.066981][ T5731]  ? clear_bhb_loop+0x60/0xb0
[   40.067693][ T5731]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   40.068606][ T5731] RIP: 0033:0x41249d
[   40.069193][ T5731] Code: 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 f0 ff ff ff f7 d8 8
[   40.071482][ T5731] RSP: 002b:00007ffd243419b8 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
[   40.072442][ T5731] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 000000000041249d
[   40.073353][ T5731] RDX: 0000000000000000 RSI: 0000200000000280 RDI: 0000000000000003
[   40.074295][ T5731] RBP: 00007ffd24341a60 R08: 0000000000000000 R09: 0000000000000000
[   40.075177][ T5731] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffd24341ad8
[   40.076062][ T5731] R13: 00007ffd24341ae8 R14: 0000000000000002 R15: 00000000004a1e20
[   40.076937][ T5731]  </TASK>
[   40.077304][ T5731] Modules linked in:
[   40.077806][ T5731] ---[ end trace 0000000000000000 ]---
[   40.078581][ T5731] RIP: 0010:dev_set_group+0x1e1/0x250
[   40.079328][ T5731] Code: 48 89 fa 48 c1 ea 03 80 3c 02 00 75 76 48 b8 00 00 00 00 00 fc ff df 4c 8b 63 08 49 8d bc 24 a8 02 00 00 48 89 fa 48 c1 ea 03 <80> 3c 02 00 75 42 49 83 bc 24 a8 02 00 00 00 0f 84 ac 9
[   40.081486][ T5731] RSP: 0018:ffffc90002d273b8 EFLAGS: 00010206
[   40.082342][ T5731] RAX: dffffc0000000000 RBX: ffff88802606c000 RCX: ffffffff895cad35
[   40.083324][ T5731] RDX: 0000000000000055 RSI: ffffffff895cae89 RDI: 00000000000002a8
[   40.084314][ T5731] RBP: ffff88802606ccc5 R08: ffffffff895cad35 R09: 0000000000000000
[   40.085282][ T5731] R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000000
[   40.086258][ T5731] R13: 0000000055690764 R14: 0000000000000001 R15: ffff88804478a010
[   40.087223][ T5731] FS:  000000002e00b3c0(0000) GS:ffff888097962000(0000) knlGS:0000000000000000
[   40.088347][ T5731] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   40.089406][ T5731] CR2: 0000200000001140 CR3: 0000000044e00000 CR4: 0000000000352ef0
[   40.090639][ T5731] Kernel panic - not syncing: Fatal exception
[   40.091664][ T5731] Kernel Offset: disabled
[   40.092312][ T5731] Rebooting in 86400 seconds..


patch 적용 및 재구축

원래 커널에서 버그가 확인되면 다음 단계는 업스트림 수정사항이 해당 버그를 해결하는지 검증하는 것이다. 이를 위해 패치가 포함된 커밋을 체크아웃하고 해당 버전을 기반으로 새 커널 이미지를 빌드한다.


패치는 다음과 같다.

9337c54401a5 — veth: prevent NULL pointer dereference in veth_xdp_rcv

커널 소스를 해당 커밋으로 체크아웃하고 이전에 사용한 config를 재사용하여 빌드한다.

git checkout 9337c54401a5
make olddefconfig
make -j$(nproc)


이렇게 하면 수정 사항이 포함된 새 bzImage 가 생성된다. 동일한 디스크 이미지와 구성을 이용하여 다시 qemu 로 가상머신을 실행한다.

qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35,accel=kvm \
  -cpu host \
  -kernel ./arch/x86/boot/bzImage \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=../../syz-reproduce/disk-b56bbaf8.raw,format=raw \
  -nographic \
  -enable-kvm \
  -net user,hostfwd=tcp::10022-:22 -net nic


패치 후 잘못된 입력에 대해 정상적으로 거부하는 출력을 확인할 수 있었다.



CLion 원격 디버거 설정

1단계: compile_commands.json 생성

커널 소스 디렉토리에서

./scripts/clang-tools/gen_compile_commands.py

이 파일이 있어야 CLion이 코드 인덱싱/자동완성을 제공한다.


2단계: CLion에서 프로젝트 열기

clion에서 kernel 프로젝트를 열면 compile_commands.json을 인식하면 자동으로 인덱싱한다.


3단계: QEMU 를 디버그 모드로 실행

-s -S 옵션을 추가하여 qemu를 띄운다.

qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35,accel=kvm \
  -cpu host \
  -kernel arch/x86/boot/bzImage \
  -append "root=/dev/sda1 console=ttyS0 nokaslr" \
  -drive file=../../syz-reproduce/disk-b56bbaf8.raw,format=raw \
  -nographic \
  -enable-kvm \
  -s -S

-s -S 옵션:

  • -s: localhost:1234에서 GDB 서버 실행
  • -S: 시작 시 CPU 일시 중지 (GDB 연결 대기)


4단계: CLion에서 GDB Remote Debug 설정


  • Run → Edit Configurations
  • + 클릭 → Remote Debug 선택
  • 설정:



5단계: 브레이크포인트 설정

crash 발생 위치에 브레이크포인트 설정:

  1. net/core/rtnetlink.c 파일 열기
  2. rtnl_create_link 함수 찾기
  3. 해당 라인에 클릭해서 브레이크포인트 설정


6단계: 디버깅 시작

  1. CLion에서 Run → Debug 'Kernel Debug'
  2. GDB가 QEMU에 연결됨
  3. 연결되면 QEMU가 실행 재개
  4. VM에서 reproducer 실행



netif_set_group에서 다음 메세지를 출력 후 패닉 없이 넘어가는 것을 확인할 수 있었다.

[  200.607971][ T5893] netlink: 'repro': attribute type 1 has an invalid length.


References




syzbot, qemu, gdb를 사용하여 linux kernel의 버그 수정

  공개된 Syzbot 버그를 통해 보고서부터 시작하여 제공된 환경을 사용하여 원래 충돌을 확인하고 영향을 받는 커널 버전을 로컬에서 다시 빌드한 다음, 상위 버전 수정 사항을 적용한 후 문제가 해결되었는지 확인하는 절차를 알아보자. 목표는 근본 원인...