Syzkaller Part 2: A Damn Vulnerable Kernel Module, Syzlang, and the Full Demo Pipeline
Part 2 of the Syzkaller writeup. Part 1 covered layout, install, and building a fuzzable 6.19 kernel for QEMU. Here I bolt on a deliberately broken driver, wire syzkaller to it with syzlang, run the manager, and follow crashes into reproducers and a tiny hand-written C program.
Lab and education only. Do not paste this into a product kernel unless you enjoy incident response.
Pieces on the bench
| Piece | Purpose |
|---|---|
DVKM misc driver | /dev/dvkm, ioctls that botch sizes and lifetimes on purpose |
include/uapi/linux/dvkm.h | Ioctl numbers + packed structs for userspace and tooling |
sys/linux/dev_dvkm.txt | Syzlang: openat$dvkm, ioctl$DVKM_*, shapes for args |
make descriptions | Regenerates syscall metadata so openat$dvkm exists in binaries |
| Manager JSON | Either full syscall set or a short enable_syscalls list for demos |
What the driver does
Code lives at drivers/misc/dvkm.c, enabled with CONFIG_DVKM=y, registered as a misc device.
Three intentional patterns:
- Heap:
kmalloc(32), thencopy_from_userwith a user-controlled length (too large). - Stack: 128-byte buffer on stack, same bad trust in length.
- UAF: ioctl ops for alloc / free / use, but the pointer is not cleared after
kfree.
With KASAN and hardened usercopy you tend to see either a sanitizer report or __copy_overflow with a stack that mentions dvkm_ioctl. Good enough for a live demo.
UAPI header
Ioctl commands use _IOWR / _IOW. Structs are packed so the size baked into the ioctl number matches what userspace and the fuzzer send:
1
2
3
4
5
6
7
8
#define DVKM_IOC_MAGIC 0xD8
struct dvkm_heap_overflow {
__u64 user_ptr;
__u32 len;
} __attribute__((packed));
#define DVKM_IOCTL_HEAP_OVERFLOW _IOWR(DVKM_IOC_MAGIC, 1, struct dvkm_heap_overflow)
Full file sits beside the kernel tree: include/uapi/linux/dvkm.h.
Syzlang file
Descriptions are not C. You define a fd_dvkm resource, openat$dvkm pointing at /dev/dvkm, and per-ioctl lines. Command codes can be literal hex if you do not want to run header extraction yet:
1
ioctl$DVKM_IOCTL_HEAP_OVERFLOW(fd fd_dvkm, cmd const[0xc00cd801], arg ptr[in, dvkm_heap_overflow])
After any edit under sys/linux/:
1
2
3
4
cd /path/to/syzkaller
rm -f .descriptions # optional, forces a full regen
make descriptions
make manager target # or plain make
Skip make descriptions and openat$dvkm never lands in bin/syz-manager. Configs that whitelist openat$dvkm in enable_syscalls then die with unknown enabled syscall. I hit that once with a stale build; rebuilding fixed it.
Adding DVKM to the kernel (step by step)
The driver is not in upstream Linux. You add a small set of files under your kernel tree, wire Kbuild, turn the option on, rebuild, and boot. Do this on a copy of the tree you already use for syzkaller (same version you intend to fuzz).
1. Files and where they live
| Path | Role |
|---|---|
include/uapi/linux/dvkm.h | UAPI: ioctl numbers, packed structs, enum for UAF ops. Userspace and syzkaller include this by path. |
drivers/misc/dvkm.c | The misc device driver: /dev/dvkm, unlocked_ioctl, the intentional bugs. |
dvkm.c includes <uapi/linux/dvkm.h> so the ioctl definitions match in one place.
2. Hook it into drivers/misc
drivers/misc/Makefile (append one line):
1
obj-$(CONFIG_DVKM) += dvkm.o
drivers/misc/Kconfig (inside the menu "Misc devices" block, before endmenu):
config DVKM
bool "Damn Vulnerable Kernel Module (fuzzing demo only)"
default n
help
Exposes /dev/dvkm with intentional bugs for research demos only.
Do not enable in production kernels.
Run make menuconfig later and you will find it under Device Drivers then Misc devices, or set the symbol from the command line (below).
3. Enable the option
Pick one approach.
A. Kconfig fragment (good for automation)
If you already merge a syzkaller-oriented fragment (see Part 1), add:
1
CONFIG_DVKM=y
Then:
1
2
./scripts/kconfig/merge_config.sh -m .config your-fragment.config
make olddefconfig
B. Interactive
1
make menuconfig
Navigate to Device Drivers → Misc devices → enable Damn Vulnerable Kernel Module. Save and exit.
C. One-shot without menu
1
2
./scripts/config --enable DVKM
make olddefconfig
4. Built-in (y) vs module (m)
| Choice | When to use |
|---|---|
CONFIG_DVKM=y | Simplest for QEMU demos. The driver is always there; /dev/dvkm exists as soon as the kernel boots. No insmod, no copying dvkm.ko into the disk image. |
CONFIG_DVKM=m | You load it by hand or from an init script. You must install drivers/misc/dvkm.ko into the guest’s /lib/modules/$(uname -r)/ (or scp it in and insmod). Easy to forget. |
For fewer surprises, use y unless you have a reason to keep it modular.
5. Build
From the kernel tree:
1
2
make olddefconfig
make -j"$(nproc)"
Fix compile errors before moving on. Typical first-time issues:
misc_unregistervsmisc_deregister: older examples usemisc_unregister; current trees exposemisc_deregister(seeinclude/linux/miscdevice.h). If the compiler complains about an implicit declaration, fix the name to match your kernel version.- Missing header:
dvkm.cmust seeinclude/uapi/linux/dvkm.h. It should if the file is underinclude/uapi/linux/.
Outputs you need for syzkaller unchanged: vmlinux, arch/x86/boot/bzImage.
6. Sanity check after boot
On the guest (or QEMU serial shell):
1
ls -l /dev/dvkm
You want a char device (misc creates it with the name dvkm). If it is missing:
- Confirm you booted this
bzImage(not an old kernel). - For
=m, runlsmod | grep dvkmand load the module if needed. - Check
dmesgfor registration errors.
Optional quick test (as root):
1
python3 -c "import os,fcntl; f=os.open('/dev/dvkm',os.O_RDWR); print('ok', f)"
If that opens, the node and permissions are fine.
7. VM image and permissions
The syzkaller image usually runs fuzzing as root, so 0666 on the misc device (if you set that in code) or root-only access both work. If you tighten permissions later, keep syzkaller’s executor able to open the device.
8. Checklist before you fuzz
include/uapi/linux/dvkm.hpresentdrivers/misc/dvkm.cpresentdrivers/misc/Makefilehasobj-$(CONFIG_DVKM)drivers/misc/Kconfighasconfig DVKMCONFIG_DVKM=y(orm+ module installed in guest)- Full rebuild done,
bzImagecopied or pointed to by syzkaller config - Guest shows
/dev/dvkmafter boot
Once that is green, wire syzkaller (dev_dvkm.txt, make descriptions, manager config) as in the sections above.
Two config styles
Full fuzz: point kernel_obj, image, sshkey, and vm.kernel at your bzImage and disk. Leave enable_syscalls out so everything in the description set can fire.
Narrow demo: set enable_syscalls to openat$dvkm, the ioctl$DVKM_* names, plus helpers like mmap, close, munmap. Logs get easier to read for a talk; coverage numbers shrink. Use a separate workdir (e.g. workdir-dvkm) so you do not mix corpora with a wide fuzz run.
Crash output layout
When the guest dies interestingly, syz-manager writes under:
1
<workdir>/crashes/<hash>/
Useful names:
| File | What it is |
|---|---|
logN | Raw log from executor / kernel |
reportN | Parsed report |
repro.prog | Minimized syz program if repro succeeded |
repro.cprog | C output only if that stage succeeded |
repro0 … repro2 | Logs from failed automatic repro attempts, not a working PoC |
Do not assume you get repro.cprog. Often you stop at repro.prog. Upstream doc: Reproducing crashes.
syz-repro
Same manager JSON as fuzzing, one argument: a single crash log file (e.g. log0), not the whole directory:
1
2
./bin/syz-repro -config=trixie-fuzz-dvkm.cfg \
./workdir-dvkm/crashes/<id>/log0
Remember: regenerate syscall descriptions and rebuild syz-repro after adding dev_dvkm.txt. Pass a file path. If enable_syscalls keeps biting you during repro, point at a config without that key but with the same QEMU/kernel/image.
Hand-written C PoC
For a demo you sometimes want a twenty-line program: open /dev/dvkm, one ioctl with len bigger than the kmalloc, no dependency on syzkaller’s C generator. Build it, scp to the guest, run, read dmesg. Boring but reliable when repro.cprog never appears.
Wrap-up
You end up with: fuzzable kernel, silly driver in-tree, syzlang so the fuzzer reaches it, manager for continuous runs, optional syz-repro on a saved log, and a manual C file when you need a guaranteed slide.




