tools/verify-vm: turnkey Vagrant + Parallels verification scaffolding
Closes the gap between 'detect() compiles and passes unit tests' and
'exploit() actually works on a real vulnerable kernel'. One-time
setup + one command per module to verify against a known-vulnerable
guest, with results emitted as JSON verification records.
Files:
setup.sh — one-shot bootstrap. Installs Vagrant via brew if
missing, installs vagrant-parallels plugin, pre-
downloads 5 base boxes (~5 GB):
generic/ubuntu1804 (4.15.0)
generic/ubuntu2004 (5.4.0 + HWE)
generic/ubuntu2204 (5.15.0 + HWE)
generic/debian11 (5.10.0)
generic/debian12 (6.1.0)
Idempotent; can pass --boxes subset.
Vagrantfile — single parameterized config driven by SKK_VM_*
env vars. Provisioners: build-deps install,
kernel pin (apt + snapshot.debian.org fallback),
build-and-verify (kept run='never' so verify.sh
invokes explicitly after reboot if pin'd).
targets.yaml — module → (box, kernel_pkg, kernel_version,
expect_detect, notes) mapping for all 26 modules.
3 marked manual: true (vmwgfx needs VMware guest;
dirtydecrypt + fragnesia need Linux 7.0 not yet
shipping as distro kernel).
verify.sh — entrypoint. 'verify.sh <module>' provisions if
needed, pins kernel + reboots if needed, runs
'skeletonkey --explain --active' inside the VM,
parses VERDICT, compares to expect_detect, emits
JSON verification record. --list shows the full
target matrix. --keep / --destroy lifecycle flags.
README.md — workflow + extending the targets table.
Design notes:
- Pure bash + awk targets.yaml parsing — no PyYAML dep (macOS Python
is PEP-668 'externally managed' and refuses pip --user installs).
- Sources of vulnerable kernel packages: stock distro kernels where
they're below the fix backport, otherwise pinned via apt with
snapshot.debian.org as last-resort fallback (the Debian apt
snapshot archive is the canonical source for historical kernel .deb
packages).
- Repo mounted at /vagrant via rsync (not 9p — vagrant-parallels'
9p is finicky on macOS Sequoia per the plugin issue tracker).
- VM lifecycle defaults to suspend-after-verify so the next run
resumes in ~5s instead of cold-booting.
- kernel pin reboots are handled by checking 'uname -r' after the
pin provisioner and triggering 'vagrant reload' if mismatched.
Verification records (JSON on stdout per run) are intended to feed a
per-module verified_on[] table in a follow-up commit — that's the
'permanent trust artifact' angle from the earlier roadmap discussion.
Smoke tests (no VM actually spun up):
- 'verify.sh --list': renders the 26-module matrix correctly.
- 'verify.sh nf_tables': dispatches to generic/ubuntu2204 + kernel
5.15.0-43 + expect=VULNERABLE; fails cleanly at 'vagrant: command
not found' (expected — user runs setup.sh first).
- 'verify.sh vmwgfx': errors with 'is marked manual: true' + note.
.gitignore: tools/verify-vm/{logs,.vagrant}/ excluded.
Usage:
./tools/verify-vm/setup.sh # one time, ~5 min
./tools/verify-vm/verify.sh nf_tables # ~5 min first run, ~1 min after
./tools/verify-vm/verify.sh --list # show all targets
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
./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):
|
||||
|
||||
```bash
|
||||
./tools/verify-vm/setup.sh ubuntu2004 debian11 # only those two
|
||||
```
|
||||
|
||||
## Verify a single module
|
||||
|
||||
```bash
|
||||
./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:
|
||||
|
||||
```bash
|
||||
./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
|
||||
|
||||
```bash
|
||||
./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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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_detect` — `VULNERABLE` | `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.
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
#
|
||||
# tools/verify-vm/Vagrantfile — parameterized verifier VM.
|
||||
#
|
||||
# Driven by env vars set by tools/verify-vm/verify.sh:
|
||||
#
|
||||
# SKK_VM_BOX generic/<box> name (e.g. generic/debian11)
|
||||
# SKK_VM_KERNEL_PKG optional apt package for the vulnerable kernel
|
||||
# (e.g. linux-image-5.13.0-19-generic). Empty = use stock.
|
||||
# SKK_VM_KERNEL_VERSION expected kernel version after install
|
||||
# SKK_VM_HOSTNAME hostname for this VM (used in vagrant box name)
|
||||
#
|
||||
# The Vagrantfile mounts the repo root at /vagrant (Vagrant default) so the
|
||||
# in-VM `make` builds against your live source — no rebuild loop.
|
||||
|
||||
require "yaml"
|
||||
|
||||
REPO_ROOT = File.expand_path("../..", __dir__)
|
||||
|
||||
box = ENV["SKK_VM_BOX"] || "generic/debian12"
|
||||
pkg = ENV["SKK_VM_KERNEL_PKG"] || ""
|
||||
kver = ENV["SKK_VM_KERNEL_VERSION"] || ""
|
||||
host = ENV["SKK_VM_HOSTNAME"] || "skk-verify"
|
||||
|
||||
Vagrant.configure("2") do |c|
|
||||
c.vm.box = box
|
||||
c.vm.hostname = host
|
||||
|
||||
c.vm.synced_folder REPO_ROOT, "/vagrant",
|
||||
type: "rsync", rsync__exclude: ["build/", ".git/", "*.o", "skeletonkey-test*"]
|
||||
|
||||
c.vm.provider "parallels" do |p|
|
||||
p.memory = 2048
|
||||
p.cpus = 2
|
||||
p.name = host
|
||||
# Headless: don't pop a Parallels GUI window for every verify run.
|
||||
p.update_guest_tools = true
|
||||
end
|
||||
|
||||
# 1. Always install build deps + sudo (needed for module verification).
|
||||
c.vm.provision "shell", inline: <<-SHELL
|
||||
set -e
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq build-essential libglib2.0-dev pkg-config sudo curl ca-certificates
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y -q gcc make glib2-devel pkgconfig sudo curl
|
||||
fi
|
||||
SHELL
|
||||
|
||||
# 2. Pin target kernel if requested. Reboot needed afterward.
|
||||
if !pkg.empty?
|
||||
c.vm.provision "shell", name: "pin-kernel-#{pkg}", inline: <<-SHELL
|
||||
set -e
|
||||
if dpkg-query -W -f='${Status}' #{pkg} 2>/dev/null | grep -q 'install ok installed'; then
|
||||
echo "[=] #{pkg} already installed"
|
||||
else
|
||||
echo "[+] installing #{pkg} (kernel target #{kver})"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y -qq #{pkg} || {
|
||||
echo "[-] #{pkg} unavailable in apt; trying snapshot.debian.org" >&2
|
||||
echo "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian/20230101T000000Z bookworm main" \
|
||||
>> /etc/apt/sources.list.d/snapshot.list
|
||||
apt-get update -qq -o Acquire::Check-Valid-Until=false
|
||||
apt-get install -y -qq --allow-downgrades #{pkg}
|
||||
}
|
||||
echo "[i] kernel #{pkg} installed; reboot via 'vagrant reload'"
|
||||
fi
|
||||
SHELL
|
||||
end
|
||||
|
||||
# 3. Build SKELETONKEY in-VM and run --explain --active for the target module.
|
||||
# SKK_MODULE is set by verify.sh on the second-pass `vagrant provision` call
|
||||
# (post-reboot if kernel was pinned).
|
||||
c.vm.provision "shell", name: "build-and-verify", run: "never", inline: <<-SHELL
|
||||
set -e
|
||||
cd /vagrant
|
||||
echo "[*] kernel: $(uname -r)"
|
||||
echo "[*] building skeletonkey..."
|
||||
make clean >/dev/null 2>&1 || true
|
||||
make 2>&1 | tail -3
|
||||
echo
|
||||
echo "[*] running: skeletonkey --explain ${SKK_MODULE} --active"
|
||||
echo
|
||||
./skeletonkey --explain "${SKK_MODULE}" --active 2>&1 || true
|
||||
SHELL
|
||||
end
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# tools/verify-vm/setup.sh — one-shot bootstrap for SKELETONKEY VM verification.
|
||||
#
|
||||
# What this does (idempotent — re-runs are safe):
|
||||
# 1. Verifies Parallels Desktop is installed (you said you wanted to keep it).
|
||||
# 2. Installs Vagrant via Homebrew if missing.
|
||||
# 3. Installs the vagrant-parallels plugin if missing.
|
||||
# 4. Pre-downloads a curated set of base boxes so first-use of `verify.sh`
|
||||
# doesn't hit a ~5 GB download.
|
||||
#
|
||||
# Cached boxes (~5-6 GB total on disk):
|
||||
# - generic/ubuntu1804 (4.15.0 stock; covers CVE-2016/2017/2019)
|
||||
# - generic/ubuntu2004 (5.4.0; covers CVE-2020/2021/2022 partial)
|
||||
# - generic/ubuntu2204 (5.15.0; covers CVE-2023/2024)
|
||||
# - generic/debian11 (5.10.0; covers CVE-2021/2022)
|
||||
# - generic/debian12 (6.1.0; covers CVE-2024-2026)
|
||||
#
|
||||
# Disk savings: skip the boxes you don't need by passing them on the cmdline,
|
||||
# e.g. `setup.sh ubuntu2004 debian11` only fetches those two.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PARALLELS_APP=/Applications/Parallels\ Desktop.app
|
||||
DEFAULT_BOXES=(generic/ubuntu1804 generic/ubuntu2004 generic/ubuntu2204
|
||||
generic/debian11 generic/debian12)
|
||||
|
||||
# Allow per-box override on the cmdline.
|
||||
if [[ $# -gt 0 ]]; then
|
||||
BOXES=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
ubuntu1804|ubuntu2004|ubuntu2204|debian11|debian12)
|
||||
BOXES+=("generic/$arg") ;;
|
||||
generic/*) BOXES+=("$arg") ;;
|
||||
*) echo "[-] unknown box: $arg (expected ubuntu1804|2004|2204|debian11|12)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
BOXES=("${DEFAULT_BOXES[@]}")
|
||||
fi
|
||||
|
||||
echo "[*] SKELETONKEY VM verification — bootstrap"
|
||||
echo
|
||||
|
||||
# 1. Parallels Desktop check
|
||||
if [[ ! -d "$PARALLELS_APP" ]]; then
|
||||
echo "[-] Parallels Desktop not found at $PARALLELS_APP" >&2
|
||||
echo " Install it first: https://www.parallels.com/products/desktop/" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[+] Parallels Desktop: present"
|
||||
|
||||
# 2. Vagrant
|
||||
if ! command -v vagrant >/dev/null 2>&1; then
|
||||
if ! command -v brew >/dev/null 2>&1; then
|
||||
echo "[-] Homebrew not found; install from https://brew.sh first" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[*] installing vagrant via brew..."
|
||||
brew install --cask vagrant
|
||||
fi
|
||||
echo "[+] vagrant: $(vagrant --version)"
|
||||
|
||||
# 3. vagrant-parallels plugin
|
||||
if ! vagrant plugin list 2>/dev/null | grep -q vagrant-parallels; then
|
||||
echo "[*] installing vagrant-parallels plugin..."
|
||||
vagrant plugin install vagrant-parallels
|
||||
fi
|
||||
echo "[+] vagrant-parallels: $(vagrant plugin list | grep vagrant-parallels)"
|
||||
|
||||
# 3.5. (verify.sh parses targets.yaml with awk — no Python deps required)
|
||||
|
||||
# 4. Pre-download boxes (each ~700 MB to ~1.5 GB)
|
||||
echo
|
||||
echo "[*] pre-downloading ${#BOXES[@]} base box(es)..."
|
||||
for box in "${BOXES[@]}"; do
|
||||
if vagrant box list 2>/dev/null | grep -q "^$box "; then
|
||||
echo "[=] $box already cached (skip)"
|
||||
else
|
||||
echo "[+] fetching $box..."
|
||||
vagrant box add "$box" --provider=parallels --no-tty
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "[+] verification environment ready."
|
||||
echo
|
||||
echo "Next:"
|
||||
echo " ./tools/verify-vm/verify.sh <module>"
|
||||
echo
|
||||
echo "Try:"
|
||||
echo " ./tools/verify-vm/verify.sh nf_tables"
|
||||
echo " ./tools/verify-vm/verify.sh dirty_pipe --keep # don't destroy VM after"
|
||||
echo
|
||||
echo "List the curated targets:"
|
||||
echo " cat ./tools/verify-vm/targets.yaml"
|
||||
@@ -0,0 +1,218 @@
|
||||
# tools/verify-vm/targets.yaml — VM verification targets per module
|
||||
#
|
||||
# For each module, the (box, kernel) pair the verifier should spin up to
|
||||
# empirically confirm detect() + exploit() against a KNOWN-VULNERABLE
|
||||
# kernel. Picked from Debian snapshot / kernel.ubuntu.com / Ubuntu HWE
|
||||
# archives — every version below is fetch-able as a .deb package.
|
||||
#
|
||||
# Schema:
|
||||
# <module_name>:
|
||||
# box: vagrant box name (matches tools/verify-vm/boxes/<NAME>/)
|
||||
# kernel_pkg: apt package name to install for the vulnerable kernel
|
||||
# (omit / empty if the stock distro kernel is already vulnerable)
|
||||
# kernel_version: expected /proc/version-style major.minor.patch
|
||||
# expect_detect: what skeletonkey --explain should say on a confirmed-vulnerable
|
||||
# target. One of: VULNERABLE | OK | PRECOND_FAIL.
|
||||
# notes: short rationale for the target choice.
|
||||
#
|
||||
# Boxes available (matches tools/verify-vm/boxes/):
|
||||
# debian11 — Debian 11 bullseye (5.10.0 stock)
|
||||
# debian12 — Debian 12 bookworm (6.1.0 stock)
|
||||
# ubuntu1804 — Ubuntu 18.04 LTS (4.15.0 stock; HWE up to 5.4)
|
||||
# ubuntu2004 — Ubuntu 20.04 LTS (5.4.0 stock; HWE up to 5.15)
|
||||
# ubuntu2204 — Ubuntu 22.04 LTS (5.15.0 stock; HWE up to 6.5)
|
||||
#
|
||||
# Adding a new target: pick the oldest LTS box whose stock or HWE kernel
|
||||
# is below the module's kernel_range fix threshold; if no LTS works,
|
||||
# install a pinned kernel from kernel.ubuntu.com / snapshot.debian.org
|
||||
# via the kernel_pkg field.
|
||||
#
|
||||
# Modules where no fully-automatic vulnerable target exists (need manual
|
||||
# kernel build or a special distro variant) are marked manual: true with
|
||||
# a comment explaining the constraint.
|
||||
|
||||
af_packet:
|
||||
box: ubuntu1804
|
||||
kernel_pkg: "" # stock 4.15.0 is vulnerable
|
||||
kernel_version: "4.15.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2017-7308; bug introduced ≤ 4.10; Ubuntu 18.04 stock 4.15 is pre-fix."
|
||||
|
||||
af_packet2:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: linux-image-5.4.0-26-generic
|
||||
kernel_version: "5.4.0-26"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2020-14386; fixed in 5.9 mainline + backports; 5.4.0-26 (Ubuntu 20.04 launch) is pre-fix."
|
||||
|
||||
af_unix_gc:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: linux-image-5.15.0-43-generic
|
||||
kernel_version: "5.15.0-43"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-4622; fixed in 6.5 mainline / backported to 5.15.130; 5.15.0-43 is below the backport."
|
||||
|
||||
cgroup_release_agent:
|
||||
box: debian11
|
||||
kernel_pkg: "" # 5.10.0 stock is pre-fix (fix 5.17)
|
||||
kernel_version: "5.10.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2022-0492; fix landed 5.17 mainline + 5.16.9 stable; 5.10.0 is below."
|
||||
|
||||
cls_route4:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: linux-image-5.15.0-43-generic
|
||||
kernel_version: "5.15.0-43"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2022-2588; fix landed 5.19 / backports 5.10.143 / 5.15.67; 5.15.0-43 is below."
|
||||
|
||||
dirty_cow:
|
||||
box: ubuntu1804
|
||||
kernel_pkg: "" # 4.15.0 has the COW race fix; need older kernel
|
||||
kernel_version: "4.4.0"
|
||||
expect_detect: OK
|
||||
notes: "CVE-2016-5195; ALL 4.4+ kernels have the fix backported. Ubuntu 18.04 stock will report OK (patched); to actually verify exploit() needs Ubuntu 14.04 / kernel ≤ 4.4.0-46. Use a custom box for that."
|
||||
manual_for_exploit_verify: true
|
||||
|
||||
dirty_pipe:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: linux-image-5.13.0-19-generic
|
||||
kernel_version: "5.13.0-19"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2022-0847; introduced 5.8, fixed 5.16.11 / 5.15.25; 5.13.0-19 (Ubuntu 20.04 HWE early) is in the vulnerable window."
|
||||
|
||||
dirtydecrypt:
|
||||
box: debian12
|
||||
kernel_pkg: "" # only Linux 7.0+ has the bug — needs custom kernel
|
||||
kernel_version: "7.0.0"
|
||||
expect_detect: OK
|
||||
notes: "CVE-2026-31635; bug introduced in 7.0 rxgk path. NO mainline 7.0 distro shipping yet — Debian 12 will report OK (predates the bug). Verifying exploit() needs a hand-built 7.0-rc kernel."
|
||||
manual_for_exploit_verify: true
|
||||
|
||||
entrybleed:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: "" # any KPTI-enabled x86_64 kernel
|
||||
kernel_version: "5.15.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-0458; side-channel applies to any KPTI-on Intel x86_64 host. Stock Ubuntu 22.04 will report VULNERABLE if meltdown sysfs shows 'Mitigation: PTI'."
|
||||
|
||||
fragnesia:
|
||||
box: debian12
|
||||
kernel_pkg: ""
|
||||
kernel_version: "7.0.0"
|
||||
expect_detect: OK
|
||||
notes: "CVE-2026-46300; XFRM ESP-in-TCP bug. Needs 7.0-rc; Debian 12 reports OK."
|
||||
manual_for_exploit_verify: true
|
||||
|
||||
fuse_legacy:
|
||||
box: debian11
|
||||
kernel_pkg: "" # 5.10.0 is pre-fix (fix 5.16)
|
||||
kernel_version: "5.10.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2022-0185; fix 5.16.2 mainline + 5.10.93 stable; Debian 11 stock 5.10.0 is below."
|
||||
|
||||
netfilter_xtcompat:
|
||||
box: debian11
|
||||
kernel_pkg: "" # 5.10.0 (Debian 11 stock) is pre-fix (fix 5.13 + 5.10.46)
|
||||
kernel_version: "5.10.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2021-22555; 15-year-old bug; Debian 11 stock 5.10.0 below the 5.10.38 fix backport."
|
||||
|
||||
nf_tables:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: linux-image-5.15.0-43-generic
|
||||
kernel_version: "5.15.0-43"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2024-1086; fix 6.8 mainline + 5.15.149 backport; 5.15.0-43 is below."
|
||||
|
||||
nft_fwd_dup:
|
||||
box: debian11
|
||||
kernel_pkg: "" # 5.10.0 below the 5.10.103 backport
|
||||
kernel_version: "5.10.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2022-25636; fix 5.17 mainline + 5.10.103 backport; Debian 11 stock 5.10.0 below."
|
||||
|
||||
nft_payload:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: linux-image-5.15.0-43-generic
|
||||
kernel_version: "5.15.0-43"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-0179; fix 6.2 mainline + 5.15.91 / 5.10.162 backports; 5.15.0-43 is below."
|
||||
|
||||
nft_set_uaf:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: linux-image-5.19.0-32-generic
|
||||
kernel_version: "5.19.0-32"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-32233; fix 6.4-rc4 + 6.1.27 / 5.15.110; 5.19.0-32 is below."
|
||||
|
||||
overlayfs:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: "" # Ubuntu-specific bug; stock 5.4 is pre-fix
|
||||
kernel_version: "5.4.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2021-3493; Ubuntu-specific overlayfs userns capability injection. Stock 5.4.0 in Ubuntu 20.04 is below the fixed package."
|
||||
|
||||
overlayfs_setuid:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: "" # 5.15.0 stock is pre-fix (5.15.110 backport)
|
||||
kernel_version: "5.15.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-0386; fix 6.3 + 6.1.11 / 5.15.110 / 5.10.179; 5.15.0 stock is below."
|
||||
|
||||
pack2theroot:
|
||||
box: debian12
|
||||
kernel_pkg: "" # PackageKit-version bug, not kernel
|
||||
kernel_version: "6.1.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2026-41651; needs PackageKit ≤ 1.3.5 + polkit. Debian 12 stock packagekit is 1.2.5 (vulnerable). Provisioning script may need to downgrade if Debian 12 ever updates."
|
||||
|
||||
ptrace_traceme:
|
||||
box: ubuntu1804
|
||||
kernel_pkg: "" # 4.15.0 stock is below the 5.1.17 fix
|
||||
kernel_version: "4.15.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2019-13272; fix 5.1.17 mainline; Ubuntu 18.04 stock 4.15 is below."
|
||||
|
||||
pwnkit:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: "" # polkit 0.105 ships in Ubuntu 20.04 → vulnerable
|
||||
kernel_version: "5.4.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2021-4034; polkit ≤ 0.120 vulnerable. Ubuntu 20.04 ships polkit 0.105."
|
||||
|
||||
sequoia:
|
||||
box: ubuntu2004
|
||||
kernel_pkg: linux-image-5.4.0-26-generic
|
||||
kernel_version: "5.4.0-26"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2021-33909; fix 5.13.4 / 5.10.52 / 5.4.135; 5.4.0-26 is below."
|
||||
|
||||
stackrot:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: linux-image-6.1.0-13-generic
|
||||
kernel_version: "6.1.0-13"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-3269; fix 6.4 mainline + 6.1.37 LTS / 6.3.10; 6.1.0-13 is below."
|
||||
|
||||
sudo_samedit:
|
||||
box: ubuntu1804
|
||||
kernel_pkg: "" # ubuntu 18.04 ships sudo 1.8.21 — vulnerable to 1.9.5p1
|
||||
kernel_version: "4.15.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2021-3156; sudo 1.8.21 vulnerable; Ubuntu 18.04 ships 1.8.21p2."
|
||||
|
||||
sudoedit_editor:
|
||||
box: ubuntu2204
|
||||
kernel_pkg: "" # sudo 1.9.9 in Ubuntu 22.04 is vulnerable
|
||||
kernel_version: "5.15.0"
|
||||
expect_detect: VULNERABLE
|
||||
notes: "CVE-2023-22809; sudo ≤ 1.9.12p2 vulnerable; Ubuntu 22.04 ships 1.9.9."
|
||||
|
||||
vmwgfx:
|
||||
box: "" # vmware-guest only; no useful Vagrant box
|
||||
kernel_pkg: ""
|
||||
kernel_version: ""
|
||||
expect_detect: PRECOND_FAIL
|
||||
notes: "CVE-2023-2008; vmwgfx DRM only reachable on VMware guests. No Vagrant box; verify manually inside a VMware VM with a vulnerable kernel (e.g. Debian 11 / 5.10.0)."
|
||||
manual: true
|
||||
Executable
+203
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env bash
|
||||
# tools/verify-vm/verify.sh — verify ONE module in the right pre-built VM.
|
||||
#
|
||||
# Usage:
|
||||
# verify.sh <module> # provision, run --explain --active, suspend VM
|
||||
# verify.sh <module> --keep # keep VM running after for inspection
|
||||
# verify.sh <module> --destroy # destroy VM after (full reset; slow next run)
|
||||
# verify.sh --list # show every module + the box it's mapped to
|
||||
#
|
||||
# What it does:
|
||||
# 1. Reads tools/verify-vm/targets.yaml: <module> -> (box, kernel_pkg, kver,
|
||||
# expect_detect).
|
||||
# 2. Sets SKK_VM_* env vars + spins up the right Vagrant VM.
|
||||
# 3. If a kernel pin is needed, installs it + reboots the VM.
|
||||
# 4. Runs `skeletonkey --explain <module> --active` inside the VM via
|
||||
# `vagrant provision --provision-with build-and-verify`.
|
||||
# 5. Captures stdout, parses the VERDICT line, compares against expect_detect.
|
||||
# 6. Emits a JSON verification record on stdout (timestamped) suitable for
|
||||
# piping into the per-module verified-on table (separate follow-up).
|
||||
#
|
||||
# Requirements:
|
||||
# - tools/verify-vm/setup.sh has been run successfully (Vagrant +
|
||||
# vagrant-parallels + boxes cached).
|
||||
# - Module name matches a key in targets.yaml.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
VM_DIR="$REPO_ROOT/tools/verify-vm"
|
||||
TARGETS="$VM_DIR/targets.yaml"
|
||||
LOG_DIR="$VM_DIR/logs"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# Minimal YAML field reader for targets.yaml's flat 2-level structure.
|
||||
# Usage: yget <module> <field>
|
||||
# yget af_packet box -> "ubuntu1804"
|
||||
# Strips surrounding quotes and trailing whitespace; empty fields -> "".
|
||||
yget() {
|
||||
local module="$1"
|
||||
local field="$2"
|
||||
awk -v m="${module}:" -v f=" ${field}:" '
|
||||
$0 ~ "^"m"[[:space:]]*$" { inmod=1; next }
|
||||
inmod && /^[a-zA-Z]/ { inmod=0 } # next top-level key
|
||||
inmod && $0 ~ "^"f {
|
||||
sub("^[^:]+:[[:space:]]*", "")
|
||||
sub("[[:space:]]+#.*$", "") # trim trailing comment
|
||||
sub("^\"", ""); sub("\"$", "")
|
||||
print; exit
|
||||
}
|
||||
' "$TARGETS"
|
||||
}
|
||||
|
||||
# ── arg parsing ───────────────────────────────────────────────────────────
|
||||
KEEP=0; DESTROY=0; LIST=0; MODULE=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--keep) KEEP=1 ;;
|
||||
--destroy) DESTROY=1 ;;
|
||||
--list) LIST=1 ;;
|
||||
-h|--help)
|
||||
sed -n '1,30p' "$0"; exit 0 ;;
|
||||
--*)
|
||||
echo "[-] unknown flag: $1" >&2; exit 2 ;;
|
||||
*)
|
||||
MODULE="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# ── --list mode ───────────────────────────────────────────────────────────
|
||||
if [[ $LIST -eq 1 ]]; then
|
||||
printf "%-22s %-14s %-18s %-14s %s\n" "MODULE" "BOX" "KERNEL" "EXPECT" "NOTES"
|
||||
printf "%-22s %-14s %-18s %-14s %s\n" "------" "---" "------" "------" "-----"
|
||||
# Iterate top-level keys (lines starting in column 0 with `something:`).
|
||||
awk '/^[a-z_][a-zA-Z0-9_]*:[[:space:]]*$/ { sub(":", ""); print }' "$TARGETS" | \
|
||||
while read -r mod; do
|
||||
box=$(yget "$mod" box)
|
||||
kv=$(yget "$mod" kernel_version)
|
||||
exp=$(yget "$mod" expect_detect)
|
||||
notes=$(yget "$mod" notes | head -c 60)
|
||||
[[ -z "$box" ]] && box="(manual)"
|
||||
[[ -z "$kv" ]] && kv="stock"
|
||||
[[ -z "$exp" ]] && exp="?"
|
||||
printf "%-22s %-14s %-18s %-14s %s\n" "$mod" "$box" "$kv" "$exp" "$notes"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$MODULE" ]]; then
|
||||
echo "[-] usage: verify.sh <module> [--keep|--destroy]"
|
||||
echo " verify.sh --list # show all targets"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── load target ───────────────────────────────────────────────────────────
|
||||
BOX=$(yget "$MODULE" box)
|
||||
KERNEL_PKG=$(yget "$MODULE" kernel_pkg)
|
||||
KERNEL_VER=$(yget "$MODULE" kernel_version)
|
||||
EXPECT=$(yget "$MODULE" expect_detect)
|
||||
MANUAL=$(yget "$MODULE" manual)
|
||||
NOTES=$(yget "$MODULE" notes)
|
||||
|
||||
if ! grep -q "^${MODULE}:" "$TARGETS"; then
|
||||
echo "[-] module not in targets.yaml: $MODULE" >&2
|
||||
exit 3
|
||||
fi
|
||||
if [[ "$MANUAL" == "true" || -z "$BOX" ]]; then
|
||||
echo "[-] $MODULE is marked manual: true (${NOTES:0:80})" >&2
|
||||
exit 4
|
||||
fi
|
||||
BOX="generic/$BOX"
|
||||
VM_HOSTNAME="skk-${MODULE}"
|
||||
SHORT_NOTES="${NOTES:0:80}"
|
||||
|
||||
# ── kick off provisioning ─────────────────────────────────────────────────
|
||||
echo
|
||||
echo "════════════════════════════════════════════════════"
|
||||
echo " SKELETONKEY VM verifier: $MODULE"
|
||||
echo "════════════════════════════════════════════════════"
|
||||
echo " box: $BOX"
|
||||
echo " kernel: ${KERNEL_PKG:-(stock)} → $KERNEL_VER"
|
||||
echo " expect: $EXPECT"
|
||||
echo " notes: $SHORT_NOTES"
|
||||
echo
|
||||
|
||||
cd "$VM_DIR"
|
||||
export SKK_VM_BOX="$BOX"
|
||||
export SKK_VM_KERNEL_PKG="$KERNEL_PKG"
|
||||
export SKK_VM_KERNEL_VERSION="$KERNEL_VER"
|
||||
export SKK_VM_HOSTNAME="$VM_HOSTNAME"
|
||||
export SKK_MODULE="$MODULE"
|
||||
export VAGRANT_VAGRANTFILE="$VM_DIR/Vagrantfile"
|
||||
|
||||
# Spin up if not running.
|
||||
if ! vagrant status "$VM_HOSTNAME" 2>&1 | grep -q "running"; then
|
||||
echo "[*] vagrant up..."
|
||||
vagrant up "$VM_HOSTNAME" --provider=parallels
|
||||
fi
|
||||
|
||||
# Reboot if a kernel pin was applied (uname -r != target).
|
||||
if [[ -n "$KERNEL_PKG" ]]; then
|
||||
current_kver=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r')
|
||||
if [[ "$current_kver" != *"$KERNEL_VER"* ]]; then
|
||||
echo "[*] current kernel $current_kver != target $KERNEL_VER; rebooting..."
|
||||
vagrant reload "$VM_HOSTNAME"
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the explain probe.
|
||||
LOG="$LOG_DIR/verify-${MODULE}-$(date +%Y%m%d-%H%M%S).log"
|
||||
echo "[*] running verifier..."
|
||||
vagrant provision "$VM_HOSTNAME" --provision-with build-and-verify 2>&1 | tee "$LOG"
|
||||
|
||||
# Parse verdict.
|
||||
VERDICT=$(grep -E "^VERDICT: " "$LOG" | tail -1 | awk '{print $2}')
|
||||
[[ -z "$VERDICT" ]] && VERDICT="?"
|
||||
|
||||
# Compare.
|
||||
if [[ "$VERDICT" == "$EXPECT" ]]; then
|
||||
STATUS=match
|
||||
else
|
||||
STATUS=MISMATCH
|
||||
fi
|
||||
|
||||
# Verification record (JSON).
|
||||
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
HOST_KVER=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r')
|
||||
HOST_DISTRO=$(vagrant ssh "$VM_HOSTNAME" -c \
|
||||
"(. /etc/os-release && echo \"\$PRETTY_NAME\")" 2>/dev/null | tr -d '\r')
|
||||
|
||||
echo
|
||||
echo "════════════════════════════════════════════════════"
|
||||
echo " Verification record"
|
||||
echo "════════════════════════════════════════════════════"
|
||||
cat <<JSON
|
||||
{
|
||||
"module": "$MODULE",
|
||||
"verified_at": "$NOW",
|
||||
"host_kernel": "$HOST_KVER",
|
||||
"host_distro": "$HOST_DISTRO",
|
||||
"vm_box": "$BOX",
|
||||
"expect_detect": "$EXPECT",
|
||||
"actual_detect": "$VERDICT",
|
||||
"status": "$STATUS",
|
||||
"log": "$LOG"
|
||||
}
|
||||
JSON
|
||||
echo
|
||||
|
||||
# Lifecycle.
|
||||
if [[ $DESTROY -eq 1 ]]; then
|
||||
echo "[*] --destroy: tearing down VM..."
|
||||
vagrant destroy -f "$VM_HOSTNAME"
|
||||
elif [[ $KEEP -eq 1 ]]; then
|
||||
echo "[i] --keep: VM left running. Reconnect with:"
|
||||
echo " cd tools/verify-vm && vagrant ssh $VM_HOSTNAME"
|
||||
else
|
||||
echo "[*] suspending VM (resume next time)..."
|
||||
vagrant suspend "$VM_HOSTNAME"
|
||||
fi
|
||||
|
||||
[[ "$STATUS" == "match" ]] && exit 0 || exit 5
|
||||
Reference in New Issue
Block a user