Kernel VM Verification
pwnkit ingest --verify can run C reproducers inside a local QEMU guest and compare
the guest dmesg output with the imported kernel crash report. Without this VM,
kernel verification remains static-only and pwnkit will not claim that a crash was
reproduced.
What The Repo Provides
Section titled “What The Repo Provides”pwnkit ships a maintained build recipe at
packages/core/src/triage/kernel-vm/. The recipe builds:
bzImage: Linux 6.8.12 for x86_64 with KASAN, UBSAN, KCSAN, lock debugging, RCU stall detection, virtio, 9p, ext4, NFS/NFSd, Bluetooth, WiFi, and SCTP support enabled.rootfs.img: a 512 MB Debian Bookworm ext4 image withgcc,binutils,make,procps,kmod,strace,gdb, OpenSSH, and/sbin/pwnkit-init.kernel.config: the exact kernel config used for the build.pwnkit_vm_keyandpwnkit_vm_key.pub: a root SSH keypair for manual guest debugging. The current pwnkit verifier does not use SSH; it communicates through a QEMU 9p shared directory.
The repository does not commit prebuilt VM artifacts. Build them locally or let the GitHub Actions E2E workflow build and cache them.
Requirements
Section titled “Requirements”Install these host tools before building or running VM-backed verification:
- Docker for the reproducible guest build.
- QEMU, usually
qemu-system-x86_64, for local verification runs. - At least 20 GB of free disk space for the Docker build cache and kernel build.
- Enough memory for the guest. The runner defaults to 2048 MB.
- Optional KVM acceleration on Linux. macOS and CI can run without acceleration, but boot and reproducer timeouts may need to be higher.
Known-Good Build Recipe
Section titled “Known-Good Build Recipe”From the repository root:
pnpm install --frozen-lockfile
cd packages/core/src/triage/kernel-vmPWNKIT_KERNEL_VM_MAKE_JOBS=4 ./build.sh "$HOME/.pwnkit/kernel-vm/linux-6.8.12-kasan"Expected output directory:
$HOME/.pwnkit/kernel-vm/linux-6.8.12-kasan/ bzImage rootfs.img kernel.config pwnkit_vm_key pwnkit_vm_key.pubThe Dockerfile pins the kernel source to linux-6.8.12 from cdn.kernel.org
and builds an amd64 Debian Bookworm guest. Treat the output directory as a local
cache: regenerate it when the Dockerfile, kernel version, or guest package list
changes.
Configure pwnkit
Section titled “Configure pwnkit”Set the required environment variables:
export PWNKIT_KERNEL_QEMU=1export PWNKIT_KERNEL_QEMU_KERNEL="$HOME/.pwnkit/kernel-vm/linux-6.8.12-kasan/bzImage"export PWNKIT_KERNEL_QEMU_DISK="$HOME/.pwnkit/kernel-vm/linux-6.8.12-kasan/rootfs.img"Recommended local defaults:
export PWNKIT_KERNEL_QEMU_MEMORY_MB=2048export PWNKIT_KERNEL_QEMU_SMP=2export PWNKIT_KERNEL_QEMU_BOOT_TIMEOUT_SEC=180export PWNKIT_KERNEL_QEMU_TIMEOUT_SEC=60export PWNKIT_KERNEL_QEMU_ARTIFACT_DIR="$HOME/.pwnkit/kernel-vm/runs"On Linux hosts with KVM:
export PWNKIT_KERNEL_QEMU_ACCEL=kvmLeave PWNKIT_KERNEL_QEMU_APPEND unset unless you use a custom guest. The default
is:
console=ttyS0 root=/dev/vda rw nokaslr panic=-1 init=/sbin/pwnkit-initRun Verification
Section titled “Run Verification”Place syzbot-style crash reports and reproducers in the same directory. File stems are matched:
crashes/ bug-001.log bug-001.c bug-002.report bug-002.syzRun:
pwnkit ingest ./crashes --verify --output jsonFor each C reproducer, pwnkit:
- Creates a temporary host directory.
- Writes
repro.candrunner.shinto that directory. - Boots QEMU with
bzImage,rootfs.img, and a 9p share mounted aspwnkitshare. - Lets
/sbin/pwnkit-initmount the share and execute/mnt/pwnkit/runner.sh. - Compiles the reproducer with guest
gcc. - Runs the reproducer under the configured timeout.
- Copies
compile.log,run.log,dmesg.log, marker files, and the serial log back to the host artifact directory when configured.
If PWNKIT_KERNEL_QEMU_ARTIFACT_DIR is unset, pwnkit deletes the temporary run
directory after each verification attempt.
Guest Contract
Section titled “Guest Contract”A custom guest must satisfy this contract:
| Requirement | Contract |
|---|---|
| Architecture | x86_64 guest bootable by qemu-system-x86_64 |
| Root device | root=/dev/vda by default, or matching custom append line |
| Init path | /sbin/pwnkit-init, unless PWNKIT_KERNEL_QEMU_APPEND is changed |
| Host share | Mount QEMU 9p tag pwnkitshare at /mnt/pwnkit |
| Runner | Execute /mnt/pwnkit/runner.sh and leave results in the same share |
| Compiler | /usr/bin/gcc plus libc headers and binutils |
| Logs | dmesg readable after the reproducer runs |
| Kernel | Debug-friendly kernel with crash signal visible in dmesg |
SSH is not part of the verifier contract. The generated keypair is exported only for manual debugging if you boot the image yourself with a network device and port forwarding.
Environment Variables
Section titled “Environment Variables”| Variable | Required | Default | Description |
|---|---|---|---|
PWNKIT_KERNEL_QEMU | Yes | - | Set to 1 to enable VM execution. |
PWNKIT_KERNEL_QEMU_KERNEL | Yes | - | Path to bzImage. |
PWNKIT_KERNEL_QEMU_DISK | Yes | - | Path to rootfs.img or another bootable disk. |
PWNKIT_KERNEL_QEMU_BINARY | No | qemu-system-x86_64 | QEMU binary to execute. |
PWNKIT_KERNEL_QEMU_DISK_FORMAT | No | inferred | raw or qcow2; inferred from extension when unset. |
PWNKIT_KERNEL_QEMU_MEMORY_MB | No | 2048 | Guest memory in MB. |
PWNKIT_KERNEL_QEMU_SMP | No | 2 | Guest CPU count. |
PWNKIT_KERNEL_QEMU_APPEND | No | see above | Kernel command line. |
PWNKIT_KERNEL_QEMU_ACCEL | No | - | QEMU accelerator, for example kvm. |
PWNKIT_KERNEL_QEMU_INITRD | No | - | Optional initrd path for custom guests. |
PWNKIT_KERNEL_QEMU_BOOT_TIMEOUT_SEC | No | 120 | Time allowed for boot and guest setup. |
PWNKIT_KERNEL_QEMU_TIMEOUT_SEC | No | 60 | Time allowed for the reproducer. |
PWNKIT_KERNEL_QEMU_SHARE_TAG | No | pwnkitshare | 9p mount tag expected by the guest init. |
PWNKIT_KERNEL_QEMU_ARTIFACT_DIR | No | - | Host directory where per-run artifacts are preserved. |
Troubleshooting
Section titled “Troubleshooting”If the VM exits before producing results, inspect serial.log in
PWNKIT_KERNEL_QEMU_ARTIFACT_DIR. Common causes:
- The guest did not mount the 9p share. Keep
PWNKIT_KERNEL_QEMU_SHARE_TAGand/sbin/pwnkit-initin sync. gccor libc headers are missing in a custom guest.- The guest cannot read
dmesg. - The boot timeout is too low on hosts without KVM.
- A custom kernel append line no longer points at the correct root disk or init.
The repository E2E workflow, .github/workflows/kernel-validator-e2e.yml, is the
smoke-tested reference for CI. It builds the same artifacts, boots QEMU, runs a
real ingest --verify flow, and uploads the VM logs plus runner outputs as
artifacts.
Batch Validation Workflow
Section titled “Batch Validation Workflow”Maintainers can run .github/workflows/kernel-validator-batch.yml manually from
GitHub Actions to validate a curated syzbot corpus against the real VM runner.
The default corpus lives at scripts/kernel-validator-batch-corpus.json; the
workflow also accepts a JSON override for one-off corpus experiments.
The workflow uploads summary.json, summary.md, each case’s result.json,
raw CLI output, and per-case VM artifacts. summary.json separates
VM-reproduced verdicts from static-only verdicts with these top-level counts:
verified, reproduced, crashMatch, reproducedMismatch, staticOnly,
failed, and errored.
The batch workflow is workflow_dispatch only. Add a schedule only after the
curated corpus and VM artifact cache are stable enough for unattended runs.