2019년 2월 7일 목요일

[How Syzkaller Works] 03 - Fuzzing the Driver (Example)


:) Goal


- Write a simple Linux kernel driver that has bugs, and Fuzzing the driver using syzkaller to find the bugs.


:) Architecture & Setup & Fuzzing the kernel core


- Docs in syzkaller github explains very well about their architectur,  how to setup, how to use it for fuzzing the kernel core.
  So please check it out their github!
- I'll focus only on fuzzing the driver in this posting.


:) Codes


- The all of the codes described at this posting are available at below link.


:) Bugs in an example driver


struct poc_lkm_object {
    unsigned char data0;
    unsigned char data1;
    unsigned long data2;
    unsigned long data3;
};

noinline void poc_lkm_cmd0(struct poc_lkm_object *obj)
{
    if (obj->data0 == 32)      // condition to trigger bug0
        poc_lkm_bug0(obj);  // where bug0 occurs
}
noinline void poc_lkm_cmd1(struct poc_lkm_object *obj)
{
    if (obj->data1 == 64)     // condition to trigger bug1
        poc_lkm_bug1(obj);  // where bug1 occurs
}

noinline long poc_lkm_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
struct poc_lkm_object obj;
...
if (copy_from_user(&obj, (void *)arg, sizeof(obj))) {
...
switch (cmd) {
case POC_LKM_CMD_0:
    poc_lkm_cmd0(&obj);
    break;
case POC_LKM_CMD_1:
    poc_lkm_cmd1(&obj);
    break;
}

- There are two bugs in the example driver.
  bug0 will be triggered if data0 is 32.
  bug1 will be triggered if data1 is 64.
  and the data used in triggering each bug is taken from an user.

- An user can reach to both bugs via ioctl system call.
- Our goal is guiding syzkaller to find the bugs.


:) Syscall Description for the example driver


...
syz_open_dev$poc_lkm(dev ptr[in, string["/proc/poc_lkm"]], flags flags[open_flags], mode flags[open_mode]) fd_poc_lkm

ioctl$poc_lkm(fd fd_poc_lkm, cmd flags[poc_lkm_cmds], arg ptr[in, poc_lkm_object])
close$poc_lkm(fd fd_poc_lkm)

poc_lkm_object {
Data0 int8  # Mutate this field
Data1 int8   # Mutate this field
Data2 const[0, int64]   # Do not mutate this field
Data3 const[0, int64]   # Do not mutate this field
}

poc_lkm_cmds = 0xc0185000, 0xc0185001
...

- What we're interesting in is ioctl() since it allows us to reach the bugs.
  Therefore, We will use the constants as parameters to call open(), close().

- Look at the ioctl() description.
  We're not interesting in some arguments, fd that is 1st arg, cmd that is 2nd arg.
  So we're going to only focus on arg.
  Note that the 2nd arg cmd could be the one to be used in fuzzing. Because an wrong cmd can lead the kernel to a crash.

- The type of 3rd arg is struct poc_lkm_object that has 4 fields.
  Fields :  data0, data1, data2, data3.
  We also select some fields to be mutated by syzkaller, The fields we select are data0, data1 that are able to hit the bugs.
  Note that we recommend you to mutate all fields of an object if you'd like to fuzz real-world driver.


:) Run fuzzing


2019/02/08 00:30:50 VMs 1, executed 0, cover 2090, crashes 0, repro 0
2019/02/08 00:31:00 VMs 1, executed 879, cover 2352, crashes 0, repro 0
2019/02/08 00:31:10 VMs 1, executed 879, cover 2375, crashes 0, repro 0
2019/02/08 00:31:15 vm-0: crash: general protection fault in poc_lkm_bug1
2019/02/08 00:31:20 VMs 0, executed 879, cover 2375, crashes 0, repro 0
.....
2019/02/08 00:31:41 reproducing crash 'general protection fault in poc_lkm_bug1': 1804 programs, 1 VMs
2019/02/08 00:31:50 VMs 0, executed 879, cover 2375, crashes 1, repro 1
......

- Next, we can run fuzzing on the example driver.
  As the above log we see,  Syzkaller has found the bug1 very quickly.
  Once the bug1 found, Syzkaller attempts to reproduce the bug1, Print out the result of reproducing.

- Unfortunately,  I couldn't find both bugs.  In other words, Syzkaller couldn't find the bug0 after finding the bug1.  I don't know why exactly...
  

:) References