Skip to content

Kernel VM Verification

0 ingest --verify runs C reproducers inside a local QEMU guest and compares the guest dmesg against the imported kernel crash report. Without the VM, kernel verification is static-only.

For choosing between ingest --verify, research linux, research linux-matrix, dynamic-witness hunting, and weaponization, see Research Workflows. Importing an external boot matrix does not execute the boots; a source checkout alone does not provision the guest.

A maintained build recipe at packages/core/src/triage/kernel-vm/ builds:

  • bzImage — Linux 6.8.12 for x86_64. The recipe requests KASAN, UBSAN, KCSAN, lock debugging, RCU stall detection, and virtio/9p/ext4/NFS/Bluetooth/WiFi/SCTP support; inspect the generated config rather than assuming every requested option survives olddefconfig. Tree builds use separate kasan and kcsan profiles.
  • rootfs.img — 512 MB Debian Bookworm ext4 with gcc, binutils, make, procps, kmod, strace, gdb, OpenSSH, and /sbin/0-init.
  • kernel.config — the exact config used for the build.
  • osec_vm_key[.pub] — root SSH keypair for manual debugging only (the verifier uses a QEMU 9p share, not SSH).

Prebuilt artifacts are not committed. Build locally; the earlier kernel-validator GitHub Actions workflows are not present in this checkout.

  • Docker (reproducible guest build)
  • QEMU (qemu-system-x86_64)
  • ~20 GB free disk for the build cache
  • Enough guest memory (default 2048 MB)
  • Optional KVM acceleration on Linux; macOS/CI run without it, but may need higher boot/reproducer timeouts.

From the repo root:

Terminal window
# Docker builds the guest; the published 0 CLI and QEMU are needed to run it.
cd packages/core/src/triage/kernel-vm
env ZERO_KERNEL_VM_MAKE_JOBS=4 \
./build.sh "$HOME/.0/kernel-vm/linux-6.8.12-kasan"

Output:

$HOME/.0/kernel-vm/linux-6.8.12-kasan/
bzImage
rootfs.img
kernel.config
osec_vm_key
osec_vm_key.pub

Treat the output directory as a local cache; regenerate it when the Dockerfile, kernel version, or guest package list changes.

Required values must be passed with env: ZERO_* names begin with a digit and cannot be exported by POSIX shells.

Terminal window
env \
ZERO_KERNEL_QEMU=1 \
ZERO_KERNEL_QEMU_KERNEL="$HOME/.0/kernel-vm/linux-6.8.12-kasan/bzImage" \
ZERO_KERNEL_QEMU_DISK="$HOME/.0/kernel-vm/linux-6.8.12-kasan/rootfs.img" \
ZERO_KERNEL_QEMU_CONFIG="$HOME/.0/kernel-vm/linux-6.8.12-kasan/kernel.config" \
ZERO_KERNEL_QEMU_EXPECTED_RELEASE=6.8.12 \
0 ingest --verify ./crashes

Recommended local defaults can be added to the same command:

Terminal window
env \
ZERO_KERNEL_QEMU=1 \
ZERO_KERNEL_QEMU_KERNEL="$HOME/.0/kernel-vm/linux-6.8.12-kasan/bzImage" \
ZERO_KERNEL_QEMU_DISK="$HOME/.0/kernel-vm/linux-6.8.12-kasan/rootfs.img" \
ZERO_KERNEL_QEMU_CONFIG="$HOME/.0/kernel-vm/linux-6.8.12-kasan/kernel.config" \
ZERO_KERNEL_QEMU_EXPECTED_RELEASE=6.8.12 \
ZERO_KERNEL_QEMU_MEMORY_MB=2048 \
ZERO_KERNEL_QEMU_SMP=2 \
ZERO_KERNEL_QEMU_BOOT_TIMEOUT_SEC=180 \
ZERO_KERNEL_QEMU_TIMEOUT_SEC=60 \
ZERO_KERNEL_QEMU_ARTIFACT_DIR="$HOME/.0/kernel-vm/runs" \
0 ingest --verify ./crashes

On Linux hosts with KVM, add ZERO_KERNEL_QEMU_ACCEL=kvm to that env invocation.

Leave ZERO_KERNEL_QEMU_APPEND unset unless using a custom guest. Default:

console=ttyS0 root=/dev/vda rw nokaslr panic=-1 init=/sbin/0-init

The release above applies only to the unmodified 6.8.12 recipe. For custom images, supply the exact built release, including any local-version suffix. Direct VM execution requires the expected release and a real config for receipt binding; a filename is not provenance.

Place crash reports and reproducers in one directory; file stems are matched:

crashes/
bug-001.log
bug-001.c
bug-002.report
bug-002.syz
Terminal window
0 ingest ./crashes --verify --output json

Run this with the same env settings as above; they apply only to that one command and are not saved by the first invocation.

For a standalone reproducer, use a source tree and the current profile flag:

Terminal window
0 ingest --reproducer ./poc.c --kernel-tree /path/to/linux \
--kernel-config kasan --output json

This resolves/builds cached artifacts (default ~/.0/kernel-cache, override with --kernel-cache-dir or ZERO_KERNEL_BUILD_CACHE). Built-in profiles are kasan, kcsan, and plain. Existing ZERO_KERNEL_QEMU_KERNEL/DISK overrides take precedence unless --force-kernel-build is used, so unset those overrides when you intend to test the supplied tree. --syz ./program.syz requires syz-execprog in the guest; the stock rootfs recipe does not install it.

For each C reproducer 0 writes repro.c and runner.sh to a temp dir, boots QEMU with a 9p share (osecshare), lets /sbin/0-init run /mnt/0/runner.sh, compiles and runs the reproducer under the timeout, and copies compile.log, run.log, dmesg.log, markers, and the serial log back to the artifact directory (when configured).

The guest runs reproducers as UID 0 by default, so it can prove repeatable crash behavior but not unprivileged reachability — such evidence is marked privileged. Zero-cap certification uses a trusted launcher that drops all IDs, groups, and capabilities, sets no_new_privs, and binds a hashed receipt to a nonce and the reproducer digest; missing or inconsistent evidence falls back to privileged.

Schema-v2 receipts also bind a staged copy of the bzImage, its config SHA-256, and the expected kernel release; QEMU boots the staged image and the host re-hashes it before and after. The guest supplies its runtime release (/proc/sys/kernel/osrelease) and boot UUID; a release mismatch, malformed or repeated UUID, or staged-image change invalidates the gate. This catches ordinary label/artifact mixups but is not hardware attestation (no TPM / SEV-SNP) and does not defend against a malicious host or guest kernel, nor prove the running kernel config without a runtime measurement like /proc/config.gz.

If ZERO_KERNEL_QEMU_ARTIFACT_DIR is unset, the temp run directory is deleted after each attempt.

A custom guest must satisfy:

RequirementContract
Architecturex86_64, bootable by qemu-system-x86_64
Root deviceroot=/dev/vda (or matching custom append)
Init path/sbin/0-init (unless ZERO_KERNEL_QEMU_APPEND changed)
Host shareMount 9p tag osecshare at /mnt/0
RunnerExecute /mnt/0/runner.sh, leave results in the share
Compiler/usr/bin/gcc plus libc headers and binutils
Logsdmesg readable after the reproducer runs
KernelDebug-friendly, crash signal visible in dmesg

SSH is not part of the contract; the keypair is only for manual debugging.

VariableRequiredDefaultDescription
ZERO_KERNEL_QEMUYes-1 to enable VM execution
ZERO_KERNEL_QEMU_KERNELYes-Path to bzImage
ZERO_KERNEL_QEMU_DISKYes-Path to rootfs.img or other bootable disk
ZERO_KERNEL_QEMU_CONFIGFor direct execution receipts-Config used to build the selected kernel
ZERO_KERNEL_QEMU_EXPECTED_RELEASEFor direct/prebuilt execution-Exact expected uname -r; never inferred from filename
ZERO_KERNEL_QEMU_BINARYNoqemu-system-x86_64QEMU binary
ZERO_KERNEL_QEMU_DISK_FORMATNoinferredraw or qcow2
ZERO_KERNEL_QEMU_MEMORY_MBNo2048Guest memory (MB)
ZERO_KERNEL_QEMU_SMPNo2Guest CPU count
ZERO_KERNEL_QEMU_APPENDNosee aboveKernel command line
ZERO_KERNEL_QEMU_ACCELNo-Accelerator, e.g. kvm
ZERO_KERNEL_QEMU_INITRDNo-Optional initrd for custom guests
ZERO_KERNEL_QEMU_BOOT_TIMEOUT_SECNo120Boot + setup time
ZERO_KERNEL_QEMU_TIMEOUT_SECNo60Reproducer time
ZERO_KERNEL_QEMU_SHARE_TAGNoosecshare9p mount tag
ZERO_KERNEL_QEMU_ARTIFACT_DIRNo-Where per-run artifacts are preserved

If the VM exits early, inspect serial.log in the configured artifact directory. The retained manual smoke script is scripts/kernel-validator-e2e.sh; it expects built packages/cli/dist/index.js, downloads a syzbot report/reproducer, and uses your configured VM artifacts. It is not a current GitHub Actions workflow.

Maintainers can run node scripts/kernel-validator-batch.mjs --help from the repository root. The script accepts --corpus, --out-dir, --cli, and --limit; its default corpus is scripts/kernel-validator-batch-corpus.json. Supply the VM configuration above and a built CLI for actual execution. --dry-run writes skipped summaries without QEMU and is not reproduction proof. Outputs include summary.json, summary.md, per-case results, raw CLI output, and retained VM artifacts. Inspect verified, crashMatch, and reason: reproduced alone can mean execution occurred without a recognized crash.