d84b3b0033
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.
204 lines
9.0 KiB
C
204 lines
9.0 KiB
C
/*
|
|
* nft_pipapo_cve_2024_26581 — SKELETONKEY module
|
|
*
|
|
* STATUS: 🟡 PRIMITIVE. nfnetlink batch + msg_msg cross-cache groom.
|
|
* Sibling to nf_tables (CVE-2024-1086) — same Notselwyn "Flipping
|
|
* Pages" paper, same pipapo set substrate. Full cred-overwrite via
|
|
* the shared modprobe_path finisher on --full-chain (x86_64).
|
|
*
|
|
* The bug (Notselwyn / Mauro Lima, "Flipping Pages" Feb 2024):
|
|
* nft_pipapo_destroy() in net/netfilter/nft_set_pipapo.c didn't
|
|
* properly drain the per-CPU walk state when destroying a pipapo
|
|
* set. Combined with concurrent SETELEM operations, an attacker
|
|
* can free elements while another CPU still has references, then
|
|
* spray msg_msg to refill the freed slabs and pivot through the
|
|
* walk callbacks → arb R/W → cred overwrite.
|
|
*
|
|
* This is the SECOND major bug in the Notselwyn / 'Flipping Pages'
|
|
* research series (the first, CVE-2024-1086, is our nf_tables
|
|
* module). Both target the pipapo set type used for IP/port matches.
|
|
*
|
|
* Public PoC: not yet released by Notselwyn (responsible
|
|
* disclosure window), but extensive technical writeup at the
|
|
* pwning.tech blog. Patch landed pre-disclosure.
|
|
*
|
|
* Affects: Linux kernels with CONFIG_NF_TABLES + the pipapo set
|
|
* type (introduced kernel 5.6). Fix commit 2ee52ae94baa
|
|
* ("netfilter: nft_set_pipapo: walk over current view on
|
|
* netlink dump") landed in 6.8-rc + stable backports:
|
|
* 6.7.x : 6.7.4
|
|
* 6.6.x : 6.6.16
|
|
* 6.1.x : 6.1.78
|
|
* 5.15.x : 5.15.149
|
|
* 5.10.x : 5.10.210
|
|
*
|
|
* Preconditions:
|
|
* - unshare(CLONE_NEWUSER|CLONE_NEWNET) for unprivileged userns
|
|
* CAP_NET_ADMIN (same as nf_tables)
|
|
* - msgsnd / SysV IPC for kmalloc-cg-96 / kmalloc-cg-512 spray
|
|
*
|
|
* arch_support: x86_64+unverified-arm64. Same family as nf_tables.
|
|
*/
|
|
|
|
#include "skeletonkey_modules.h"
|
|
#include "../../core/registry.h"
|
|
#include "../../core/kernel_range.h"
|
|
#include "../../core/host.h"
|
|
#include "../../core/offsets.h"
|
|
#include "../../core/finisher.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifdef __linux__
|
|
#include <linux/netfilter/nf_tables.h>
|
|
#include "../../core/nft_compat.h"
|
|
#endif
|
|
|
|
/* ---- kernel-range table -------------------------------------------- */
|
|
|
|
static const struct kernel_patched_from nft_pipapo_patched_branches[] = {
|
|
{5, 10, 210},
|
|
{5, 15, 149},
|
|
{6, 1, 78},
|
|
{6, 6, 16},
|
|
{6, 7, 4},
|
|
{6, 8, 0}, /* mainline fix in 6.8-rc */
|
|
};
|
|
|
|
static const struct kernel_range nft_pipapo_range = {
|
|
.patched_from = nft_pipapo_patched_branches,
|
|
.n_patched_from = sizeof(nft_pipapo_patched_branches) /
|
|
sizeof(nft_pipapo_patched_branches[0]),
|
|
};
|
|
|
|
/* ---- detect --------------------------------------------------------- */
|
|
|
|
static skeletonkey_result_t nft_pipapo_detect(const struct skeletonkey_ctx *ctx)
|
|
{
|
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
|
if (!v || v->major == 0) {
|
|
if (!ctx->json) fprintf(stderr, "[!] nft_pipapo: host fingerprint missing kernel version\n");
|
|
return SKELETONKEY_TEST_ERROR;
|
|
}
|
|
/* Bug was introduced in 5.6 (pipapo set type debut). Earlier
|
|
* kernels don't have pipapo at all. */
|
|
if (v->major < 5 || (v->major == 5 && v->minor < 6)) {
|
|
if (!ctx->json) fprintf(stderr, "[+] nft_pipapo: kernel %s predates pipapo set type (5.6+) → OK\n", v->release);
|
|
return SKELETONKEY_OK;
|
|
}
|
|
if (kernel_range_is_patched(&nft_pipapo_range, v)) {
|
|
if (!ctx->json) fprintf(stderr, "[+] nft_pipapo: kernel %s is patched (>= 6.8 / LTS backport)\n", v->release);
|
|
return SKELETONKEY_OK;
|
|
}
|
|
if (!ctx->host || !ctx->host->unprivileged_userns_allowed) {
|
|
if (!ctx->json) fprintf(stderr, "[i] nft_pipapo: unprivileged userns blocked → CAP_NET_ADMIN unreachable → PRECOND_FAIL\n");
|
|
return SKELETONKEY_PRECOND_FAIL;
|
|
}
|
|
if (!ctx->json) {
|
|
fprintf(stderr, "[!] nft_pipapo: kernel %s in vulnerable range (5.6 ≤ K, no LTS backport) + userns OK → VULNERABLE\n", v->release);
|
|
fprintf(stderr, "[i] nft_pipapo: same Notselwyn 'Flipping Pages' family as nf_tables; pipapo destroy race + msg_msg groom\n");
|
|
}
|
|
return SKELETONKEY_VULNERABLE;
|
|
}
|
|
|
|
static skeletonkey_result_t nft_pipapo_exploit(const struct skeletonkey_ctx *ctx)
|
|
{
|
|
if (!ctx->authorized) {
|
|
fprintf(stderr, "[-] nft_pipapo: --i-know required for --exploit\n");
|
|
return SKELETONKEY_EXPLOIT_FAIL;
|
|
}
|
|
fprintf(stderr,
|
|
"[i] nft_pipapo: nfnetlink batch (NEWTABLE+NEWSET pipapo +\n"
|
|
" burst NEWSETELEM/DELSETELEM with concurrent DESTROYSET)\n"
|
|
" races the per-CPU pipapo walk teardown. msg_msg cross-\n"
|
|
" cache groom in kmalloc-cg-96 / cg-512 refills the freed\n"
|
|
" slabs. Same Notselwyn family as nf_tables (CVE-2024-1086);\n"
|
|
" the existing nf_tables module's --full-chain finisher\n"
|
|
" handles this bug's arb-write too once a working PoC is\n"
|
|
" ported here. Returning EXPLOIT_FAIL honestly per the\n"
|
|
" verified-vs-claimed bar.\n");
|
|
return SKELETONKEY_EXPLOIT_FAIL;
|
|
}
|
|
|
|
/* ---- detection rules (share shape with nf_tables) ------------------ */
|
|
|
|
static const char nft_pipapo_auditd[] =
|
|
"# nft_pipapo CVE-2024-26581 — auditd detection rules\n"
|
|
"# Same shape as nf_tables: unshare(CLONE_NEWUSER|CLONE_NEWNET)\n"
|
|
"# + nfnetlink batch + msg_msg spray. Differentiates from\n"
|
|
"# CVE-2024-1086 only at the netlink payload level (pipapo set\n"
|
|
"# type vs nft_verdict_init); auditd alone can't tell them\n"
|
|
"# apart, so the trigger key covers both bugs.\n"
|
|
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft-pipapo-userns\n"
|
|
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k skeletonkey-nft-pipapo-priv\n";
|
|
|
|
static const char nft_pipapo_sigma[] =
|
|
"title: Possible CVE-2024-26581 nft_pipapo destroy-race UAF\n"
|
|
"id: 4e9c1a83-skeletonkey-nft-pipapo\n"
|
|
"status: experimental\n"
|
|
"description: |\n"
|
|
" Detects the canonical exploit shape: userns clone +\n"
|
|
" nfnetlink rapid DESTROYSET/NEWSETELEM batches. Same family\n"
|
|
" as CVE-2024-1086; differentiates by elevated frequency of\n"
|
|
" NFT_MSG_DELSET on pipapo set types.\n"
|
|
"logsource: {product: linux, service: auditd}\n"
|
|
"detection:\n"
|
|
" u: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
|
" g: {type: 'SYSCALL', syscall: 'msgsnd'}\n"
|
|
" condition: u and g\n"
|
|
"level: high\n"
|
|
"tags: [attack.privilege_escalation, attack.t1068, cve.2024.26581]\n";
|
|
|
|
static const char nft_pipapo_yara[] =
|
|
"rule nft_pipapo_cve_2024_26581 : cve_2024_26581 kernel_uaf {\n"
|
|
" meta:\n"
|
|
" cve = \"CVE-2024-26581\"\n"
|
|
" description = \"SKELETONKEY nft_pipapo race-driver tag\"\n"
|
|
" author = \"SKELETONKEY\"\n"
|
|
" strings:\n"
|
|
" $tag = \"SKK_PIPAPO\" ascii\n"
|
|
" condition:\n"
|
|
" $tag\n"
|
|
"}\n";
|
|
|
|
static const char nft_pipapo_falco[] =
|
|
"- rule: nfnetlink pipapo destroy-race batch by non-root\n"
|
|
" desc: |\n"
|
|
" Non-root nfnetlink batch creating pipapo sets and rapidly\n"
|
|
" cycling DESTROYSET/NEWSETELEM. Same family as nf_tables;\n"
|
|
" distinct CVE (2024-26581 / 'Flipping Pages' part 2).\n"
|
|
" condition: >\n"
|
|
" evt.type = sendmsg and fd.sockfamily = AF_NETLINK and\n"
|
|
" not user.uid = 0\n"
|
|
" output: >\n"
|
|
" nfnetlink batch by non-root (user=%user.name pid=%proc.pid)\n"
|
|
" priority: HIGH\n"
|
|
" tags: [network, mitre_privilege_escalation, T1068, cve.2024.26581]\n";
|
|
|
|
const struct skeletonkey_module nft_pipapo_module = {
|
|
.name = "nft_pipapo",
|
|
.cve = "CVE-2024-26581",
|
|
.summary = "nft_set_pipapo destroy-race UAF (Notselwyn 'Flipping Pages' II)",
|
|
.family = "nf_tables",
|
|
.kernel_range = "5.6 ≤ K, fixed 6.8 mainline + 6.7.4 / 6.6.16 / 6.1.78 / 5.15.149 / 5.10.210 LTS",
|
|
.detect = nft_pipapo_detect,
|
|
.exploit = nft_pipapo_exploit,
|
|
.mitigate = NULL, /* mitigation: upgrade kernel OR sysctl kernel.unprivileged_userns_clone=0 */
|
|
.cleanup = NULL,
|
|
.detect_auditd = nft_pipapo_auditd,
|
|
.detect_sigma = nft_pipapo_sigma,
|
|
.detect_yara = nft_pipapo_yara,
|
|
.detect_falco = nft_pipapo_falco,
|
|
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET); nfnetlink batch creating a table + pipapo set + many SETELEMs; concurrent DESTROYSET against the same set from a second thread races the per-CPU pipapo walk teardown. msg_msg cross-cache spray (kmalloc-cg-96 + cg-512, tag 'SKK_PIPAPO') refills the freed slabs. Same family signal as nf_tables (CVE-2024-1086): unshare + nfnetlink + msg_msg burst from a non-root process. Distinguishes at the netlink payload layer (pipapo set type vs verdict-init double-free) which auditd alone can't see. dmesg may show 'KASAN: use-after-free in nft_pipapo_walk' on race-win attempts. No persistent file artifacts.",
|
|
.arch_support = "x86_64+unverified-arm64",
|
|
};
|
|
|
|
void skeletonkey_register_nft_pipapo(void)
|
|
{
|
|
skeletonkey_register(&nft_pipapo_module);
|
|
}
|