core/host: skeletonkey_host_kernel_at_least + 9 new detect() tests
core/host helper:
- Adds bool skeletonkey_host_kernel_at_least(h, M, m, p) — the
canonical 'kernel >= X.Y.Z' check. Replaces the manual
'v->major < X || (v->major == X && v->minor < Y)' pattern that
many modules use for their 'predates the bug' pre-check. Returns
false when h is NULL or h->kernel.major == 0 (degenerate cases),
true otherwise iff the host kernel sorts at or above the supplied
version.
- dirtydecrypt migrated as the demo: the 'kernel < 7.0 → predates'
pre-check now reads 'if (!host_kernel_at_least(ctx->host, 7, 0, 0))'.
Other modules still using the manual pattern continue to work
unchanged; migrating them is incremental polish.
tests/test_detect.c expansion (8 → 17 cases):
New fingerprints:
- h_kernel_4_4 — ancient (Linux 4.4 LTS); used for 'predates the
bug' on dirty_pipe.
- h_kernel_6_12 — recent (Linux 6.12 LTS); above every backport
threshold in the corpus — modules report OK via
the 'patched by mainline inheritance' branch of
kernel_range_is_patched.
- h_kernel_5_14_no_userns — vulnerable-era kernel (5.14.0, past
every relevant predates check while below every
backport entry) with unprivileged_userns_allowed
deliberately false; lets the userns gate fire
after the version check confirms vulnerable.
New tests (9):
- dirty_pipe + kernel 4.4 → OK (predates 5.8 introduction)
- dirty_pipe + kernel 6.12 → OK (above every backport)
- dirty_cow + kernel 6.12 → OK (above 4.9 fix)
- ptrace_traceme + kernel 6.12 → OK (above 5.1.17 fix)
- cgroup_release_agent + kernel 6.12 → OK (above 5.17 fix)
- nf_tables + vuln kernel + userns=false → PRECOND_FAIL
- fuse_legacy + vuln kernel + userns=false → PRECOND_FAIL
- cls_route4 + vuln kernel + userns=false → PRECOND_FAIL
- overlayfs_setuid + vuln kernel + userns=false → PRECOND_FAIL
Process note: initial 8th and 9th userns tests failed because the
chosen test kernel (5.10.0) tripped each module's predates check
(nf_tables bug introduced 5.14; overlayfs_setuid 5.11). Switched to
5.14.0, which is past every predates threshold AND below every
backport entry in this batch — the version verdict is now genuinely
'vulnerable' and the userns gate fires next. The bug-finding tests
caught a real-but-narrow modeling gap in the original picks.
Verification:
- Linux (docker gcc:latest, non-root user): 17/17 pass.
- macOS (local): builds clean, suite reports 'skipped — Linux-only'
as designed.
This commit is contained in:
+10
@@ -242,6 +242,16 @@ const struct skeletonkey_host *skeletonkey_host_get(void)
|
|||||||
return &g_host;
|
return &g_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool skeletonkey_host_kernel_at_least(const struct skeletonkey_host *h,
|
||||||
|
int major, int minor, int patch)
|
||||||
|
{
|
||||||
|
if (!h || h->kernel.major == 0)
|
||||||
|
return false;
|
||||||
|
if (h->kernel.major != major) return h->kernel.major > major;
|
||||||
|
if (h->kernel.minor != minor) return h->kernel.minor > minor;
|
||||||
|
return h->kernel.patch >= patch;
|
||||||
|
}
|
||||||
|
|
||||||
void skeletonkey_host_print_banner(const struct skeletonkey_host *h, bool json)
|
void skeletonkey_host_print_banner(const struct skeletonkey_host *h, bool json)
|
||||||
{
|
{
|
||||||
if (json || h == NULL) return;
|
if (json || h == NULL) return;
|
||||||
|
|||||||
+15
@@ -88,4 +88,19 @@ const struct skeletonkey_host *skeletonkey_host_get(void);
|
|||||||
* --auto / --scan verbose output. Silent on JSON mode. */
|
* --auto / --scan verbose output. Silent on JSON mode. */
|
||||||
void skeletonkey_host_print_banner(const struct skeletonkey_host *h, bool json);
|
void skeletonkey_host_print_banner(const struct skeletonkey_host *h, bool json);
|
||||||
|
|
||||||
|
/* True iff h->kernel >= the (major, minor, patch) provided. Returns
|
||||||
|
* false if h is NULL or its kernel version was never populated (major
|
||||||
|
* == 0). Replaces the manual `v->major < X` / `(v->major == X &&
|
||||||
|
* v->minor < Y)` patterns scattered across detect()s — cleaner reads
|
||||||
|
* and one place to get the comparison right.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* if (!host_kernel_at_least(h, 7, 0, 0)) // kernel predates 7.0
|
||||||
|
* return SKELETONKEY_OK;
|
||||||
|
* if ( host_kernel_at_least(h, 6, 8, 0)) // kernel post-fix
|
||||||
|
* return SKELETONKEY_OK;
|
||||||
|
*/
|
||||||
|
bool skeletonkey_host_kernel_at_least(const struct skeletonkey_host *h,
|
||||||
|
int major, int minor, int patch);
|
||||||
|
|
||||||
#endif /* SKELETONKEY_HOST_H */
|
#endif /* SKELETONKEY_HOST_H */
|
||||||
|
|||||||
@@ -697,7 +697,7 @@ static skeletonkey_result_t dd_detect(const struct skeletonkey_ctx *ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Predates the bug: rxgk RESPONSE-handling code was added in 7.0. */
|
/* Predates the bug: rxgk RESPONSE-handling code was added in 7.0. */
|
||||||
if (v->major < 7) {
|
if (!skeletonkey_host_kernel_at_least(ctx->host, 7, 0, 0)) {
|
||||||
if (!ctx->json)
|
if (!ctx->json)
|
||||||
fprintf(stderr, "[i] dirtydecrypt: kernel %s predates the rxgk "
|
fprintf(stderr, "[i] dirtydecrypt: kernel %s predates the rxgk "
|
||||||
"RESPONSE-handling code added in 7.0 — not applicable\n",
|
"RESPONSE-handling code added in 7.0 — not applicable\n",
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ extern const struct skeletonkey_module dirtydecrypt_module;
|
|||||||
extern const struct skeletonkey_module fragnesia_module;
|
extern const struct skeletonkey_module fragnesia_module;
|
||||||
extern const struct skeletonkey_module pack2theroot_module;
|
extern const struct skeletonkey_module pack2theroot_module;
|
||||||
extern const struct skeletonkey_module overlayfs_module;
|
extern const struct skeletonkey_module overlayfs_module;
|
||||||
|
extern const struct skeletonkey_module dirty_pipe_module;
|
||||||
|
extern const struct skeletonkey_module dirty_cow_module;
|
||||||
|
extern const struct skeletonkey_module ptrace_traceme_module;
|
||||||
|
extern const struct skeletonkey_module cgroup_release_agent_module;
|
||||||
|
extern const struct skeletonkey_module nf_tables_module;
|
||||||
|
extern const struct skeletonkey_module fuse_legacy_module;
|
||||||
|
extern const struct skeletonkey_module cls_route4_module;
|
||||||
|
extern const struct skeletonkey_module overlayfs_setuid_module;
|
||||||
|
|
||||||
static int g_pass = 0;
|
static int g_pass = 0;
|
||||||
static int g_fail = 0;
|
static int g_fail = 0;
|
||||||
@@ -132,6 +140,54 @@ static const struct skeletonkey_host h_ubuntu_24_userns_ok = {
|
|||||||
.has_dbus_system = true,
|
.has_dbus_system = true,
|
||||||
.has_systemd = true,
|
.has_systemd = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Ancient kernel that predates many bugs (Linux 4.4 LTS). Useful for
|
||||||
|
* the "kernel predates the bug → OK" path in dirty_pipe (bug
|
||||||
|
* introduced 5.8). */
|
||||||
|
static const struct skeletonkey_host h_kernel_4_4 = {
|
||||||
|
.kernel = { .major = 4, .minor = 4, .patch = 0,
|
||||||
|
.release = "4.4.0-ancient" },
|
||||||
|
.arch = "x86_64",
|
||||||
|
.nodename = "test",
|
||||||
|
.distro_id = "debian",
|
||||||
|
.is_linux = true,
|
||||||
|
.is_debian_family = true,
|
||||||
|
.unprivileged_userns_allowed = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Recent kernel (Linux 6.12 LTS). Above virtually every backport
|
||||||
|
* threshold in the corpus — modules should report OK via the
|
||||||
|
* "patched by mainline inheritance" branch of kernel_range_is_patched. */
|
||||||
|
static const struct skeletonkey_host h_kernel_6_12 = {
|
||||||
|
.kernel = { .major = 6, .minor = 12, .patch = 0,
|
||||||
|
.release = "6.12.0-recent" },
|
||||||
|
.arch = "x86_64",
|
||||||
|
.nodename = "test",
|
||||||
|
.distro_id = "debian",
|
||||||
|
.is_linux = true,
|
||||||
|
.is_debian_family = true,
|
||||||
|
.unprivileged_userns_allowed = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Vulnerable-era kernel (5.14.0) with userns DISABLED. Most
|
||||||
|
* netfilter / overlayfs / cgroup-class modules need both an in-range
|
||||||
|
* kernel AND unprivileged userns. Kernel 5.14 was deliberately
|
||||||
|
* chosen to clear every module's "predates the bug" pre-check in
|
||||||
|
* this batch (nf_tables introduced 5.14; overlayfs_setuid 5.11;
|
||||||
|
* cls_route4/fuse_legacy older still) while remaining below every
|
||||||
|
* stable-branch backport entry (5.15.x / 5.18.x / 5.19.x in the
|
||||||
|
* relevant tables). The version check therefore says "VULNERABLE by
|
||||||
|
* version", and the userns gate fires next. */
|
||||||
|
static const struct skeletonkey_host h_kernel_5_14_no_userns = {
|
||||||
|
.kernel = { .major = 5, .minor = 14, .patch = 0,
|
||||||
|
.release = "5.14.0-vuln-no-userns" },
|
||||||
|
.arch = "x86_64",
|
||||||
|
.nodename = "test",
|
||||||
|
.distro_id = "debian",
|
||||||
|
.is_linux = true,
|
||||||
|
.is_debian_family = true,
|
||||||
|
.unprivileged_userns_allowed = false,
|
||||||
|
};
|
||||||
#endif /* __linux__ */
|
#endif /* __linux__ */
|
||||||
|
|
||||||
/* ── tests ───────────────────────────────────────────────────────── */
|
/* ── tests ───────────────────────────────────────────────────────── */
|
||||||
@@ -175,6 +231,57 @@ static void run_all(void)
|
|||||||
run_one("overlayfs: distro=fedora → not Ubuntu → OK",
|
run_one("overlayfs: distro=fedora → not Ubuntu → OK",
|
||||||
&overlayfs_module, &h_fedora_no_debian,
|
&overlayfs_module, &h_fedora_no_debian,
|
||||||
SKELETONKEY_OK);
|
SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* ── kernel-version-gate cases (post-migration coverage) ──── */
|
||||||
|
|
||||||
|
/* dirty_pipe: bug introduced in 5.8; kernel 4.4 predates → OK */
|
||||||
|
run_one("dirty_pipe: kernel 4.4 predates 5.8 → OK",
|
||||||
|
&dirty_pipe_module, &h_kernel_4_4,
|
||||||
|
SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* dirty_pipe: kernel 6.12 is above every backport entry → OK */
|
||||||
|
run_one("dirty_pipe: kernel 6.12 above all backports → OK",
|
||||||
|
&dirty_pipe_module, &h_kernel_6_12,
|
||||||
|
SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* dirty_cow: fix in mainline 4.9; kernel 6.12 is far above → OK */
|
||||||
|
run_one("dirty_cow: kernel 6.12 above 4.9 fix → OK",
|
||||||
|
&dirty_cow_module, &h_kernel_6_12,
|
||||||
|
SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* ptrace_traceme: fix in 5.1.17; kernel 6.12 above → OK */
|
||||||
|
run_one("ptrace_traceme: kernel 6.12 above 5.1.17 fix → OK",
|
||||||
|
&ptrace_traceme_module, &h_kernel_6_12,
|
||||||
|
SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* cgroup_release_agent: fix in mainline 5.17; kernel 6.12 above → OK */
|
||||||
|
run_one("cgroup_release_agent: kernel 6.12 above 5.17 fix → OK",
|
||||||
|
&cgroup_release_agent_module, &h_kernel_6_12,
|
||||||
|
SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* ── userns-gate cases ───────────────────────────────────── */
|
||||||
|
|
||||||
|
/* nf_tables: vulnerable kernel 5.10.0 + userns off → PRECOND_FAIL */
|
||||||
|
run_one("nf_tables: vuln kernel + userns=false → PRECOND_FAIL",
|
||||||
|
&nf_tables_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
|
|
||||||
|
/* fuse_legacy: vulnerable kernel + userns off → PRECOND_FAIL */
|
||||||
|
run_one("fuse_legacy: vuln kernel + userns=false → PRECOND_FAIL",
|
||||||
|
&fuse_legacy_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
|
|
||||||
|
/* cls_route4: vulnerable kernel + userns off → PRECOND_FAIL */
|
||||||
|
run_one("cls_route4: vuln kernel + userns=false → PRECOND_FAIL",
|
||||||
|
&cls_route4_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
|
|
||||||
|
/* overlayfs_setuid: vulnerable kernel (5.14, past the 5.11
|
||||||
|
* introduction and below every backport) + userns off
|
||||||
|
* → PRECOND_FAIL via userns gate */
|
||||||
|
run_one("overlayfs_setuid: vuln kernel + userns=false → PRECOND_FAIL",
|
||||||
|
&overlayfs_setuid_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
#else
|
#else
|
||||||
fprintf(stderr, "[i] non-Linux platform: detect() bodies are stubbed; "
|
fprintf(stderr, "[i] non-Linux platform: detect() bodies are stubbed; "
|
||||||
"tests skipped (would tautologically pass).\n");
|
"tests skipped (would tautologically pass).\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user