From 36814f272dd75f7f3290ef3b3de75c4f4f20a915 Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Fri, 22 May 2026 23:43:20 -0400 Subject: [PATCH] modules: migrate remaining 22 modules to ctx->host fingerprint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the host-fingerprint refactor that started in c00c3b4. Every module now consults the shared ctx->host (populated once at startup by core/host.c) instead of re-doing uname / geteuid / /etc/os-release parsing / fork+unshare(CLONE_NEWUSER) probes per detect(). Migrations applied per module (mechanical, no exploit logic touched): 1. #include "../../core/host.h" inside each module's #ifdef __linux__. 2. kernel_version_current(&v) -> ctx->host->kernel (with the v -> v-> arrow-vs-dot fix for all later usage). Drops ~20 redundant uname() calls across the corpus. 3. geteuid() == 0 (the 'already root, nothing to escalate' gate) -> bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); This is the key change that lets the unit test suite construct non-root fingerprints regardless of the test process's actual euid. 4. Per-detect fork+unshare(CLONE_NEWUSER) probe helpers (named can_unshare_userns / can_unshare_userns_mount across the corpus) are removed wholesale; their call sites now consult ctx->host->unprivileged_userns_allowed, which was probed once at startup. Removes ~10 per-scan fork()s. Modules touched by this commit (22): Batch A (7): dirty_pipe, dirty_cow, ptrace_traceme, pwnkit, cgroup_release_agent, overlayfs_setuid, and entrybleed (no migration target — KPTI gate stays as direct sysfs read; documented as 'no applicable pattern'). Batch B (7): nf_tables, cls_route4, netfilter_xtcompat, af_packet, af_packet2, af_unix_gc, fuse_legacy. Batch C (8): stackrot, nft_set_uaf, nft_fwd_dup, nft_payload, sudo_samedit, sequoia, sudoedit_editor, vmwgfx. Combined with the 4 modules already migrated (dirtydecrypt, fragnesia, pack2theroot, overlayfs) and the 5-module copy_fail_family bridge, the entire registered corpus now goes through ctx->host. The 4 'fork+unshare per detect()' helpers that existed across nf_tables, cls_route4, netfilter_xtcompat, af_packet, af_packet2, fuse_legacy, nft_set_uaf, nft_fwd_dup, nft_payload, sequoia, cgroup_release_agent, and overlayfs_setuid are now gone — replaced by the single startup probe in core/host.c. Verification: - Linux (docker gcc:latest + libglib2.0-dev): full clean build links 31 modules; tests/test_detect.c: 8/8 pass. - macOS (local): full clean build links 31 modules (Mach-O, 172KB); test suite reports skipped as designed on non-Linux. Subsequent commits can add more EXPECT_DETECT cases in tests/test_detect.c — the host-fingerprint paths in every module are now uniformly testable via synthetic struct skeletonkey_host instances. --- .../skeletonkey_modules.c | 49 ++++++++-------- .../skeletonkey_modules.c | 56 +++++++++---------- .../skeletonkey_modules.c | 21 ++++--- .../skeletonkey_modules.c | 46 +++++++-------- .../skeletonkey_modules.c | 41 ++++++-------- .../skeletonkey_modules.c | 23 +++++--- .../skeletonkey_modules.c | 37 +++++++----- .../skeletonkey_modules.c | 54 +++++++++--------- .../skeletonkey_modules.c | 49 ++++++++-------- .../skeletonkey_modules.c | 50 ++++++++--------- .../skeletonkey_modules.c | 41 +++++--------- .../skeletonkey_modules.c | 41 +++++--------- .../skeletonkey_modules.c | 41 +++++--------- .../skeletonkey_modules.c | 50 ++++++++--------- .../skeletonkey_modules.c | 27 ++++++--- .../skeletonkey_modules.c | 6 +- .../skeletonkey_modules.c | 42 ++++---------- .../skeletonkey_modules.c | 24 ++++---- .../skeletonkey_modules.c | 4 +- .../skeletonkey_modules.c | 4 +- .../skeletonkey_modules.c | 20 ++++--- 21 files changed, 345 insertions(+), 381 deletions(-) diff --git a/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c index 339aa11..77d2533 100644 --- a/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c +++ b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c @@ -56,6 +56,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -91,53 +92,44 @@ static const struct kernel_range af_packet2_range = { sizeof(af_packet2_patched_branches[0]), }; -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] af_packet2: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] af_packet2: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 4.6 (tpacket_rcv VLAN path). Pre-4.6 immune. */ - if (v.major < 4 || (v.major == 4 && v.minor < 6)) { + if (v->major < 4 || (v->major == 4 && v->minor < 6)) { if (!ctx->json) { fprintf(stderr, "[+] af_packet2: kernel %s predates the bug (introduced in 4.6)\n", - v.release); + v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&af_packet2_range, &v); + bool patched = kernel_range_is_patched(&af_packet2_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] af_packet2: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] af_packet2: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { - fprintf(stderr, "[i] af_packet2: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] af_packet2: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] af_packet2: user_ns+net_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] af_packet2: user_ns denied → unprivileged exploit unreachable\n"); } @@ -513,8 +505,11 @@ static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx return pre; } - /* 2. Refuse if already root. */ - if (geteuid() == 0) { + /* 2. Refuse if already root. Consult ctx->host first so unit tests + * can construct a non-root fingerprint regardless of the test + * process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] af_packet2: already running as root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/af_packet_cve_2017_7308/skeletonkey_modules.c b/modules/af_packet_cve_2017_7308/skeletonkey_modules.c index fa5fb66..4b3dd31 100644 --- a/modules/af_packet_cve_2017_7308/skeletonkey_modules.c +++ b/modules/af_packet_cve_2017_7308/skeletonkey_modules.c @@ -71,6 +71,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -111,44 +112,35 @@ static const struct kernel_range af_packet_range = { sizeof(af_packet_patched_branches[0]), }; -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static skeletonkey_result_t af_packet_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] af_packet: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] af_packet: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } - bool patched = kernel_range_is_patched(&af_packet_range, &v); + bool patched = kernel_range_is_patched(&af_packet_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] af_packet: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] af_packet: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { - fprintf(stderr, "[i] af_packet: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] af_packet: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] af_packet: user_ns+net_ns clone (CAP_NET_RAW gate): %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] af_packet: user_ns denied → " "unprivileged exploit unreachable\n"); @@ -723,8 +715,11 @@ static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx) return pre; } - /* 2. Refuse if already root. */ - if (geteuid() == 0) { + /* 2. Refuse if already root. Consult ctx->host first so unit tests + * can construct a non-root fingerprint regardless of the test + * process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] af_packet: already root — nothing to escalate\n"); return SKELETONKEY_OK; } @@ -732,16 +727,19 @@ static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx) /* 3. Resolve offsets for THIS kernel. If we don't have them, bail * early — the kernel-write walk needs them. The integrator can * extend known_offsets[] for new distro builds. */ - struct kernel_version v; - if (!kernel_version_current(&v)) { + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] af_packet: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } struct af_packet_offsets off; - if (!resolve_offsets(&off, &v)) { + if (!resolve_offsets(&off, v)) { fprintf(stderr, "[-] af_packet: no offset table for kernel %s\n" " set SKELETONKEY_AFPACKET_OFFSETS=::\n" " (hex). Known table covers Ubuntu 16.04 (4.4) and 18.04 (4.15).\n", - v.release); + v->release); return SKELETONKEY_PRECOND_FAIL; } if (!ctx->json) { diff --git a/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c b/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c index 03b9f0b..d6692ca 100644 --- a/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c +++ b/modules/af_unix_gc_cve_2023_4622/skeletonkey_modules.c @@ -58,6 +58,7 @@ #include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -129,9 +130,14 @@ static bool can_create_af_unix(void) static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] af_unix_gc: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] af_unix_gc: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } @@ -139,10 +145,10 @@ static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx) * the dawn of time. ANY kernel below the fix is vulnerable. The * kernel_range walker handles "older than every entry" correctly * (returns false → not patched → vulnerable). */ - bool patched = kernel_range_is_patched(&af_unix_gc_range, &v); + bool patched = kernel_range_is_patched(&af_unix_gc_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] af_unix_gc: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] af_unix_gc: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } @@ -157,7 +163,7 @@ static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx) } if (!ctx->json) { - fprintf(stderr, "[!] af_unix_gc: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[!] af_unix_gc: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] af_unix_gc: bug is reachable as PLAIN UNPRIVILEGED USER\n" " (no userns / no CAP_* required — AF_UNIX is universally\n" " creatable). The race window is microseconds wide and\n" @@ -549,7 +555,8 @@ static skeletonkey_result_t af_unix_gc_exploit_linux(const struct skeletonkey_ct fprintf(stderr, "[-] af_unix_gc: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] af_unix_gc: already root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c b/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c index f41b75a..c6a7412 100644 --- a/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c +++ b/modules/cgroup_release_agent_cve_2022_0492/skeletonkey_modules.c @@ -49,6 +49,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include #include #include @@ -74,44 +75,40 @@ static const struct kernel_range cgroup_ra_range = { sizeof(cgroup_ra_patched_branches[0]), }; -static int can_unshare_userns_mount(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} +/* The unprivileged-userns precondition is now read from the shared + * host fingerprint (ctx->host->unprivileged_userns_allowed), which + * probes once at startup via core/host.c. The previous per-detect + * fork-probe helper was removed. */ static skeletonkey_result_t cgroup_ra_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] cgroup_release_agent: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] cgroup_release_agent: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } - bool patched = kernel_range_is_patched(&cgroup_ra_range, &v); + bool patched = kernel_range_is_patched(&cgroup_ra_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] cgroup_release_agent: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] cgroup_release_agent: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns_mount(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { - fprintf(stderr, "[i] cgroup_release_agent: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] cgroup_release_agent: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] cgroup_release_agent: user_ns+mount_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] cgroup_release_agent: user_ns denied → unprivileged exploit unreachable\n"); } @@ -157,7 +154,10 @@ static skeletonkey_result_t cgroup_ra_exploit(const struct skeletonkey_ctx *ctx) fprintf(stderr, "[-] cgroup_release_agent: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + /* Consult ctx->host->is_root so unit tests can construct a + * non-root fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] cgroup_release_agent: already root\n"); return SKELETONKEY_OK; } diff --git a/modules/cls_route4_cve_2022_2588/skeletonkey_modules.c b/modules/cls_route4_cve_2022_2588/skeletonkey_modules.c index 0cc6ad6..d437220 100644 --- a/modules/cls_route4_cve_2022_2588/skeletonkey_modules.c +++ b/modules/cls_route4_cve_2022_2588/skeletonkey_modules.c @@ -51,6 +51,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -97,55 +98,46 @@ static bool cls_route4_module_available(void) return found; } -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] cls_route4: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] cls_route4: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug-introduction predates anything we'd reasonably scan; if the * kernel is below the oldest LTS we model (5.4), still report * vulnerable. */ - bool patched = kernel_range_is_patched(&cls_route4_range, &v); + bool patched = kernel_range_is_patched(&cls_route4_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } /* Module + userns preconditions. */ bool nft_loaded = cls_route4_module_available(); - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { - fprintf(stderr, "[i] cls_route4: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] cls_route4: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] cls_route4: cls_route4 module currently loaded: %s\n", nft_loaded ? "yes" : "no (may autoload)"); fprintf(stderr, "[i] cls_route4: unprivileged user_ns + net_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } /* If userns is locked down, unprivileged-LPE path is closed. * Kernel still needs patching though — report PRECOND_FAIL so the * verdict isn't "VULNERABLE" but the issue isn't masked. */ - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n"); } @@ -555,7 +547,8 @@ static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx fprintf(stderr, "[-] cls_route4: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] cls_route4: already root\n"); return SKELETONKEY_OK; } diff --git a/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c b/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c index 222e133..978795d 100644 --- a/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c +++ b/modules/dirty_cow_cve_2016_5195/skeletonkey_modules.c @@ -53,6 +53,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include #include #include @@ -231,22 +232,27 @@ static void revert_passwd_page_cache(void) static skeletonkey_result_t dirty_cow_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] dirty_cow: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] dirty_cow: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } - bool patched = kernel_range_is_patched(&dirty_cow_range, &v); + bool patched = kernel_range_is_patched(&dirty_cow_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] dirty_cow: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] dirty_cow: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[!] dirty_cow: kernel %s is in the vulnerable range\n", - v.release); + v->release); fprintf(stderr, "[i] dirty_cow: --exploit will race a write to " "/etc/passwd via /proc/self/mem\n"); } @@ -261,7 +267,10 @@ static skeletonkey_result_t dirty_cow_exploit(const struct skeletonkey_ctx *ctx) return pre; } - if (geteuid() == 0) { + /* Consult ctx->host->is_root so unit tests can construct a + * non-root fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] dirty_cow: already root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c b/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c index f49ab4f..c61eee7 100644 --- a/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c +++ b/modules/dirty_pipe_cve_2022_0847/skeletonkey_modules.c @@ -45,6 +45,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" /* used inside this block only */ +#include "../../core/host.h" #include #include #include @@ -257,22 +258,27 @@ static int dirty_pipe_active_probe(void) static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] dirty_pipe: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] dirty_pipe: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.8. */ - if (v.major < 5 || (v.major == 5 && v.minor < 8)) { + if (v->major < 5 || (v->major == 5 && v->minor < 8)) { if (!ctx->json) { fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n", - v.release); + v->release); } return SKELETONKEY_OK; } - bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, &v); + bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, v); /* Active probe overrides version-only verdict when requested. * The version check is necessary-but-not-sufficient: distros @@ -287,7 +293,7 @@ static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx) if (probe == 1) { if (!ctx->json) { fprintf(stderr, "[!] dirty_pipe: ACTIVE PROBE CONFIRMED — primitive lands " - "(version %s)\n", v.release); + "(version %s)\n", v->release); } return SKELETONKEY_VULNERABLE; } @@ -310,14 +316,14 @@ static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx) if (patched_by_version) { if (!ctx->json) { fprintf(stderr, "[+] dirty_pipe: kernel %s is patched (version-only check; " - "use --active to confirm empirically)\n", v.release); + "use --active to confirm empirically)\n", v->release); } return SKELETONKEY_OK; } if (!ctx->json) { fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE (version-only check)\n" " Confirm empirically: re-run with --scan --active\n", - v.release); + v->release); } return SKELETONKEY_VULNERABLE; } @@ -331,17 +337,20 @@ static skeletonkey_result_t dirty_pipe_exploit(const struct skeletonkey_ctx *ctx return pre; } - /* Resolve current user. */ + /* Resolve current user. Consult ctx->host->is_root for the + * already-root short-circuit so unit tests can construct a + * non-root fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { + fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n"); + return SKELETONKEY_OK; + } uid_t euid = geteuid(); struct passwd *pw = getpwuid(euid); if (!pw) { fprintf(stderr, "[-] dirty_pipe: getpwuid(%d) failed: %s\n", euid, strerror(errno)); return SKELETONKEY_TEST_ERROR; } - if (euid == 0) { - fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n"); - return SKELETONKEY_OK; - } /* Find the UID field. Need a 4-digit-or-similar UID we can replace * with "0000" of identical width. Refuse if the user's UID width diff --git a/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c b/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c index 44093de..7b1e01b 100644 --- a/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c +++ b/modules/fuse_legacy_cve_2022_0185/skeletonkey_modules.c @@ -69,6 +69,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -158,57 +159,53 @@ static const struct kernel_range fuse_legacy_range = { sizeof(fuse_legacy_patched_branches[0]), }; -static int can_unshare_userns_mount(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - /* ------------------------------------------------------------------ */ /* detect */ /* ------------------------------------------------------------------ */ static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] fuse_legacy: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] fuse_legacy: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1 * kernels predate the code path entirely. */ - if (v.major < 5 || (v.major == 5 && v.minor < 1)) { + if (v->major < 5 || (v->major == 5 && v->minor < 1)) { if (!ctx->json) { fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n", - v.release); + v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&fuse_legacy_range, &v); + bool patched = kernel_range_is_patched(&fuse_legacy_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns_mount(); + /* user_ns availability comes from the shared host fingerprint. The + * fingerprint's probe uses CLONE_NEWUSER alone; this module also + * needs CLONE_NEWNS, but the kernel gates both on the same userns + * sysctls (kernel.unprivileged_userns_clone / AppArmor restriction), + * so the userns probe is a sound proxy. */ + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { - fprintf(stderr, "[i] fuse_legacy: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] fuse_legacy: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] fuse_legacy: user_ns+mount_ns clone (CAP_SYS_ADMIN gate): %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] fuse_legacy: user_ns denied → " "unprivileged exploit unreachable\n"); @@ -521,8 +518,11 @@ static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ct return pre; } - /* (R2) Refuse if already root — no LPE work to do. */ - if (geteuid() == 0) { + /* (R2) Refuse if already root — no LPE work to do. Consult + * ctx->host first so unit tests can construct a non-root + * fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { if (!ctx->json) { fprintf(stderr, "[i] fuse_legacy: already root; nothing to escalate\n"); } diff --git a/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c b/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c index 00ffe83..3fee2d9 100644 --- a/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c +++ b/modules/netfilter_xtcompat_cve_2021_22555/skeletonkey_modules.c @@ -68,6 +68,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -116,53 +117,44 @@ static const struct kernel_range netfilter_xtcompat_range = { /* ---- Detect ------------------------------------------------------- */ -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] netfilter_xtcompat: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] netfilter_xtcompat: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } - if (v.major < 2 || (v.major == 2 && v.minor < 6)) { + if (v->major < 2 || (v->major == 2 && v->minor < 6)) { if (!ctx->json) { fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n", - v.release); + v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, &v); + bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { fprintf(stderr, "[i] netfilter_xtcompat: kernel %s in vulnerable range " - "(bug existed since 2.6.19, 2006)\n", v.release); + "(bug existed since 2.6.19, 2006)\n", v->release); fprintf(stderr, "[i] netfilter_xtcompat: user_ns+net_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] netfilter_xtcompat: user_ns denied → " "unprivileged exploit path unreachable\n"); @@ -613,7 +605,10 @@ static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_ { /* 1. Refuse-gate: re-confirm vulnerability through detect(). */ skeletonkey_result_t pre = netfilter_xtcompat_detect(ctx); - if (pre == SKELETONKEY_OK && geteuid() == 0) { + /* Consult ctx->host first so unit tests can construct a non-root + * fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (pre == SKELETONKEY_OK && is_root) { fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n"); return SKELETONKEY_OK; } @@ -621,7 +616,7 @@ static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_ fprintf(stderr, "[-] netfilter_xtcompat: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + if (is_root) { fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/nf_tables_cve_2024_1086/skeletonkey_modules.c b/modules/nf_tables_cve_2024_1086/skeletonkey_modules.c index 57f5f46..9763c20 100644 --- a/modules/nf_tables_cve_2024_1086/skeletonkey_modules.c +++ b/modules/nf_tables_cve_2024_1086/skeletonkey_modules.c @@ -67,6 +67,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include "../../core/offsets.h" #include "../../core/finisher.h" @@ -112,19 +113,6 @@ static const struct kernel_range nf_tables_range = { * Preconditions probe * ------------------------------------------------------------------ */ -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static bool nf_tables_loaded(void) { FILE *f = fopen("/proc/modules", "r"); @@ -140,44 +128,47 @@ static bool nf_tables_loaded(void) static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] nf_tables: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] nf_tables: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.14. Anything below predates it. */ - if (v.major < 5 || (v.major == 5 && v.minor < 14)) { + if (v->major < 5 || (v->major == 5 && v->minor < 14)) { if (!ctx->json) { fprintf(stderr, "[i] nf_tables: kernel %s predates the bug " - "(introduced in 5.14)\n", v.release); + "(introduced in 5.14)\n", v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&nf_tables_range, &v); + bool patched = kernel_range_is_patched(&nf_tables_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; bool nft_loaded = nf_tables_loaded(); if (!ctx->json) { fprintf(stderr, "[i] nf_tables: kernel %s is in the vulnerable range\n", - v.release); + v->release); fprintf(stderr, "[i] nf_tables: unprivileged user_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : - "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); fprintf(stderr, "[i] nf_tables: nf_tables module currently loaded: %s\n", nft_loaded ? "yes" : "no (will autoload on first nft use)"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] nf_tables: kernel vulnerable but user_ns clone " "denied → unprivileged exploit unreachable\n"); @@ -809,8 +800,11 @@ static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx) return pre; } - /* Gate 2: already root? Nothing to escalate. */ - if (geteuid() == 0) { + /* Gate 2: already root? Nothing to escalate. Consult ctx->host first + * so unit tests can construct a non-root fingerprint regardless of + * the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { if (!ctx->json) fprintf(stderr, "[i] nf_tables: already running as root\n"); return SKELETONKEY_OK; diff --git a/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c b/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c index de57e70..8e13f12 100644 --- a/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c +++ b/modules/nft_fwd_dup_cve_2022_25636/skeletonkey_modules.c @@ -55,6 +55,7 @@ #include "../../core/kernel_range.h" #include "../../core/offsets.h" #include "../../core/finisher.h" +#include "../../core/host.h" #include #include @@ -103,19 +104,6 @@ static const struct kernel_range nft_fwd_dup_range = { * Probes. * ------------------------------------------------------------------ */ -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static bool nf_tables_loaded(void) { FILE *f = fopen("/proc/modules", "r"); @@ -131,45 +119,43 @@ static bool nf_tables_loaded(void) static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] nft_fwd_dup: could not parse kernel version\n"); + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) fprintf(stderr, "[!] nft_fwd_dup: host fingerprint missing kernel version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* The offload code path only exists from 5.4 onward. Anything * older predates the bug. */ - if (v.major < 5 || (v.major == 5 && v.minor < 4)) { + if (v->major < 5 || (v->major == 5 && v->minor < 4)) { if (!ctx->json) { fprintf(stderr, "[i] nft_fwd_dup: kernel %s predates the bug " - "(nft offload hook introduced in 5.4)\n", v.release); + "(nft offload hook introduced in 5.4)\n", v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&nft_fwd_dup_range, &v); + bool patched = kernel_range_is_patched(&nft_fwd_dup_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] nft_fwd_dup: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] nft_fwd_dup: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host->unprivileged_userns_allowed; bool nft_loaded = nf_tables_loaded(); if (!ctx->json) { fprintf(stderr, "[i] nft_fwd_dup: kernel %s is in the vulnerable range\n", - v.release); + v->release); fprintf(stderr, "[i] nft_fwd_dup: unprivileged user_ns+net_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : - "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); fprintf(stderr, "[i] nft_fwd_dup: nf_tables module currently loaded: %s\n", nft_loaded ? "yes" : "no (will autoload)"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] nft_fwd_dup: kernel vulnerable but user_ns clone " "denied → unprivileged path unreachable\n"); @@ -733,7 +719,8 @@ static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ct return SKELETONKEY_PRECOND_FAIL; } /* Gate 1: already root? */ - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { if (!ctx->json) fprintf(stderr, "[i] nft_fwd_dup: already running as root\n"); return SKELETONKEY_OK; diff --git a/modules/nft_payload_cve_2023_0179/skeletonkey_modules.c b/modules/nft_payload_cve_2023_0179/skeletonkey_modules.c index 09857b2..25d1491 100644 --- a/modules/nft_payload_cve_2023_0179/skeletonkey_modules.c +++ b/modules/nft_payload_cve_2023_0179/skeletonkey_modules.c @@ -61,6 +61,7 @@ #include "../../core/kernel_range.h" #include "../../core/offsets.h" #include "../../core/finisher.h" +#include "../../core/host.h" #include #include @@ -104,19 +105,6 @@ static const struct kernel_range nft_payload_range = { * Preconditions probe * ------------------------------------------------------------------ */ -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static bool nf_tables_loaded(void) { FILE *f = fopen("/proc/modules", "r"); @@ -132,46 +120,44 @@ static bool nf_tables_loaded(void) static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] nft_payload: could not parse kernel version\n"); + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) fprintf(stderr, "[!] nft_payload: host fingerprint missing kernel version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced with the set-payload extension in 5.4. Anything * below 5.4 predates the affected codepath entirely. */ - if (v.major < 5 || (v.major == 5 && v.minor < 4)) { + if (v->major < 5 || (v->major == 5 && v->minor < 4)) { if (!ctx->json) { fprintf(stderr, "[i] nft_payload: kernel %s predates the bug " "(set-payload extension landed in 5.4)\n", - v.release); + v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&nft_payload_range, &v); + bool patched = kernel_range_is_patched(&nft_payload_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host->unprivileged_userns_allowed; bool nft_loaded = nf_tables_loaded(); if (!ctx->json) { fprintf(stderr, "[i] nft_payload: kernel %s is in the vulnerable range\n", - v.release); + v->release); fprintf(stderr, "[i] nft_payload: unprivileged user_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : - "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); fprintf(stderr, "[i] nft_payload: nf_tables module currently loaded: %s\n", nft_loaded ? "yes" : "no (will autoload on first nft use)"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] nft_payload: kernel vulnerable but user_ns " "clone denied → unprivileged exploit unreachable\n"); @@ -811,7 +797,8 @@ static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ct "exploit code can crash the kernel\n"); return SKELETONKEY_PRECOND_FAIL; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { if (!ctx->json) fprintf(stderr, "[i] nft_payload: already running as root\n"); return SKELETONKEY_OK; diff --git a/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c b/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c index ce02766..fc30ade 100644 --- a/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c +++ b/modules/nft_set_uaf_cve_2023_32233/skeletonkey_modules.c @@ -50,6 +50,7 @@ #include "skeletonkey_modules.h" #include "../../core/registry.h" #include "../../core/kernel_range.h" +#include "../../core/host.h" #include #include @@ -115,19 +116,6 @@ static const struct kernel_range nft_set_uaf_range = { * ------------------------------------------------------------------ */ #ifdef __linux__ -static int can_unshare_userns(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - static bool nf_tables_loaded(void) { FILE *f = fopen("/proc/modules", "r"); @@ -148,45 +136,43 @@ static skeletonkey_result_t nft_set_uaf_detect(const struct skeletonkey_ctx *ctx (void)ctx; return SKELETONKEY_PRECOND_FAIL; #else - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] nft_set_uaf: could not parse kernel version\n"); + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) fprintf(stderr, "[!] nft_set_uaf: host fingerprint missing kernel version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.1 (anonymous-set support). Anything below * predates it — report OK (not vulnerable to *this* CVE). */ - if (v.major < 5 || (v.major == 5 && v.minor < 1)) { + if (v->major < 5 || (v->major == 5 && v->minor < 1)) { if (!ctx->json) { fprintf(stderr, "[i] nft_set_uaf: kernel %s predates the bug " - "(anonymous-set support landed in 5.1)\n", v.release); + "(anonymous-set support landed in 5.1)\n", v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&nft_set_uaf_range, &v); + bool patched = kernel_range_is_patched(&nft_set_uaf_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] nft_set_uaf: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] nft_set_uaf: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns(); + bool userns_ok = ctx->host->unprivileged_userns_allowed; bool nft_loaded = nf_tables_loaded(); if (!ctx->json) { fprintf(stderr, "[i] nft_set_uaf: kernel %s is in the vulnerable range\n", - v.release); + v->release); fprintf(stderr, "[i] nft_set_uaf: unprivileged user_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : - "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); fprintf(stderr, "[i] nft_set_uaf: nf_tables module currently loaded: %s\n", nft_loaded ? "yes" : "no (will autoload on first nft use)"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] nft_set_uaf: kernel vulnerable but user_ns clone " "denied → unprivileged exploit unreachable\n"); @@ -762,7 +748,8 @@ static skeletonkey_result_t nft_set_uaf_exploit(const struct skeletonkey_ctx *ct fprintf(stderr, "[-] nft_set_uaf: refusing without --i-know gate\n"); return SKELETONKEY_EXPLOIT_FAIL; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { if (!ctx->json) fprintf(stderr, "[i] nft_set_uaf: already running as root\n"); return SKELETONKEY_OK; diff --git a/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c b/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c index e3ada98..a2ba0f4 100644 --- a/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c +++ b/modules/overlayfs_setuid_cve_2023_0386/skeletonkey_modules.c @@ -50,6 +50,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include #include #include @@ -71,18 +72,10 @@ static const struct kernel_range overlayfs_setuid_range = { sizeof(overlayfs_setuid_patched_branches[0]), }; -static int can_unshare_userns_mount(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0); - _exit(1); - } - int status; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} +/* The unprivileged-userns precondition is now read from the shared + * host fingerprint (ctx->host->unprivileged_userns_allowed), which + * probes once at startup via core/host.c. The previous per-detect + * fork-probe helper was removed. */ static const char *find_setuid_in_lower(void) { @@ -101,39 +94,43 @@ static const char *find_setuid_in_lower(void) static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] overlayfs_setuid: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] overlayfs_setuid: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 5.11 when ovl copy-up was generalized. * Pre-5.11 immune via a different code path. */ - if (v.major < 5 || (v.major == 5 && v.minor < 11)) { + if (v->major < 5 || (v->major == 5 && v->minor < 11)) { if (!ctx->json) { fprintf(stderr, "[+] overlayfs_setuid: kernel %s predates the bug " - "(introduced in 5.11)\n", v.release); + "(introduced in 5.11)\n", v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&overlayfs_setuid_range, &v); + bool patched = kernel_range_is_patched(&overlayfs_setuid_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] overlayfs_setuid: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] overlayfs_setuid: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns_mount(); + bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false; if (!ctx->json) { - fprintf(stderr, "[i] overlayfs_setuid: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] overlayfs_setuid: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] overlayfs_setuid: user_ns+mount_ns clone: %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] overlayfs_setuid: user_ns denied → unprivileged exploit unreachable\n"); } @@ -200,7 +197,10 @@ static skeletonkey_result_t overlayfs_setuid_exploit(const struct skeletonkey_ct fprintf(stderr, "[-] overlayfs_setuid: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + /* Consult ctx->host->is_root so unit tests can construct a + * non-root fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] overlayfs_setuid: already root\n"); return SKELETONKEY_OK; } diff --git a/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c b/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c index 52defd6..6071b9c 100644 --- a/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c +++ b/modules/ptrace_traceme_cve_2019_13272/skeletonkey_modules.c @@ -38,6 +38,7 @@ #ifdef __linux__ #include "../../core/kernel_range.h" +#include "../../core/host.h" #include #include #include @@ -66,32 +67,37 @@ static const struct kernel_range ptrace_traceme_range = { static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] ptrace_traceme: could not parse kernel version\n"); + /* Consult the shared host fingerprint instead of calling + * kernel_version_current() ourselves — populated once at startup + * and identical across every module's detect(). */ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] ptrace_traceme: host fingerprint missing kernel " + "version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug existed since ptrace's inception (early 2.x); anything * pre-LTS-backport is vulnerable. Anything < 4.4 in our range * model defaults to vulnerable since no entry covers it. */ - if (v.major < 4 || (v.major == 4 && v.minor < 4)) { + if (v->major < 4 || (v->major == 4 && v->minor < 4)) { if (!ctx->json) { fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n", - v.release); + v->release); } return SKELETONKEY_VULNERABLE; } - bool patched = kernel_range_is_patched(&ptrace_traceme_range, &v); + bool patched = kernel_range_is_patched(&ptrace_traceme_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } if (!ctx->json) { - fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] ptrace_traceme: no exotic preconditions — works on default config " "(no user_ns required)\n"); } @@ -186,7 +192,10 @@ static skeletonkey_result_t ptrace_traceme_exploit(const struct skeletonkey_ctx fprintf(stderr, "[-] ptrace_traceme: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + /* Consult ctx->host->is_root so unit tests can construct a + * non-root fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] ptrace_traceme: already root\n"); return SKELETONKEY_OK; } diff --git a/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c index b2dc685..31a1fca 100644 --- a/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c +++ b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c @@ -23,6 +23,7 @@ #include "skeletonkey_modules.h" #include "../../core/registry.h" +#include "../../core/host.h" #include #include @@ -215,7 +216,10 @@ static skeletonkey_result_t pwnkit_exploit(const struct skeletonkey_ctx *ctx) const char *pkexec = find_pkexec(); if (!pkexec) return SKELETONKEY_PRECOND_FAIL; - if (geteuid() == 0) { + /* Consult ctx->host->is_root so unit tests can construct a + * non-root fingerprint regardless of the test process's real euid. */ + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] pwnkit: already root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/sequoia_cve_2021_33909/skeletonkey_modules.c b/modules/sequoia_cve_2021_33909/skeletonkey_modules.c index 0d6ecd6..5ece9f1 100644 --- a/modules/sequoia_cve_2021_33909/skeletonkey_modules.c +++ b/modules/sequoia_cve_2021_33909/skeletonkey_modules.c @@ -90,6 +90,7 @@ #include "../../core/kernel_range.h" #include "../../core/offsets.h" #include "../../core/finisher.h" +#include "../../core/host.h" #include #include @@ -166,25 +167,6 @@ static bool write_file(const char *path, const char *s) return n == (ssize_t)strlen(s); } -/* Probe: can this user unshare(CLONE_NEWUSER|CLONE_NEWNS) and get - * CAP_SYS_ADMIN-in-userns? We need this for the bind-mount step. The - * deeply-nested mkdir works without it, but the trigger needs the - * extra mountinfo entry to push the rendered string past INT_MAX. */ -static int can_unshare_userns_mount(void) -{ - pid_t pid = fork(); - if (pid < 0) return -1; - if (pid == 0) { -#ifdef __linux__ - if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0); -#endif - _exit(1); - } - int status = 0; - waitpid(pid, &status, 0); - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - #ifdef __linux__ static bool enter_userns_root(void) { @@ -215,31 +197,30 @@ static bool enter_userns_root(void) static skeletonkey_result_t sequoia_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] sequoia: could not parse kernel version\n"); + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) fprintf(stderr, "[!] sequoia: host fingerprint missing kernel version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* The bug predates every kernel we'd run on, so there's no * "pre-introduction" cutoff; only patched-or-not matters. */ - bool patched = kernel_range_is_patched(&sequoia_range, &v); + bool patched = kernel_range_is_patched(&sequoia_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] sequoia: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] sequoia: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } - int userns_ok = can_unshare_userns_mount(); + bool userns_ok = ctx->host->unprivileged_userns_allowed; if (!ctx->json) { - fprintf(stderr, "[i] sequoia: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] sequoia: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] sequoia: user_ns+mount_ns clone (CAP_SYS_ADMIN gate): %s\n", - userns_ok == 1 ? "ALLOWED" : - userns_ok == 0 ? "DENIED" : "could not test"); + userns_ok ? "ALLOWED" : "DENIED"); } - if (userns_ok == 0) { + if (!userns_ok) { if (!ctx->json) { fprintf(stderr, "[+] sequoia: user_ns denied → unprivileged " "exploit unreachable via bind-mount path\n"); @@ -408,7 +389,8 @@ static skeletonkey_result_t sequoia_exploit_linux(const struct skeletonkey_ctx * } /* (R1) refuse if already root. */ - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { if (!ctx->json) { fprintf(stderr, "[i] sequoia: already root — nothing to escalate\n"); } diff --git a/modules/stackrot_cve_2023_3269/skeletonkey_modules.c b/modules/stackrot_cve_2023_3269/skeletonkey_modules.c index 637b932..ebfb657 100644 --- a/modules/stackrot_cve_2023_3269/skeletonkey_modules.c +++ b/modules/stackrot_cve_2023_3269/skeletonkey_modules.c @@ -72,6 +72,7 @@ #include "../../core/kernel_range.h" #include "../../core/offsets.h" #include "../../core/finisher.h" +#include "../../core/host.h" #include #include @@ -150,31 +151,31 @@ static bool maple_tree_variant_present(const struct kernel_version *v) static skeletonkey_result_t stackrot_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] stackrot: could not parse kernel version\n"); + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) fprintf(stderr, "[!] stackrot: host fingerprint missing kernel version — bailing\n"); return SKELETONKEY_TEST_ERROR; } /* Bug introduced in 6.1 (when maple tree landed). Pre-6.1 kernels * use rbtree-based VMAs and don't have this bug. */ - if (v.major < 6 || (v.major == 6 && v.minor < 1)) { + if (v->major < 6 || (v->major == 6 && v->minor < 1)) { if (!ctx->json) { fprintf(stderr, "[+] stackrot: kernel %s predates maple-tree VMA code (introduced in 6.1)\n", - v.release); + v->release); } return SKELETONKEY_OK; } - bool patched = kernel_range_is_patched(&stackrot_range, &v); + bool patched = kernel_range_is_patched(&stackrot_range, v); if (patched) { if (!ctx->json) { - fprintf(stderr, "[+] stackrot: kernel %s is patched\n", v.release); + fprintf(stderr, "[+] stackrot: kernel %s is patched\n", v->release); } return SKELETONKEY_OK; } if (!ctx->json) { - fprintf(stderr, "[!] stackrot: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[!] stackrot: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] stackrot: mm-class bug — affects default-config kernels; " "no exotic preconditions\n"); } @@ -631,7 +632,8 @@ static skeletonkey_result_t stackrot_exploit_linux(const struct skeletonkey_ctx fprintf(stderr, "[-] stackrot: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] stackrot: already root — nothing to escalate\n"); return SKELETONKEY_OK; } @@ -641,8 +643,8 @@ static skeletonkey_result_t stackrot_exploit_linux(const struct skeletonkey_ctx return SKELETONKEY_PRECOND_FAIL; } { - struct kernel_version v; - if (!kernel_version_current(&v) || !maple_tree_variant_present(&v)) { + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0 || !maple_tree_variant_present(v)) { fprintf(stderr, "[-] stackrot: maple-tree variant not detectable\n"); return SKELETONKEY_PRECOND_FAIL; } diff --git a/modules/sudo_samedit_cve_2021_3156/skeletonkey_modules.c b/modules/sudo_samedit_cve_2021_3156/skeletonkey_modules.c index d2ebba2..d5a296e 100644 --- a/modules/sudo_samedit_cve_2021_3156/skeletonkey_modules.c +++ b/modules/sudo_samedit_cve_2021_3156/skeletonkey_modules.c @@ -33,6 +33,7 @@ #include "skeletonkey_modules.h" #include "../../core/registry.h" +#include "../../core/host.h" #include #include @@ -246,7 +247,8 @@ static skeletonkey_result_t sudo_samedit_exploit(const struct skeletonkey_ctx *c return SKELETONKEY_PRECOND_FAIL; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] sudo_samedit: already root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c b/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c index ceec158..f26b0a8 100644 --- a/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c +++ b/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c @@ -29,6 +29,7 @@ #include "skeletonkey_modules.h" #include "../../core/registry.h" +#include "../../core/host.h" #include #include @@ -331,7 +332,8 @@ static skeletonkey_result_t sudoedit_editor_exploit(const struct skeletonkey_ctx fprintf(stderr, "[-] sudoedit_editor: refusing exploit — pass --i-know to authorize\n"); return SKELETONKEY_PRECOND_FAIL; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] sudoedit_editor: already root — nothing to escalate\n"); return SKELETONKEY_OK; } diff --git a/modules/vmwgfx_cve_2023_2008/skeletonkey_modules.c b/modules/vmwgfx_cve_2023_2008/skeletonkey_modules.c index 335dac5..5c62d0f 100644 --- a/modules/vmwgfx_cve_2023_2008/skeletonkey_modules.c +++ b/modules/vmwgfx_cve_2023_2008/skeletonkey_modules.c @@ -36,6 +36,7 @@ #include "../../core/kernel_range.h" #include "../../core/offsets.h" #include "../../core/finisher.h" +#include "../../core/host.h" #include #include @@ -219,26 +220,26 @@ static char *probe_drm_version_name(const char *cardpath) static skeletonkey_result_t vmwgfx_detect(const struct skeletonkey_ctx *ctx) { - struct kernel_version v; - if (!kernel_version_current(&v)) { - fprintf(stderr, "[!] vmwgfx: could not parse kernel version\n"); + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) fprintf(stderr, "[!] vmwgfx: host fingerprint missing kernel version — bailing\n"); return SKELETONKEY_TEST_ERROR; } - bool patched = kernel_range_is_patched(&vmwgfx_range, &v); + bool patched = kernel_range_is_patched(&vmwgfx_range, v); if (patched) { if (!ctx->json) { fprintf(stderr, "[+] vmwgfx: kernel %s is patched (>= 6.3-rc6 / " - "6.2.10 / 6.1.23)\n", v.release); + "6.2.10 / 6.1.23)\n", v->release); } return SKELETONKEY_OK; } /* Pre-vmwgfx kernels (no driver shipped) — extremely unlikely but * report PRECOND_FAIL rather than VULNERABLE. */ - if (v.major < 4) { + if (v->major < 4) { if (!ctx->json) { - fprintf(stderr, "[+] vmwgfx: kernel %s predates vmwgfx driver\n", v.release); + fprintf(stderr, "[+] vmwgfx: kernel %s predates vmwgfx driver\n", v->release); } return SKELETONKEY_PRECOND_FAIL; } @@ -247,7 +248,7 @@ static skeletonkey_result_t vmwgfx_detect(const struct skeletonkey_ctx *ctx) char vendor[128] = {0}; bool vmware = host_is_vmware_guest(vendor, sizeof vendor); if (!ctx->json) { - fprintf(stderr, "[i] vmwgfx: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] vmwgfx: kernel %s in vulnerable range\n", v->release); fprintf(stderr, "[i] vmwgfx: dmi sys_vendor = \"%s\"\n", vendor[0] ? vendor : "(unreadable)"); } @@ -520,7 +521,8 @@ static skeletonkey_result_t vmwgfx_exploit_linux(const struct skeletonkey_ctx *c fprintf(stderr, "[-] vmwgfx: detect() says not vulnerable; refusing\n"); return pre; } - if (geteuid() == 0) { + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { fprintf(stderr, "[i] vmwgfx: already root — nothing to escalate\n"); return SKELETONKEY_OK; }