使用Syzkaller&QEMU对针对内核进行QA

by kaipeng94@gmail.com

本文将介绍如何用Syzkaller和QEMU联合调试内核,捕捉bug。 本文包含三个部分:

  • 编译Syzkaller
  • 安装配置支持Syzkaller支持的虚机
  • 编译待测内核,修改Syzkaller配置文件并运行

安装Go语言编译器编译Syzkaller

下载解压Go语言编译器

wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz tar -xvzf go1.8.1.linux-amd64.tar.gz /home/root/go 运行完上述命令,Go语言的编译器会在/home/root/go目录下。

添加环境变量

将下列环境变量追加到~/.bashrc文件中

  • Go语言编译器的路径:

export GOROOT=/home/root/go export PATH=$GOROOT/bin;$PATH

  • Go的工作目录:

export GOPATH=/home/root/syzkalls/

  • Syzkaller二进制可执行文件的路径:

export PATH=/home/root/syzkalls/bin:$PATH $(GOROOT)是Go语言编译器所在的目录,当你在你的机器运行 “go” 命令时,将会基于$(GOPATH)目录工作。最后的把编译成功的Syzkaller二进制可执行文件添加到环境变量中去。所有路径可根据需要进行调整。

下载Syzkaller并编译

go get -u -d -v github.com/google/syzkaller/... 经过漫长的等待以后,执行: cd $(GOPATH)/src/github.com/google/syzkaller make -j4 cp $(GOPATH)/src/github.com/google/syzkaller/bin $(GOPATH)/ 运行成功后,syz-* 可执行文件将会出现在$(GOPATH)/bin目录下。

安装支持Syzkaller的虚机

安装一个QEMU使用的虚拟机

qemu-img create /PATH/TO/YOUR/VM_HDA $(SIZE)G 该命令会生成一个路径为”/PATH/TO/YOUR/VM_HDA”,大小为”$(SIZE)G”的供虚拟机使用的虚拟硬盘。

qemu-system-x86_64 -m 4096 -hda /PATH/TO/YOUR/VM_HDA -enable-kvm -cdrom /PATH/TO/YOUR/IMG -boot d -cdrom是你的虚机选择的发行版的安装盘。

  • sshd是一个ssh的服务程序,必须在安装虚机时选择安装或在虚机中人工安装。

运行带有ssh服务的虚机:

qemu-system-x86_64 -m 4096 -hda /PATH/TO/YOUR/VM_HDA --enable-kvm -net nic -net user,host=10.0.2.10,hostfwd=tcp::$(SSH_PORT)-:22 $(SSH_PORT) 可以按照你的意愿指定一个未用的端口。

配置虚机无密码登录

开启一个虚机并且在你的宿主机上运行: ssh-keygen -t rsa 这个命令会生成公钥id_isa.pub和私钥id_isa。将公钥拷贝到虚机上,运行(虚机必须在运行): scp id_rsa.pub -P $(SSH_PORT) root@vm:/root/.ssh/id_isa.pub

修改虚机的sshd配置

通过ssh登录到虚机: ssh -p $(SSH_PORT) root@127.0.0.1 检查修改如下配置选项: nano /etc/ssh/sshd_config

PermitRootLogin without-password

RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile	/root/.ssh/id_rsa.pub

RhostsRSAAuthentication yes
PermitEmptyPasswords no
PasswordAuthentication no

UsePAM no

运行sudo /etc/init.d/ssh restart重启ssh服务。

验证无密码登录虚机

在本地开启虚机: qemu-system-x86_64 -m 4096 -hda /PATH/TO/YOUR/VM_HDA --enable-kvm -net nic -net user,host=10.0.2.10,hostfwd=tcp::23505-:22 通过指定密钥,无密码ssh登录虚机: ssh -p $(SSH_PORT) -i ssh_key/id_rsa root@127.0.0.1 -v

###复制syzkaller可执行文件到虚机 scp -P $(SSH_PORT) -i ~/.ssh/id_rsa -r $(GOPATH)/bin root@127.0.0.1:/root/bin 把路径添加到虚机的环境变量中: export PATH=/home/root/bin:&PATH

编译待测内核,修改config文件运行Syzkaller

支持Syzkaller的内核配置

你可以先执行使用make defconfig生成默认配置,然后根据需求修改下列配置选项: 关于代码覆盖信息收集的配置:

CONFIG_KCOV=y
CONFIG_KCOV_INSTRUMENT_ALL=y
CONFIG_DEBUG_FS=y

关于代码覆盖信息的web接口的配置:

CONFIG_DEBUG_INFO=y

命名空间沙箱相关的:

CONFIG_NAMESPACES=y
CONFIG_USER_NS=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y

KASAN,对use-after-free和out-of-bounds内存溢出的探测:

CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y

其他debuf相关的配置选项:

CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y
CONFIG_DEBUG_ATOMIC_SLEEP=y
CONFIG_PROVE_RCU=y
CONFIG_DEBUG_VM=y

关掉以下配置选项:

# CONFIG_RANDOMIZE_BASE is not set

调整RCU的超时值:

CONFIG_RCU_CPU_STALL_TIMEOUT=60

编译好内核后尝试下列命令行,检查内核是否能跑起来: qemu-system-x86_64 -m 4096 -hda /PATH/TO/YOUR/VM_HDA --enable-kvm -net nic -net -kernel /PATH/TO/KERNEL/arch/x86_64/boot/bzImage -initrd /PATH/TO/INITRD/initrd.img -append root=/dev/sda1 user,host=10.0.2.10,hostfwd=tcp::$(SSH_PORT)-:22

  • initrd需自行生成,此处不展开.

修改配置文件,运行Syzkaller和虚机联调内核

用于syz-maneger的config.json:

{
	"http": "127.0.0.1:50000",                        http服务配置
	"workdir": "/home/bins/syzkaller/log",            工作目录,包括日志等
	"kernel": "/CUSTOM/BUILD/linux/arch/x86/boot/bzImage",  指定虚机运行的内核
	"initrd": "/PATH/TO/initrd.img"                 指定虚机运行的initrd镜像
	"vmlinux": "/PATH/TO/your/vmlinux",               用于debug的vmlinux
	"image": "/PATH/TO/your/hda",                     虚机所需的虚拟硬盘
	"syzkaller": $(GOPATH),                           syzkaller所在的目录
	"type": "qemu",                                   虚机类型
    "sshkey":"/path/to/.ssh/id_rsa",              ssh登录到虚机的密钥
	"count": 3,                                       虚机个数
	"procs": 1,                                       处理器数
    "cpu": 1,
	"mem": 1024,                                      内存大小
    "bin": "/usr/bin/qemu-system-x86_64",         QEMU所在目录
    "cmdline":"console=ttyS0 vsyscall=native rodata=n oops=panic panic_on_warn=1 panic=-1 ftrace_dump_on_oops=orig_cpu earlyprintk=serial slub_debug=UZ root=/dev/sda1",
                                                      虚机启动的命令行
    "enable_syscalls": [                          待测系统调用,不指定是默认全部
               ],
    "disable_syscalls": [                         免测系统调用
               "keyctl",
               "add_key",
               "request_key"
               ],
               "some know bug"
               ]
}

配置好后,指定好配置文件,运行syz-mamager。 syz-manager -config config.json 打开浏览器输入127.0.0.1:50000,可以看到一个包括系统调用的覆盖量,syzkaller生成的程序数量,内核被crash的日志报告等调试信息。