diff --git a/README.md b/README.md index 07c82c3..477f497 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Latest release](https://img.shields.io/github/v/release/KaraZajac/SKELETONKEY?label=release)](https://github.com/KaraZajac/SKELETONKEY/releases/latest) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Modules](https://img.shields.io/badge/CVEs-22%20VM--verified%20%2F%2034-brightgreen.svg)](docs/VERIFICATIONS.jsonl) +[![Modules](https://img.shields.io/badge/CVEs-27%20VM--verified%20%2F%2034-brightgreen.svg)](docs/VERIFICATIONS.jsonl) [![Platform: Linux](https://img.shields.io/badge/platform-linux-lightgrey.svg)](#) > **One curated binary. 39 Linux LPE modules covering 34 CVEs from 2016 → 2026. -> Every year 2016 → 2026 covered. 22 confirmed end-to-end against real Linux +> Every year 2016 → 2026 covered. 27 confirmed end-to-end against real Linux > VMs via `tools/verify-vm/`. Detection rules in the box. One command picks > the safest one and runs it.** @@ -44,10 +44,11 @@ for every CVE in the bundle — same project for red and blue teams. ## Corpus at a glance -**31 modules covering 26 distinct CVEs** across the 2016 → 2026 LPE -timeline. **22 of the 26 CVEs have been empirically verified** in real -Linux VMs via `tools/verify-vm/`; the 4 still-pending entries are -blocked by their target environment, not by missing code. +**39 modules covering 34 distinct CVEs** across the 2016 → 2026 LPE +timeline. **27 of the 34 CVEs have been empirically verified** in real +Linux VMs via `tools/verify-vm/`; the 7 still-pending entries are +blocked by their target environment (legacy hypervisor, EOL kernel, or +not-yet-shipped Linux 7.0), not by missing code. | Tier | Count | What it means | |---|---|---| @@ -65,25 +66,27 @@ af_packet · af_packet2 · af_unix_gc · cls_route4 · fuse_legacy · nf_tables · nft_set_uaf · nft_fwd_dup · nft_payload · netfilter_xtcompat · stackrot · sudo_samedit · sequoia · vmwgfx -### Empirical verification (22 of 26 CVEs) +### Empirical verification (27 of 34 CVEs) Records in [`docs/VERIFICATIONS.jsonl`](docs/VERIFICATIONS.jsonl) prove each verdict against a known-target VM. Coverage: | Distro / kernel | Modules verified | |---|---| -| Ubuntu 18.04 (4.15.0) | af_packet · ptrace_traceme · sudo_samedit | -| Ubuntu 20.04 (5.4 stock + 5.15 HWE) | af_packet2 · cls_route4 · nft_payload · overlayfs · pwnkit · sequoia | -| Ubuntu 22.04 (5.15 stock + mainline 5.15.5 / 6.1.10) | af_unix_gc · dirty_pipe · entrybleed · nf_tables · nft_set_uaf · overlayfs_setuid · stackrot · sudoedit_editor | +| Ubuntu 18.04 (4.15.0, sudo 1.8.21p2) | af_packet · ptrace_traceme · sudo_samedit · sudo_runas_neg1 | +| Ubuntu 20.04 (5.4.0-26 pinned + 5.15 HWE) | af_packet2 · cls_route4 · nft_payload · overlayfs · pwnkit · sequoia · tioscpgrp | +| Ubuntu 22.04 (5.15 stock + mainline 5.15.5 / 6.1.10) | af_unix_gc · dirty_pipe · entrybleed · nf_tables · nft_set_uaf · nft_pipapo · overlayfs_setuid · stackrot · sudoedit_editor · sudo_chwoot | | Debian 11 (5.10 stock) | cgroup_release_agent · fuse_legacy · netfilter_xtcompat · nft_fwd_dup | -| Debian 12 (6.1 stock) | pack2theroot | +| Debian 12 (6.1 stock + udisks2 / polkit allow rule) | pack2theroot · udisks_libblockdev | -**Not yet verified (4):** `vmwgfx` (VMware-guest-only — no public -Vagrant box), `dirty_cow` (needs ≤ 4.4 kernel — older than every -supported box), `dirtydecrypt` & `fragnesia` (need Linux 7.0 — not -shipping as any distro kernel yet). All four are flagged in -[`tools/verify-vm/targets.yaml`](tools/verify-vm/targets.yaml) with -rationale. +**Not yet verified (7):** `vmwgfx` (VMware-guest-only — no public Vagrant +box), `dirty_cow` (needs ≤ 4.4 kernel — older than every supported box), +`mutagen_astronomy` (mainline 4.14.70 kernel-panics on Ubuntu 18.04 +rootfs — needs CentOS 6 / Debian 7), `pintheft` & `vsock_uaf` (kernel +modules not loaded on common Vagrant boxes), `dirtydecrypt` & `fragnesia` +(need Linux 7.0 — not shipping as any distro kernel yet). All seven are +flagged in [`tools/verify-vm/targets.yaml`](tools/verify-vm/targets.yaml) +with rationale. See [`CVES.md`](CVES.md) for per-module CVE, kernel range, and detection status. Run `skeletonkey --module-info ` for the @@ -205,7 +208,7 @@ year 2016 → 2026 now covered**. v0.9.0 adds 5 gap-fillers: (CVE-2024-50264 — Pwnie 2025 winner), `nft_pipapo` (CVE-2024-26581 — Notselwyn II). v0.8.0 added 3 (`sudo_chwoot`/CVE-2025-32463, `udisks_libblockdev`/CVE-2025-6019, `pintheft`/CVE-2026-43494). -**22 empirically verified** against real Linux VMs (Ubuntu 18.04 / +**27 empirically verified** against real Linux VMs (Ubuntu 18.04 / 20.04 / 22.04 + Debian 11 / 12 + mainline kernels 5.15.5 / 6.1.10 from kernel.ubuntu.com). 88-test unit harness + ASan/UBSan + clang-tidy on every push. 4 prebuilt binaries (x86_64 + arm64, each @@ -221,7 +224,7 @@ Reliability + accuracy work in v0.7.x: - **VM verifier** (`tools/verify-vm/`) — Vagrant + Parallels scaffold that boots known-vulnerable kernels (stock distro + mainline via kernel.ubuntu.com), runs `--explain --active` per module, records - match/MISMATCH/PRECOND_FAIL as JSON. 22 modules confirmed end-to-end. + match/MISMATCH/PRECOND_FAIL as JSON. 27 modules confirmed end-to-end. - **`--explain `** — single-page operator briefing: CVE / CWE / MITRE ATT&CK / CISA KEV status, host fingerprint, live detect() trace, OPSEC footprint, detection-rule coverage, verified-on diff --git a/core/verifications.c b/core/verifications.c index 79a1869..9d40f63 100644 --- a/core/verifications.c +++ b/core/verifications.c @@ -136,6 +136,16 @@ const struct verification_record verifications[] = { .actual_detect = "VULNERABLE", .status = "match", }, + { + .module = "nft_pipapo", + .verified_at = "2026-05-24", + .host_kernel = "5.15.5-051505-generic", + .host_distro = "Ubuntu 22.04.3 LTS", + .vm_box = "generic/ubuntu2204", + .expect_detect = "VULNERABLE", + .actual_detect = "VULNERABLE", + .status = "match", + }, { .module = "nft_set_uaf", .verified_at = "2026-05-23", @@ -216,6 +226,26 @@ const struct verification_record verifications[] = { .actual_detect = "VULNERABLE", .status = "match", }, + { + .module = "sudo_chwoot", + .verified_at = "2026-05-24", + .host_kernel = "5.15.0-91-generic", + .host_distro = "Ubuntu 22.04.3 LTS", + .vm_box = "generic/ubuntu2204", + .expect_detect = "VULNERABLE", + .actual_detect = "VULNERABLE", + .status = "match", + }, + { + .module = "sudo_runas_neg1", + .verified_at = "2026-05-24", + .host_kernel = "4.15.0-213-generic", + .host_distro = "Ubuntu 18.04.6 LTS", + .vm_box = "generic/ubuntu1804", + .expect_detect = "VULNERABLE", + .actual_detect = "VULNERABLE", + .status = "match", + }, { .module = "sudo_samedit", .verified_at = "2026-05-23", @@ -236,6 +266,26 @@ const struct verification_record verifications[] = { .actual_detect = "PRECOND_FAIL", .status = "match", }, + { + .module = "tioscpgrp", + .verified_at = "2026-05-24", + .host_kernel = "5.4.0-26-generic", + .host_distro = "Ubuntu 20.04.6 LTS", + .vm_box = "generic/ubuntu2004", + .expect_detect = "VULNERABLE", + .actual_detect = "VULNERABLE", + .status = "match", + }, + { + .module = "udisks_libblockdev", + .verified_at = "2026-05-24", + .host_kernel = "6.1.0-17-amd64", + .host_distro = "Debian GNU/Linux 12 (bookworm)", + .vm_box = "generic/debian12", + .expect_detect = "VULNERABLE", + .actual_detect = "VULNERABLE", + .status = "match", + }, }; const size_t verifications_count = diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 0feb174..a5e2621 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,3 +1,54 @@ +## SKELETONKEY v0.9.1 — VM verification sweep (22 → 27) + +Five more CVEs empirically confirmed end-to-end against real Linux VMs +via `tools/verify-vm/`: + +| CVE | Module | Target environment | +|---|---|---| +| CVE-2019-14287 | `sudo_runas_neg1` | Ubuntu 18.04 (sudo 1.8.21p2 + `(ALL,!root)` grant via provisioner) | +| CVE-2020-29661 | `tioscpgrp` | Ubuntu 20.04 pinned to `5.4.0-26` (genuinely below the 5.4.85 backport) | +| CVE-2024-26581 | `nft_pipapo` | Ubuntu 22.04 + mainline `5.15.5` (below the 5.15.149 fix) | +| CVE-2025-32463 | `sudo_chwoot` | Ubuntu 22.04 + sudo `1.9.16p1` built from upstream into `/usr/local/bin` | +| CVE-2025-6019 | `udisks_libblockdev` | Debian 12 + `udisks2` 2.9.4 + polkit allow rule for the verifier user | + +Footer goes from `22 empirically verified` → `27 empirically verified`. + +### Verifier infrastructure (the why) + +These verifications required real plumbing work that didn't exist before: + +- **Per-module provisioner hook** (`tools/verify-vm/provisioners/.sh`) + — per-target setup that doesn't belong in the Vagrantfile (build sudo + from source, install udisks2 + polkit rule, drop a sudoers grant) now + lives in checked-in scripts that re-run idempotently on every verify. +- **Two-phase provisioning** in `verify.sh` — prep provisioners run + first (install kernel, set grub default, drop polkit rule), then a + conditional reboot if `uname -r` doesn't match the target, then the + verifier proper. Fixes the silent-fail where the new kernel was + installed but the VM never actually rebooted into it. +- **GRUB_DEFAULT pin in both `pin-kernel` and `pin-mainline` blocks** — + without this, grub's debian-version-compare picks the highest-sorting + vmlinuz as default; for downgrades (stock 4.15 → mainline 4.14.70, or + stock 5.4.0-169 → pinned 5.4.0-26) the wrong kernel won boot. +- **Old-mainline URL fallback** — kernel.ubuntu.com puts ≤ 4.15 mainline + debs at `/v${KVER}/` not `/v${KVER}/amd64/`. Fallback handles both. + +### Honest residuals — 7 of 34 still unverified + +| Module | Why not verified | +|---|---| +| `vmwgfx` | needs a VMware guest; we're on Parallels | +| `dirty_cow` | needs ≤ 4.4 kernel — older than any supported Vagrant box | +| `mutagen_astronomy` | mainline 4.14.70 kernel-panics on Ubuntu 18.04 rootfs (`Failed to execute /init (error -8)` — kernel config mismatch). Genuinely needs CentOS 6 / Debian 7. | +| `pintheft` | needs RDS kernel module loaded (Arch only autoloads it) | +| `vsock_uaf` | needs `vsock_loopback` loaded — not autoloaded on common Vagrant boxes | +| `dirtydecrypt`, `fragnesia` | need Linux 7.0 — not yet shipping as any distro kernel | + +All seven are flagged in `tools/verify-vm/targets.yaml` with `manual: true` +and a rationale. + +--- + ## SKELETONKEY v0.9.0 — every year 2016 → 2026 now covered Five gap-filling modules. Closes the 2018 hole entirely and thickens diff --git a/docs/VERIFICATIONS.jsonl b/docs/VERIFICATIONS.jsonl index 829ea66..dd79dab 100644 --- a/docs/VERIFICATIONS.jsonl +++ b/docs/VERIFICATIONS.jsonl @@ -28,3 +28,8 @@ {"module":"af_unix_gc","verified_at":"2026-05-23T21:27:13Z","host_kernel":"5.15.5-051505-generic","host_distro":"Ubuntu 22.04.3 LTS","vm_box":"generic/ubuntu2204","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} {"module":"nft_set_uaf","verified_at":"2026-05-23T21:30:41Z","host_kernel":"5.15.5-051505-generic","host_distro":"Ubuntu 22.04.3 LTS","vm_box":"generic/ubuntu2204","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} {"module":"stackrot","verified_at":"2026-05-23T21:34:12Z","host_kernel":"6.1.10-060110-generic","host_distro":"Ubuntu 22.04.3 LTS","vm_box":"generic/ubuntu2204","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} +{"module":"sudo_chwoot","verified_at":"2026-05-24T02:39:11Z","host_kernel":"5.15.0-91-generic","host_distro":"Ubuntu 22.04.3 LTS","vm_box":"generic/ubuntu2204","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} +{"module":"udisks_libblockdev","verified_at":"2026-05-24T02:44:17Z","host_kernel":"6.1.0-17-amd64","host_distro":"Debian GNU/Linux 12 (bookworm)","vm_box":"generic/debian12","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} +{"module":"nft_pipapo","verified_at":"2026-05-24T03:27:10Z","host_kernel":"5.15.5-051505-generic","host_distro":"Ubuntu 22.04.3 LTS","vm_box":"generic/ubuntu2204","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} +{"module":"sudo_runas_neg1","verified_at":"2026-05-24T03:29:18Z","host_kernel":"4.15.0-213-generic","host_distro":"Ubuntu 18.04.6 LTS","vm_box":"generic/ubuntu1804","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} +{"module":"tioscpgrp","verified_at":"2026-05-24T03:31:08Z","host_kernel":"5.4.0-26-generic","host_distro":"Ubuntu 20.04.6 LTS","vm_box":"generic/ubuntu2004","expect_detect":"VULNERABLE","actual_detect":"VULNERABLE","status":"match"} diff --git a/docs/index.html b/docs/index.html index 0fa455a..7f6e253 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,9 +4,9 @@ SKELETONKEY — Linux LPE corpus, VM-verified, SOC-ready detection - + - + @@ -56,14 +56,14 @@
- v0.9.0 — released 2026-05-24 + v0.9.1 — released 2026-05-24

SKELETONKEY

One binary. 39 Linux LPE modules covering 34 CVEs — - every year 2016 → 2026. 22 of 34 confirmed against + every year 2016 → 2026. 27 of 34 confirmed against real Linux kernels in VMs. SOC-ready detection rules in four SIEM formats. MITRE ATT&CK + CWE + CISA KEV annotated. --explain gives a one-page operator briefing per CVE. @@ -82,7 +82,7 @@

0modules
-
0✓ VM-verified
+
0✓ VM-verified
0★ in CISA KEV
0detection rules
@@ -598,7 +598,7 @@ uid=0(root) gid=0(root) who found the bugs.

diff --git a/docs/og.png b/docs/og.png index a1901bf..fca51b5 100644 Binary files a/docs/og.png and b/docs/og.png differ diff --git a/docs/og.svg b/docs/og.svg index c6bf513..d728496 100644 --- a/docs/og.svg +++ b/docs/og.svg @@ -39,7 +39,7 @@ Curated Linux LPE corpus. - Every year 2016 → 2026. 22 of 34 verified. + Every year 2016 → 2026. 27 of 34 verified. @@ -49,9 +49,9 @@ 39 modules - + - 22 + 27 ✓ VM-verified diff --git a/skeletonkey.c b/skeletonkey.c index a6e5b7d..642f42d 100644 --- a/skeletonkey.c +++ b/skeletonkey.c @@ -35,7 +35,7 @@ #include #include -#define SKELETONKEY_VERSION "0.9.0" +#define SKELETONKEY_VERSION "0.9.1" static const char BANNER[] = "\n" diff --git a/tools/verify-vm/Vagrantfile b/tools/verify-vm/Vagrantfile index 04fb054..7e046c0 100644 --- a/tools/verify-vm/Vagrantfile +++ b/tools/verify-vm/Vagrantfile @@ -73,7 +73,19 @@ Vagrant.configure("2") do |c| echo "[+] installing #{pkg} (kernel target #{kver})" export DEBIAN_FRONTEND=noninteractive apt-get install -y -qq #{pkg} - echo "[i] kernel #{pkg} installed; reboot via 'vagrant reload'" + echo "[i] kernel #{pkg} installed" + fi + # Pin grub default to this specific kernel. Without it, grub + # picks the highest-versioned kernel installed (typically a + # stock HWE backport that's POST-fix), defeating the pin's + # purpose. Find the kver string by stripping linux-image- + # prefix from the pkg name. + PINNED_KVER="$(echo '#{pkg}' | sed 's/^linux-image-//')" + if [ -f "/boot/vmlinuz-${PINNED_KVER}" ]; then + GRUB_ENTRY="Advanced options for Ubuntu>Ubuntu, with Linux ${PINNED_KVER}" + sed -i "s|^GRUB_DEFAULT=.*|GRUB_DEFAULT=\\"${GRUB_ENTRY}\\"|" /etc/default/grub + echo "[+] GRUB_DEFAULT pinned to: ${GRUB_ENTRY}" + update-grub 2>&1 | tail -3 fi SHELL end @@ -90,16 +102,24 @@ Vagrant.configure("2") do |c| m.vm.provision "shell", name: "pin-mainline-#{mainline}", inline: <<-SHELL set -e KVER="#{mainline}" - # already booted into it? + # already booted into it? Still fall through to grub-pin to + # make sure GRUB_DEFAULT stays correct even after stock kernel + # upgrades that might reorder grub entries. + BOOTED_INTO_TARGET=0 if uname -r | grep -q "^${KVER}-[0-9]\\+-generic"; then echo "[=] mainline ${KVER} already booted ($(uname -r))" - exit 0 + BOOTED_INTO_TARGET=1 fi - # already installed on disk (waiting on reboot)? + + # already installed on disk? Skip the download/install but + # still run the grub-pin block at the end. + SKIP_INSTALL=0 if ls /boot/vmlinuz-${KVER}-* >/dev/null 2>&1; then - echo "[=] mainline ${KVER} already installed; needs reboot" - exit 0 + echo "[=] mainline ${KVER} already installed on disk" + SKIP_INSTALL=1 fi + + if [ "$SKIP_INSTALL" -eq 0 ]; then echo "[+] fetching kernel.ubuntu.com mainline v${KVER}" # Newer mainline kernels live under /v${KVER}/amd64/; older ones # (≤ ~4.15) put debs at /v${KVER}/ directly. Try /amd64/ first; @@ -131,6 +151,22 @@ Vagrant.configure("2") do |c| done export DEBIAN_FRONTEND=noninteractive dpkg -i *.deb || apt-get install -f -y -qq + fi # end SKIP_INSTALL guard + + # Pin grub default to the just-installed mainline kernel. Without + # this, grub's debian-version-compare picks the highest-sorting + # vmlinuz-* as default; for downgrades (e.g. stock 4.15 → mainline + # 4.14.70), the OLD kernel wins because 4.15 > 4.14 numerically. + MAINLINE_VMLINUZ=$(ls /boot/vmlinuz-${KVER}-* 2>/dev/null | head -1) + if [ -n "$MAINLINE_VMLINUZ" ]; then + MAINLINE_KVER=$(basename "$MAINLINE_VMLINUZ" | sed 's/^vmlinuz-//') + # The "Advanced options" submenu entry id is stable across + # update-grub runs as "gnulinux-advanced->gnulinux--advanced-". + # Easier: use the human menuentry path. + GRUB_ENTRY="Advanced options for Ubuntu>Ubuntu, with Linux ${MAINLINE_KVER}" + sed -i "s|^GRUB_DEFAULT=.*|GRUB_DEFAULT=\\"${GRUB_ENTRY}\\"|" /etc/default/grub + echo "[+] GRUB_DEFAULT pinned to: ${GRUB_ENTRY}" + fi update-grub 2>&1 | tail -3 echo "[i] mainline ${KVER} installed; reboot via 'vagrant reload'" SHELL diff --git a/tools/verify-vm/provisioners/udisks_libblockdev.sh b/tools/verify-vm/provisioners/udisks_libblockdev.sh index 38d5b25..143a693 100755 --- a/tools/verify-vm/provisioners/udisks_libblockdev.sh +++ b/tools/verify-vm/provisioners/udisks_libblockdev.sh @@ -9,7 +9,7 @@ set -e export DEBIAN_FRONTEND=noninteractive -apt-get install -y -qq udisks2 libblockdev-utils3 >/dev/null +apt-get install -y -qq udisks2 libblockdev-utils2 >/dev/null mkdir -p /etc/polkit-1/rules.d cat >/etc/polkit-1/rules.d/49-skk-verify.rules <<'EOF' @@ -31,4 +31,4 @@ sleep 2 echo "[+] udisks2 status:" systemctl is-active udisks2.service echo "[+] udisks2 version: $(dpkg-query -W -f='${Version}' udisks2)" -echo "[+] libblockdev version: $(dpkg-query -W -f='${Version}' libblockdev-utils3 2>/dev/null || dpkg-query -W -f='${Version}' libblockdev-utils2)" +echo "[+] libblockdev version: $(dpkg-query -W -f='${Version}' libblockdev-utils2)" diff --git a/tools/verify-vm/targets.yaml b/tools/verify-vm/targets.yaml index 07caca3..891eb0f 100644 --- a/tools/verify-vm/targets.yaml +++ b/tools/verify-vm/targets.yaml @@ -248,12 +248,12 @@ pintheft: # ── v0.9.0 additions (gap fillers 2018 / 2019 / 2020 / 2024) ────── mutagen_astronomy: - box: ubuntu1804 # stock 4.15.0-213 is post-fix; mainline 4.14.70 is one below the .71 fix + box: "" kernel_pkg: "" - mainline_version: "4.14.70" - kernel_version: "4.14.70" - expect_detect: VULNERABLE - notes: "CVE-2018-14634; Qualys Mutagen Astronomy. Mainline 4.14.70 sits one stable below the 4.14.71 backport. Old mainline kernels live at /v${KVER}/ directly (no /amd64/ subdir); Vagrantfile's pin-mainline provisioner falls back to the bare path." + kernel_version: "" + expect_detect: "" + manual: true + notes: "CVE-2018-14634; Qualys Mutagen Astronomy. No good Vagrant verification environment: stock Ubuntu 18.04 (4.15.0-213) returns detect()=VULNERABLE because the module's kernel_range table has no entry for the 4.15.x series (Ubuntu's HWE backports are not modeled), but the kernel IS actually patched — false-positive of the conservative module logic. Mainline 4.14.70 (target VULNERABLE kernel) panics on Ubuntu 18.04's rootfs with 'Failed to execute /init (error -8)' — kernel config mismatch (binfmt_elf as module rather than baked-in). Genuinely vulnerable verification needs a contemporary CentOS 6 / Debian 7 image with original-vintage kernel; deferred to custom-box workflow." sudo_runas_neg1: box: ubuntu1804 # ships sudo 1.8.21p2 (vulnerable; pre-1.8.28 fix) @@ -279,7 +279,8 @@ vsock_uaf: nft_pipapo: box: ubuntu2204 # 5.15 stock + HWE — same pipapo set substrate as nf_tables - kernel_pkg: linux-image-5.15.0-43-generic - kernel_version: "5.15.0-43" + kernel_pkg: "" + mainline_version: "5.15.5" + kernel_version: "5.15.5" expect_detect: VULNERABLE - notes: "CVE-2024-26581; nft_pipapo destroy-race (Notselwyn II). Same Vagrant target as nf_tables works here — stock 5.15.0-43 is below the 5.15.149 backport. Userns gate must be open (sysctl kernel.unprivileged_userns_clone=1)." + notes: "CVE-2024-26581; nft_pipapo destroy-race (Notselwyn II). Same mainline 5.15.5 target as nf_tables works here — 5.15.5 is below the 5.15.149 backport. (Switched from apt-pinned 5.15.0-43 after that package was removed from Ubuntu repos.) Userns gate must be open (sysctl kernel.unprivileged_userns_clone=1)." diff --git a/tools/verify-vm/verify.sh b/tools/verify-vm/verify.sh index f5abcf3..e179318 100755 --- a/tools/verify-vm/verify.sh +++ b/tools/verify-vm/verify.sh @@ -139,19 +139,6 @@ if ! vagrant status "$VM_HOSTNAME" 2>&1 | grep -q "running"; then vagrant up "$VM_HOSTNAME" --provider=parallels fi -# Reboot if any kernel pin was applied (uname -r != target). -if [[ -n "$KERNEL_PKG" || -n "$MAINLINE" ]]; then - current_kver=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r') - target_match="$KERNEL_VER" - [[ -n "$MAINLINE" ]] && target_match="$MAINLINE" - if [[ "$current_kver" != *"$target_match"* ]]; then - echo "[*] current kernel $current_kver != target $target_match; 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" # Force rsync the source tree in. vagrant up runs rsync automatically on @@ -160,8 +147,46 @@ LOG="$LOG_DIR/verify-${MODULE}-$(date +%Y%m%d-%H%M%S).log" echo "[*] syncing source into VM..." vagrant rsync "$VM_HOSTNAME" 2>&1 | tail -5 +# Two-phase provisioning so the new kernel actually boots before verify: +# PREP: install kernel (apt or mainline) + pin grub default + run any +# module-specific provisioner (sudoers grant, sudo build, ...). +# ── conditional reboot if uname -r doesn't match target ── +# VERIFY: build skeletonkey + run --explain --active. +PREP_PROVS=() +[[ -n "$KERNEL_PKG" ]] && PREP_PROVS+=("pin-kernel-${KERNEL_PKG}") +[[ -n "$MAINLINE" ]] && PREP_PROVS+=("pin-mainline-${MAINLINE}") +[[ -f "$VM_DIR/provisioners/${MODULE}.sh" ]] && PREP_PROVS+=("module-provision-${MODULE}") + +if [[ ${#PREP_PROVS[@]} -gt 0 ]]; then + echo "[*] running prep provisioners: ${PREP_PROVS[*]}" + vagrant provision "$VM_HOSTNAME" \ + --provision-with "$(IFS=,; echo "${PREP_PROVS[*]}")" 2>&1 | tee "$LOG" +fi + +# Reboot if a kernel pin moved us off the target. This must run AFTER +# the prep provisioners (which install the kernel + set GRUB_DEFAULT), +# otherwise the reboot picks the stock kernel and we never land on the +# target. +if [[ -n "$KERNEL_PKG" || -n "$MAINLINE" ]]; then + current_kver=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r') + target_match="$KERNEL_VER" + [[ -n "$MAINLINE" ]] && target_match="$MAINLINE" + if [[ "$current_kver" != *"$target_match"* ]]; then + echo "[*] current kernel $current_kver != target $target_match; rebooting..." + vagrant reload "$VM_HOSTNAME" 2>&1 | tee -a "$LOG" + sleep 5 + post_kver=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r') + echo "[*] post-reboot kernel: $post_kver" | tee -a "$LOG" + if [[ "$post_kver" != *"$target_match"* ]]; then + echo "[!] reboot did NOT land on target kernel $target_match (got $post_kver)" | tee -a "$LOG" + echo " detect() will still run, but verification is on the wrong kernel" | tee -a "$LOG" + fi + fi +fi + echo "[*] running verifier..." -vagrant provision "$VM_HOSTNAME" --provision-with build-and-verify 2>&1 | tee "$LOG" +vagrant provision "$VM_HOSTNAME" \ + --provision-with build-and-verify 2>&1 | tee -a "$LOG" # Parse verdict. Vagrant prefixes provisioner output with the VM name # (e.g. " skk-pwnkit: VERDICT: VULNERABLE"), so anchor on the VERDICT