Syzbot을 위한 환경 설정
git clone 리눅스 커널 가져오기
버그 찾기
c repro가 있는 것, open되어 있는 것 중에서 찾고, out of bounds를 위주로 찾아보자!! 이게 쉽다. 그리고 로컬 QEMU환경에서 버그를 재현해야한다.
Kernel Build 하기
리포트에 명시되어 있는, config를 활용하여 commit hash로 checkout한다. 그리고 이 상태에서 빌드한다.
- make olddefconfig : 커널의 루트에 .config작성하고 적용
- make -j$(nproc) bzImage : bzImage로 빌드
환경 만들기
- syzkaller clone해오기
- 파일 시스템 이미지 준비 (Debian Trixie): trixie.img
- 엔진: bzImage
- 차체: trixie.img
- syzkaller/tools에 ./create-image.sh 를 실행해서 trixie.img를 만든다
- repro.c다운 받아서 컴파일: gcc -pthread -static -o repro repro.c
QEMU 실행 스크립트
bzImage와 trixie.img는 경로를 맞게 설정해준다.
qemu-system-x86_64 \
-m 4G \
-smp 2 \
-kernel ./arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/sda earlyprintk=serial nokaslr panic=1" \
-drive file=./trixie.img,format=raw \
-net user,host=10.0.2.10,hostfwd=tcp::10022-:22 -net nic \
-enable-kvm \
-nographicrepro실행파일 로컬에서 전송
scp -i ./trixie.id_rsa -P 10022 ../../crepro/repro root@localhost:/root/ 를 사용하여 repro를 전송하기
그리고 ./repro를 실행하여 버그재현
여기서 trixie.id_rsa는 키이다.
실제 버그 분석
executing program
[ 337.735312][ T9986] loop0: detected capacity change from 0 to 32768
[ 337.791047][ T9986] MetaData crosses page boundary!!
[ 337.791053][ T9986] lblock = 8bffffffff, size = 1154220032
[ 337.791066][ T9986] CPU: 1 UID: 0 PID: 9986 Comm: repro Not tainted 6.19.0-03606-g192c0159402e #1 PREEMPT_{RT,(full)}
[ 337.791076][ T9986] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 337.791082][ T9986] Call Trace:
[ 337.791085][ T9986] <TASK>
[ 337.791090][ T9986] dump_stack_lvl+0x164/0x1f0
[ 337.791114][ T9986] __get_metapage+0x6d1/0x1090
[ 337.791131][ T9986] dtReadFirst+0x396/0x990
[ 337.791141][ T9986] ? __pfx_jfs_readdir+0x10/0x10
[ 337.791151][ T9986] jfs_readdir+0x5ce/0x4780
[ 337.791166][ T9986] ? __lock_acquire+0x45c/0x25f0
[ 337.791178][ T9986] ? __pfx_jfs_readdir+0x10/0x10
[ 337.791191][ T9986] ? do_raw_spin_lock+0x128/0x270
[ 337.791205][ T9986] ? __pfx_rwbase_write_lock+0x10/0x10
[ 337.791217][ T9986] ? __pfx___might_resched+0x10/0x10
[ 337.791229][ T9986] ? wrap_directory_iterator+0x52/0xe0
[ 337.791242][ T9986] ? __pfx_jfs_readdir+0x10/0x10
[ 337.791252][ T9986] wrap_directory_iterator+0xa8/0xe0
[ 337.791264][ T9986] iterate_dir+0x2ab/0xb10
[ 337.791277][ T9986] __x64_sys_getdents64+0x13b/0x2c0
[ 337.791289][ T9986] ? __pfx___x64_sys_getdents64+0x10/0x10
[ 337.791300][ T9986] ? __x64_sys_openat+0x141/0x200
[ 337.791311][ T9986] ? __pfx_filldir64+0x10/0x10
[ 337.791326][ T9986] do_syscall_64+0x112/0xf80
[ 337.791337][ T9986] ? irqentry_exit+0x122/0x7a0
[ 337.791349][ T9986] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 337.791358][ T9986] RIP: 0033:0x4203ad
[ 337.791368][ T9986] Code: b3 66 2e 0f 1f 84 00 00 00 00 00 66 90 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 c0 ff ff ff f7 d8 64 89 01 48
[ 337.791375][ T9986] RSP: 002b:00007ffdd842a9a8 EFLAGS: 00000213 ORIG_RAX: 00000000000000d9
[ 337.791383][ T9986] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00000000004203ad
[ 337.791388][ T9986] RDX: 0000000000001000 RSI: 0000200000000f80 RDI: 0000000000000006
[ 337.791393][ T9986] RBP: 00007ffdd842a9c0 R08: 00007ffdd842a9c0 R09: 00007ffdd842a9c0
[ 337.791398][ T9986] R10: 00007ffdd842a9c0 R11: 0000000000000213 R12: 00007ffdd842ab48
[ 337.791402][ T9986] R13: 00007ffdd842ab58 R14: 00000000004b2f08 R15: 0000000000000001
[ 337.791413][ T9986] </TASK>
[ 337.791416][ T9986] bread failed!
repro로 버그를 재현한 결과, 위와 같은 콜 스택이 나왔다.
여기서 확인할 수 있는건, __get_metapage에서 종료가 되었고, 이 함수를 유발시킨 함수는 dtReadFirst라는 함수이다. 여기서 보면 dtReadFirst+0x396/0x990 이렇게 나와있는데, 이건 dtReadFirst함수로부터 0x396바이트가 떨어진 곳에서 에러가 발생했다는 뜻이다.
그러면 이 관측 결과를 토대로, 어디서 어떤 코드에서 에러가 발생했는지 알 수 있다.
버그가 어디서 발생했는지?
faddr2line으로 버그가 어떤 코드에서 발생했는지 확인해보자.
./scripts/faddr2line vmlinux dtReadFirst+0x396 실행
dtReadFirst+0x396/0x990:
dtReadFirst at fs/jfs/jfs_dtree.c:3079 (discriminator 2)
이렇게 하면, 위치가 jfs_dtree.c의 3079번 줄에서 에러가 났음을 알 수 있다.
struct metapage *__get_metapage(struct inode *inode, unsigned long lblock,
unsigned int size, int absolute,
unsigned long new)
{
int l2BlocksPerPage;
int l2bsize;
struct address_space *mapping;
struct metapage *mp = NULL;
struct folio *folio;
unsigned long page_index;
unsigned long page_offset;
jfs_info("__get_metapage: ino = %ld, lblock = 0x%lx, abs=%d",
inode->i_ino, lblock, absolute);
l2bsize = inode->i_blkbits;
l2BlocksPerPage = PAGE_SHIFT - l2bsize;
page_index = lblock >> l2BlocksPerPage;
page_offset = (lblock - (page_index << l2BlocksPerPage)) << l2bsize;
if ((page_offset + size) > PAGE_SIZE) {
jfs_err("MetaData crosses page boundary!!");
jfs_err("lblock = %lx, size = %d", lblock, size);
dump_stack();
return NULL;
}
여기서, 아까 발생했던 log
[ 337.791047][ T9986] MetaData crosses page boundary!!
[ 337.791053][ T9986] lblock = 8bffffffff, size = 1154220032
이 로그가 어디서 발생했는지 알 수 있다.
이 로그가 왜 발생했냐면, if ((page_offset + size) > PAGE_SIZE)에서 걸렸기 때문에 에러가 발생했다는 것을 알 수 있다. 여기서 lblock = 8bffffffff 라는건 말이 안되는 숫자이다.
즉, 인자로 들어온 lblock이 문제가 발생했다는 것이다.
static int dtReadFirst(struct inode *ip, struct btstack * btstack)
{
int rc = 0;
s64 bn;
int psize = 288; /* initial in-line directory */
struct metapage *mp;
dtpage_t *p;
s8 *stbl;
struct btframe *btsp;
pxd_t *xd;
BT_CLR(btstack); /* reset stack */
/*
* descend leftmost path of the tree
*
* by convention, root bn = 0.
*/
for (bn = 0;;) {
DT_GETPAGE(ip, bn, mp, psize, p, rc);
if (rc)
return rc;
/*
* leftmost leaf page
*/
if (p->header.flag & BT_LEAF) {
/* return leftmost entry */
btsp = btstack->top;
btsp->bn = bn;
btsp->index = 0;
btsp->mp = mp;
return 0;
}
/*
* descend down to leftmost child page
*/
if (BT_STACK_FULL(btstack)) {
DT_PUTPAGE(mp);
jfs_error(ip->i_sb, "btstack overrun\\n");
BT_STACK_DUMP(btstack);
return -EIO;
}
/* push (bn, index) of the parent page/entry */
BT_PUSH(btstack, bn, 0);
/* get the leftmost entry */
stbl = DT_GETSTBL(p);
if (stbl[0] < 0) {
DT_PUTPAGE(mp);
jfs_error(ip->i_sb, "stbl[0] out of bound\\n");
return -EIO;
}
xd = (pxd_t *) & p->slot[stbl[0]];
/* get the child page block address */
bn = addressPXD(xd);
psize = lengthPXD(xd) << JFS_SBI(ip->i_sb)->l2bsize;
/* unpin the parent page */
DT_PUTPAGE(mp);
}
}
문제가 발생했던 부분은, DT_GETPAGE에서 인자로 전달되는 bn에 문제가 생겼었던 것이고, bn은 bn = addressPXD(xd)에 의해서 얻게된다. bn에 쓰레기값이 그대로 전달되기 때문에 에러가 발생했던 것이다.
따라서, bn = addressPXD(xd)로 얻은 bn을 검사하기만 하면 에러를 해결할 수 있다.
bn = addressPXD(xd);
if (bn >= JFS_SBI(ip->i_sb)->bmap->db_bmap.dn_mapsize) {
jfs_error(ip->i_sb, "invalid block number\\n");
return -EFSCORRUPTED;
}
를 추가한다.
그리고 다시 재빌드를 한 후, repro를 돌려봤더니 커널 패닉이 발생하지 않는 것을 알 수 있었다.
그대로 syzbot에게 이 패치를 제출한다.
누가 이미 제출했다
확인해보니, 이미 누가 이 패치를 그새 제출했다… 또 다른 버그를 찾아서 고쳤으나 1달전에 이미 누가 제출했다…ㅠ
'Linux > start_contribute()' 카테고리의 다른 글
| syzbot - memory leak in gsm_activate_mux (0) | 2026.04.24 |
|---|---|
| 커널 기여: race condition 가능성 해결 (0) | 2026.02.01 |
| 커널 기여 근황 (0) | 2026.01.27 |
| 리눅스 커널 2번째 기여: 수동 메모리 정렬 연산 PTR_ALIGN으로 최적화 (2) | 2026.01.12 |
| 첫 리눅스 커널 기여 (4) | 2025.12.22 |