verify-vm: per-module provisioner hook + old-mainline URL fallback

Adds tools/verify-vm/provisioners/<module>.sh hook so per-module setup
(build vulnerable sudo from source, drop polkit allow rule, add sudoers
grant) lives in checked-in scripts rather than manual VM steps. Vagrantfile
runs the script as root before build-and-verify if it exists.

Also fixes mainline kernel fetch to fall back from /v${KVER}/amd64/ to
/v${KVER}/ for old kernels (≤ ~4.15) where debs aren't under the amd64
subdir, and accepts both 'linux-image-' (old) and 'linux-image-unsigned-'
(new) deb names.

Wires up 4 previously-deferred targets to expect VULNERABLE:
- sudo_chwoot: builds sudo 1.9.16p1 from upstream into /usr/local
- udisks_libblockdev: installs udisks2 + polkit rule for vagrant user
- mutagen_astronomy: pins mainline 4.14.70 (one below the .71 fix)
- sudo_runas_neg1: adds (ALL,!root) sudoers grant
This commit is contained in:
2026-05-23 22:36:02 -04:00
parent 7f4a6e1c7c
commit 270ddc1681
5 changed files with 126 additions and 16 deletions
+30 -5
View File
@@ -101,17 +101,28 @@ Vagrant.configure("2") do |c|
exit 0 exit 0
fi fi
echo "[+] fetching kernel.ubuntu.com mainline v${KVER}" echo "[+] fetching kernel.ubuntu.com mainline v${KVER}"
URL="https://kernel.ubuntu.com/mainline/v${KVER}/amd64/" # Newer mainline kernels live under /v${KVER}/amd64/; older ones
# (≤ ~4.15) put debs at /v${KVER}/ directly. Try /amd64/ first;
# fall back to bare. linux-image-unsigned was renamed from
# linux-image- around 4.18 — old kernels use the plain name.
BASE="https://kernel.ubuntu.com/mainline/v${KVER}"
for URL in "${BASE}/amd64/" "${BASE}/"; do
INDEX=$(curl -sL "$URL")
if echo "$INDEX" | grep -q '\\.deb"'; then
break
fi
done
TMP=$(mktemp -d) TMP=$(mktemp -d)
cd "$TMP" cd "$TMP"
# Pick the 4 canonical generic-kernel .debs by pattern match against # Pick the 4 canonical generic-kernel .debs by pattern match against
# the directory index. Skip lowlatency variants. # the directory index. Skip lowlatency variants. Accept both
DEBS=$(curl -sL "$URL" | \\ # 'linux-image-unsigned-' (newer) and 'linux-image-' (older).
DEBS=$(echo "$INDEX" | \\
grep -oE 'href="[^"]+\\.deb"' | sed 's/href="//; s/"$//' | \\ grep -oE 'href="[^"]+\\.deb"' | sed 's/href="//; s/"$//' | \\
grep -E '(linux-image-unsigned|linux-modules|linux-headers)-[0-9.]+-[0-9]+-generic_|linux-headers-[0-9.]+-[0-9]+_[^_]+_all\\.deb' | \\ grep -E '(linux-image(-unsigned)?|linux-modules|linux-headers)-[0-9.]+-[0-9]+-generic_|linux-headers-[0-9.]+-[0-9]+_[^_]+_all\\.deb' | \\
grep -v lowlatency) grep -v lowlatency)
if [ -z "$DEBS" ]; then if [ -z "$DEBS" ]; then
echo "[-] no .debs found at $URL — does the version exist on kernel.ubuntu.com?" >&2 echo "[-] no .debs found at ${BASE}/ (tried /amd64/ and bare)" >&2
exit 2 exit 2
fi fi
for f in $DEBS; do for f in $DEBS; do
@@ -125,6 +136,20 @@ Vagrant.configure("2") do |c|
SHELL SHELL
end end
# 2c. Optional per-module provisioner. If
# tools/verify-vm/provisioners/<module>.sh exists, run it as root
# before build-and-verify. Used for things only meaningful per-module:
# build sudo 1.9.16 from source (sudo_chwoot), drop a polkit allow
# rule (udisks_libblockdev), add a sudoers grant (sudo_runas_neg1).
skk_mod = ENV["SKK_MODULE"] || ""
if !skk_mod.empty?
prov_path = File.join(__dir__, "provisioners", "#{skk_mod}.sh")
if File.exist?(prov_path)
m.vm.provision "shell", name: "module-provision-#{skk_mod}",
path: prov_path
end
end
# 3. Build SKELETONKEY in-VM and run --explain --active for the target # 3. Build SKELETONKEY in-VM and run --explain --active for the target
# module. Runs as the unprivileged 'vagrant' user (NOT root) — most # module. Runs as the unprivileged 'vagrant' user (NOT root) — most
# detect()s gate on "are you already root?" and short-circuit if so, # detect()s gate on "are you already root?" and short-circuit if so,
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# CVE-2025-32463 sudo --chroot NSS injection (Stratascale). Vulnerable
# range is sudo [1.9.14, 1.9.17p0]. Ubuntu 22.04 ships 1.9.9 which
# PREDATES the --chroot code path. Build sudo 1.9.16p1 from upstream
# and install to /usr/local (which precedes /usr/bin in Ubuntu's default
# PATH so plain `sudo` resolves to the vulnerable binary).
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get install -y -qq libpam0g-dev libssl-dev wget make gcc >/dev/null
cd /tmp
TARBALL=sudo-1.9.16p1.tar.gz
URL="https://www.sudo.ws/dist/${TARBALL}"
if [ -x /usr/local/bin/sudo ] && /usr/local/bin/sudo --version 2>&1 | head -1 | grep -q "1.9.16p1"; then
echo "[=] sudo 1.9.16p1 already at /usr/local/bin/sudo"
else
[ -f "${TARBALL}" ] || wget -q "${URL}"
rm -rf sudo-1.9.16p1
tar xzf "${TARBALL}"
cd sudo-1.9.16p1
# --sysconfdir=/etc so it honors the existing /etc/sudoers (vagrant's
# NOPASSWD grant). --disable-shared keeps the build self-contained.
./configure --prefix=/usr/local --sysconfdir=/etc \
--disable-shared --quiet >/dev/null 2>&1
make -j"$(nproc)" >/tmp/sudo-build.log 2>&1 || { tail -40 /tmp/sudo-build.log; exit 1; }
make install >/tmp/sudo-install.log 2>&1 || { tail -40 /tmp/sudo-install.log; exit 1; }
fi
# Verify what the unprivileged user's PATH resolves to.
echo "[+] which sudo (root): $(which sudo)"
echo "[+] /usr/local/bin/sudo version: $(/usr/local/bin/sudo --version | head -1)"
sudo -u vagrant bash -c 'echo "[+] vagrant PATH: $PATH"; echo "[+] vagrant sees: $(which sudo)"; sudo --version | head -1'
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# CVE-2019-14287 needs a (ALL,!root) grant for find_runas_blacklist_grant()
# to fire. Ubuntu 18.04 ships sudo 1.8.21p2 (in the vulnerable range) but
# Vagrant's default sudoers doesn't include the grant. Add it.
set -e
cat >/etc/sudoers.d/99-skk-runas-neg1 <<'EOF'
vagrant ALL=(ALL,!root) NOPASSWD: /bin/vi
EOF
chmod 0440 /etc/sudoers.d/99-skk-runas-neg1
echo "[+] sudoers grant installed:"
grep . /etc/sudoers.d/99-skk-runas-neg1
echo
echo "[+] sudo -ln -U vagrant tail:"
sudo -ln -U vagrant 2>&1 | tail -10 || true
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# CVE-2025-6019 udisks/libblockdev SUID-on-mount (Qualys). Debian 12's
# cloud image is server-oriented and doesn't ship udisks2. Install it,
# and drop a polkit rule allowing the vagrant user to invoke the
# affected action.ids — the real-world bug-path is "active console
# user invokes loop-setup", and we don't have a graphical session in
# Vagrant. The polkit rule simulates the trust polkit would give a
# logged-in workstation user.
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get install -y -qq udisks2 libblockdev-utils3 >/dev/null
mkdir -p /etc/polkit-1/rules.d
cat >/etc/polkit-1/rules.d/49-skk-verify.rules <<'EOF'
polkit.addRule(function(action, subject) {
if (subject.user == "vagrant" &&
(action.id == "org.freedesktop.UDisks2.loop-setup" ||
action.id == "org.freedesktop.UDisks2.filesystem-mount" ||
action.id == "org.freedesktop.UDisks2.filesystem-mount-other-seat" ||
action.id == "org.freedesktop.UDisks2.modify-device")) {
return polkit.Result.YES;
}
});
EOF
systemctl enable udisks2.service >/dev/null 2>&1 || true
systemctl restart udisks2.service
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)"
+12 -11
View File
@@ -224,18 +224,18 @@ vmwgfx:
# ── v0.8.0 additions ────────────────────────────────────────────── # ── v0.8.0 additions ──────────────────────────────────────────────
sudo_chwoot: sudo_chwoot:
box: ubuntu2204 # 22.04 ships sudo 1.9.9 (pre-feature) — need a 1.9.14+ install box: ubuntu2204 # 22.04 ships sudo 1.9.9 — provisioner builds 1.9.16p1 over it
kernel_pkg: "" # this bug is sudo-version-gated, not kernel kernel_pkg: "" # this bug is sudo-version-gated, not kernel
kernel_version: "5.15.0" kernel_version: "5.15.0"
expect_detect: OK expect_detect: VULNERABLE
notes: "CVE-2025-32463; sudo --chroot NSS shim. Vulnerable range is sudo [1.9.14, 1.9.17p0]. Ubuntu 22.04 ships sudo 1.9.9 which PREDATES the vulnerable --chroot code path — so detect correctly returns OK. To validate VULNERABLE empirically, provision a vulnerable sudo build into the VM (e.g. apt install -t backports sudo=1.9.16-1 or build from source). Deferred." notes: "CVE-2025-32463; sudo --chroot NSS shim. Vulnerable range is sudo [1.9.14, 1.9.17p0]. provisioners/sudo_chwoot.sh builds sudo 1.9.16p1 from upstream sources into /usr/local/bin (which precedes /usr/bin in PATH so plain `sudo` resolves to the vulnerable binary)."
udisks_libblockdev: udisks_libblockdev:
box: debian12 # 12 ships udisks2 2.10.x + libblockdev 3.0.x — vulnerable box: debian12 # 12 ships udisks2 2.10.x + libblockdev 3.0.x — vulnerable
kernel_pkg: "" kernel_pkg: ""
kernel_version: "6.1.0" kernel_version: "6.1.0"
expect_detect: PRECOND_FAIL expect_detect: VULNERABLE
notes: "CVE-2025-6019; udisks/libblockdev SUID-on-mount. Debian 12's cloud image is server-oriented — udisksd is NOT installed by default. detect correctly returns PRECOND_FAIL ('udisksd not installed; bug unreachable here'). To validate VULNERABLE empirically, install udisks2 + log in as an active-session user (Vagrant SSH session is NOT active per polkit — needs a real console session). Both gates are real and the detect honestly surfaces them; deferred." notes: "CVE-2025-6019; udisks/libblockdev SUID-on-mount. provisioners/udisks_libblockdev.sh installs udisks2 + libblockdev-utils3 and drops a polkit rule allowing the vagrant user to invoke loop-setup/filesystem-mount — simulating the trust polkit would give a logged-in workstation user (the real-world bug-path). Without that rule, the SSH session is not 'active' per polkit and the D-Bus call short-circuits."
pintheft: pintheft:
box: "" # RDS is blacklisted on every common Vagrant box's stock kernel box: "" # RDS is blacklisted on every common Vagrant box's stock kernel
@@ -248,18 +248,19 @@ pintheft:
# ── v0.9.0 additions (gap fillers 2018 / 2019 / 2020 / 2024) ────── # ── v0.9.0 additions (gap fillers 2018 / 2019 / 2020 / 2024) ──────
mutagen_astronomy: mutagen_astronomy:
box: ubuntu1804 # 4.15.0-213 stock — already > 4.14.71 backport → OK box: ubuntu1804 # stock 4.15.0-213 is post-fix; mainline 4.14.70 is one below the .71 fix
kernel_pkg: "" kernel_pkg: ""
kernel_version: "4.15.0" mainline_version: "4.14.70"
expect_detect: OK kernel_version: "4.14.70"
notes: "CVE-2018-14634; Qualys Mutagen Astronomy. Ubuntu 18.04 ships 4.15.0-213 which is post-fix. detect correctly returns OK. Verifying the VULNERABLE path empirically needs a 2.6.x / 3.10.x EOL kernel (e.g. RHEL 6 / CentOS 6 / Debian 7); deferred to a custom-box workflow." 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."
sudo_runas_neg1: sudo_runas_neg1:
box: ubuntu1804 # ships sudo 1.8.21p2 (vulnerable; pre-1.8.28 fix) box: ubuntu1804 # ships sudo 1.8.21p2 (vulnerable; pre-1.8.28 fix)
kernel_pkg: "" kernel_pkg: ""
kernel_version: "4.15.0" kernel_version: "4.15.0"
expect_detect: PRECOND_FAIL expect_detect: VULNERABLE
notes: "CVE-2019-14287; sudo Runas -u#-1. Ubuntu 18.04 ships sudo 1.8.21p2 which IS in the vulnerable range — but the default vagrant user has no (ALL,!root) sudoers grant for find_runas_blacklist_grant() to abuse, so detect correctly returns PRECOND_FAIL. To validate VULNERABLE empirically, provision a sudoers entry of the form 'vagrant ALL=(ALL,!root) /bin/vi' before verifying." notes: "CVE-2019-14287; sudo Runas -u#-1. Ubuntu 18.04 ships sudo 1.8.21p2 (vulnerable). provisioners/sudo_runas_neg1.sh adds 'vagrant ALL=(ALL,!root) NOPASSWD: /bin/vi' to /etc/sudoers.d/ so find_runas_blacklist_grant() has a grant to abuse."
tioscpgrp: tioscpgrp:
box: ubuntu2004 # 5.4 stock kernels (5.4.0-26) are below the 5.4.85 backport box: ubuntu2004 # 5.4 stock kernels (5.4.0-26) are below the 5.4.85 backport