Mastering Kernel Fuzzing with Syzkaller (Part 1): Architecture, Setup, and Building a Fuzz-Friendly Kernel
Fuzzing matters for OS security. Syzkaller is Google’s coverage-guided kernel fuzzer (powers syzbot too). It does not poke a single userspace binary: it generates syscall programs in a small language called syzlang, runs them in VMs, and uses KCOV so the kernel reports which edges executed. Programs that find new coverage get kept and mutated.
This post is Part 1: how the pieces fit, what to install, and how to build a kernel that is actually useful to fuzz. Part 2 adds a toy buggy driver, syzlang for it, manager configs, and the crash/repro story. Continue to Part 2 when you want that.
Why the kernel is awkward to fuzz
Userspace fuzzers (AFL, libFuzzer) run in one process and call your code directly. The kernel does not hand you LLVMFuzzerTestOneInput. You need:
- A kernel built with KCOV (and usually KASAN or similar).
- VMs (I use QEMU) so a panic does not kill your host.
- A syscall description layer: syzlang lives in
sys/linux/*.txtinside the syzkaller repo.
More moving parts than fuzzing a parser, but you hit real entry points: syscalls, ioctls, netlink, the messy stuff.
How it fits together
The usual mental model matches what Andre Almeida described at Collabora: manager software on the host runs the show; it spawns VMs with fuzzers inside that generate and run syscall programs; those guests talk back over RPC (programs, coverage, traces, crashes); the manager stores state (corpus, DB) and exposes a web UI. Physical machines are supported too, but VMs keep panics off your laptop.
Credit: The host / VM / RPC / storage breakdown follows Collabora’s Using syzkaller, part 1: Fuzzing the Linux kernel (March 2020). That article also introduces syzlang with open and ioctl examples; use it next to the official syscall descriptions doc.
| Piece | Role |
|---|---|
syz-manager | On the host: starts VMs, hands out work, merges coverage, writes crashes, HTTP UI. |
syz-fuzzer | In each guest: RPC to manager, receives programs, drives the executor. |
syz-executor | In the guest: runs one syscall program (sandbox, KCOV). |
| VM backend | QEMU + image with sshd, root key, debugfs for KCOV. |
| Kernel under test | KCOV + optional KASAN, fault injection, … |
| Workdir | On the host: corpus.db, crash dirs, seeds. |
Loop in one sentence: the manager pushes a program into a VM, the executor runs it against the kernel, new coverage is kept and mutated; on an oops the manager saves logs and may later run syz-repro with the same JSON and a crash log.
flowchart TB
subgraph host["Host (workstation)"]
mgr["syz-manager"]
wd[("workdir: corpus, crashes")]
web["HTTP dashboard"]
mgr --> wd
mgr --> web
end
subgraph vm["Each VM (e.g. QEMU)"]
fz["syz-fuzzer"]
ex["syz-executor"]
kern["Kernel + KCOV"]
fz --> ex
ex --> kern
end
mgr <-->|SSH / RPC| fz
What I actually ran (so you can compare)
| Item | What I used |
|---|---|
| Host | Linux (Kali rolling), amd64 |
| Sources | Linux 6.19 from the upstream tarball, defconfig + kvm_guest.config |
| Syzkaller | Upstream git, Go 1.23+ per their docs |
| Disk image | Debian Trixie-style image from tools/create-image.sh (trixie.img + key) |
| Hypervisor | QEMU with KVM when /dev/kvm exists |
Paths on your machine will differ. Keep mental aliases for syzkaller checkout, kernel tree, and image.
Host packages
You will want: Go (1.23+), GCC (docs say 6+ for KCOV), QEMU for x86_64, kernel build deps (make, flex, bison, libssl-dev, libelf-dev, ncurses dev package). Add yourself to the kvm group if QEMU complains about KVM.
Clone and build:
1
2
3
git clone https://github.com/google/syzkaller.git
cd syzkaller
make
Outputs land in bin/ (syz-manager, syz-repro, syz-execprog, per-arch syz-executor, …). Upstream likes tools/syz-env for reproducible builds; on a normal distro make alone often works.
Kernel config that does not waste your time
Plain defconfig is not enough. Turn on at least:
CONFIG_KCOV=yso the kernel can report coverage.CONFIG_KCOV_INSTRUMENT_ALL=yandCONFIG_KCOV_ENABLE_COMPARISONS=yif you want decent signal.CONFIG_DEBUG_FS=y(KCOV hangs off debugfs).CONFIG_KASAN=yandCONFIG_KASAN_INLINE=yfor memory bug reports most people can read.CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=yor whatever your tree uses for debug info.CONFIG_CONFIGFS_FS=yandCONFIG_SECURITYFS=y(images fromcreate-image.shexpect them).- Run
make kvm_guest.configonce so virtio and guest-ish defaults are sane.
The full shopping list is in docs/linux/kernel_configs.md. syzbot’s huge reference configs live under dashboard/config/linux/; for learning I merged a small fragment on top of defconfig instead of diffing a 5000-line file.
Example syzkaller.fragment
Put a file of CONFIG_*=y (and string) lines anywhere you like; the merge script applies it on top of your current .config. Below is the fragment I used with Linux 6.19. CONFIG_DVKM=y is only valid after you add the demo driver from Part 2 (Kconfig + sources). For Part 1 alone, delete that line or comment it out so olddefconfig does not depend on a symbol your tree does not ship.
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
# Syzkaller-oriented options (see syzkaller docs/linux/kernel_configs.md)
# Merged on top of defconfig + kvm_guest.config
# Coverage
CONFIG_KCOV=y
CONFIG_KCOV_INSTRUMENT_ALL=y
CONFIG_KCOV_ENABLE_COMPARISONS=y
CONFIG_DEBUG_FS=y
# Debug info for symbolization (Linux >= 5.12)
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
# Namespaces / sandboxing
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_CGROUP_PIDS=y
CONFIG_MEMCG=y
CONFIG_USER_NS=y
# VM image helpers (create-image.sh / Debian)
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y
# KASAN
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
# Fault injection (syzkaller uses these when available)
CONFIG_FAULT_INJECTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_FAULT_INJECTION_USERCOPY=y
CONFIG_FAILSLAB=y
CONFIG_FAIL_PAGE_ALLOC=y
CONFIG_FAIL_MAKE_REQUEST=y
CONFIG_FAIL_IO_TIMEOUT=y
CONFIG_FAIL_FUTEX=y
# Predictable interface names off (optional but recommended)
CONFIG_CMDLINE_BOOL=y
CONFIG_CMDLINE="net.ifnames=0"
# Part 2 only: Damn Vulnerable Kernel Module (/dev/dvkm). Omit until that driver exists in your tree.
CONFIG_DVKM=y
Sequence I used for x86_64:
1
2
3
4
5
6
cd /path/to/linux-6.19
make defconfig
make kvm_guest.config
./scripts/kconfig/merge_config.sh -m .config /path/to/syzkaller.fragment
make olddefconfig
make -j"$(nproc)"
What you boot: arch/x86/boot/bzImage. What you keep for symbolization: vmlinux at the tree root.
If merge_config complains, run make olddefconfig again after editing the fragment.
You still need a rootfs
Syzkaller fuzzes a machine, not a kernel file in a vacuum. The image must boot your bzImage, start sshd, accept root with the key you put in the manager config, and mount debugfs at /sys/kernel/debug. The script tools/create-image.sh (Debian-based) does the boring work; you end up with something like trixie.img and a matching *.id_rsa.
Manager JSON (minimal shape)
syz-manager wants real JSON (no // comments). Fields you will touch constantly:
target: e.g."linux/amd64"workdir: corpus + crasheskernel_obj: build tree (findsvmlinux)image: raw disk pathsshkey: private key, mode0600syzkaller: checkout path (it findsbin/under there)type:qemuvm:kernel(path tobzImage),count,cpu,mem
Example skeleton:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"workdir": "/path/to/workdir",
"kernel_obj": "/path/to/linux-6.19",
"image": "/path/to/trixie.img",
"sshkey": "/path/to/trixie.id_rsa",
"syzkaller": "/path/to/syzkaller",
"procs": 4,
"type": "qemu",
"vm": {
"count": 2,
"cpu": 2,
"mem": 2048,
"kernel": "/path/to/linux-6.19/arch/x86/boot/bzImage"
}
}
Run:
1
./bin/syz-manager -config /path/to/manager.cfg
Hit the http URL: exec rate, coverage, crashes.
syzlang (one paragraph)
Everything the fuzzer knows about open, ioctl, sockets, etc. comes from sys/linux/*.txt. syz-sysgen compiles those into what lives in the binaries. Add a custom driver in Part 2 and you add another .txt and run make descriptions. That is the whole trick.
When something breaks
- QEMU/KVM weirdness: trim
qemu_argsto-enable-kvmor turn KVM off (slower but boring). - SSH: key permissions, sshd not up, hostfwd port wrong.
unknown enabled syscall: the name is not in the syscall table you built. Runmake descriptionsand rebuild after editingsys/linux/*.txt.- First boot spams “disabled syscall” lines: normal. The fuzzer probes the guest and turns off what the kernel does not implement.
Next post
Part 2 plugs in a small misc device at /dev/dvkm, adds dev_dvkm.txt, shows narrow enable_syscalls configs for demos, and walks through workdir/crashes/ plus syz-repro on a log0 file.
Part 2: Damn vulnerable module + demo pipeline
References
- Collabora: Using syzkaller, part 1: Fuzzing the Linux kernel (Andre Almeida, 2020). Architecture overview and syzlang intro; cited above for the host/VM/RPC diagram.
- Syzkaller setup
- Kernel configs for fuzzing
- QEMU x86_64 + image
- Configuration
- Reproducing crashes




