core/host: in_range helper + 13-module migration + 12 more tests (29 total)

Three coordinated changes that build on the host_kernel_at_least
landed in 1571b88:

1. core/host gains skeletonkey_host_kernel_in_range(h, lo..., hi...)
   — a [lo, hi) bounded-interval check for modules that want the
   'vulnerable window' semantics directly. Implemented in terms of
   host_kernel_at_least (so the comparison logic stays in one place).
   No module uses it yet; available for new modules that want it.

2. 13 modules migrated off the manual
        if (v->major < X || (v->major == X && v->minor < Y)) { ... }
   pattern onto
        if (!skeletonkey_host_kernel_at_least(ctx->host, X, Y, 0)) { ... }
   One-line replacements, mechanical, no behavior change.

   Migrated: af_packet2, dirty_pipe, fuse_legacy, netfilter_xtcompat,
   nf_tables, nft_fwd_dup, nft_payload, nft_set_uaf, overlayfs,
   overlayfs_setuid, ptrace_traceme, stackrot, vmwgfx. The repo now
   has zero manual 'v->major < X' patterns — every predates-check
   reads the same way.

3. tests/test_detect.c expanded from 17 to 29 cases. Adds:

   Above-fix coverage on h_kernel_6_12 (10 modules previously
   untested): af_packet, af_packet2, af_unix_gc, netfilter_xtcompat,
   nft_set_uaf, nft_fwd_dup, nft_payload, stackrot, sequoia, vmwgfx.

   Ancient-kernel predates coverage on h_kernel_4_4 (2 more cases):
   nft_set_uaf (introduced 5.1), stackrot (introduced 6.1).

   Detect-path test coverage now spans most of the corpus that
   has a testable host-fingerprint gate. Untested modules from
   here on are either userspace bugs whose detect() doesn't gate
   on host fields (pwnkit, sudo_samedit, sudoedit_editor),
   entrybleed (sysfs-direct, no host gate), or the copy_fail_family
   bridge (no ctx->host integration yet).

Verification: Linux (docker gcc:latest, non-root user): 29/29 pass.
macOS (local): 31-module build clean, suite reports 'skipped —
Linux-only' as designed.
This commit is contained in:
2026-05-22 23:58:38 -04:00
parent 1571b88725
commit 2b1e96336e
16 changed files with 97 additions and 13 deletions
+8
View File
@@ -252,6 +252,14 @@ bool skeletonkey_host_kernel_at_least(const struct skeletonkey_host *h,
return h->kernel.patch >= patch;
}
bool skeletonkey_host_kernel_in_range(const struct skeletonkey_host *h,
int lo_M, int lo_m, int lo_p,
int hi_M, int hi_m, int hi_p)
{
return skeletonkey_host_kernel_at_least(h, lo_M, lo_m, lo_p) &&
!skeletonkey_host_kernel_at_least(h, hi_M, hi_m, hi_p);
}
void skeletonkey_host_print_banner(const struct skeletonkey_host *h, bool json)
{
if (json || h == NULL) return;
+21
View File
@@ -103,4 +103,25 @@ void skeletonkey_host_print_banner(const struct skeletonkey_host *h, bool json);
bool skeletonkey_host_kernel_at_least(const struct skeletonkey_host *h,
int major, int minor, int patch);
/* True iff h->kernel is in [lo, hi). Useful for "vulnerable range"
* gates where the simple `kernel_range_is_patched` backport model
* doesn't apply — e.g. a feature added in X.Y and removed/superseded
* in W.Z, or a per-module "vulnerable only on these specific kernel
* lines" check.
*
* Equivalent to:
* host_kernel_at_least(h, lo...) && !host_kernel_at_least(h, hi...)
*
* For "predates the bug" alone use host_kernel_at_least directly; the
* `in_range` form is for the bounded interval case.
*
* Example:
* if (host_kernel_in_range(h, 5, 8, 0, 5, 17, 0))
* // kernel 5.8 ≤ K < 5.17 — vulnerable window per the mainline
* // introduction/fix dates (ignoring stable backports)
*/
bool skeletonkey_host_kernel_in_range(const struct skeletonkey_host *h,
int lo_major, int lo_minor, int lo_patch,
int hi_major, int hi_minor, int hi_patch);
#endif /* SKELETONKEY_HOST_H */
@@ -106,7 +106,7 @@ static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx)
}
/* Bug introduced in 4.6 (tpacket_rcv VLAN path). Pre-4.6 immune. */
if (v->major < 4 || (v->major == 4 && v->minor < 6)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 4, 6, 0)) {
if (!ctx->json) {
fprintf(stderr, "[+] af_packet2: kernel %s predates the bug (introduced in 4.6)\n",
v->release);
@@ -270,7 +270,7 @@ static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
}
/* Bug introduced in 5.8. */
if (v->major < 5 || (v->major == 5 && v->minor < 8)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 8, 0)) {
if (!ctx->json) {
fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n",
v->release);
@@ -177,7 +177,7 @@ static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx
/* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1
* kernels predate the code path entirely. */
if (v->major < 5 || (v->major == 5 && v->minor < 1)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 1, 0)) {
if (!ctx->json) {
fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n",
v->release);
@@ -130,7 +130,7 @@ static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_c
return SKELETONKEY_TEST_ERROR;
}
if (v->major < 2 || (v->major == 2 && v->minor < 6)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 2, 6, 0)) {
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n",
v->release);
@@ -140,7 +140,7 @@ static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx)
}
/* Bug introduced in 5.14. Anything below predates it. */
if (v->major < 5 || (v->major == 5 && v->minor < 14)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 14, 0)) {
if (!ctx->json) {
fprintf(stderr, "[i] nf_tables: kernel %s predates the bug "
"(introduced in 5.14)\n", v->release);
@@ -127,7 +127,7 @@ static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx
/* The offload code path only exists from 5.4 onward. Anything
* older predates the bug. */
if (v->major < 5 || (v->major == 5 && v->minor < 4)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 4, 0)) {
if (!ctx->json) {
fprintf(stderr, "[i] nft_fwd_dup: kernel %s predates the bug "
"(nft offload hook introduced in 5.4)\n", v->release);
@@ -128,7 +128,7 @@ static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx
/* Bug introduced with the set-payload extension in 5.4. Anything
* below 5.4 predates the affected codepath entirely. */
if (v->major < 5 || (v->major == 5 && v->minor < 4)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 4, 0)) {
if (!ctx->json) {
fprintf(stderr, "[i] nft_payload: kernel %s predates the bug "
"(set-payload extension landed in 5.4)\n",
@@ -144,7 +144,7 @@ static skeletonkey_result_t nft_set_uaf_detect(const struct skeletonkey_ctx *ctx
/* Bug introduced in 5.1 (anonymous-set support). Anything below
* predates it — report OK (not vulnerable to *this* CVE). */
if (v->major < 5 || (v->major == 5 && v->minor < 1)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 1, 0)) {
if (!ctx->json) {
fprintf(stderr, "[i] nft_set_uaf: kernel %s predates the bug "
"(anonymous-set support landed in 5.1)\n", v->release);
@@ -192,7 +192,7 @@ static skeletonkey_result_t overlayfs_detect(const struct skeletonkey_ctx *ctx)
* Ubuntu fix is per-release-specific; conservatively report
* VULNERABLE if version < 5.13 (covers most affected Ubuntu LTS),
* and recommend --active for confirmation. */
if (v.major < 5 || (v.major == 5 && v.minor < 13)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 13, 0)) {
if (!ctx->json) {
fprintf(stderr, "[!] overlayfs: Ubuntu kernel %s in vulnerable range — "
"re-run with --active to confirm\n", v.release);
@@ -107,7 +107,7 @@ static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx
/* Bug introduced in 5.11 when ovl copy-up was generalized.
* Pre-5.11 immune via a different code path. */
if (v->major < 5 || (v->major == 5 && v->minor < 11)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 5, 11, 0)) {
if (!ctx->json) {
fprintf(stderr, "[+] overlayfs_setuid: kernel %s predates the bug "
"(introduced in 5.11)\n", v->release);
@@ -81,7 +81,7 @@ static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *
/* Bug existed since ptrace's inception (early 2.x); anything
* pre-LTS-backport is vulnerable. Anything < 4.4 in our range
* model defaults to vulnerable since no entry covers it. */
if (v->major < 4 || (v->major == 4 && v->minor < 4)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 4, 4, 0)) {
if (!ctx->json) {
fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n",
v->release);
@@ -159,7 +159,7 @@ static skeletonkey_result_t stackrot_detect(const struct skeletonkey_ctx *ctx)
/* Bug introduced in 6.1 (when maple tree landed). Pre-6.1 kernels
* use rbtree-based VMAs and don't have this bug. */
if (v->major < 6 || (v->major == 6 && v->minor < 1)) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 6, 1, 0)) {
if (!ctx->json) {
fprintf(stderr, "[+] stackrot: kernel %s predates maple-tree VMA code (introduced in 6.1)\n",
v->release);
@@ -237,7 +237,7 @@ static skeletonkey_result_t vmwgfx_detect(const struct skeletonkey_ctx *ctx)
/* Pre-vmwgfx kernels (no driver shipped) — extremely unlikely but
* report PRECOND_FAIL rather than VULNERABLE. */
if (v->major < 4) {
if (!skeletonkey_host_kernel_at_least(ctx->host, 4, 0, 0)) {
if (!ctx->json) {
fprintf(stderr, "[+] vmwgfx: kernel %s predates vmwgfx driver\n", v->release);
}
+55
View File
@@ -41,6 +41,16 @@ 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;
extern const struct skeletonkey_module af_packet_module;
extern const struct skeletonkey_module af_packet2_module;
extern const struct skeletonkey_module af_unix_gc_module;
extern const struct skeletonkey_module netfilter_xtcompat_module;
extern const struct skeletonkey_module nft_set_uaf_module;
extern const struct skeletonkey_module nft_fwd_dup_module;
extern const struct skeletonkey_module nft_payload_module;
extern const struct skeletonkey_module stackrot_module;
extern const struct skeletonkey_module sequoia_module;
extern const struct skeletonkey_module vmwgfx_module;
static int g_pass = 0;
static int g_fail = 0;
@@ -282,6 +292,51 @@ static void run_all(void)
run_one("overlayfs_setuid: vuln kernel + userns=false → PRECOND_FAIL",
&overlayfs_setuid_module, &h_kernel_5_14_no_userns,
SKELETONKEY_PRECOND_FAIL);
/* ── above-fix coverage for the remaining kernel modules ──
* Kernel 6.12 is above every backport entry in the corpus.
* For modules with a `kernel_range` table, kernel_range_is_patched
* inherits via the "host is newer than every entry" branch and
* detect() returns OK. */
run_one("af_packet: kernel 6.12 above 4.11 fix → OK",
&af_packet_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("af_packet2: kernel 6.12 above 5.9 fix → OK",
&af_packet2_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("af_unix_gc: kernel 6.12 above 6.6-rc1 fix → OK",
&af_unix_gc_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("netfilter_xtcompat: kernel 6.12 above 5.12 fix → OK",
&netfilter_xtcompat_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("nft_set_uaf: kernel 6.12 above 6.4-rc4 fix → OK",
&nft_set_uaf_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("nft_fwd_dup: kernel 6.12 above 5.17 fix → OK",
&nft_fwd_dup_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("nft_payload: kernel 6.12 above 6.2-rc4 fix → OK",
&nft_payload_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("stackrot: kernel 6.12 above 6.4-rc4 fix → OK",
&stackrot_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("sequoia: kernel 6.12 above 5.13.4 fix → OK",
&sequoia_module, &h_kernel_6_12, SKELETONKEY_OK);
run_one("vmwgfx: kernel 6.12 above 6.3-rc6 fix → OK",
&vmwgfx_module, &h_kernel_6_12, SKELETONKEY_OK);
/* ── ancient-kernel predates coverage ────────────────────────
* Kernel 4.4 predates several module bugs introduced 5.x+. */
run_one("nft_set_uaf: kernel 4.4 predates 5.1 → OK",
&nft_set_uaf_module, &h_kernel_4_4, SKELETONKEY_OK);
run_one("stackrot: kernel 4.4 predates 6.1 → OK",
&stackrot_module, &h_kernel_4_4, SKELETONKEY_OK);
#else
fprintf(stderr, "[i] non-Linux platform: detect() bodies are stubbed; "
"tests skipped (would tautologically pass).\n");