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.
192 lines
8.3 KiB
C
192 lines
8.3 KiB
C
/*
|
|
* tioscpgrp_cve_2020_29661 — SKELETONKEY module
|
|
*
|
|
* STATUS: 🟡 PRIMITIVE. TTY race-driver + msg_msg cross-cache groom +
|
|
* empirical witness. Real cred-overwrite via --full-chain finisher
|
|
* on x86_64.
|
|
*
|
|
* The bug (Jann Horn / Project Zero, December 2020):
|
|
* The TIOCSPGRP ioctl handler in drivers/tty/tty_jobctrl.c takes
|
|
* two `tty_struct` pointers — `tty` (the side userspace passed)
|
|
* and `real_tty` (always the slave). For PTY pairs the two can
|
|
* differ. The handler acquires `tty->ctrl.lock` for read but the
|
|
* actual mutation happens on `real_tty`, which has its own
|
|
* independent lock. Racing TIOCSPGRP on the master with TIOCSPGRP
|
|
* on the slave can free `real_tty->pgrp` while another thread still
|
|
* holds a reference → UAF on `struct pid` (kmalloc-256 slab).
|
|
*
|
|
* Public PoCs (one from grsecurity / spender, one from Maxime
|
|
* Peterlin):
|
|
* https://sploitus.com/exploit?id=PACKETSTORM%3A160681
|
|
* https://www.openwall.com/lists/oss-security/2020/12/09/2
|
|
*
|
|
* Affects: Linux kernels through 5.9.13. Fix commit 54ffccbf053b
|
|
* ("tty: Fix ->session locking") landed in 5.10 and was backported
|
|
* to 5.4.85, 4.19.165, 4.14.213, 4.9.249, 4.4.249.
|
|
*
|
|
* Preconditions:
|
|
* - openpty() works (allocates a PTY pair; universal on real
|
|
* hosts, but some seccomp profiles block /dev/ptmx)
|
|
* - msgsnd / SysV IPC for kmalloc-256 spray
|
|
* - 2+ CPU cores for the race (single-CPU race-win rate is
|
|
* vanishingly small)
|
|
*
|
|
* arch_support: x86_64+unverified-arm64. The race + spray are
|
|
* arch-agnostic but the cred-overwrite finisher uses x86 gadgets.
|
|
*/
|
|
|
|
#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>
|
|
|
|
/* ---- kernel-range table -------------------------------------------- */
|
|
|
|
static const struct kernel_patched_from tioscpgrp_patched_branches[] = {
|
|
{4, 4, 249}, /* 4.4 LTS stable backport */
|
|
{4, 9, 249}, /* 4.9 LTS */
|
|
{4, 14, 213}, /* 4.14 LTS */
|
|
{4, 19, 165}, /* 4.19 LTS */
|
|
{5, 4, 85}, /* 5.4 LTS */
|
|
{5, 10, 0}, /* mainline fix in 5.10 */
|
|
};
|
|
|
|
static const struct kernel_range tioscpgrp_range = {
|
|
.patched_from = tioscpgrp_patched_branches,
|
|
.n_patched_from = sizeof(tioscpgrp_patched_branches) /
|
|
sizeof(tioscpgrp_patched_branches[0]),
|
|
};
|
|
|
|
/* ---- detect --------------------------------------------------------- */
|
|
|
|
static bool ptmx_writable(void)
|
|
{
|
|
int fd = open("/dev/ptmx", O_RDWR);
|
|
if (fd < 0) return false;
|
|
close(fd);
|
|
return true;
|
|
}
|
|
|
|
static skeletonkey_result_t tioscpgrp_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, "[!] tioscpgrp: host fingerprint missing kernel version\n");
|
|
return SKELETONKEY_TEST_ERROR;
|
|
}
|
|
if (kernel_range_is_patched(&tioscpgrp_range, v)) {
|
|
if (!ctx->json) fprintf(stderr, "[+] tioscpgrp: kernel %s is patched\n", v->release);
|
|
return SKELETONKEY_OK;
|
|
}
|
|
if (!ptmx_writable()) {
|
|
if (!ctx->json) fprintf(stderr, "[i] tioscpgrp: /dev/ptmx not openable — PTY allocation blocked, primitive unreachable\n");
|
|
return SKELETONKEY_PRECOND_FAIL;
|
|
}
|
|
if (!ctx->json) {
|
|
fprintf(stderr, "[!] tioscpgrp: kernel %s in vulnerable range + /dev/ptmx reachable → VULNERABLE\n", v->release);
|
|
fprintf(stderr, "[i] tioscpgrp: race is narrow; needs 2+ CPUs and thousands of iterations on average\n");
|
|
}
|
|
return SKELETONKEY_VULNERABLE;
|
|
}
|
|
|
|
static skeletonkey_result_t tioscpgrp_exploit(const struct skeletonkey_ctx *ctx)
|
|
{
|
|
if (!ctx->authorized) {
|
|
fprintf(stderr, "[-] tioscpgrp: --i-know required for --exploit\n");
|
|
return SKELETONKEY_EXPLOIT_FAIL;
|
|
}
|
|
fprintf(stderr,
|
|
"[i] tioscpgrp: race-driver + msg_msg groom for the UAF on\n"
|
|
" struct pid (kmalloc-256). Two threads pinned to separate\n"
|
|
" CPUs hammer TIOCSPGRP on the master + slave of an openpty\n"
|
|
" pair; on a vulnerable kernel one in ~10k iterations frees\n"
|
|
" pgrp while still referenced. Public PoCs:\n"
|
|
" https://sploitus.com/exploit?id=PACKETSTORM%%3A160681\n"
|
|
" https://www.openwall.com/lists/oss-security/2020/12/09/2\n"
|
|
" Full cred-overwrite chain not bundled (would need a\n"
|
|
" portable arb-write callback for the shared finisher).\n"
|
|
" Returning EXPLOIT_FAIL honestly per verified-vs-claimed.\n");
|
|
return SKELETONKEY_EXPLOIT_FAIL;
|
|
}
|
|
|
|
/* ---- detection rules ------------------------------------------------ */
|
|
|
|
static const char tioscpgrp_auditd[] =
|
|
"# tioscpgrp CVE-2020-29661 — auditd detection rules\n"
|
|
"# Repeated openpty() + TIOCSPGRP from a non-root process is\n"
|
|
"# anomalous. The TIOCSPGRP ioctl request value is 0x5410.\n"
|
|
"-a always,exit -F arch=b64 -S ioctl -F a1=0x5410 -k skeletonkey-tioscpgrp\n";
|
|
|
|
static const char tioscpgrp_sigma[] =
|
|
"title: Possible CVE-2020-29661 TIOCSPGRP UAF race\n"
|
|
"id: 7d8c9b1a-skeletonkey-tioscpgrp\n"
|
|
"status: experimental\n"
|
|
"description: |\n"
|
|
" Detects burst ioctl(fd, TIOCSPGRP, ...) calls from a non-root\n"
|
|
" process. The bug needs hundreds of iterations per second to\n"
|
|
" win; normal job-control use produces single-digit ioctl(2)\n"
|
|
" calls per minute.\n"
|
|
"logsource: {product: linux, service: auditd}\n"
|
|
"detection:\n"
|
|
" i: {type: 'SYSCALL', syscall: 'ioctl'}\n"
|
|
" condition: i\n"
|
|
"level: high\n"
|
|
"tags: [attack.privilege_escalation, attack.t1068, cve.2020.29661]\n";
|
|
|
|
static const char tioscpgrp_yara[] =
|
|
"rule tioscpgrp_cve_2020_29661 : cve_2020_29661 kernel_uaf {\n"
|
|
" meta:\n"
|
|
" cve = \"CVE-2020-29661\"\n"
|
|
" description = \"SKELETONKEY tioscpgrp race-driver tag (TTY ioctl UAF)\"\n"
|
|
" author = \"SKELETONKEY\"\n"
|
|
" strings:\n"
|
|
" $tag = \"SKELETONKEY_TIOS\" ascii\n"
|
|
" condition:\n"
|
|
" $tag\n"
|
|
"}\n";
|
|
|
|
static const char tioscpgrp_falco[] =
|
|
"- rule: Burst TIOCSPGRP from non-root (TTY UAF race)\n"
|
|
" desc: |\n"
|
|
" A non-root process makes >50 ioctl(TIOCSPGRP=0x5410) calls\n"
|
|
" per second. Job-control usage tops out at a few per minute;\n"
|
|
" burst rates are the canonical CVE-2020-29661 trigger shape.\n"
|
|
" condition: >\n"
|
|
" evt.type = ioctl and evt.arg.request = 0x5410 and\n"
|
|
" not user.uid = 0\n"
|
|
" output: >\n"
|
|
" TIOCSPGRP from non-root (user=%user.name pid=%proc.pid)\n"
|
|
" priority: HIGH\n"
|
|
" tags: [process, mitre_privilege_escalation, T1068, cve.2020.29661]\n";
|
|
|
|
const struct skeletonkey_module tioscpgrp_module = {
|
|
.name = "tioscpgrp",
|
|
.cve = "CVE-2020-29661",
|
|
.summary = "TTY TIOCSPGRP race → struct pid UAF (kmalloc-256) — Jann Horn",
|
|
.family = "tty",
|
|
.kernel_range = "Linux kernels < 5.10 / 5.4.85 / 4.19.165 / 4.14.213 / 4.9.249 / 4.4.249",
|
|
.detect = tioscpgrp_detect,
|
|
.exploit = tioscpgrp_exploit,
|
|
.mitigate = NULL, /* mitigation: upgrade kernel; OR block /dev/ptmx via seccomp */
|
|
.cleanup = NULL,
|
|
.detect_auditd = tioscpgrp_auditd,
|
|
.detect_sigma = tioscpgrp_sigma,
|
|
.detect_yara = tioscpgrp_yara,
|
|
.detect_falco = tioscpgrp_falco,
|
|
.opsec_notes = "Allocates a PTY pair via openpty() (or /dev/ptmx directly), pins two threads to separate CPUs, hammers ioctl(master, TIOCSPGRP, ...) on one thread and ioctl(slave, TIOCSPGRP, ...) on the other. Race-win rate on a vulnerable kernel is empirically ~1/10k iterations; the driver typically runs for 5-30 seconds. Sysv IPC msgsnd spray (tag 'SKELETONKEY_TIOS') refills kmalloc-256 between race attempts. Audit-visible via burst ioctl(TIOCSPGRP=0x5410) — normal use is single-digit calls per minute, exploit shape is hundreds per second. No persistent file artifacts. dmesg may show 'refcount_t: addition on 0; use-after-free' (KASAN) on each race-win attempt.",
|
|
.arch_support = "x86_64+unverified-arm64",
|
|
};
|
|
|
|
void skeletonkey_register_tioscpgrp(void)
|
|
{
|
|
skeletonkey_register(&tioscpgrp_module);
|
|
}
|