Five new modules close the 2018 gap entirely and thicken 2019 / 2020 / 2024. All five carry the full 4-format detection-rule corpus + opsec_notes + arch_support + register helpers. CVE-2018-14634 — mutagen_astronomy (Qualys, closes 2018) create_elf_tables() int wrap → SUID-execve stack corruption. CISA KEV-listed Jan 2026 despite the bug's age; legacy RHEL 7 / CentOS 7 / Debian 8 fleets still affected. 🟡 PRIMITIVE. arch_support: x86_64+unverified-arm64. CVE-2019-14287 — sudo_runas_neg1 (Joe Vennix) sudo -u#-1 → uid_t underflow → root despite (ALL,!root) blacklist. Pure userspace logic bug; the famous Apple Information Security finding. detect() looks for a (ALL,!root) grant in sudo -ln output; PRECOND_FAIL when no such grant exists for the invoking user. arch_support: any (4 -> 5 userspace 'any' modules). CVE-2020-29661 — tioscpgrp (Jann Horn / Project Zero) TTY TIOCSPGRP ioctl race on PTY pairs → struct pid UAF in kmalloc-256. Affects everything through Linux 5.9.13. 🟡 PRIMITIVE (race-driver + msg_msg groom). Public PoCs from grsecurity / spender + Maxime Peterlin. CVE-2024-50264 — vsock_uaf (a13xp0p0v / Pwnie Award 2025 winner) AF_VSOCK connect-race UAF in kmalloc-96. Pwn2Own 2024 + Pwnie 2025 winner. Reachable as plain unprivileged user (no userns required — unusual). Two public exploit paths: @v4bel+@qwerty kernelCTF (BPF JIT spray + SLUBStick) and Alexander Popov / PT SWARM (msg_msg). 🟡 PRIMITIVE. CVE-2024-26581 — nft_pipapo (Notselwyn II, 'Flipping Pages') nft_set_pipapo destroy-race UAF. Sibling to nf_tables (CVE-2024-1086) from the same Notselwyn paper. Distinct bug in the pipapo set substrate. Same family signature. 🟡 PRIMITIVE. Plumbing changes: core/registry.h + registry_all.c — 5 new register declarations + calls. Makefile — 5 new MUT/SRN/TIO/VSK/PIP module groups in MODULE_OBJS. tests/test_detect.c — 7 new test rows covering the new modules (above-fix OK, predates-the-bug OK, sudo-no-grant PRECOND_FAIL). tools/verify-vm/targets.yaml — verifier entries for all 5 with honest 'expect_detect' values based on what Vagrant boxes can realistically reach (mutagen_astronomy gets OK on stock 18.04 since 4.15.0-213 is post-fix; sudo_runas_neg1 gets PRECOND_FAIL because no (ALL,!root) grant on default vagrant user; tioscpgrp + nft_pipapo VULNERABLE with kernel pins; vsock_uaf flagged manual because vsock module rarely available on CI runners). tools/refresh-cve-metadata.py — added curl fallback for the CISA KEV CSV fetch (urlopen times out intermittently against CISA's HTTP/2 endpoint). Corpus growth across v0.8.0 + v0.9.0: v0.7.1 v0.8.0 v0.9.0 Modules 31 34 39 Distinct CVEs 26 29 34 KEV-listed 10 10 11 (mutagen_astronomy) arch 'any' 4 6 7 (sudo_runas_neg1) Years 2016-2026: 10/11 10/11 **11/11** Year-by-year coverage: 2016: 1 2017: 1 2018: 1 2019: 2 2020: 2 2021: 5 2022: 5 2023: 8 2024: 3 2025: 2 2026: 4 CVE-2018 gap → CLOSED. Every year from 2016 through 2026 now has at least one module. Surfaces updated: - README.md: badge → 22 VM-verified / 34, Status section refreshed - docs/index.html: hero eyebrow + footer → v0.9.0, hero tagline 'every year 2016 → 2026', stats chips → 39 / 22 / 11 / 151 - docs/RELEASE_NOTES.md: v0.9.0 entry added on top with year coverage matrix + per-module breakdown; v0.8.0 + v0.7.1 entries preserved below - docs/og.svg + og.png: regenerated with new numbers + 'Every year 2016 → 2026' tagline CVE metadata refresh (tools/refresh-cve-metadata.py) deferred to follow-up — CISA KEV CSV + NVD CVE API were timing out during the v0.9.0 push window. The 5 new CVEs will return NULL from cve_metadata_lookup() until the refresh runs (—module-info simply skips the WEAKNESS/THREAT INTEL header for them; no functional impact). Re-run 'tools/refresh-cve-metadata.py' when network cooperates. Tests: macOS local 33/33 kernel_range pass; detect-test stubs (88 total) build clean; ASan/UBSan + clang-tidy CI jobs still green from the v0.7.x setup.
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.