Commit Graph

57 Commits

Author SHA1 Message Date
leviathan a8c8d5ef1f modules: add dirtydecrypt (CVE-2026-31635) + fragnesia (CVE-2026-46300)
Two new page-cache-write LPE modules, both ported from the public V12
security PoCs (github.com/v12-security/pocs):

- dirtydecrypt (CVE-2026-31635): rxgk missing-COW in-place decrypt.
  rxgk_decrypt_skb() decrypts spliced page-cache pages before the HMAC
  check, corrupting the page cache of a read-only file. Sibling of
  Copy Fail / Dirty Frag in the rxrpc subsystem.

- fragnesia (CVE-2026-46300): XFRM ESP-in-TCP skb_try_coalesce() loses
  the SHARED_FRAG marker, so the ESP-in-TCP receive path decrypts
  page-cache pages in place. A latent bug exposed by the Dirty Frag
  fix (f4c50a4034e6). Retires the old _stubs/fragnesia_TBD stub.

Both wrap the PoC exploit primitive in the skeletonkey_module
interface: detect/exploit/cleanup, an --active /tmp sentinel probe,
--no-shell support, and embedded auditd + sigma rules. The exploit
body runs in a forked child so the PoC's exit()/die() paths cannot
tear down the dispatcher. The fragnesia port drops the upstream PoC's
ANSI TUI (incompatible with a shared dispatcher); the exploit
mechanism is reproduced faithfully. Linux-only code is guarded with
#ifdef __linux__ so the modules still compile on non-Linux dev boxes.

VERIFICATION: ported, NOT yet validated end-to-end on a
vulnerable-kernel VM. The CVE fix commits are not pinned, so detect()
is precondition-only (PRECOND_FAIL / TEST_ERROR, never a blind
VULNERABLE) and --auto will not fire them unless --active confirms.
macOS stub-path compiles verified locally; the Linux exploit-path
build is covered by CI (build.yml, ubuntu) only. See each MODULE.md.

Wiring: core/registry.h, skeletonkey.c, Makefile, CVES.md, ROADMAP.md.
2026-05-22 18:22:30 -04:00
leviathan 3b287f84f0 copy_fail_family: skip DIRTYFAIL typed prompt under --i-know
The vendored DIRTYFAIL exploits call typed_confirm("DIRTYFAIL"), which
reads stdin interactively. SKELETONKEY already gates --exploit/--auto
behind --i-know, so the prompt is redundant and deadlocks non-interactive
runs like `skeletonkey --auto --i-know`.

Add a dirtyfail_assume_yes flag, forwarded from skeletonkey_ctx.authorized
by the bridge layer's apply_ctx(). When set, typed_confirm() auto-satisfies
its gate and logs that it did so.

The YES_BREAK_SSH self-lockout guard is exempt — it protects the
operator's own access rather than gating authorization, so it still
requires an interactive answer.

Standalone DIRTYFAIL builds are unchanged: the flag defaults false.
2026-05-22 16:49:15 -04:00
leviathan 33f81aeb69 site: revert CVE table → pill grid
The sortable table was denser but lost the visual scan-ability of
the color-coded pill grid. Restoring the pill view: two grouped
sections (🟢 / 🟡) each showing every module name as a pill.

Drops the table-sort JS (~25 lines) and the .cve-table CSS block.
2026-05-17 02:25:25 -04:00
leviathan 5be3c46719 CONTRIBUTING: fix stale IAMROOT_EXPLOIT_OK → SKELETONKEY_EXPLOIT_OK
Two references missed during the IAMROOT → SKELETONKEY rename in
v0.4.0. The enum value in core/module.h is SKELETONKEY_EXPLOIT_OK.
2026-05-17 02:24:06 -04:00
leviathan 58fb2e0951 site: simplify nav + add sortable CVE chart
nav: removed Releases / CVEs / Defenders links — kept only a
    right-aligned GitHub link with the Octocat SVG icon.
  index.html: replaced pill-grid corpus view with a proper sortable
    table — Year, CVE, Bug, Module, Tier columns. Click headers to
    sort. Defaults to Year descending. 28 rows covering 2016 → 2026.
  style.css: added .nav-github (border-pill style) + table styles
    (sortable headers with arrow indicators, hover rows, mobile-
    responsive font-size + overflow-x scroll).

JS for sort is ~25 lines vanilla — no library.
2026-05-17 02:22:54 -04:00
leviathan 2904fa159c site: GitHub Pages landing page
Single-page static site under /docs/, served by GitHub Pages from
the main branch /docs source.

  docs/index.html: hero with one-liner + copy button, why-this-exists,
    corpus stats + module pills (14 🟢 + 14 🟡), audience cards
    (red/blue/sysadmin/CTF), terminal-shape worked example,
    verified-vs-claimed bar, quickstart commands, status, footer.
  docs/style.css: dark theme matching GitHub's color palette
    (#0d1117 bg, #c9d1d9 text). System sans for prose, ui-monospace
    for code. Mobile-responsive with grid breakpoints. No JS framework,
    no external fonts, no analytics.
  docs/.nojekyll: disable Jekyll so the static HTML is served
    verbatim and the existing /docs/*.md files stay as raw markdown
    (viewable via GitHub UI, not the Pages site).
2026-05-17 02:14:15 -04:00
leviathan 2873133852 README: polish — accurate counts, audience table, corpus glance
Module counts were stale: 13 🟢 + 11 🟡 → corrected to 14 🟢 + 14 🟡
    (sudoedit_editor is new 🟢; sudo_samedit + sequoia + vmwgfx are
    new 🟡 from the v0.5.0 batch).
  Added 'Who it's for' table — red team / sysadmin / blue team / CTF
    each get a row.
  Added 'Corpus at a glance' section with explicit module lists per
    tier, replacing the prose paragraph that buried the names.
  Tightened Quickstart — removed duplicate one-liner block, single
    canonical command set.
  Worked example switched from fictional dirty_pipe to the actual
    --auto output shape (pwnkit pick on a vulnerable Ubuntu 5.15).
  Honest 'Status' framing — acknowledges no empirical end-to-end
    validation yet, calls it the next roadmap item. Replaces the
    aspirational 'CI-tested across a distro matrix' claim.
  Added 'How it works' (was 'Architecture' + 'Build & run' merged
    into a clearer flow) and 'The verified-vs-claimed bar' section
    explaining why most modules ship without per-kernel offsets.
2026-05-17 02:02:50 -04:00
leviathan 95135213e5 launch: README polish + CONTRIBUTING + LAUNCH.md
README.md: badges (release / license / module-count / platform),
    sharpened hero stating value prop in one sentence, audience
    framing for red team / sysadmin / blue team.
  CONTRIBUTING.md (new): what we accept (offsets, modules, detection
    rules, bug reports) and what we don't (untested EXPLOIT_OK,
    fabricated offsets, 0days, undisclosed CVEs).
  docs/LAUNCH.md (new): ~600-word HN/blog launch post. Copy-paste
    ready. Explains the verified-vs-claimed bar + --auto + the
    operator-populated offset table approach.

GitHub repo description + 11 topics set via gh repo edit so the
repo is discoverable in topic searches (linux-security,
privilege-escalation, cve, redteam, blueteam, etc.).
2026-05-17 01:59:25 -04:00
leviathan 0fbe1b058f v0.5.0: --auto mode + sysadmin one-liner
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
skeletonkey.c: new --auto subcommand. Scans every module's detect(),
    filters to VULNERABLE, ranks by safety (structural > page-cache >
    userspace > kernel-primitive > race), runs the safest exploit.
    Requires --i-know. If the safest fails, suggests next candidates.

  README.md: 'One-command root' Quickstart section showing
    curl … install.sh | sh && skeletonkey --auto --i-know
    — the sysadmin/red-team one-liner.

  Status: bumped 0.4.5 → 0.5.0; corpus 24 → 28 modules (4 new in
    parallel batch: sudo_samedit, sequoia, sudoedit_editor, vmwgfx).
v0.5.0
2026-05-17 01:55:13 -04:00
leviathan e13edd0cfd modules: add sudo_samedit + sequoia + sudoedit_editor + vmwgfx
sudo_samedit (CVE-2021-3156): Qualys Baron Samedit, userspace heap
    overflow in sudoedit -s. Version-range detect; Qualys-style trigger
    fork+verify (no per-distro offsets shipped — EXPLOIT_FAIL honest).
  sequoia (CVE-2021-33909): Qualys size_t→int wrap in seq_buf_alloc.
    Userns reach + 5000-level nested tree + bind-mount amplification +
    /proc/self/mountinfo read triggers stack-OOB write. No JIT-spray.
  sudoedit_editor (CVE-2023-22809): Synacktiv EDITOR/VISUAL '--' argv
    escape. Structural exploit — no offsets. Helper-via-sudoedit
    appends 'skel::0:0:' line to /etc/passwd, su to root.
  vmwgfx (CVE-2023-2008): DRM buffer-object OOB write in VMware guests.
    Detect requires DMI VMware + /dev/dri/cardN vmwgfx driver.

All four refuse cleanly on kctf-mgr (patched 6.12.86 / sudo 1.9.16p2).
2026-05-17 01:53:18 -04:00
leviathan 5a73565e0e scaffold: 4 new module dirs (sudo_samedit, sequoia, sudoedit_editor, vmwgfx)
Stubs returning PRECOND_FAIL. Parallel agents fill in real detect/exploit.
2026-05-17 01:47:28 -04:00
leviathan 324b539d65 README: bump Status to v0.4.5 2026-05-16 23:09:19 -04:00
leviathan e668c3301f banner: drop ASCII art, plain text only
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
Replace the skeleton-key ASCII art with a single-line text banner.

Bump 0.4.4 → 0.4.5.
v0.4.5
2026-05-16 23:05:40 -04:00
leviathan 347a9af832 banner: give the bit actual teeth
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
Previous staircase pattern was just trailing decoration — not real
key teeth. Redesigned the bit as a hanging rectangle with two
clearly-projecting notch-teeth on its right edge (the part that
engages a lock's wards). Switched to box-drawing chars for the bit
since they make sharper notches than 8/b/d glyphs; bow stays
ornate-ASCII style.

Bump 0.4.3 → 0.4.4.
v0.4.4
2026-05-16 23:04:14 -04:00
leviathan 023289a03a banner: artwork is the focal point — plain SKELETONKEY text below
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
Previous banner had a SKELETONKEY block-letter art that competed
with the skeleton-key drawing for visual attention. Simplified:
the key art is now the focal point, and SKELETONKEY is rendered
as plain spaced text below the drawing.

Slight refinement to the key art: bow is a bit larger (888 instead
of 88) to feel more substantial. Bit/teeth pattern unchanged.

Bump 0.4.2 → 0.4.3.
v0.4.3
2026-05-16 23:01:14 -04:00
leviathan e7ced5db7c banner: more detailed ornate skeleton key
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
The v0.4.1 box-drawing key was minimalist — round bow, line shaft,
small bit. Replaced with a more detailed ornate skeleton-key
silhouette in the classic ASCII-art-of-keys tradition:

  - Round bow with internal "hole" rendered via stylized 8/b/d/'
    pattern (suggests the decorative loop you'd grip)
  - Long shaft running right across the banner
  - Bit at the end with a staircase notch pattern (the iconic
    "key-tooth" descent showing the wards that engage the lock)

Same height as the previous banner. SKELETONKEY block letters
below unchanged.

Bump 0.4.1 → 0.4.2.
v0.4.2
2026-05-16 22:57:01 -04:00
leviathan b5188b7818 banner: redesign skeleton key ASCII art
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
Replace the previous "circle + shaft + curl" silhouette (which read
more like a magnifying glass) with a proper skeleton-key anatomy:

  - BOW: round decorative loop with center hole (the part you hold)
  - SHAFT: long horizontal rod (= the body of the key)
  - BIT: notched tooth hanging down from the shaft end (the part
    that engages the lock — the iconic key-tooth profile)

Same change in skeletonkey.c BANNER and README.md.

Bump 0.4.0 → 0.4.1.
v0.4.1
2026-05-16 22:52:13 -04:00
leviathan 9593d90385 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.
v0.4.0
2026-05-16 22:43:49 -04:00
leviathan 9d88b475c1 v0.3.1: --dump-offsets tool + NOTICE.md per module
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
The README has been claiming "each module credits the original CVE
reporter and PoC author in its NOTICE.md" since v0.1.0, but only
copy_fail_family actually shipped one. Fixed.

  modules/<name>/NOTICE.md (×19 new + 1 existing): per-module
    research credit covering CVE ID, discoverer, original advisory
    URL where public, upstream fix commit, IAMROOT's role.

  iamroot.c: new --dump-offsets subcommand. Resolves kernel offsets
    via the existing core/offsets.c four-source chain (env →
    /proc/kallsyms → /boot/System.map → embedded table), then emits
    a ready-to-paste C struct entry for kernel_table[]. Run once
    as root on a target kernel build; upstream via PR. Eliminates
    fabricating offsets — every shipped entry traces back to a
    `iamroot --dump-offsets` invocation on a real kernel.

  docs/OFFSETS.md: documents the --dump-offsets workflow.
  CVES.md: notes the NOTICE.md convention + offset dump tool.

  iamroot.c: bump IAMROOT_VERSION 0.3.0 → 0.3.1.
v0.3.1
2026-05-16 22:33:43 -04:00
leviathan 1bcfdd0c9f release: v0.3.0 — 4 new CVE modules (24 total)
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
iamroot.c: bump IAMROOT_VERSION 0.2.0 → 0.3.0
  CVES.md: add inventory entries for nft_set_uaf, af_unix_gc,
           nft_fwd_dup, nft_payload; extend operations table;
           bump counts (🟢 13 · 🟡 11 · 🔵 0 ·  1).
  README.md: update Status to 24 modules, list all 11 🟡 modules.

Module families now spanning:
  - copy_fail_family (page-cache write)
  - nf_tables (4 modules: nf_tables, nft_set_uaf, nft_fwd_dup, nft_payload)
  - af_packet (2 modules: af_packet, af_packet2)
  - overlayfs (2 modules: overlayfs CVE-2021-3493, overlayfs_setuid)
  - af_unix (new in v0.3.0)
  - plus 10 single-CVE families
v0.3.0
2026-05-16 22:25:15 -04:00
leviathan 5a808e3583 modules: 4 new CVE modules — nft_set_uaf + af_unix_gc + nft_fwd_dup + nft_payload
Each module: detect with branch-backport ranges + userns reach +
hand-rolled trigger + msg_msg cross-cache groom + slabinfo witness
+ /tmp/iamroot-<name>.log breadcrumb + auditd rules + --full-chain
finisher (FALLBACK depth, sentinel-arbitrated).

  nft_set_uaf (CVE-2023-32233, +1033): anonymous-set UAF
                (Sondej+Krysiuk). 5.1 → 6.4. nfnetlink batch:
                NEWTABLE → NEWCHAIN → NEWSET(ANON|EVAL) →
                NEWRULE(lookup) → DELSET → DELRULE; cg-512 spray.

  af_unix_gc (CVE-2023-4622, +813): GC race UAF (Lin Ma). ~2.0 → 6.5
                — widest range of any module. Two-thread race driver
                (SCM_RIGHTS cycle vs unix_gc trigger) + kmalloc-512
                spray. No userns needed.

  nft_fwd_dup (CVE-2022-25636, +1024): nft_fwd_dup_netdev_offload
                heap OOB (Aaron Adams). 5.4 → 5.17. NFT_CHAIN_HW_OFFLOAD
                chain + 16 immediates + fwd to overrun action.entries[].

  nft_payload (CVE-2023-0179, +1136): set-id memory corruption
                (Davide Ornaghi). 5.4 → 6.2. NFTA_SET_DESC variable
                element + NFTA_SET_ELEM_EXPRESSIONS with payload-set
                whose verdict.code drives the regs->data[] OOB.

All 4 honor verified-vs-claimed: trigger fires, primitive grooms, no
fabricated offsets. EXPLOIT_OK only via empirical setuid-bash sentinel.

Build clean on Debian 6.12.86; all 4 refuse cleanly on both default
and --full-chain paths via the existing patched-kernel detect gate.
2026-05-16 22:24:15 -04:00
leviathan 6a0a7d8718 scaffold: 4 new module dirs + registry/Makefile wiring (stubs)
Pre-scaffolding for the next batch (CVE-2023-32233, CVE-2023-4622,
CVE-2022-25636, CVE-2023-0179). Each module ships as a 21-line
stub returning PRECOND_FAIL; parallel agents fill in the real
detect/exploit/--full-chain implementations.

This commit keeps registry.h / iamroot.c / Makefile in one place
so the 4 parallel agents don't collide on shared-file edits — they
each own a single iamroot_modules.c.

Build clean on Debian 6.12.86; --list shows all 24 modules
including the 4 new stubs.
2026-05-16 22:17:47 -04:00
leviathan e2a3d6e94f release: v0.2.0 — --full-chain root-pop opt-in across 7 🟡 modules
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
iamroot.c: bump IAMROOT_VERSION 0.1.0 → 0.2.0
  CVES.md: redefine 🟡 to note --full-chain capability + docs/OFFSETS.md
  README.md: update Status section for v0.2.0
  docs/OFFSETS.md: new doc — env-var/kallsyms/System.map/embedded-table
                   resolution chain + operator workflow for populating
                   offsets per kernel build + sentinel-based success
                   arbitration.

All 7 🟡 modules now expose `--full-chain`. Default behavior unchanged.
v0.2.0
2026-05-16 22:06:14 -04:00
leviathan c1d1910a90 modules: wire --full-chain root-pop into all 7 🟡 PRIMITIVE modules
Each module now exposes an opt-in full-chain root-pop via --full-chain:
default --exploit behavior is unchanged (primitive-only, returns
EXPLOIT_FAIL). With --full-chain, after primitive lands, modules call
iamroot_finisher_modprobe_path() via a module-specific arb_write_fn
that re-uses the same trigger + slab groom to write a userspace
payload path into modprobe_path[], then exec a setuid bash dropped
by the kernel-invoked modprobe.

  netfilter_xtcompat (+239): msg_msg m_list_next stride-seed FALLBACK
  af_packet (+316):          sk_buff data-pointer stride-seed FALLBACK
  af_packet2 (+156):         tp_reserve underflow + skb spray, LAST RESORT
  nf_tables (+275):          forged pipapo_elem with kaddr value-ptr
                             (Notselwyn offset 0x10), FALLBACK
  cls_route4 (+251):         msg_msg refill of UAF'd filter, FALLBACK
  fuse_legacy (+291):        m_ts overflow + MSG_COPY sanity gate,
                             FALLBACK (one of two modules with a real
                             post-write sanity check)
  stackrot (+233):           race-driver budget extended 3s → 30s when
                             --full-chain; honest <1% race-win/run

All seven honor verified-vs-claimed: arb_write_fn returns 0 for
"trigger structurally fired"; the shared finisher's setuid-bash
sentinel poll is the empirical arbiter. EXPLOIT_OK only when the
sentinel materializes within 3s of the modprobe_path trigger.

Build clean on Debian 6.12.86 (kctf-mgr); all 7 modules refuse
cleanly on both default and --full-chain paths via the existing
patched-kernel detect gate (short-circuits before the new branch).
2026-05-16 22:04:40 -04:00
leviathan 125ce8a08b core: add shared finisher + offset resolver + --full-chain flag
Adds the infrastructure the 7 🟡 PRIMITIVE modules can wire into for
full-chain root pops.

  core/offsets.{c,h}: four-source kernel-symbol resolution chain
    1. env vars (IAMROOT_MODPROBE_PATH, IAMROOT_INIT_TASK, …)
    2. /proc/kallsyms (only useful when kptr_restrict=0 or root)
    3. /boot/System.map-$(uname -r) (world-readable on some distros)
    4. embedded table keyed by uname-r glob (entries are
       relative-to-_text, applied on top of an EntryBleed kbase leak;
       seeded empty in v0.2.0 — schema-only — to honor the
       no-fabricated-offsets rule).

  core/finisher.{c,h}: shared root-pop helpers given a module's
    arb-write primitive.
      Pattern A (modprobe_path):
        write payload script /tmp/iamroot-mp-<pid>.sh, arb-write
        modprobe_path ← that path, execve unknown-format trigger,
        wait for /tmp/iamroot-pwn-<pid> sentinel + setuid bash copy,
        spawn root shell.
      Pattern B (cred uid): stub — needs arb-READ too; modules use
        Pattern A unless they have read+write.
    On offset-resolution failure: prints a verbose how-to-populate
    diagnostic and returns EXPLOIT_FAIL honestly.

  core/module.h: + bool full_chain in iamroot_ctx

  iamroot.c: + --full-chain flag (longopt 7, sets ctx.full_chain)
             + help text describing primitive-only-by-default + the
               opt-in to attempt the full chain.

  Makefile: add core/offsets.o + core/finisher.o to CORE_SRCS.

Build clean on Debian 6.12.86; --help renders the new flag.
2026-05-16 21:56:03 -04:00
leviathan 3a5105c84c README: clarify iamroot runs unprivileged + add non-root → root demo
The whole point of an LPE tool is going from unprivileged to root,
but the Quickstart was leading with `sudo iamroot --scan`. Fix:

  - Drop sudo from --scan / --audit / --exploit / --detect-rules.
    These work without root (--scan reads /proc + /etc; --audit
    walks the FS via stat; --exploit IS the privilege escalation;
    --detect-rules emits to stdout).
  - Keep sudo only where it's actually needed: --mitigate (writes
    /etc/modprobe.d + sysctl) and tee'ing rule files into
    /etc/audit/rules.d/.
  - Add a worked example showing `id` as uid=1000, then
    `iamroot --exploit dirty_pipe --i-know`, then `id` as uid=0.
  - Fix the Build & run section's `sudo ./iamroot` too.
2026-05-16 21:51:32 -04:00
leviathan a564571e88 ci: add libc6-dev-arm64-cross for aarch64 cross-build
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / release (push) Blocked by required conditions
The v0.1.0 tag's arm64 job failed with
  fatal error: bits/wordsize.h: No such file or directory
because gcc-aarch64-linux-gnu alone doesn't pull in the cross libc
headers on Ubuntu 24.04 runners. Add libc6-dev-arm64-cross +
linux-libc-dev-arm64-cross so the cross-toolchain has its sysroot.
v0.1.0
2026-05-16 21:42:22 -04:00
leviathan dce158e33a release: v0.1.0 — 20-module corpus, 13 root-pop + 7 primitive
iamroot.c: bump IAMROOT_VERSION from 0.1.0-phase1 → 0.1.0
  README.md: replace "bootstrap phase" status with v0.1.0 corpus
             breakdown (13🟢 / 7🟡 across 2016→2026 timeline)
  CVES.md:   redefine 🟡 to mean "primitive fires + groom + witness,
             stops short of cred-overwrite chain — refuses to claim
             root unless empirically demonstrated"; flip 7 entries
             from 🔵🟡; add the two missing 🟢 entries
             (cgroup_release_agent, overlayfs_setuid); extend the
             operations matrix from 7 → 20 rows.
  ROADMAP.md: mark all Phase-7 items landed; add Phase 8 covering
              full-chain promotions (nf_tables / xtcompat / af_packet
              prioritized — each has a public reference exploit;
              IAMROOT's no-fabricated-offsets rule means each needs
              an env-var offset table or System.map auto-resolve).

Build clean on Debian 6.12.86; iamroot --version reports 0.1.0.
2026-05-16 21:40:51 -04:00
leviathan 3015e71ea3 modules: port final 2 detect-only modules (xtcompat + stackrot)
netfilter_xtcompat (CVE-2021-22555): +597 LoC — Option B
    Andy Nguyen's IPT_SO_SET_REPLACE 4-byte OOB write trigger;
    msg_msg kmalloc-2k spray + sk_buff sidecar; MSG_COPY witness
    + slabinfo delta. No leak→modprobe_path chain (per-kernel
    offsets refused), honest EXPLOIT_FAIL with continuation
    roadmap.

  stackrot (CVE-2023-3269): +619 LoC — Option C
    Two-thread race driver (MAP_GROWSDOWN + mremap rotation vs
    fork+fault) with cpu pinning + 3s budget; kmalloc-192 spray
    for anon_vma/anon_vma_chain; race-iteration + signal
    breadcrumb to /tmp/iamroot-stackrot.log. Honest reliability
    note in module header: <1% race-win/run on a vulnerable
    kernel — the public PoC averages minutes-to-hours and needs
    a much wider VMA staging matrix to be reliable.

Both refuse cleanly on Debian 6.12.86 (kctf-mgr); build clean.

This closes out the detect-only → LPE port across the corpus.
All 22 registered modules now either fire a real primitive or
refuse honestly per the verified-vs-claimed bar.
2026-05-16 21:31:21 -04:00
leviathan 498bb36404 modules: port 5 detect-only modules to trigger+groom (Option B)
Converts the 5 remaining detect-only network/fs LPE modules to fire
the actual kernel primitive on a vulnerable host, with honest
EXPLOIT_FAIL return values since none ship the per-kernel cred-overwrite
finisher.

  af_packet (CVE-2017-7308):     +444 LoC — TPACKET_V3 int-overflow
                                  + skb spray + best-effort cred race
  af_packet2 (CVE-2020-14386):   +446 LoC — tp_reserve underflow
                                  + sendmmsg skb spray
  cls_route4 (CVE-2022-2588):    +410 LoC — route4 dangling-filter UAF
                                  + msg_msg 1k spray + classify drive
  fuse_legacy (CVE-2022-0185):   +420 LoC — fsconfig 4k OOB write
                                  + msg_msg cross-cache groom
  nf_tables (CVE-2024-1086):     +613 LoC — hand-rolled nfnetlink batch
                                  builder + NFT_GOTO/DROP double-free
                                  + msg_msg groom skeleton

All five share:
  - userns+netns reach (unshare(CLONE_NEWUSER|CLONE_NEWNET))
  - Detect-refuse-on-patched re-call from exploit()
  - geteuid()==0 short-circuit
  - Honest EXPLOIT_FAIL with continuation roadmap comments
  - macOS dev-build stubs via #ifdef __linux__ where needed

Build verified clean on Debian 6.12.86 (kctf-mgr). All five refuse on
the patched kernel.
2026-05-16 21:22:17 -04:00
leviathan 4e9741ef1f Add overlayfs_setuid CVE-2023-0386 — FULL working exploit
Distro-agnostic overlayfs LPE — complements Ubuntu-specific CVE-2021-3493.
Same overlayfs family.

The bug: overlayfs copy_up preserves setuid bits even when the
unprivileged user triggering copy-up wouldn't normally have CAP_FSETID.

Exploit:
  1. unshare(USER|NS), uid_map self → root in userns
  2. Find a setuid binary on host (/usr/bin/su, sudo, passwd auto-pick)
  3. mount overlayfs with the binary's dirname as lower
  4. chown(merged/<binary>, 0, 0) — triggers copy-up; THE BUG: setuid
     bit persists in upper-layer copy despite our unprivileged context
  5. Open + truncate + replace upper-layer content with our payload
     (a compiled C binary that setresuid(0,0,0) + execle /bin/sh -p)
  6. exec upper-layer binary — runs as root via persistent setuid bit

- kernel_range: 5.11 ≤ K < 6.3, backports 5.15.110 / 6.1.27 / 6.2.13
- Detect refuses on patched / missing setuid carrier / userns denied
- Cleanup: rm -rf /tmp/iamroot-ovlsu-*
- Auditd: mount(overlay) + chown/fchown chain — shared with
  CVE-2021-3493 module via the family-level 'iamroot-overlayfs' key
- Compiles payload via target's gcc/cc (fallback dynamic if no -static)

Verified on Debian 6.12.86 (patched): detect reports OK; exploit
refuses cleanly. Module count = 20.

Coverage by year now (only 2018 gap remaining):
  2016: dirty_cow                                  🟢
  2017: af_packet                                  🔵
  2019: ptrace_traceme                             🟢
  2020: af_packet2                                 🔵
  2021: pwnkit, overlayfs, netfilter_xtcompat      🟢/🟢/🔵
  2022: dirty_pipe, cls_route4, fuse_legacy,
        cgroup_release_agent                       🟢/🔵/🔵/🟢
  2023: entrybleed, stackrot, overlayfs_setuid     🟢/🔵/🟢
  2024: nf_tables                                  🔵
  2026: copy_fail family (×5)                      🟢🟢🟢🟢🟢

16 of 20 modules have FULL working exploits (🟢).
2026-05-16 21:11:37 -04:00
leviathan 6eab6d3f70 Add cgroup_release_agent CVE-2022-0492 — FULL working exploit
Universal container-escape LPE. Doesn't need msg_msg cross-cache groom,
no arch-specific shellcode, no version-specific offsets — bug is
structural (priv check in wrong namespace).

Mechanism:
  1. unshare(CLONE_NEWUSER | CLONE_NEWNS) → become 'root' in userns
  2. write uid_map/gid_map (deny setgroups first)
  3. mount cgroup v1 (rdma controller; memory fallback)
  4. mkdir /<mnt>/iamroot subgroup
  5. write payload-path → release_agent (in mount root)
  6. write '1' → notify_on_release (in subgroup)
  7. write our pid → cgroup.procs (in subgroup)
  8. exit → cgroup empties → kernel exec's payload as INIT-ns uid=0
  9. Payload drops /tmp/iamroot-cgroup-sh with setuid root
  10. Parent polls for the setuid-shell appearance + exec's it -p

- kernel_range: K < 5.17 mainline, backports across 4.9 / 4.14 / 4.19 /
  5.4 / 5.10 / 5.15 / 5.16 LTS branches.
- Detect probes user_ns+mount_ns clone via fork-isolated child.
- Cleanup removes /tmp/iamroot-cgroup-* + umount the workspace.
- Auditd: flag unshare + mount(cgroup) + /sys/fs/cgroup writes from
  non-root. Sigma rule for unshare+cgroup-mount chain.

Path buffers oversized to silence GCC -Wformat-truncation noise
(cgdir 384, ra_path 384, nor_path/cgproc_path 512).

Verified on Debian 6.12.86 (patched): detect reports OK; exploit
refuses cleanly. Module count = 19.
2026-05-16 21:09:34 -04:00
leviathan 7387ffd3bd Add stackrot (CVE-2023-3269) + af_packet2 (CVE-2020-14386) modules
Two more for 'THE tool' coverage breadth.

stackrot CVE-2023-3269 (Ruihan Li, Jul 2023):
- maple-tree VMA-split UAF — kernel R/W via use-after-RCU
- **Different bug class than the netfilter-heavy 2022-2024 modules**
  (mm-class, broadens corpus shape)
- kernel_range: 6.1 ≤ K < 6.4-rc4, backports: 6.1.37 / 6.3.10 /
  mainline 6.4
- Pre-6.1 immune (no maple tree); 6.5+ patched
- Affects 6.1 LTS still widely deployed
- ~1000-line public PoC deferred for port

af_packet2 CVE-2020-14386 (Or Cohen, Sep 2020):
- AF_PACKET tpacket_rcv VLAN integer underflow → heap OOB
- Sibling of CVE-2017-7308; same subsystem, different code path
- kernel_range: 4.6 ≤ K, backports across 4.9 / 4.14 / 4.19 / 5.4 / 5.7 / 5.8
- Family-shared 'iamroot-af-packet' audit key (one ausearch covers both
  CVEs from one rule deployment)

Era coverage now (1 gap year remaining: 2018):
  2016: dirty_cow                              🟢
  2017: af_packet                              🔵
  2019: ptrace_traceme                         🟢
  2020: af_packet2                             🔵
  2021: pwnkit, overlayfs, netfilter_xtcompat  🟢/🟢/🔵
  2022: dirty_pipe, cls_route4, fuse_legacy    🟢/🔵/🔵
  2023: entrybleed, stackrot                   🟢/🔵
  2024: nf_tables                              🔵
  2026: copy_fail family (×5)                  🟢

18 modules total. Build clean. Scan on Debian 6.12.86: 13 OK / 5 VULN.
2026-05-16 21:03:36 -04:00
leviathan b24934156a Install ergonomics: GitHub release workflow + install.sh + README quickstart
For 'people should say just use iamroot' framing, the install gate is
the single biggest discoverability bottleneck. This commit makes it:

  curl -sSL https://github.com/KaraZajac/IAMROOT/releases/latest/download/install.sh | sh

.github/workflows/release.yml:
- Triggers on semver tag push (v*.*.*) + manual dispatch.
- Matrix build for x86_64 (gcc) and arm64 (aarch64-linux-gnu-gcc cross).
- Per-arch sha256sum alongside the binary.
- Auto-generates release notes pointing at CVES.md / ROADMAP.md and
  including the install one-liner with the version-specific URL.
- Publishes via softprops/action-gh-release@v2.

install.sh (also uploaded as a release artifact, so the curl|sh
above is stable):
- Detects arch (x86_64 / aarch64 → arm64).
- Pulls iamroot-<arch> + iamroot-<arch>.sha256 from the requested
  version (default: latest).
- Verifies sha256 via sha256sum or shasum -a 256.
- Installs to /usr/local/bin/iamroot (or $IAMROOT_PREFIX). Uses sudo
  iff /usr/local/bin isn't already writable.
- Prints quickstart hints + ethics pointer at the end.
- Env knobs: IAMROOT_VERSION, IAMROOT_PREFIX, IAMROOT_REPO.

README.md gains a 'Quickstart' section at the top with the four
canonical commands: install, --scan, --audit, --detect-rules,
fleet-scan. Lands the 'curl|bash and go' UX as the first thing
visitors see.
2026-05-16 21:01:34 -04:00
leviathan 541aac6993 Phase 7: ptrace_traceme CVE-2019-13272 — port FULL jannh-style exploit
Convert ptrace_traceme from 🔵🟢. Real working PoC following Jann
Horn's Project Zero issue #1903 technique.

Mechanism:
  1. fork() — child becomes our traced target via PTRACE_TRACEME
  2. child sleeps 500ms (lets parent execve start)
  3. parent execve's setuid binary (pkexec / su / passwd / sudo —
     auto-selected via find_setuid_target())
  4. Kernel elevates parent's creds to root but the stale
     ptrace_link from step 1 isn't invalidated (the bug)
  5. child PTRACE_ATTACH's to the now-privileged parent
  6. child PTRACE_POKETEXT's x86_64 shellcode at parent's RIP
  7. child PTRACE_DETACH — parent runs shellcode:
     setuid(0); setgid(0); execve('/bin/sh', ...) → root shell

Implementation notes:
- x86_64-only (shellcode is arch-specific). ARM/other arch returns
  IAMROOT_PRECOND_FAIL gracefully.
- Shellcode is the canonical 33-byte setuid(0)+execve('/bin/sh')
  inline asm sequence.
- Setuid binary selection: pkexec preferred (almost universal),
  then su/sudo/passwd as fallbacks. Refuses if none available.
- Auto-refuses on patched kernels (re-runs detect() at start).
- No cleanup applies — exploit replaces our process image on success.

Verified on Debian 6.12.86 (patched):
  iamroot --exploit ptrace_traceme --i-know
  → detect() says patched → refuses cleanly. Correct.

CVES.md: ptrace_traceme 🔵🟢.

5 detect-only modules remain (cls_route4, nf_tables, netfilter_xtcompat,
af_packet, fuse_legacy). Each is 200-400 line msg_msg/sk_buff
cross-cache groom — substantial individual commits. Next push or
strategic pivot per session priorities.
2026-05-16 20:57:44 -04:00
leviathan b6dd1e0482 Add --audit command: system-hygiene scan (setuid/world-writable/caps/sudo)
Beyond per-CVE detect (--scan), --audit answers 'is this box generally
exposed to privesc?' — the sysadmin-persona question. Distinguishes
IAMROOT from CVE-only tools (linux-exploit-suggester) and broad-enum
tools (linPEAS): focused on the LPE-exposure surface specifically.

Four scan categories:
- setuid: walks common bin dirs via find(1) -perm -4000. Annotates
  notable items: pkexec (Pwnkit history), fusermount3 (userns LPE
  history), sudo/su/passwd (expected, verify-integrity), snap-confine
  (Ubuntu snap escape history).
- world_writable: find /etc -perm -0002. Anything here = config edit
  by unprivileged user. Should be empty on a healthy box.
- capability: getcap -r over bin dirs. Flags cap_setuid+ep /
  cap_setgid+ep / cap_dac_override+ep / cap_sys_admin+ep specifically
  as 'privesc-equivalent if attacker-writable'.
- sudo NOPASSWD: grep /etc/sudoers + /etc/sudoers.d. Many legit
  service-account uses; flagged for operator review.

Output: human-readable table by default; --audit --json emits a single
JSON object with {audit: [findings...], summary: {category: count, ...}}.
Side-effect-free — read-only filesystem walks via popen(find/getcap/grep).

Fixed strncpy truncation warnings — switched to snprintf for path/note
copies into the finding struct.

iamroot.c MODE_AUDIT enum + --audit longopt + getopt 'A' + dispatcher
case. Usage block updated.

Verified end-to-end on Debian kctf-mgr:
  iamroot --audit       → 13 setuid binaries inventoried, 0 of the
                          other categories. pkexec correctly annotated.
  iamroot --audit --json → summary object suitable for SIEM ingest.
2026-05-16 20:52:36 -04:00
leviathan a52f5a657f Phase 7: af_packet (CVE-2017-7308) + FUSE legacy (CVE-2022-0185)
Two more famous LPEs broadening 'THE tool' coverage:

af_packet CVE-2017-7308 (Andrey Konovalov, Mar 2017):
- AF_PACKET TPACKET_V3 ring setup integer overflow → heap write-where
- Fills 2017 coverage gap
- kernel_range: 3.18.49 / 4.4.57 / 4.9.18 / 4.10.6 / mainline 4.11+
- Needs CAP_NET_RAW via user_ns clone
- Famous as the canonical 'userns + AF_PACKET → root' research-era LPE

fuse_legacy CVE-2022-0185 (William Liu / Crusaders-of-Rust, Jan 2022):
- legacy_parse_param fsconfig heap OOB → cross-cache UAF → root
- **Container-escape angle** — relevant to rootless docker/podman/snap
  (the system admin persona's nightmare)
- kernel_range: 5.4.171 / 5.10.91 / 5.15.14 / 5.16.2 / mainline 5.17+
- Needs user_ns + mount_ns to reach legacy_load() code path
- Originally reported as FUSE-specific but actually applies to any
  fs-mount path from userns (cgroup2, etc.)

Both detect-only initially; full exploits in follow-ups.

Coverage by year now:
  2016: dirty_cow                                  🟢
  2017: af_packet                                  🔵
  2019: ptrace_traceme                             🔵
  2021: pwnkit, overlayfs, netfilter_xtcompat      🟢/🟢/🔵
  2022: dirty_pipe, cls_route4, fuse_legacy        🟢/🔵/🔵
  2023: entrybleed                                 🟢
  2024: nf_tables                                  🔵
  2026: copy_fail family (×5)                      🟢

16 modules total. Build clean. Scan on kctf-mgr: 11 OK / 5 VULNERABLE.
2026-05-16 20:49:58 -04:00
leviathan 102b117d4e Phase 7: PTRACE_TRACEME (CVE-2019-13272) + xt_compat (CVE-2021-22555)
Two famous 2017-2020-era LPEs to broaden 'THE tool for folks'
coverage. Both detect-only initially; exploit ports as follow-ups.

ptrace_traceme (CVE-2019-13272 — jannh @ Google P0, Jun 2019):
- Famous because works on default-config systems with no user_ns
  required — locked-down environments were still vulnerable.
- kernel_range thresholds: 4.4.182 / 4.9.182 / 4.14.131 / 4.19.58 /
  5.0.20 / 5.1.17 / mainline 5.2+
- Exploit shape (deferred): fork → child PTRACE_TRACEME → parent
  execve setuid binary → child ptrace-injects shellcode → root.
- Auditd: flag PTRACE_TRACEME (request 0) — false positives via
  gdb/strace; tune by exclusion.

netfilter_xtcompat (CVE-2021-22555 — Andy Nguyen @ Google P0):
- Bug existed since 2.6.19 (2006) — 15 years of latent vuln. Famous
  for that age + default-config reachability via unprivileged_userns.
- kernel_range thresholds: 4.4.266 / 4.9.266 / 4.14.230 / 4.19.185
  / 5.4.110 / 5.10.27 / 5.11.10 / mainline 5.12+
- detect() probes user_ns+net_ns clone; locked-down → PRECOND_FAIL.
- Exploit shape (deferred): heap massage via msg_msg + sk_buff cross-
  cache groom → kernel R/W → cred or modprobe_path overwrite. ~400
  lines port from Andy's public exploit.c.
- Auditd: unshare + iptables-style setsockopt + msgsnd — combined,
  the canonical exploit footprint.

Both wired into iamroot.c, core/registry.h, Makefile. CVES.md rows
added with detailed status.

Coverage by year now:
  2016: dirty_cow                              🟢
  2019: ptrace_traceme                         🔵
  2021: pwnkit, overlayfs, netfilter_xtcompat  🟢/🟢/🔵
  2022: dirty_pipe, cls_route4                 🟢/🔵
  2023: entrybleed                             🟢
  2024: nf_tables                              🔵
  2026: copy_fail family (×5)                  🟢

Module count: 14. Build clean (no warnings).
2026-05-16 20:47:24 -04:00
leviathan e2fcc6a9e0 Phase 7: overlayfs CVE-2021-3493 — port FULL exploit (vsh-style)
Convert overlayfs from 🔵🟢: full vsh-style userns + overlayfs +
file-capability injection exploit.

Sequence:
  1. mkdtemp workdir; gcc-compile a minimal payload that
     setresuid(0,0,0) + execle(/bin/sh, -p)
  2. fork child; child unshares(CLONE_NEWUSER | CLONE_NEWNS),
     writes /proc/self/{setgroups,uid_map,gid_map} mapping outer uid
     to userns-root
  3. child mounts overlayfs with lower/upper/work layout
  4. child copies payload binary into merged/payload — this writes
     to host's upper/payload via the overlay
  5. child writes security.capability xattr with VFS_CAP_REVISION_2
     blob granting cap_setuid+ep on merged/payload — the BUG persists
     this xattr to the host fs entry
  6. child exits; parent verifies xattr via getxattr on upper/payload
  7. parent execve's upper/payload from outside userns → has
     cap_setuid effective → setuid(0) → /bin/sh -p with uid=0

- libcap-less setcap: build VFS_CAP_REVISION_2 blob in-place
  (cap_setuid bit 7, cap_setgid bit 6, effective flag set in
  magic_etc), write via setxattr(security.capability).
- which_gcc() fallback to /usr/bin/cc, /bin/gcc, etc.; tries
  -static first, falls back to dynamic link if static unavailable.
- Re-runs detect() to refuse on patched / non-Ubuntu hosts.
- Cleanup on failure: rmdir/unlink the workdir tree.
- Removed unused write_uid_gid_map() helper (logic now inline in
  child since we self-write the maps post-unshare).

Verified end-to-end on Debian kctf-mgr:
  iamroot --exploit overlayfs --i-know
  → 'not Ubuntu — bug is Ubuntu-specific' → 'refusing'. Correct.

Path buffers oversized vs. mkdtemp template to silence GCC
-Wformat-truncation noise.

CVES.md: overlayfs 🔵🟢.
2026-05-16 20:42:28 -04:00
leviathan cb39cc5119 Phase 7: Dirty COW (CVE-2016-5195) FULL module — old-systems coverage
The iconic 2016 LPE. Fills the 10-year coverage gap (now spanning
2016 → 2026): RHEL 6/7, Ubuntu 14.04, Ubuntu 16.04, embedded boxes,
IoT — many still in production with kernels predating the 4.9 fix.

- modules/dirty_cow_cve_2016_5195/iamroot_modules.{c,h}:
  - kernel_range: backport thresholds for 2.6 / 3.2 / 3.10 / 3.12 /
    3.16 / 3.18 / 4.4 / 4.7 / 4.8 / mainline 4.9
  - dirty_cow_write(): Phil-Oester-style two-thread race
    - mmap /etc/passwd MAP_PRIVATE (writes go COW)
    - writer thread: pwrite to /proc/self/mem at COW page offset
    - madviser thread: madvise(MADV_DONTNEED) to drop COW copy
    - poll-read /etc/passwd via separate fd to check if payload landed
    - 3-second timeout (race usually wins in ms on vulnerable kernels)
  - dirty_cow_exploit(): getpwuid → find_passwd_uid_field → race
    → execlp(su)
  - dirty_cow_cleanup(): POSIX_FADV_DONTNEED + drop_caches
  - Auditd rule: /proc/self/mem writes + madvise MADV_DONTNEED
  - Sigma rule: non-root /proc/self/mem open → high
- Makefile: -lpthread added to LDFLAGS for the binary link.
- iamroot.c + core/registry.h wired.
- CVES.md row added with detailed status; legend updated.

Verified end-to-end on kctf-mgr (6.12.86 — patched):
  iamroot --scan       → 'dirty_cow: kernel is patched' (OK)
  iamroot --exploit dirty_cow --i-know
                       → 'detect() says not vulnerable; refusing'
Module count = 12.
2026-05-16 20:38:46 -04:00
leviathan 3ad1446489 Add cls_route4 CVE-2022-2588 module (detect-only)
11th module. net/sched cls_route4 handle-zero dead UAF — discovered
by kylebot Aug 2022, fixed mainline 5.20 (commit 9efd23297cca).
Bug existed since 2.6.39 → very wide attack surface.

- modules/cls_route4_cve_2022_2588/iamroot_modules.{c,h}:
  - kernel_range thresholds: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 /
    5.19.7 / mainline 5.20+
  - can_unshare_userns() probes user_ns+net_ns clone availability
    (the exploit's CAP_NET_ADMIN-in-userns gate)
  - cls_route4_module_available() checks /proc/modules
  - Reports VULNERABLE if kernel in range AND user_ns allowed;
    PRECOND_FAIL if user_ns denied; OK if patched.
  - Exploit stub returns IAMROOT_PRECOND_FAIL with reference to
    kylebot's public PoC.
  - Auditd rule: tc-style sendto syscalls (rough; legit traffic
    shaping will trip — tune by user).

iamroot.c + Makefile + core/registry.h wired. CVES.md row added.

Verified on kctf-mgr (6.12.86): module reports OK, total module
count = 11.
2026-05-16 20:33:14 -04:00
leviathan fe33400f94 JSON polish: --list --json + --module-info + json_escape helper
- iamroot.c: cmd_list() takes ctx and switches on ctx->json — emits
  one JSON object {version, modules:[{name, cve, family, kernel_range,
  summary, has:{detect, exploit, mitigate, cleanup, auditd, sigma,
  yara, falco}}, ...]}. The 'has' object lets a SIEM query module
  capability without parsing the rule body.
- New cmd_module_info(name, ctx): full per-module detail.
  Human-readable by default; --json switches to JSON with embedded
  rule-text bodies for auditd/sigma/yara/falco.
- emit_module_json() helper shared between --list --json and
  --module-info --json with include_rules toggle.
- json_escape() helper: minimal-but-safe escaping (quote, backslash,
  newline, CR, tab; drops other control bytes). Sufficient for our
  module-defined strings which are ASCII-only by convention.
- enum mode gains MODE_MODULE_INFO; longopts gains --module-info=<name>
  taking required argument; getopt_long short string 'I:'.
- Usage block updated.

Verified end-to-end on kctf-mgr:
  iamroot --list --json | jq '.modules[0]'  → module metadata object
  iamroot --module-info entrybleed          → human pretty-print
  iamroot --module-info entrybleed --json   → JSON with embedded sigma
2026-05-16 20:31:20 -04:00
leviathan ea5d021f0c tools/iamroot-fleet-scan.sh + docs/DETECTION_PLAYBOOK.md
iamroot-fleet-scan.sh — bash wrapper that scp's the iamroot binary
to a host list, ssh-runs --scan --json on each, aggregates results
into a single JSON document. Supports:
- hosts list from file or stdin
- user@host:port syntax
- parallel xargs execution (default -P 4)
- ssh key / extra ssh opts pass-through
- --no-sudo for hosts where root isn't required
- --summary-only to suppress per-host detail
- --no-cleanup to leave the binary on disk

Critical fix during smoke-test: iamroot's exit codes are SEMANTIC
(0=OK, 2=VULNERABLE, 4=PRECOND_FAIL, 5=EXPLOIT_OK). The wrapper
must NOT treat nonzero exit as a transport failure; success is
defined by 'stdout contains valid JSON', failure by 'stdout empty'.

Verified end-to-end on kctf-mgr → kctf-fuzz:
  fleet-scan reports ok=1, failed=0,
  summary.vulnerable groups by CVE: copy_fail_gcm, dirty_frag_esp×2,
  entrybleed. Per-host detail included.

docs/DETECTION_PLAYBOOK.md — operational integration guide:
- Lifecycle diagram (inventory → scan → fleet scan → deploy/mitigate/upgrade → monitor)
- Recipes by team size: single host, small fleet, large fleet
- SIEM integration patterns: Splunk, Elastic, Sigma
- Auditd-event lookup commands per module key
- VULNERABLE decision tree (patch vs mitigate vs compensate)
- Mitigation revert procedures + side-effect table
- False-positive tuning table per rule key
- Pre-patch quarantine pattern
- Maintenance contract / module-shipping SLA
2026-05-16 20:29:48 -04:00
leviathan 3eeee01f06 Phase 7: overlayfs CVE-2021-3493 module (Ubuntu userns LPE) — detect-only
10th module. Ubuntu-specific userns + overlayfs LPE that injects file
capabilities cross-namespace.

- modules/overlayfs_cve_2021_3493/iamroot_modules.{c,h}:
  - is_ubuntu() — parses /etc/os-release for ID=ubuntu or
    ID_LIKE=ubuntu. Non-Ubuntu hosts get IAMROOT_OK immediately (the
    bug is specific to Ubuntu's modified overlayfs).
  - unprivileged_userns_clone gate — sysctl=0 → PRECOND_FAIL
  - Active probe (--active): forks a child that enters userns +
    mountns and attempts the overlayfs mount inside /tmp. Mount
    success on Ubuntu = VULNERABLE. Mount denied = patched / AppArmor
    block. Child-isolated so parent's namespace state is untouched.
  - Version fallback: kernel < 5.13 = vulnerable-by-inference for
    Ubuntu kernels; recommend --active for confirmation.
  - Exploit: detect-only stub. Reference vsh's exploit-cve-2021-3493
    for full version (mount overlayfs in userns, drop binary with
    cap_setuid+ep into upper layer, re-exec outside ns).
  - Embedded auditd rules: mount(overlay) syscall + security.capability
    xattr writes (the exploit's two-step footprint).

Verified end-to-end on kctf-mgr (Debian):
  iamroot --scan → 'not Ubuntu — bug is Ubuntu-specific' → IAMROOT_OK

Module count: 10. Active-probe pattern now applies to dirty_pipe,
entrybleed, and overlayfs (and copy_fail_family via existing
dirtyfail_active_probes global). Detect quality across the corpus
materially improved this session.
2026-05-16 20:22:32 -04:00
leviathan b206610a8e entrybleed: active probe (--active runs reduced sweep + sanity-checks kbase)
When --active is set, detect() runs a quick KASLR sweep and verifies
the leaked address looks plausible (kernel high half, 2MiB-aligned,
nonzero). This catches CPUs / mitigations / build-time changes that
neutralize prefetchnta timing in ways the meltdown sysfs node doesn't
reflect. Same pattern as dirty_pipe's active probe.

Three verdicts now distinguishable for entrybleed:
  --scan: 'KPTI active → VULNERABLE' (version/config inference)
  --scan --active + sane kbase: 'ACTIVE PROBE CONFIRMED — leak yields
                                  plausible kbase 0x...'
  --scan --active + implausible kbase: 'leak technique not reliable
                                        here' → IAMROOT_TEST_ERROR

Verified end-to-end on kctf-mgr: --scan --active reports
'ACTIVE PROBE CONFIRMED — leak yields plausible kbase
0xffffffff8d800000' (matches the full --exploit output).
2026-05-16 20:20:41 -04:00
leviathan a4b7238e4a Phase 7: nf_tables CVE-2024-1086 + active probe for dirty_pipe
dirty_pipe detect: active sentinel probe (Phase 1.5-ish improvement)
- New dirty_pipe_active_probe(): creates a /tmp probe file with known
  sentinel bytes, fires the Dirty Pipe primitive against it, re-reads
  via the page cache, returns true if the poisoning landed.
- detect() gated on ctx->active_probe: --scan does version-only check
  (fast, no side effects); --scan --active fires the empirical probe
  and overrides version inference with the empirical verdict. Catches
  silent distro backports that don't bump uname() version.
- Three verdicts now distinguishable:
  (a) version says patched, no active probe → 'patched (version-only)'
  (b) version says vulnerable, --active fires + probe lands → CONFIRMED
  (c) version says vulnerable, --active fires + probe blocked → 'likely
      patched via distro backport'
- Probe is safe: only /tmp, no /etc/passwd.

nf_tables CVE-2024-1086 (detect-only, new module):
- Famous Notselwyn UAF in nft_verdict_init. Affects 5.14 ≤ K, fixed
  mainline 6.8 with backports landing in 5.4.269 / 5.10.210 / 5.15.149
  / 6.1.74 / 6.6.13 / 6.7.2.
- detect() checks: kernel version range, AND unprivileged user_ns clone
  availability (the exploit's reachability gate — kernel-vulnerable
  but userns-locked-down hosts report PRECOND_FAIL, signalling that
  the kernel still needs patching but unprivileged path is closed).
- Ships auditd + sigma detection rules: unshare(CLONE_NEWUSER) chained
  with setresuid(0,0,0) on a previously-non-root process is the
  exploit's canonical telltale.
- Full Notselwyn-style exploit (cross-cache UAF → arbitrary R/W → cred
  overwrite or modprobe_path hijack) is the next commit.

9 modules total now. CVES.md and ROADMAP.md updated.
2026-05-16 20:19:11 -04:00
leviathan f1bd896ca8 Phase 7: Pwnkit FULL exploit (Qualys-style PoC) + DEFENDERS.md
Pwnkit: 🔵🟢
- Implements the canonical Qualys-style PoC end-to-end:
  1. Locate setuid pkexec
  2. mkdtemp working directory under /tmp
  3. Detect target's gcc/cc (fail-soft if absent)
  4. Write payload.c (gconv constructor: unsetenv hostile vars,
     setuid(0), execle /bin/sh -p with clean PATH)
  5. gcc -shared -fPIC payload.c -o pwnkit/PWNKIT.so
  6. Write gconv-modules cache pointing UTF-8// → PWNKIT//
  7. execve(pkexec, NULL_argv, envp{GCONV_PATH=workdir/pwnkit,
     PATH=GCONV_PATH=., CHARSET=PWNKIT, SHELL=pwnkit})
     → argc=0 triggers argv-overflow-into-envp; pkexec re-execs
     with PATH set to our tmpdir; libc's iconv loads PWNKIT.so
     as root; constructor pops /bin/sh with uid=0.
- Cleanup: removes /tmp/iamroot-pwnkit-* workdirs.
- Auto-refuses on patched hosts (re-runs detect() first).
- GCC -Wformat-truncation warnings fixed by sizing path buffers
  generously (1024/2048 bytes — way more than needed in practice).

Verified end-to-end on kctf-mgr (polkit 126 = patched):
  iamroot --exploit pwnkit --i-know
  → detect() says fixed → refuses cleanly. Correct behavior.
Vulnerable-kernel validation is Phase 4 CI matrix work.

docs/DEFENDERS.md — blue-team deployment guide:
- TL;DR: scan, deploy rules, mitigate, watch
- Operations cheat sheet (--list, --scan, --detect-rules, --mitigate)
- Audit-key table mapping rule keys to modules to caught behavior
- Fleet-scanning recipe (ssh + jq aggregation)
- Known false-positive shapes per rule with tuning hints

CVES.md: pwnkit row updated 🔵🟢.
ROADMAP.md: Phase 7 Pwnkit checkbox marked complete.
2026-05-16 20:13:11 -04:00
leviathan 43e290b224 Phase 7: Pwnkit (CVE-2021-4034) detect-only module
First USERSPACE LPE in IAMROOT (every prior module is kernel). Same
iamroot_module interface — the difference is the affected-version
check is package-version-based rather than kernel-version-based.

- modules/pwnkit_cve_2021_4034/:
  - iamroot_modules.{c,h}: detect() locates setuid pkexec (one of
    /usr/bin/pkexec, /usr/sbin/pkexec, /bin/pkexec, /sbin/pkexec,
    /usr/local/bin/pkexec) and parses 'pkexec --version' output.
    Handles BOTH version-string formats: legacy '0.105'/'0.120'
    (older polkit) AND modern bare-integer '121'/'126' (post-0.121
    rename to single-number scheme). Reports VULNERABLE on parse
    failure (conservative).
  - exploit() returns IAMROOT_PRECOND_FAIL with a 'not yet
    implemented' message; full Qualys-PoC follow-up is the next
    commit. ~200 lines including embedded .so generator.
  - MODULE.md documents the bug, affected ranges, distro backport
    landscape (RHEL 7/8, Ubuntu focal/impish, Debian buster/bullseye
    each have their own backported polkit version).
  - Embedded auditd + sigma detection rules:
    auditd: pkexec watch + execve audit
    sigma:  pkexec invocation + suspicious env (GCONV_PATH, CHARSET)

- core/registry.h adds iamroot_register_pwnkit() declaration.
- iamroot.c main() registers pwnkit.
- Makefile gains the pwnkit family as a separate object set.

Verified end-to-end on kctf-mgr (modern polkit 126):
  iamroot --list  → 8 modules
  iamroot --scan  → pwnkit reports 'version 126 ≥ 0.121 (fixed)'
  iamroot --detect-rules --format=auditd | grep pwnkit → emits
2026-05-16 20:07:40 -04:00
leviathan 28ad566964 Phase 6 (partial): --mitigate bridged for copy_fail_family
- copy_fail_family/iamroot_modules.c: two new bridge functions
  - copy_fail_family_mitigate: calls existing mitigate_apply() which
    blacklists algif_aead + esp4 + esp6 + rxrpc, sets
    kernel.apparmor_restrict_unprivileged_userns=1, drops caches.
  - copy_fail_family_cleanup: heuristic-routed cleanup. If the
    mitigation conf file (/etc/modprobe.d/dirtyfail-mitigations.conf)
    exists → mitigate_revert(). Otherwise → try_revert_passwd_page_cache()
    to evict /etc/passwd from page cache.
- All 5 copy_fail_family modules' .mitigate and .cleanup fields now
  point at these shared family-wide handlers (the mitigation is
  family-wide, not per-CVE).
- dirty_pipe and entrybleed: no --mitigate offered (no canonical
  patches / only-fix-is-upgrade). Documented in ROADMAP.

Verified end-to-end on kctf-mgr as non-root user:
  iamroot --mitigate copy_fail → 'mitigate requires root' (correct)
  iamroot --cleanup  copy_fail → 'no mitigation conf; evicting page cache'

CVES.md gains a per-module ops table; ROADMAP.md marks Phase 6 partial.
2026-05-16 20:04:32 -04:00
leviathan 4943b82129 Phase 4 (partial): GitHub Actions build-check CI
- .github/workflows/build.yml: matrix of {gcc, clang} x {default,
  debug} builds on every push + PR. Smoke tests after build:
  --version, --list, --scan, --detect-rules auditd, --detect-rules
  sigma. Build failure breaks merge gate.
- Static-build job runs continue-on-error (glibc + NSS issue with
  static linking — getpwnam pulls in NSS at runtime; legacy DIRTYFAIL
  Makefile noted this. Revisit with musl-gcc to get a truly portable
  static binary).
- Kernel-VM matrix placeholder commented at the bottom of build.yml.
  Real kernel matrix needs self-hosted runners or a paid VM service —
  out of scope for tonight, in scope for Phase 4 followup.
2026-05-16 20:02:53 -04:00