diff --git a/core/host.c b/core/host.c index 9ba8870..60c1e35 100644 --- a/core/host.c +++ b/core/host.c @@ -190,6 +190,7 @@ static void populate_caps(struct skeletonkey_host *h) h->apparmor_restrict_userns = false; h->unprivileged_bpf_disabled = false; h->kpti_enabled = false; + h->meltdown_mitigation[0] = '\0'; h->kernel_lockdown_active = false; h->selinux_enforcing = false; h->yama_ptrace_restricted = false; @@ -208,8 +209,17 @@ static void populate_caps(struct skeletonkey_host *h) h->yama_ptrace_restricted = (v > 0); char buf[256]; - if (read_first_line("/sys/devices/system/cpu/vulnerabilities/meltdown", buf, sizeof buf)) + if (read_first_line("/sys/devices/system/cpu/vulnerabilities/meltdown", buf, sizeof buf)) { h->kpti_enabled = (strstr(buf, "Mitigation: PTI") != NULL); + /* Stash the raw value so modules that need richer matching + * (e.g. entrybleed distinguishing "Not affected" CPUs from + * "Vulnerable" / "Mitigation: PTI") don't re-read sysfs. */ + size_t L = strlen(buf); + if (L >= sizeof h->meltdown_mitigation) + L = sizeof h->meltdown_mitigation - 1; + memcpy(h->meltdown_mitigation, buf, L); + h->meltdown_mitigation[L] = '\0'; + } /* /sys/kernel/security/lockdown format: "[none] integrity confidentiality" * — whichever level is bracketed is the active one. */ diff --git a/core/host.h b/core/host.h index 3649fd8..48e130f 100644 --- a/core/host.h +++ b/core/host.h @@ -61,6 +61,11 @@ struct skeletonkey_host { bool apparmor_restrict_userns; /* sysctl: 1 = AA blocks unpriv userns */ bool unprivileged_bpf_disabled; /* /proc/sys/kernel/unprivileged_bpf_disabled = 1 */ bool kpti_enabled; /* /sys/.../meltdown contains "Mitigation: PTI" */ + char meltdown_mitigation[64]; /* raw first line of + * /sys/devices/system/cpu/vulnerabilities/meltdown + * — empty string if unreadable. Modules that need + * to distinguish "Not affected" (CPU immune) from + * "Mitigation: PTI" / "Vulnerable" can read this. */ bool kernel_lockdown_active; /* /sys/kernel/security/lockdown != [none] */ bool selinux_enforcing; /* /sys/fs/selinux/enforce = 1 */ bool yama_ptrace_restricted; /* /proc/sys/kernel/yama/ptrace_scope > 0 */ diff --git a/modules/entrybleed_cve_2023_0458/skeletonkey_modules.c b/modules/entrybleed_cve_2023_0458/skeletonkey_modules.c index 3ec3ba7..81babf4 100644 --- a/modules/entrybleed_cve_2023_0458/skeletonkey_modules.c +++ b/modules/entrybleed_cve_2023_0458/skeletonkey_modules.c @@ -32,6 +32,7 @@ #include "skeletonkey_modules.h" #include "../../core/registry.h" +#include "../../core/host.h" #include #include @@ -108,40 +109,33 @@ unsigned long entrybleed_leak_kbase_lib(unsigned long entry_syscall_slot_offset) 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; -} +/* (read_first_line() removed — meltdown status now comes from + * ctx->host->meltdown_mitigation, populated once at startup in + * core/host.c. One file open across the corpus instead of per-detect.) */ static skeletonkey_result_t entrybleed_detect(const struct skeletonkey_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) { + /* KPTI status comes from the shared host fingerprint + * (ctx->host->meltdown_mitigation) — populated once at startup by + * reading /sys/devices/system/cpu/vulnerabilities/meltdown. The + * raw string is preserved (not just the kpti_enabled bool) so we + * can distinguish "Not affected" (CPU immune; OK) from + * "Mitigation: PTI" / "Vulnerable" (KPTI on; vulnerable to + * EntryBleed) without re-reading sysfs. */ + const char *meltdown = ctx->host ? ctx->host->meltdown_mitigation : ""; + if (meltdown[0] == '\0') { if (!ctx->json) { - fprintf(stderr, "[?] entrybleed: cannot read meltdown vuln status — " + fprintf(stderr, "[?] entrybleed: meltdown vuln status unknown — " "assuming KPTI on (conservative)\n"); } return SKELETONKEY_VULNERABLE; } if (!ctx->json) { - fprintf(stderr, "[i] entrybleed: meltdown status = '%s'\n", buf); + fprintf(stderr, "[i] entrybleed: meltdown status = '%s'\n", meltdown); } /* "Not affected" → CPU is Meltdown-immune → no KPTI → no EntryBleed */ - if (strstr(buf, "Not affected") != NULL) { + if (strstr(meltdown, "Not affected") != NULL) { if (!ctx->json) { fprintf(stderr, "[+] entrybleed: CPU is Meltdown-immune; KPTI off; " "EntryBleed N/A\n"); diff --git a/tests/test_detect.c b/tests/test_detect.c index 8e93851..4152898 100644 --- a/tests/test_detect.c +++ b/tests/test_detect.c @@ -34,6 +34,7 @@ extern const struct skeletonkey_module dirtydecrypt_module; extern const struct skeletonkey_module fragnesia_module; extern const struct skeletonkey_module pack2theroot_module; extern const struct skeletonkey_module overlayfs_module; +extern const struct skeletonkey_module entrybleed_module; extern const struct skeletonkey_module dirty_pipe_module; extern const struct skeletonkey_module dirty_cow_module; extern const struct skeletonkey_module ptrace_traceme_module; @@ -594,6 +595,41 @@ static void run_all(void) &nf_tables_module, &h_nf_tables_5_10_209, SKELETONKEY_OK); + /* ── entrybleed: meltdown_mitigation passthrough ──────────────── + * entrybleed reads ctx->host->meltdown_mitigation (raw sysfs line) + * instead of re-opening /sys/.../meltdown. Test the three branches: + * - empty string ("probe failed") → conservative VULNERABLE + * - "Not affected" (Meltdown-immune CPU) → OK + * - "Mitigation: PTI" (KPTI on, vulnerable) → VULNERABLE + * The module is x86_64-only; on other arches the stub returns + * PRECOND_FAIL regardless of meltdown status. We test the x86_64 + * branch via the synthetic host's `arch` field. */ +#if defined(__x86_64__) || defined(__amd64__) + struct skeletonkey_host h_entry_no_data = h_kernel_6_12; + h_entry_no_data.meltdown_mitigation[0] = '\0'; + run_one("entrybleed: meltdown probe unread → conservative VULNERABLE", + &entrybleed_module, &h_entry_no_data, + SKELETONKEY_VULNERABLE); + + struct skeletonkey_host h_entry_immune = h_kernel_6_12; + strcpy(h_entry_immune.meltdown_mitigation, "Not affected"); + run_one("entrybleed: meltdown=Not affected (immune CPU) → OK", + &entrybleed_module, &h_entry_immune, + SKELETONKEY_OK); + + struct skeletonkey_host h_entry_kpti = h_kernel_6_12; + strcpy(h_entry_kpti.meltdown_mitigation, "Mitigation: PTI"); + run_one("entrybleed: meltdown=Mitigation: PTI → VULNERABLE", + &entrybleed_module, &h_entry_kpti, + SKELETONKEY_VULNERABLE); +#else + /* On non-x86_64 dev / CI containers, the stubbed detect() returns + * PRECOND_FAIL regardless of meltdown_mitigation contents. */ + run_one("entrybleed: non-x86_64 arch → PRECOND_FAIL (stub)", + &entrybleed_module, &h_kernel_6_12, + SKELETONKEY_PRECOND_FAIL); +#endif + /* ── coverage report ───────────────────────────────────────── * Iterate the runtime registry (populated by skeletonkey_register_* * calls in main()) and warn for any module that was not touched