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