From 150f16bc970fa394a5e4c2ff89f218b9c4ed499b Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Sat, 23 May 2026 00:15:01 -0400 Subject: [PATCH] pwnkit + sudoedit_editor: ctx->host migration + 4 more tests (39 total) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pwnkit: migrate detect() to consult ctx->host->polkit_version with the same graceful-fallback pattern as the sudo modules. The version is populated once at startup by core/host.c (via pkexec --version); detect() skips the per-scan popen when the host fingerprint has the version. Falls back to the inline popen path when ctx->host is missing the version (degenerate test contexts). sudoedit_editor: already migrated; this commit adds direct test coverage. tests/test_detect.c expansion (35 → 39): - pwnkit: polkit_version='0.105' -> VULNERABLE (pre-0.121 fix) - pwnkit: polkit_version='0.121' -> OK (fix release) - sudoedit_editor: vuln sudo + no sudoers grant -> PRECOND_FAIL (documented behaviour: vulnerable version, but the dispatcher has no usable sudoedit grant on the host) - sudoedit_editor: fixed sudo (1.9.13p1) -> OK The sudoedit_editor 'vuln + no grant' case is the first test to exercise the second-level precondition gate AFTER the version check passes — proves the version-pinned detect logic AND the sudo -ln target-discovery short-circuit both work as intended. The h_vuln_sudo / h_fixed_sudo synthetic fingerprints gained the .polkit_version field alongside .sudo_version so a single fingerprint exercises both pwnkit and the sudo modules. Verification: 39/39 pass on Linux (docker gcc:latest + libglib2.0-dev + sudo, non-root user skeletonkeyci). macOS dev box still reports 'skipped — Linux-only' as designed. --- .../skeletonkey_modules.c | 80 +++++++++++-------- tests/test_detect.c | 35 +++++++- 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c index 31a1fca..ac973fb 100644 --- a/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c +++ b/modules/pwnkit_cve_2021_4034/skeletonkey_modules.c @@ -77,44 +77,58 @@ static bool pkexec_version_vulnerable(const char *version_str) static skeletonkey_result_t pwnkit_detect(const struct skeletonkey_ctx *ctx) { - const char *pkexec_path = find_pkexec(); - if (!pkexec_path) { + /* Prefer the centrally-fingerprinted polkit version (populated + * once at startup by core/host.c via `pkexec --version`). Saves + * a popen per scan and lets unit tests construct synthetic + * polkit_version values. Fall back to the local popen if + * ctx->host is missing the version (degenerate test ctx or a + * future refactor that disables userspace probing). */ + char vp_buf[64] = {0}; + const char *vp = NULL; + + if (ctx->host && ctx->host->polkit_version[0]) { + snprintf(vp_buf, sizeof vp_buf, "%s", ctx->host->polkit_version); + vp = vp_buf; if (!ctx->json) { - fprintf(stderr, "[+] pwnkit: pkexec not installed; no attack surface\n"); + fprintf(stderr, "[i] pwnkit: host fingerprint reports pkexec " + "version '%s'\n", vp); + } + } else { + const char *pkexec_path = find_pkexec(); + if (!pkexec_path) { + if (!ctx->json) { + fprintf(stderr, "[+] pwnkit: pkexec not installed; no attack surface\n"); + } + return SKELETONKEY_OK; } - return SKELETONKEY_OK; - } - if (!ctx->json) { - fprintf(stderr, "[i] pwnkit: found setuid pkexec at %s\n", pkexec_path); - } - - /* Run `pkexec --version` and parse. We pipe stderr/stdout to a - * temp file because popen() can have quoting quirks. */ - char cmd[512]; - snprintf(cmd, sizeof cmd, "%s --version 2>&1 | head -1", pkexec_path); - FILE *p = popen(cmd, "r"); - if (!p) return SKELETONKEY_TEST_ERROR; - - char line[256] = {0}; - char *r = fgets(line, sizeof line, p); - pclose(p); - if (!r) { if (!ctx->json) { - fprintf(stderr, "[?] pwnkit: could not parse pkexec --version output\n"); + fprintf(stderr, "[i] pwnkit: found setuid pkexec at %s\n", pkexec_path); } - return SKELETONKEY_TEST_ERROR; - } - - /* Output format: "pkexec version 0.105\n" or "pkexec version 0.120-..." */ - char *vp = strstr(line, "version"); - if (!vp) return SKELETONKEY_TEST_ERROR; - vp += strlen("version"); - while (*vp == ' ' || *vp == '\t') vp++; - - if (!ctx->json) { - char *nl = strchr(vp, '\n'); + char cmd[512]; + snprintf(cmd, sizeof cmd, "%s --version 2>&1 | head -1", pkexec_path); + FILE *p = popen(cmd, "r"); + if (!p) return SKELETONKEY_TEST_ERROR; + char line[256] = {0}; + char *r = fgets(line, sizeof line, p); + pclose(p); + if (!r) { + if (!ctx->json) { + fprintf(stderr, "[?] pwnkit: could not parse pkexec --version output\n"); + } + return SKELETONKEY_TEST_ERROR; + } + /* Output format: "pkexec version 0.105\n" or "pkexec version 0.120-..." */ + char *vp_mut = strstr(line, "version"); + if (!vp_mut) return SKELETONKEY_TEST_ERROR; + vp_mut += strlen("version"); + while (*vp_mut == ' ' || *vp_mut == '\t') vp_mut++; + char *nl = strchr(vp_mut, '\n'); if (nl) *nl = 0; - fprintf(stderr, "[i] pwnkit: pkexec reports version '%s'\n", vp); + snprintf(vp_buf, sizeof vp_buf, "%s", vp_mut); + vp = vp_buf; + if (!ctx->json) { + fprintf(stderr, "[i] pwnkit: pkexec reports version '%s'\n", vp); + } } bool vuln = pkexec_version_vulnerable(vp); diff --git a/tests/test_detect.c b/tests/test_detect.c index b31ad94..1b2239c 100644 --- a/tests/test_detect.c +++ b/tests/test_detect.c @@ -57,6 +57,7 @@ extern const struct skeletonkey_module dirty_frag_esp6_module; extern const struct skeletonkey_module dirty_frag_rxrpc_module; extern const struct skeletonkey_module sudo_samedit_module; extern const struct skeletonkey_module sudoedit_editor_module; +extern const struct skeletonkey_module pwnkit_module; static int g_pass = 0; static int g_fail = 0; @@ -140,8 +141,9 @@ static const struct skeletonkey_host h_fedora_no_debian = { /* Modern fingerprint with a known-vulnerable sudo (1.8.31 sits in * both the samedit [1.8.2, 1.9.5p1] and sudoedit_editor - * [1.8.0, 1.9.12p2) vulnerable ranges). Used to assert the sudo - * modules accept the host-fingerprint version string and reach the + * [1.8.0, 1.9.12p2) vulnerable ranges) AND a known-vulnerable polkit + * (0.105 is pre-0.121 fix). Used to assert the sudo/pwnkit modules + * accept the host-fingerprint version strings and reach the * VULNERABLE-by-version path. */ static const struct skeletonkey_host h_vuln_sudo = { .kernel = { .major = 5, .minor = 15, .patch = 0, @@ -153,10 +155,12 @@ static const struct skeletonkey_host h_vuln_sudo = { .is_debian_family = true, .unprivileged_userns_allowed = true, .sudo_version = "1.8.31", + .polkit_version = "0.105", }; /* Modern fingerprint with a fixed sudo (1.9.13p1 is above both - * sudo_samedit and sudoedit_editor vulnerable ranges). */ + * sudo_samedit and sudoedit_editor vulnerable ranges) AND a fixed + * polkit (0.121 is the upstream pwnkit fix release). */ static const struct skeletonkey_host h_fixed_sudo = { .kernel = { .major = 6, .minor = 12, .patch = 0, .release = "6.12.0-fixedsudo" }, @@ -167,6 +171,7 @@ static const struct skeletonkey_host h_fixed_sudo = { .is_debian_family = true, .unprivileged_userns_allowed = true, .sudo_version = "1.9.13p1", + .polkit_version = "0.121", }; /* Ubuntu 24.04, userns allowed, D-Bus running, Debian family @@ -413,6 +418,30 @@ static void run_all(void) run_one("sudo_samedit: sudo_version=1.9.13p1 → OK", &sudo_samedit_module, &h_fixed_sudo, SKELETONKEY_OK); + + /* pwnkit: vulnerable polkit 0.105 (pre-0.121 fix) → VULNERABLE */ + run_one("pwnkit: polkit_version=0.105 → VULNERABLE", + &pwnkit_module, &h_vuln_sudo, + SKELETONKEY_VULNERABLE); + + /* pwnkit: fixed polkit 0.121 → OK */ + run_one("pwnkit: polkit_version=0.121 → OK", + &pwnkit_module, &h_fixed_sudo, + SKELETONKEY_OK); + + /* sudoedit_editor: vulnerable sudo 1.8.31 — but the test user + * has no sudoers grant in the CI container, so find_sudoedit_target + * fails and detect short-circuits to PRECOND_FAIL ("vulnerable + * version present, but no sudoedit grant to abuse"). That's the + * documented behaviour for a non-privileged user. */ + run_one("sudoedit_editor: vuln version, no grant → PRECOND_FAIL", + &sudoedit_editor_module, &h_vuln_sudo, + SKELETONKEY_PRECOND_FAIL); + + /* sudoedit_editor: fixed sudo 1.9.13p1 → OK regardless of grant */ + run_one("sudoedit_editor: sudo_version=1.9.13p1 → OK", + &sudoedit_editor_module, &h_fixed_sudo, + SKELETONKEY_OK); #else fprintf(stderr, "[i] non-Linux platform: detect() bodies are stubbed; " "tests skipped (would tautologically pass).\n");