Syzkaller Crash Demo

基于前一篇Syzkaller Fuzz Linux AMD64的文章,这里用一个自己写的驱动(有一个堆溢出)做演示。

在Syzkaller中添加规则

在syzkaller/sys/linux目录编辑proc_operation.txt。
proc_operation.txt

1
2
3
4
5
6
7
8
9
include <linux/fs.h>

open$proc(file ptr[in, string["/proc/test"]], flags flags[proc_open_flags], mode flags[proc_open_mode]) fd
read$proc(fd fd, buf buffer[out], count len[buf])
write$proc(fd fd, buf buffer[in], count len[buf])
close$proc(fd fd)

proc_open_flags = O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, FASYNC, O_CLOEXEC, O_CREAT, O_DIRECT, O_DIRECTORY, O_EXCL, O_LARGEFILE, O_NOATIME, O_NOCTTY, O_NOFOLLOW, O_NONBLOCK, O_PATH, O_SYNC, O_TRUNC, __O_TMPFILE
proc_open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

syzkaller/sys/linux目录下的sys.txt中有通用的调用形式可以参考。

syzkaller使用它自己的声明式语言来描述系统调用模板,docs目录下的syscall_descriptions.md中可以找到相关的说明。这些系统调用模板被翻译成syzkaller使用的代码需要经过两个步骤。第一步是使用syz-extract从linux源代码中提取符号常量的值,结果被存储在.const文件中,例如/sys/linux/tty.txt被转换为sys/linux/tty_amd64.const。第二步是根据系统调用模板和第一步中生成的const文件使用syz-sysgen生成syzkaller用的go代码。可以在/sys/linux/gen/amd64.go和/executor/syscalls.h中看到结果。最后,重新编译生成带有相应规则的syzkaller二进制可执行文件。

编译生成syz-extract、syz-sysgen

1
2
3
$ cd syzkaller
$ make bin/syz-extract
$ make bin/syz-sysgen

用syz-extract生成.const文件,接着运行syz-sysgen,最后重编译syzkaller

1
2
3
4
$ bin/syz-extract -os linux -arch amd64 -sourcedir /home/fanrong/Computer/kernel/linux-5.1 proc_operation.txt
$ bin/syz-sysgen
$ make clean
$ make all

编译有堆溢出的内核驱动

这里采用将驱动编译进内核的方式,将test.c驱动程序放到linux-5.1/drivers/char目录:
linux-5.1/drivers/char/test.c

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define MY_DEV_NAME "test"
#define DEBUG_FLAG "PROC_DEV"

static ssize_t proc_read (struct file *proc_file, char __user *proc_user, size_t n, loff_t *loff);
static ssize_t proc_write (struct file *proc_file, const char __user *proc_user, size_t n, loff_t *loff);
static int proc_open (struct inode *proc_inode, struct file *proc_file);
static struct file_operations a = {
.open = proc_open,
.read = proc_read,
.write = proc_write,
};


static int __init mod_init(void)
{
struct proc_dir_entry *test_entry;
const struct file_operations *proc_fops = &a;
printk(DEBUG_FLAG":proc init start!\n");

test_entry = proc_create(MY_DEV_NAME, S_IRUGO|S_IWUGO, NULL, proc_fops);
if(!test_entry)
printk(DEBUG_FLAG":there is somethings wrong!\n");

printk(DEBUG_FLAG":proc init over!\n");
return 0;
}

static ssize_t proc_read (struct file *proc_file, char __user *proc_user, size_t n, loff_t *loff)
{
printk(DEBUG_FLAG":finish copy_from_use,the string of newbuf is");

return 0;
}

static ssize_t proc_write (struct file *proc_file, const char __user *proc_user, size_t n, loff_t *loff)
{
char *c = kmalloc(512, GFP_KERNEL);

copy_from_user(c, proc_user, 4096);
printk(DEBUG_FLAG":into write!\n");
return 0;
}

int proc_open (struct inode *proc_inode, struct file *proc_file)
{
printk(DEBUG_FLAG":into open!\n");
return 0;
}

module_init(mod_init);

修改该目录下的Kconfig,照着其他条目的样子添加即可:

1
2
3
4
5
config PROC_OP
bool "/proc/test virtual device support"
default y
help
This is a Syzkaller test case device driver.

修改该目录下的Makefile,也是参照其他条目即可:

1
obj-$(CONFIG_PROC_OP) += test.o

在Linux内核根目录执行make menuconfig会出现.config文件的配置界面,在Device Drivers->Character devices选项中可以看到[*] /proc/test virtual device support,即刚才在Kconfig里添加的条目。保存退出,.config会比原来增加一条CONFIG_PROC_OP=y,执行make cleanmake -j8重新编译内核即可将驱动编译进内核。

修改配置文件运行Syzkaller

下面需要修改配置文件,只允许某些调用,速度更快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"workdir": "workdir",
"kernel_obj": "/home/fanrong/Computer/kernel/linux-5.1",
"image": "/home/fanrong/Computer/kernel/IMAGE/stretch.img",
"sshkey": "/home/fanrong/Computer/kernel/IMAGE/stretch.id_rsa",
"syzkaller": ".",
"procs": 8,
"type": "qemu",
"enable_syscalls": [
"open$proc",
"read$proc",
"write$proc",
"close$proc"
],
"vm": {
"count": 4,
"kernel": "$KERNEL/arch/x86/boot/bzImage",
"cpu": 2,
"mem": 2048
}
}

最后运行syzkaller:

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
$ bin/syz-manager -config my.cfg -vv 10
2019/10/15 10:02:10 loading corpus...
2019/10/15 10:02:10 serving http on http://127.0.0.1:56741
2019/10/15 10:02:10 serving rpc on tcp://[::]:33307
2019/10/15 10:02:10 booting test machines...
2019/10/15 10:02:10 wait for the connection from test machine...
2019/10/15 10:02:10 loop: phase=0 shutdown=false instances=4/4 [3 2 1 0] repro: pending=0 reproducing=0 queued=0
2019/10/15 10:02:10 loop: starting instance 0
2019/10/15 10:02:10 loop: starting instance 1
2019/10/15 10:02:10 loop: starting instance 2
2019/10/15 10:02:10 loop: starting instance 3
2019/10/15 10:02:23 fuzzer vm-3 connected
2019/10/15 10:02:23 fuzzer vm-1 connected
2019/10/15 10:02:23 fuzzer vm-0 connected
2019/10/15 10:02:23 fuzzer vm-2 connected
2019/10/15 10:02:23 machine check:
2019/10/15 10:02:23 syscalls : 4/2765
2019/10/15 10:02:23 code coverage : enabled
2019/10/15 10:02:23 comparison tracing : CONFIG_KCOV_ENABLE_COMPARISONS is not enabled
2019/10/15 10:02:23 extra coverage : extra coverage is not supported by the kernel
2019/10/15 10:02:23 setuid sandbox : enabled
2019/10/15 10:02:23 namespace sandbox : /proc/self/ns/user does not exist
2019/10/15 10:02:23 Android sandbox : enabled
2019/10/15 10:02:23 fault injection : CONFIG_FAULT_INJECTION is not enabled
2019/10/15 10:02:23 leak checking : CONFIG_DEBUG_KMEMLEAK is not enabled
2019/10/15 10:02:23 net packet injection : /dev/net/tun does not exist
2019/10/15 10:02:23 net device setup : enabled
2019/10/15 10:02:23 corpus : 67 (0 deleted)
...

crash报告如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BUG: KASAN: slab-out-of-bounds in _copy_from_user+0x83/0xd0 lib/usercopy.c:12
Write of size 4096 at addr ffff88806bce3b80 by task syz-executor680/1974

CPU: 1 PID: 1974 Comm: syz-executor680 Not tainted 5.1.5 #2
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
Call Trace:
__dump_stack lib/dump_stack.c:77 [inline]
dump_stack+0x75/0xae lib/dump_stack.c:113
print_address_description+0x65/0x270 mm/kasan/report.c:187
kasan_report+0x149/0x18d mm/kasan/report.c:317
_copy_from_user+0x83/0xd0 lib/usercopy.c:12
copy_from_user include/linux/uaccess.h:144 [inline]
proc_write+0x4d/0x70 drivers/char/test.c:45
proc_reg_write+0x1a7/0x250 fs/proc/inode.c:241
__vfs_write+0x7c/0x100 fs/read_write.c:485
vfs_write+0x168/0x4a0 fs/read_write.c:549
ksys_write+0xfc/0x230 fs/read_write.c:599
do_syscall_64+0x9a/0x2d0 arch/x86/entry/common.c:293
entry_SYSCALL_64_after_hwframe+0x44/0xa9

syzkaller在发现crash之后还会尝试产生poc,下面是针对这个bug产生的poc:

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
// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

uint64_t r[1] = {0xffffffffffffffff};

int main(void)
{
syscall(__NR_mmap, 0x20000000, 0x1000000, 3, 0x32, -1, 0);
intptr_t res = 0;
memcpy((void*)0x20000200, "/proc/test\000", 11);
res = syscall(__NR_open, 0x20000200, 1, 0x78ae05816e61a376);
if (res != -1)
r[0] = res;
syscall(__NR_write, r[0], 0, 0);
return 0;
}

reference
https://www.jianshu.com/p/790b733f80a2
https://github.com/hardenedlinux/Debian-GNU-Linux-Profiles/blob/master/docs/harbian_qa/fuzz_testing/syzkaller_crash_demo.md