Home Visualizing KCOV with syz-cover
Post
Cancel

Visualizing KCOV with syz-cover

Overview

If you have used syzkaller, you seen how they have visualizer for kernel coverage. You can actually use syz-cover to do this with any kcov.

Syzkaller Coverage Viewer

Building syz-cover

Prereq

If you never installed syzkaller before follow this guide

Build syz-cover (cover)

1
2
3
git clone https://github.com/google/syzkaller
cd syzkaller
make cover 

syz-cover should be under syzkaller/bin.

Building Kernel and Running it on QEMU

Follow syzkaller (guide)[https://github.com/google/syzkaller/blob/master/docs/linux/setup_ubuntu-host_qemu-vm_x86-64-kernel.md] to setup the environment. Most important part is making sure to enable CONFIG_KCOV=y in kernel config.

Building Test Program

I will be using modified version of code shown in official kcov guide Code it will try to get kernel coverage for is read(-1, NULL, 0);

Modification is we subtract 5 from kcov address, which effectively gives us the previous instruction. kcov by default gives you addres after CALL __sanitizer_cov_trace_pc but syz-cover parses address at CALL instruction. 5 bytes come from CALL sanitizer_cov_trace_pc being 5 bytes.

Code that calculates the PreviousInstructionPC is here

Copy this code below and compile it by doing g++ -static -o kcov_test kcov_test.cc

kcov_test.cc

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/types.h>
#include <iostream>
#include <fstream>

#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long)
#define KCOV_ENABLE _IO('c', 100)
#define KCOV_DISABLE _IO('c', 101)
#define COVER_SIZE (64 << 10)

#define KCOV_TRACE_PC 0
#define KCOV_TRACE_CMP 1

#define AMD64_PREV_INSTR_SIZE 5

int main(int argc, char **argv)
{
        int fd;
        unsigned long *cover, n, i;

        /* A single fd descriptor allows coverage collection on a single
         * thread.
         */
        fd = open("/sys/kernel/debug/kcov", O_RDWR);
        if (fd == -1)
                perror("open"), exit(1);
        /* Setup trace mode and trace size. */
        if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))
                perror("ioctl"), exit(1);
        /* Mmap buffer shared between kernel- and user-space. */
        cover = (unsigned long *)mmap(NULL, COVER_SIZE * sizeof(unsigned long),
                                      PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if ((void *)cover == MAP_FAILED)
                perror("mmap"), exit(1);
        /* Enable coverage collection on the current thread. */
        if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC))
                perror("ioctl"), exit(1);
        /* Reset coverage from the tail of the ioctl() call. */
        __atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);

        /* That's the target syscal call. */
        read(-1, NULL, 0);

        /* Read number of PCs collected. */
        n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);

        std::ofstream kcov_s("./kcov.txt");

        for (i = 0; i < n; i++)
        {
                /* Make sure address leads with 0x and substract 5 to get previous instruction address for syz-cover parser */
                kcov_s << std::showbase << std::hex << cover[i + 1] - AMD64_PREV_INSTR_SIZE << std::endl;
        }
        
        /* Disable coverage collection for the current thread. After this call
         * coverage can be enabled for a different thread.
         */
        if (ioctl(fd, KCOV_DISABLE, 0))
                perror("ioctl"), exit(1);
        /* Free resources. */
        if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))
                perror("munmap"), exit(1);
        if (close(fd))
                perror("close"), exit(1);
        return 0;
}

Running Test Binary

Transfer kcov_test to vm and run it.

From same [syzkaller installtion guide](ssh -i $IMAGE/bullseye.id_rsa -p 10021 -o “StrictHostKeyChecking no” root@localhost)

it tells you how you can ssh and scp to the vm

SSH

1
ssh -i $IMAGE/bullseye.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost

SCP

1
scp -i $IMAGE/stretch.id_rsa -P 10021 -o "StrictHostKeyChecking no" <transfer program> root@localhost:<transfer_path>

Running syz-cover on kcov.txt

kcov_test should have created kcov.txt under same directory. Grab kcov.txt from VM to your host so you can run syz-cover.

syz-cover should look something like this below. It needs to start with 0x. Also, address should all point to CALL __sanitizer_cov_trace_pc. Like mentioned earlier, kcov by default gives you addres after CALL __sanitizer_cov_trace_pc but syz-cover parses address at CALL instruction.

1
2
3
4
5
6
7
8
9
10
11
12
0xffffffff81b3a121
0xffffffff81b39f08
0xffffffff81bb41f1
0xffffffff81bb0064
0xffffffff81bb02e4
0xffffffff81bb02c1
0xffffffff81bb4244
0xffffffff81b3a015
0xffffffff810e53a7
0xffffffff810e53d8
0xffffffff810e5418
0xffffffff810e5400

Disassembly at 0xffffffff81b3a121 (First KCOV)

Running syz-cover

Note that the reason why you need absolute path is because there is bug where it will break if you don’t in earlier version of this tool. Fixed in this issue

1
syz-cover --kernel_src <absolute_linux_kernel_path> --kernel_obj <absolute_linux_kernel_path> kcov.txt                                      

Check Result

Make sure SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) is highlighted under fs/read_write.c

This post is licensed under CC BY 4.0 by the author.

CVE-2020-27786 FUSE UaF

Loadable Kernel Modules