pwnkit + sudoedit_editor: ctx->host migration + 4 more tests (39 total)
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.
This commit is contained in:
@@ -77,6 +77,23 @@ static bool pkexec_version_vulnerable(const char *version_str)
|
||||
|
||||
static skeletonkey_result_t pwnkit_detect(const struct skeletonkey_ctx *ctx)
|
||||
{
|
||||
/* 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, "[i] pwnkit: host fingerprint reports pkexec "
|
||||
"version '%s'\n", vp);
|
||||
}
|
||||
} else {
|
||||
const char *pkexec_path = find_pkexec();
|
||||
if (!pkexec_path) {
|
||||
if (!ctx->json) {
|
||||
@@ -87,14 +104,10 @@ static skeletonkey_result_t pwnkit_detect(const struct skeletonkey_ctx *ctx)
|
||||
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);
|
||||
@@ -104,18 +117,19 @@ static skeletonkey_result_t pwnkit_detect(const struct skeletonkey_ctx *ctx)
|
||||
}
|
||||
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 *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;
|
||||
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);
|
||||
|
||||
|
||||
+32
-3
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user