core/host: userspace version fingerprint (sudo, polkit)
The host fingerprint now captures sudo + polkit versions at startup
so userspace-LPE modules can consult a single source of truth
instead of each popen-ing the relevant binary themselves on every
scan. Pack2theroot already queries PackageKit version via D-Bus
in-module, so PackageKit stays there for now.
core/host.h:
- new fields: char sudo_version[64], char polkit_version[64].
Empty string when the tool isn't installed or version parse fails;
modules should treat that as PRECOND_FAIL.
- documented next to has_systemd / has_dbus_system in the struct.
core/host.c:
- new populate_userspace_versions(h) called from
skeletonkey_host_get() after the other populators.
- capture_first_line() helper runs a command via popen, grabs first
stdout line, strips newline. Best-effort: failure leaves dst empty.
- extract_version_after_prefix() pulls the version token after a
fixed prefix string ('Sudo version', 'pkexec version'), handling
the colon/space variants.
- skeletonkey_host_print_banner() gained a third line when either
version is non-empty:
[*] userspace: sudo=1.9.17p2 polkit=-
Module migration (graceful fallback pattern — modules still work
without ctx->host populated):
- sudo_samedit detect: if ctx->host->sudo_version is set, skip the
popen and synthesize a 'Sudo version <X>' line for the existing
parser. Falls back to the original find_sudo + popen path if the
host fingerprint didn't capture a version.
- sudoedit_editor detect: same pattern — host fingerprint sudo_version
takes precedence over the local get_sudo_version popen.
tests/test_detect.c additions (2 new cases, 33 → 35):
- h_vuln_sudo fingerprint (sudo_version='1.8.31', kernel 5.15) —
asserts sudo_samedit reports VULNERABLE via the host-provided
version string.
- h_fixed_sudo fingerprint (sudo_version='1.9.13p1', kernel 6.12) —
asserts sudo_samedit reports OK on a patched sudo.
This is the first test pair to cover the *vulnerable* path of a
module rather than just precondition gates — proves the
version-parsing logic itself, not only the short-circuits.
Verification: 35/35 pass on Linux. macOS banner shows
'userspace: sudo=1.9.17p2 polkit=-' as the dev box has Homebrew
sudo but no polkit.
This commit is contained in:
@@ -151,30 +151,42 @@ static const char *find_sudoedit(void)
|
||||
|
||||
static skeletonkey_result_t sudo_samedit_detect(const struct skeletonkey_ctx *ctx)
|
||||
{
|
||||
const char *sudo_path = find_sudo();
|
||||
if (!sudo_path) {
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[+] sudo_samedit: sudo not on path; no attack surface\n");
|
||||
}
|
||||
return SKELETONKEY_PRECOND_FAIL;
|
||||
}
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[i] sudo_samedit: found setuid sudo at %s\n", sudo_path);
|
||||
}
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof cmd, "%s --version 2>&1 | head -1", sudo_path);
|
||||
FILE *p = popen(cmd, "r");
|
||||
if (!p) return SKELETONKEY_TEST_ERROR;
|
||||
|
||||
/* Prefer the centrally-fingerprinted sudo version (populated once
|
||||
* at startup by core/host.c) — saves a popen per scan and gives
|
||||
* unit tests a clean mock point. Fall back to the local popen if
|
||||
* ctx->host is missing the version (e.g. degenerate test ctx, or
|
||||
* a future refactor that disables userspace probing). */
|
||||
char line[256] = {0};
|
||||
char *r = fgets(line, sizeof line, p);
|
||||
pclose(p);
|
||||
if (!r) {
|
||||
if (ctx->host && ctx->host->sudo_version[0]) {
|
||||
snprintf(line, sizeof line, "Sudo version %s",
|
||||
ctx->host->sudo_version);
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[?] sudo_samedit: could not read `sudo --version` output\n");
|
||||
fprintf(stderr, "[i] sudo_samedit: host fingerprint reports "
|
||||
"sudo version %s\n", ctx->host->sudo_version);
|
||||
}
|
||||
} else {
|
||||
const char *sudo_path = find_sudo();
|
||||
if (!sudo_path) {
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[+] sudo_samedit: sudo not on path; no attack surface\n");
|
||||
}
|
||||
return SKELETONKEY_PRECOND_FAIL;
|
||||
}
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[i] sudo_samedit: found setuid sudo at %s\n", sudo_path);
|
||||
}
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof cmd, "%s --version 2>&1 | head -1", sudo_path);
|
||||
FILE *p = popen(cmd, "r");
|
||||
if (!p) return SKELETONKEY_TEST_ERROR;
|
||||
char *r = fgets(line, sizeof line, p);
|
||||
pclose(p);
|
||||
if (!r) {
|
||||
if (!ctx->json) {
|
||||
fprintf(stderr, "[?] sudo_samedit: could not read `sudo --version` output\n");
|
||||
}
|
||||
return SKELETONKEY_TEST_ERROR;
|
||||
}
|
||||
return SKELETONKEY_TEST_ERROR;
|
||||
}
|
||||
|
||||
/* Trim newline for nicer logging. */
|
||||
|
||||
@@ -210,7 +210,13 @@ static skeletonkey_result_t sudoedit_editor_detect(const struct skeletonkey_ctx
|
||||
fprintf(stderr, "[i] sudoedit_editor: sudoedit at %s\n", sudoedit_path);
|
||||
|
||||
char ver[128] = {0};
|
||||
if (!get_sudo_version(sudo_path, ver, sizeof ver)) {
|
||||
/* Prefer the centrally-fingerprinted sudo version (populated once
|
||||
* at startup by core/host.c) — saves a popen per scan and gives
|
||||
* unit tests a clean mock point. Fall back to the local popen if
|
||||
* ctx->host is missing the version. */
|
||||
if (ctx->host && ctx->host->sudo_version[0]) {
|
||||
snprintf(ver, sizeof ver, "%s", ctx->host->sudo_version);
|
||||
} else if (!get_sudo_version(sudo_path, ver, sizeof ver)) {
|
||||
if (!ctx->json)
|
||||
fprintf(stderr, "[?] sudoedit_editor: could not parse `sudo --version`\n");
|
||||
return SKELETONKEY_TEST_ERROR;
|
||||
|
||||
Reference in New Issue
Block a user