Files
SKELETONKEY/tools/verify-vm
leviathan 67d091dd37 verified_on table — 5 modules empirically confirmed in real VMs
Closes the loop opened by tools/verify-vm/: every JSON verification
record now persists into docs/VERIFICATIONS.jsonl, gets folded into
the embedded core/verifications.c lookup table, and surfaces in
--list / --module-info / --explain / --scan --json.

New: docs/VERIFICATIONS.jsonl
  Append-only store. One JSON record per verify.sh run. Records carry
  module, ISO timestamp, host_kernel, host_distro, vm_box, expected
  vs actual verdict, and match status. 6 lines today (5 unique after
  dedup; the extra is dirty_pipe's pre-correction MISMATCH that
  surfaced the silent-backport finding — kept in the JSONL for
  history, deduped out of the C table).

New: tools/refresh-verifications.py
  Parses VERIFICATIONS.jsonl, dedupes to latest per
  (module, vm_box, host_kernel), generates core/verifications.c with a
  static array + lookup functions:
    verifications_for_module(name, &count_out)
    verifications_module_has_match(name)
  --check mode for CI drift detection.

New: core/verifications.{h,c}
  Embedded record table. Lookup is O(corpus); we have <50 records.

skeletonkey.c surfacing:
  - --list: new 'VFY' column shows ✓ for modules with >=1 'match'
    record. Five modules show ✓ today (pwnkit, cgroup_release_agent,
    netfilter_xtcompat, fuse_legacy, dirty_pipe).
  - --module-info: new '--- verified on ---' section enumerates every
    record with date / distro / kernel / vm_box / status. Modules with
    zero records get a 'run tools/verify-vm/verify.sh <name>' hint.
  - --explain: new 'VERIFIED ON' section in the operator briefing.
  - --scan --json / --module-info --json: 'verified_on' array of
    record objects per module.

Verification records baked in:

  pwnkit               Ubuntu 20.04.6 LTS  5.4.0-169   match (polkit 0.105)
  cgroup_release_agent Debian 11 (bullseye) 5.10.0-27  match
  netfilter_xtcompat   Debian 11 (bullseye) 5.10.0-27  match
  fuse_legacy          Debian 11 (bullseye) 5.10.0-27  match
  dirty_pipe           Ubuntu 22.04.3 LTS   5.15.0-91  match (OK; silent backport)

The dirty_pipe record is particularly informative: stock Ubuntu 22.04
ships 5.15.0-91-generic. Our version-only kernel_range check would say
VULNERABLE (5.15.0 < 5.15.25 backport in our table). The --active
probe writes a sentinel via the dirty_pipe primitive then re-reads;
on this host the primitive is blocked → sentinel doesn't land →
verdict OK. Ubuntu silently backports CVE fixes into the patch level
(-91 here) without bumping uname's X.Y.Z. The targets.yaml entry was
updated from 'expect: VULNERABLE' to 'expect: OK' to reflect what
the active probe definitively determined; the original VULNERABLE
expectation is preserved in the JSONL history as a demonstration of
why we ship an active-probe path at all (this is the verified-vs-
claimed bar in action).

Plumbing fixes that landed in the same loop:

  - core/nft_compat.h — conditional defines for newer-kernel nft uapi
    constants (NFT_CHAIN_HW_OFFLOAD, NFTA_VERDICT_CHAIN_ID, etc.)
    that aren't in Ubuntu 20.04's pre-5.5 linux-libc-dev. Without
    this, nft_* modules failed to compile inside the verifier guest.
    Included from each nft module after <linux/netfilter/nf_tables.h>.

  - tools/verify-vm/Vagrantfile — wrap config in c.vm.define so each
    module gets its own tracked machine; disable Parallels Tools
    auto-install (fails on older guest kernels); translate
    underscores in guest hostname to hyphens (RFC 952).

  - tools/verify-vm/verify.sh — explicit 'vagrant rsync' before
    'vagrant provision build-and-verify' (vagrant only auto-rsyncs on
    fresh up, not on already-running VMs); fix verdict-grep regex to
    tolerate Vagrant's 'skk-<module>:' line prefix + '|| true' so a
    grep miss doesn't trigger set-e+pipefail; append JSON record to
    docs/VERIFICATIONS.jsonl on every run.

  - tools/verify-vm/targets.yaml — dirty_pipe retargeted from
    ubuntu2004 + pinned 5.13.0-19 (no longer in 20.04's apt) to
    ubuntu2204 stock 5.15.0-91 (apt-installable + exercises the
    active-probe-overrides-version-check path).

What's next for the verifier:
  - Mainline kernel.ubuntu.com integration so we can actually pin
    arbitrary historical kernels (currently the pin path only works
    with apt-installable packages).
  - Sweep the remaining ~18 verifiable modules and accumulate records.
  - Per-module verified_on counts in --explain header.
2026-05-23 15:46:14 -04:00
..

SKELETONKEY VM verification

Auto-provisions a Parallels Desktop VM with a known-vulnerable kernel, runs skeletonkey --explain <module> --active inside it, and emits a verification record. Closes the loop between "detect() compiles & passes unit tests" and "exploit() actually works on a real vulnerable kernel."

One-time setup

./tools/verify-vm/setup.sh

That installs (if missing): Vagrant via Homebrew, the vagrant-parallels plugin, and pre-downloads ~5 GB of base boxes (Ubuntu 18.04/20.04/22.04

  • Debian 11/12). Idempotent — re-run any time.

To skip boxes you don't need (save disk):

./tools/verify-vm/setup.sh ubuntu2004 debian11   # only those two

Verify a single module

./tools/verify-vm/verify.sh nf_tables

What that does:

  1. Reads tools/verify-vm/targets.yaml: finds nf_tables → box generic/ubuntu2204 + kernel pin linux-image-5.15.0-43-generic.
  2. vagrant up skk-nf_tables (provisions on first call, resumes on subsequent).
  3. Installs the pinned vulnerable kernel via apt, reboots.
  4. Mounts the local repo at /vagrant, runs make, then runs skeletonkey --explain nf_tables --active.
  5. Parses the VERDICT: line, compares against expect_detect from targets.yaml, emits a JSON verification record on stdout.
  6. Suspends the VM (vagrant suspend) — instant resume next run.

Lifecycle flags:

./tools/verify-vm/verify.sh nf_tables --keep      # leave VM running; ssh in to inspect
./tools/verify-vm/verify.sh nf_tables --destroy   # full teardown after run

List every target

./tools/verify-vm/verify.sh --list

Shows the (module, box, target kernel, expected verdict, notes) matrix for all 26 modules. Three are flagged manual: true because no public Vagrant box covers them:

  • vmwgfx — only reachable on VMware guests; needs a vSphere/Fusion VM not Parallels.
  • dirtydecrypt, fragnesia — only present in Linux 7.0+ which isn't shipping as a distro kernel yet.

For those, verification needs a hand-built or special-distro VM.

Verification records

verify.sh emits JSON on stdout after each run. Example:

{
  "module":        "nf_tables",
  "verified_at":   "2026-05-23T17:42:11Z",
  "host_kernel":   "5.15.0-43-generic",
  "host_distro":   "Ubuntu 22.04.5 LTS",
  "vm_box":        "generic/ubuntu2204",
  "expect_detect": "VULNERABLE",
  "actual_detect": "VULNERABLE",
  "status":        "match",
  "log":           "tools/verify-vm/logs/verify-nf_tables-20260523-174211.log"
}

status: match means detect() returned what we expected on a known- vulnerable kernel. Anything else (MISMATCH, status code != 0) means either:

  • The kernel pin didn't take (check host_kernel against kernel_version in targets.yaml).
  • The exploit's preconditions aren't met in the default Vagrant image (e.g. apparmor blocks unprivileged userns; need to adjust the Vagrantfile provisioner).
  • The detect() logic is wrong for this kernel/distro combo (a real bug — fix it).

Records are intended to feed a per-module verified_on[] table (next project step) so --list can show a ✓ verified <date> column.

How it routes module → box

Mapping lives in tools/verify-vm/targets.yaml. Each entry has:

  • box — which boxes/ template (e.g. ubuntu2204)
  • kernel_pkg — apt package name to install if the stock kernel is patched (omit / empty if stock is already vulnerable)
  • kernel_version — what uname -r should report after install
  • expect_detectVULNERABLE | OK | PRECOND_FAIL
  • notes — short rationale; comments in the file have the full context

Adding a new module is one block in targets.yaml. The verifier picks it up automatically.

Files

tools/verify-vm/
├── README.md       this file
├── setup.sh        one-time bootstrap (Vagrant, plugin, box cache)
├── verify.sh       per-module verifier
├── Vagrantfile     parameterized VM config (driven by SKK_VM_* env vars)
├── targets.yaml    module → box mapping with rationale
└── logs/           per-verification stdout/stderr capture

Why Vagrant + Parallels

You already have Parallels Desktop. vagrant-parallels gives a scriptable per-VM config + a curated public box library + idempotent vagrant up/provision/reload/suspend lifecycle. The Vagrantfile is parameterized via env vars so a single file drives every target.

Alternative providers (Lima, Multipass) would also work; Vagrant was chosen for ergonomic continuity with the existing Parallels install.