modules: migrate remaining 22 modules to ctx->host fingerprint
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.
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
#ifdef __linux__
|
||||
|
||||
#include "../../core/kernel_range.h" /* used inside this block only */
|
||||
#include "../../core/host.h"
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user