在本地复现syzbot Bugs

本文按照以下文章内容,尝试复现一个syzbot报告的bug。

https://www.linkedin.com/pulse/proof-execution-reproducing-syzbot-bugs-local-kernel-moon-hee-lee-081bc/?trackingId=ueW806vHTCSaYeZf%2Bgzfrg%3D%3D

Step One: Choose a syzbot Report with an Upstream Fix

挑选满足以下三个条件的bug report:

  1. 有可复现的c reproducer;
  2. 有配套的bzImage, vmlinux, 和disk.raw ;
  3. 已经被修复,patch已经被合入上游分支;

https://syzkaller.appspot.com/upstream上罗列了好几千的bugs,但是本文从那些被修复的入手,选择 Fiexed 标签,查看那些已确认、已解决并已关闭的 Bug。为了尽可能贴近当前主线,点一下 Reported 使得列表按报告日期升序排列,优先显示最近的 Bug。

image.png

以下边这个为例子: https://syzkaller.appspot.com/bug?extid=3f89ec3d1d0842e95d50

image.png

该问题相关的config文件以及测试信息已经在syzbot的报告邮件中,此外该问提的讨论过程在邮件列表中:https://lore.kernel.org/all/686d5a9f.050a0220.1ffab7.0017.GAE@google.com/T/

image.png

并且修复patch已经合入上游分支:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4c4ca3c46167518f8534ed70f6e3b4bf86c4d158

image.png

Step Two: Set Up the Local Workspace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
mkdir -p ~/syz/usbnet_status_start
cd ~/syz/usbnet_status_start

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

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

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

# bzImage
wget https://storage.googleapis.com/syzbot-assets/4b24078bc227/bzImage-d1b07cc0.xz

# vmlinux (used for debugging and symbol resolution)
wget https://storage.googleapis.com/syzbot-assets/934d59614ed5/vmlinux-d1b07cc0.xz

# Disk image
wget https://storage.googleapis.com/syzbot-assets/3eab0cb43ae2/disk-d1b07cc0.raw.xz

# 解压
xz -d ./bzImage-d1b07cc0.xz ./disk-d1b07cc0.raw.xz ./vmlinux-d1b07cc0.xz

# 最终的工作空间目录结构如下
tree -a
.
├── bzImage-d1b07cc0
├── .config
├── disk-d1b07cc0.raw
├── repro.c
├── repro.syz
└── vmlinux-d1b07cc0

1 directory, 6 files

Step Three: Boot the syzbot Kernel in QEMU

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sudo apt install qemu-system-x86

qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35,accel=kvm \
  -cpu host \
  -kernel bzImage-d1b07cc0 \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=disk-d1b07cc0.raw,format=raw \
  -nographic \
  -enable-kvm \
  -netdev user,id=net0,hostfwd=tcp::10022-:22 \
  -device e1000,netdev=net0
参数解释
qemu-system-x86_64启动一个x86_64的模拟器
-m 2048分配2GB内存
-smp 2分配2个CPU核心
-machine q35,accel=kvm使用 Q35芯片组(较新的 Intel 芯片组,支持 PCIe 设备等现代硬件)accel=kvm:启用 KVM(内核虚拟化模块)加速,依赖主机支持 VT-x/AMD-V
-cpu host使用主机的 CPU 配置(即暴露主机 CPU 的全部指令集特性给虚拟机),比如 AVX、SSE 等。
-kernel bzImage-b56bbaf8指定一个已编译好的 Linux 内核镜像文件
-append “root=/dev/sda1 console=ttyS0”给内核传递启动参数:root=/dev/sda1:告诉内核,根文件系统位于 /dev/sda1,也就是第一个硬盘的第一个分区。console=ttyS0:将控制台绑定到串口 ttyS0,这是为了搭配 -nographic 让你可以通过终端看到内核输出。
-drive file=disk-b56bbaf8.raw,format=raw挂载一个原始格式(raw)的虚拟硬盘文件,默认**.raw 磁盘镜像是可写的。**也就是说,虚拟机内部所做的任何修改在重启后都会被保留。如果希望每次测试都从干净的状态开始,可以通过在 -drive 参数中添加snapshot=on 来启用快照模式,这会让虚拟机运行在一个临时的覆盖层(overlay)上,所有更改在关机后都会被丢弃。使用这个选项可以确保每次测试运行都是干净、可复现的。
-nographic不使用图形界面,所有输入输出都通过终端(命令行)进行
-enable-kvm启用 KVM硬件虚拟化加速(和 -machine accel=kvm 作用类似,通常两者一起写以防兼容性问题)。
-netdev user,id=net0,hostfwd=tcp::10022-:22使用 QEMU 的 用户模式网络(user mode networking)。id=net0 给该网络接口一个 ID 名称。hostfwd=tcp::10022-:22 实现端口转发:把主机的 10022端口 映射到虚拟机的 22端口,即虚拟机的SSH服务。
-device e1000,netdev=net0使用 e1000 模拟一个 Intel E1000 网卡(兼容性较好)。把它连接到上面定义的 net0 网络设备。

由于我本身就是在一个虚拟机中运行ubuntu,因此硬件加速相关的参数都不能用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35 \
  -cpu qemu64 \
  -kernel bzImage-d1b07cc0 \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=disk-d1b07cc0.raw,format=raw,snapshot=on \
  -nographic \
  -netdev user,id=net0,hostfwd=tcp::10022-:22 \
  -device e1000,netdev=net0

image.png

启动成功,登录用户名:root 无密码

Step Four: Run the Reproducer in the Original Image

Syzkaller 的报告通常会提供两种类型的复现程序(reproducer):

  • 一个 C 源码文件(repro.c)
  • 一个二进制格式的程序(repro.syz),用于在 syz-execprog 下运行。

如果两者都提供了,建议优先使用 C 版本,因为它通常更容易阅读、运行和调试。

使用复现器

编译 syz-execprogsyz-executor

1
2
3
4
git clone https://github.com/google/syzkaller.git
cd syzkaller
sudo apt install golang-go
make TARGETOS=linux TARGETARCH=amd64

将复现程序拷贝进qemu模拟器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cp bin/linux_amd64/syz-execprog ~/syz/usbnet_status_start/assets/
cp bin/linux_amd64/syz-executor ~/syz/usbnet_status_start/assets/

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

使用复现器复现问题:

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

这将调用与 syzkaller 构造的完全相同的系统调用序列。如果内核漏洞仍然存在,那么崩溃或相应的警告信息应该会出现在控制台上。这说明复现程序仍然有效,并且测试环境配置是正确的。

编译复现程序

另外,也可以直接运行编译后的 C 程序:

1
2
gcc -static -O2 -o repro repro.c
./repro

这种方式避开了 syzkaller 的运行时环境,通常在调试时更加简单。


由于我本地的ubuntu虚拟机是aarch64架构的,编译x86_64的目标程序需要交叉编译

1
2
sudo apt update
sudo apt install gcc-x86-64-linux-gnu g++-x86-64-linux-gnu

随便写个c程序试试

1
2
3
4
5
#include <stdio.h>
int main() {
    printf("Hello from x86_64!\n");
    return 0;
}

编译一下试试

1
2
3
4
5
6
7
8
x86_64-linux-gnu-gcc -static hello.c -o hello_x86_64

file hello_x86_64
hello_x86_64: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=829d19ddb1083607e695354fb0d8b47792aa80b6, for GNU/Linux 3.2.0, not stripped

# 也可以用clang编译
sudo apt install clang
clang --target=x86_64-linux-gnu hello.c -o hello_x86_64

看起来没问题,用qemu-user模式运行一下试试

1
2
sudo apt install qemu-user
qemu-x86_64 ./hello_x86_64

测试没问题,用交叉编译的方式编译repro.c

1
2
3
4
5
6
7
x86_64-linux-gnu-gcc -static  -O2 -o repro repro.c
# 将编译产物拷贝到模拟器中
scp -P 10022 \
  -o UserKnownHostsFile=/dev/null \
  -o StrictHostKeyChecking=no \
  ~/syz/usbnet_status_start/repro \
  root@127.0.0.1:/root/

image.png

image.png

image.png

image.png

Step Five: Build the Matching Kernel Locally

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# syzbot报告问题的邮件中:git tree:       https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
git clone https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
cd usb-testing
# syzbot报告问题的邮件中 HEAD commit:    d1b07cc0868f arm64: dts: s32g: Add USB device tree informa..
git checkout d1b07cc0868f 

# 先安装编译内核必要的依赖
sudo apt install libelf-dev libdw-dev

cp ~/syz/usbnet_status_start/.config .
make ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- olddefconfig
make ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- -j$(nproc)

# 编译报错的时候也可以用以下命令保存更多的编译日志方便排错
make V=1 ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- -j$(nproc)  2>&1 | tee build.log

Step Six: Run the Reproducer on the Locally Built Kernel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35 \
  -cpu qemu64 \
  -kernel ~/usb-testing/arch/x86_64/boot/bzImage \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=disk-d1b07cc0.raw,format=raw,snapshot=on \
  -nographic \
  -netdev user,id=net0,hostfwd=tcp::10022-:22 \
  -device e1000,netdev=net0

拷贝编译后的复现程序repro

1
2
3
4
5
scp -P 10022
	-o UserKnownHostsFile=/dev/null
	-o StrictHostKeyChecking=no
	~/syz/usbnet_status_start/repro
	root@127.0.0.1:/root/

image.png

使用复现器

拷贝复现器

1
2
3
4
5
6
7
scp -P 10022 \
  -o UserKnownHostsFile=/dev/null \
  -o StrictHostKeyChecking=no \
  ~/syz/usbnet_status_start//assets/syz-execprog \
  ~/syz/usbnet_status_start/assets/syz-executor \
  ~/syz/usbnet_status_start/assets/repro.syz \
  root@127.0.0.1:/root/

执行复现器

1
2
3
4
5
ssh -p 10022 \
  -o UserKnownHostsFile=/dev/null \
  -o StrictHostKeyChecking=no \
  root@127.0.0.1 \
  './syz-execprog -enable=all -repeat=0 -procs=6 ./repro.syz'

Step Seven: Apply the Patch and Rebuild

尝试下载并应用以下patch

image.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 下载patch
wget -O patch.diff https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/\?id\=4c4ca3c46167518f8534ed70f6e3b4bf86c4d158

# 应用patch
cd ~/usb-testing
git am ~/syz/usbnet_status_start/patch.diff

# 编译内核
cp ~/syz/usbnet_status_start/.config .
make ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- olddefconfig
make V=1 ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- -j$(nproc)  2>&1 | tee build.log

# 启动模拟器
qemu-system-x86_64 \
  -m 2048 \
  -smp 2 \
  -machine q35 \
  -cpu qemu64 \
  -kernel ~/usb-testing/arch/x86_64/boot/bzImage \
  -append "root=/dev/sda1 console=ttyS0" \
  -drive file=disk-d1b07cc0.raw,format=raw,snapshot=on \
  -nographic \
  -netdev user,id=net0,hostfwd=tcp::10022-:22 \
  -device e1000,netdev=net0

# 拷贝复现程序  
scp -P 10022 \
	-o UserKnownHostsFile=/dev/null \
	-o StrictHostKeyChecking=no \
	~/syz/usbnet_status_start/repro \
	root@127.0.0.1:/root/

# 执行复现程序	
./repro

不会再出现内核崩溃日志:

image.png

总结

本文主要根据Moon Hee LeeProof by Execution: Reproducing syzbot Bugs in a Local Kernel尝试复现syzbot发现的bug,并应用上游patch解决对应的问题。熟悉整个流程,为以后跟进上游bugfix做基础准备。