core/host: add meltdown_mitigation passthrough + migrate entrybleed

The kpti_enabled bool in struct skeletonkey_host flattens three
distinct sysfs states into one bit:

  /sys/devices/system/cpu/vulnerabilities/meltdown content:
    - 'Not affected'      → CPU is Meltdown-immune; KPTI off; EntryBleed
                            doesn't apply (verdict: OK)
    - 'Mitigation: PTI'   → KPTI on (verdict: VULNERABLE)
    - 'Vulnerable'        → KPTI off but CPU not hardened (rare;
                            verdict: VULNERABLE conservatively)
    - file unreadable     → unknown (verdict: VULNERABLE conservatively)

kpti_enabled=true only captures 'Mitigation: PTI'; kpti_enabled=false
collapses 'Not affected', 'Vulnerable', and 'unreadable' into one
indistinguishable case. That meant entrybleed_detect() had to
re-open the sysfs file to recover the raw string.

Fix by also stashing the raw first line in
ctx->host->meltdown_mitigation[64]. kpti_enabled stays for callers
that only need the simple bool; new code that needs the nuance reads
the string. populate happens once at startup, like every other host
field.

entrybleed migration:
  - reads ctx->host->meltdown_mitigation instead of opening sysfs
  - removes the file-local read_first_line() helper (now dead code)
  - same three-way verdict logic, but driven by a const char *
    instead of a fresh fopen() each detect()

Test coverage:
  - 3 new test rows on x86_64 fingerprints:
      empty mitigation       → VULNERABLE (conservative)
      'Not affected'         → OK
      'Mitigation: PTI'      → VULNERABLE
  - 1 stub-path test row on non-x86_64 fingerprints (PRECOND_FAIL)
  - registry coverage report: 30/31 modules now have direct tests
    (up from 29/31; copy_fail is the only remaining untested module)

Verification:
  - macOS: 33 kernel_range + 1 entrybleed-stub = 34 passes, 0 fails
  - Linux (docker gcc:latest): 33 kernel_range + 54 detect = 87
    passes, 0 fails. Up from 83 last commit.
This commit is contained in:
2026-05-23 01:14:38 -04:00
parent e2fef41667
commit 60d22eb4f6
4 changed files with 68 additions and 23 deletions
@@ -32,6 +32,7 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/host.h"
#include <stdio.h>
#include <stdint.h>
@@ -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");