Unblocks the 4 previously-PIN_FAIL modules by adding a fallback path to kernel.ubuntu.com/mainline/ for any kernel no longer in apt. Adds 4 more matches to the verified_on table for a total of 22 modules confirmed against real Linux VMs: af_unix_gc ubuntu2204 + mainline 5.15.5 match nf_tables ubuntu2204 + mainline 5.15.5 match nft_set_uaf ubuntu2204 + mainline 5.15.5 match stackrot ubuntu2204 + mainline 6.1.10 match Mechanism: tools/verify-vm/Vagrantfile — new 'pin-mainline-<X.Y.Z>' shell provisioner. Fetches the directory index at https://kernel.ubuntu.com/mainline/v<X.Y.Z>/amd64/, parses out the 4 canonical .deb filenames (linux-headers _all, linux-headers -generic _amd64, linux-image-unsigned -generic _amd64, linux-modules -generic _amd64; skips lowlatency), downloads them, runs 'dpkg -i' + 'update-grub', and prints a reboot hint. Mainline package version like '5.15.5-051505' sorts ABOVE Ubuntu's stock '5.15.0-91' in debian-version-compare (numeric 51505 > 91), so update-grub puts it at the top of the boot menu and the next 'vagrant reload' lands on it automatically. uname then reports '5.15.5-051505-generic' which our parser sees as 5.15.5 → in our kernel_range table's vulnerable window → empirical VULNERABLE. tools/verify-vm/verify.sh — new SKK_VM_MAINLINE_VERSION env passed to the Vagrantfile. Reload trigger now also fires when uname doesn't match the mainline target. tools/verify-vm/targets.yaml — new 'mainline_version' field on the 4 PIN_FAIL targets. kernel_pkg is left empty; mainline_version drives the fetch. Picked 5.15.5 (Nov 2021) for the 5.15-line CVEs and 6.1.10 (Feb 2023) for stackrot — both below every relevant backport. Final sweep status (22 of 26 CVEs): ✓ MATCHES (22): pwnkit, cgroup_release_agent, netfilter_xtcompat, fuse_legacy, nft_fwd_dup, entrybleed, overlayfs, overlayfs_setuid, sudoedit_editor, ptrace_traceme, sudo_samedit, af_packet, pack2theroot, cls_route4, nft_payload, af_packet2, sequoia, dirty_pipe, nf_tables, af_unix_gc, nft_set_uaf, stackrot 🚫 NOT VERIFIED (4 — flagged in targets.yaml with rationale): vmwgfx — VMware-guest only; no public Vagrant box covers it dirtydecrypt — needs Linux 7.0; not shipping as any distro kernel fragnesia — needs Linux 7.0; same dirty_cow — needs ≤ 4.4 kernel; older than every supported Vagrant box (would need a custom image) copy_fail_family entries verified indirectly via the shared infrastructure tests in the kernel_range unit-test harness. The 22 records are baked into core/verifications.c and surface in --list (VFY ✓ column), --module-info (--- verified on --- section), --explain (VERIFIED ON section), and JSON output (verified_on array). 22/26 CVEs is the new trust signal; with the mainline fetch path production-ready, additional pin targets can be added to targets.yaml without code changes.
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:
- Reads
tools/verify-vm/targets.yaml: findsnf_tables→ boxgeneric/ubuntu2204+ kernel pinlinux-image-5.15.0-43-generic. vagrant up skk-nf_tables(provisions on first call, resumes on subsequent).- Installs the pinned vulnerable kernel via
apt, reboots. - Mounts the local repo at
/vagrant, runsmake, then runsskeletonkey --explain nf_tables --active. - Parses the
VERDICT:line, compares againstexpect_detectfrom targets.yaml, emits a JSON verification record on stdout. - 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_kernelagainstkernel_versionin 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— whichboxes/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— whatuname -rshould report after installexpect_detect—VULNERABLE|OK|PRECOND_FAILnotes— 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.