Sweep results across 3 phases:
Phase 1 (no-pin, cached boxes) — 4/5 match:
entrybleed ubuntu2204 5.15.0-91-generic match
overlayfs ubuntu2004 5.4.0-169-generic match
overlayfs_setuid ubuntu2204 5.15.0-91-generic match
nft_fwd_dup debian11 5.10.0-27-amd64 match
sudoedit_editor ubuntu2204 MISMATCH (no sudoers grant — expected-fix below)
Phase 2 (new boxes ubuntu1804 + debian12) — 0/4 match:
ptrace_traceme \
sudo_samedit \ all FAILED to build: nft_fwd_dup needs
af_packet / NFTA_CHAIN_FLAGS (kernel 5.7), not in 4.15 uapi
pack2theroot /
pack2theroot also hit 'already root' early-exit (running as root via
vagrant provision's default privileged shell)
Phase 3 (kernel-pinned) — 4/8 match:
cls_route4 ubuntu2004 + 5.15.0-43 HWE match
nft_payload ubuntu2004 + 5.15.0-43 HWE match
af_packet2 ubuntu2004 + 5.4.0-26 (still in apt!) match
sequoia ubuntu2004 + 5.4.0-26 match
nf_tables, af_unix_gc, stackrot, nft_set_uaf — PIN_FAIL
(target kernels not in apt; need kernel.ubuntu.com mainline
integration — deferred)
Total: 13 modules verified end-to-end against real Linux VMs,
covering kernels 5.4 / 5.10 / 5.15 / 5.4-HWE / 5.15-HWE across
Ubuntu 18.04/20.04/22.04 + Debian 11/12.
Three fixes for the next retry pass:
1. core/nft_compat.h — added NFTA_CHAIN_FLAGS (kernel 5.7) and
NFTA_CHAIN_ID (kernel 5.13). Without these, nft_fwd_dup fails to
compile on Ubuntu 18.04's 4.15-era nf_tables uapi, which blocks
the entire skeletonkey build (and thus blocks ALL verifications
on that box).
2. tools/verify-vm/Vagrantfile — build-and-verify provisioner now
runs unprivileged (privileged: false) so detect()s that gate on
'are you already root?' don't short-circuit. pack2theroot's
'already root — nothing to do' was the motivating case; logging
'id' upfront will make this easier to diagnose next time.
3. tools/verify-vm/targets.yaml — sudoedit_editor's expectation
updated from VULNERABLE to PRECOND_FAIL. Ubuntu 22.04 ships
sudo 1.9.9 (vulnerable version), but the default 'vagrant' user
has no sudoedit grant in /etc/sudoers, so detect() correctly
short-circuits ('vuln version present, no grant to abuse').
Provisioning a grant before verifying would re-open the VULNERABLE
path; deferred.
Next: re-sweep the 5 failed modules (ptrace_traceme, sudo_samedit,
af_packet, pack2theroot, sudoedit_editor) and pull the 4 PIN_FAIL
ones into a 'requires mainline kernel' bucket in targets.yaml.
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.