基于前一篇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 clean
和make -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
|
#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