Files
SKELETONKEY/tests/test_kernel_range.c
T
leviathan 8243817f7e test harness: kernel_range unit tests + coverage report + register_all helper
Three coupled improvements to the test harness:

1. New tests/test_kernel_range.c — 32 pure unit tests covering
   kernel_range_is_patched(), skeletonkey_host_kernel_at_least(),
   and skeletonkey_host_kernel_in_range(). These are the central
   comparison primitives every module routes through; a regression
   in any of them silently mis-classifies entire CVE families. Tests
   cover exact boundary, one-below, mainline-only, multi-LTS,
   between-branch, and NULL-safety cases. Builds and runs
   cross-platform (no Linux syscalls).

2. tests/test_detect.c additions:
   - mk_host(base, major, minor, patch, release) builder so new
     fingerprint-based tests don't duplicate 14-line struct literals
     to override one (major, minor, patch) triple.
   - Post-run coverage report that iterates the runtime registry and
     warns about modules without at least one direct test row. Output
     is informational (no CI fail) so coverage grows incrementally.
   - 7 new boundary tests for the kernel_patched_from entries added
     by tools/refresh-kernel-ranges.py (commit 8de46e2):
       - af_unix_gc 6.4.12 → VULNERABLE / 6.4.13 → OK
       - vmwgfx 5.10.127 → OK
       - nft_set_uaf 5.10.179 → OK / 6.1.27 → OK
       - nft_payload 5.10.162 → OK
       - nf_tables 5.10.209 → OK

3. core/registry_all.c — extracts the 27-line 'call every
   skeletonkey_register_<family>()' enumeration from skeletonkey.c
   into a shared helper. skeletonkey.c main() now calls
   skeletonkey_register_all_modules() once; the detect-test main()
   does the same. Kept in its own translation unit so registry.c
   stays standalone for the lean kernel_range unit-test binary
   (which links core/ only, no modules).

Makefile: builds two test binaries now —
  skeletonkey-test     — detect() integration tests (full corpus)
  skeletonkey-test-kr  — kernel_range unit tests (core/ only)
'make test' runs both.

Verification:
  - macOS: 32/32 kernel_range tests pass; detect tests skipped
    (non-Linux platform, stubbed bodies).
  - Linux (docker gcc:latest): 32/32 kernel_range + 51/51 detect.
    Coverage report identifies 2 modules without direct tests
    (copy_fail, entrybleed) out of 31 registered.

Test counts: 44 -> 83 (+39).
2026-05-23 01:09:30 -04:00

223 lines
8.3 KiB
C

/*
* tests/test_kernel_range.c — unit tests for the central kernel
* version-comparison helpers in core/kernel_range.c and core/host.c.
*
* These helpers are the foundation of the host-fingerprint pattern:
* every module that gates on kernel version routes through
* skeletonkey_host_kernel_at_least(),
* skeletonkey_host_kernel_in_range(), or kernel_range_is_patched().
* A regression in any of them silently mis-classifies entire CVE
* families. The detect() integration tests in test_detect.c exercise
* these indirectly via real modules; this file pins them down with
* direct boundary-condition assertions so failures point at the right
* file.
*
* Cross-platform: pure logic, no Linux syscalls. Runs identically on
* macOS dev builds and Linux CI.
*/
#include "../core/kernel_range.h"
#include "../core/host.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int g_pass = 0;
static int g_fail = 0;
#define EXPECT(name, cond) do { \
if (cond) { \
printf("[+] PASS %s\n", (name)); \
g_pass++; \
} else { \
fprintf(stderr, "[-] FAIL %s\n", (name)); \
g_fail++; \
} \
} while (0)
/* ── kernel_range_is_patched ────────────────────────────────────────── */
static void test_kernel_range_is_patched(void)
{
/* Common single-branch-plus-mainline table: backport on 5.15.42,
* mainline fix at 5.17.0. */
static const struct kernel_patched_from pf_5_15_5_17[] = {
{5, 15, 42},
{5, 17, 0},
};
const struct kernel_range r1 = { pf_5_15_5_17, 2 };
struct kernel_version v;
v = (struct kernel_version){5, 15, 42, NULL};
EXPECT("range: exact backport boundary (5.15.42) → patched",
kernel_range_is_patched(&r1, &v));
v = (struct kernel_version){5, 15, 41, NULL};
EXPECT("range: one below backport (5.15.41) → vulnerable",
!kernel_range_is_patched(&r1, &v));
v = (struct kernel_version){5, 15, 100, NULL};
EXPECT("range: well above backport on same branch (5.15.100) → patched",
kernel_range_is_patched(&r1, &v));
v = (struct kernel_version){5, 17, 0, NULL};
EXPECT("range: mainline fix exact (5.17.0) → patched",
kernel_range_is_patched(&r1, &v));
v = (struct kernel_version){5, 16, 0, NULL};
EXPECT("range: between branches (5.16.0) → vulnerable",
!kernel_range_is_patched(&r1, &v));
v = (struct kernel_version){5, 14, 999, NULL};
EXPECT("range: branch below all entries (5.14.999) → vulnerable",
!kernel_range_is_patched(&r1, &v));
v = (struct kernel_version){6, 12, 0, NULL};
EXPECT("range: newer mainline branch (6.12.0) → patched via inheritance",
kernel_range_is_patched(&r1, &v));
/* Mainline-only entry — common pattern for a fresh CVE with no
* stable backports yet. */
static const struct kernel_patched_from pf_7_0_only[] = {
{7, 0, 0},
};
const struct kernel_range r2 = { pf_7_0_only, 1 };
v = (struct kernel_version){6, 19, 99, NULL};
EXPECT("mainline-only: kernel below mainline (6.19.99) → vulnerable",
!kernel_range_is_patched(&r2, &v));
v = (struct kernel_version){7, 0, 0, NULL};
EXPECT("mainline-only: at mainline (7.0.0) → patched",
kernel_range_is_patched(&r2, &v));
v = (struct kernel_version){7, 5, 0, NULL};
EXPECT("mainline-only: above mainline (7.5.0) → patched",
kernel_range_is_patched(&r2, &v));
/* Multi-LTS table mirroring real af_unix_gc layout. */
static const struct kernel_patched_from pf_multi[] = {
{4, 14, 326},
{4, 19, 295},
{5, 4, 257},
{5, 10, 197},
{5, 15, 130},
{6, 1, 51},
{6, 4, 13},
{6, 5, 0},
};
const struct kernel_range r3 = { pf_multi, 8 };
v = (struct kernel_version){5, 10, 196, NULL};
EXPECT("multi-LTS: 5.10.196 (one below backport) → vulnerable",
!kernel_range_is_patched(&r3, &v));
v = (struct kernel_version){5, 10, 197, NULL};
EXPECT("multi-LTS: 5.10.197 (exact backport) → patched",
kernel_range_is_patched(&r3, &v));
v = (struct kernel_version){6, 4, 12, NULL};
EXPECT("multi-LTS: 6.4.12 (just-added entry, below) → vulnerable",
!kernel_range_is_patched(&r3, &v));
v = (struct kernel_version){6, 4, 13, NULL};
EXPECT("multi-LTS: 6.4.13 (just-added entry, exact) → patched",
kernel_range_is_patched(&r3, &v));
v = (struct kernel_version){6, 2, 0, NULL};
EXPECT("multi-LTS: 6.2.0 (between LTS branches, no match) → vulnerable",
!kernel_range_is_patched(&r3, &v));
v = (struct kernel_version){5, 8, 0, NULL};
EXPECT("multi-LTS: 5.8.0 (between LTS branches) → vulnerable",
!kernel_range_is_patched(&r3, &v));
/* NULL safety. */
v = (struct kernel_version){5, 15, 42, NULL};
EXPECT("null safety: NULL range → false",
!kernel_range_is_patched(NULL, &v));
EXPECT("null safety: NULL version → false",
!kernel_range_is_patched(&r1, NULL));
}
/* ── skeletonkey_host_kernel_at_least ───────────────────────────────── */
static void test_host_kernel_at_least(void)
{
struct skeletonkey_host h = {0};
h.kernel.major = 6; h.kernel.minor = 12; h.kernel.patch = 5;
EXPECT("at_least: 6.12.5 ≥ 6.12.5 → true (exact)",
skeletonkey_host_kernel_at_least(&h, 6, 12, 5));
EXPECT("at_least: 6.12.5 ≥ 6.12.4 → true",
skeletonkey_host_kernel_at_least(&h, 6, 12, 4));
EXPECT("at_least: 6.12.5 ≥ 6.12.6 → false",
!skeletonkey_host_kernel_at_least(&h, 6, 12, 6));
EXPECT("at_least: 6.12.5 ≥ 6.11.999 → true (lower minor)",
skeletonkey_host_kernel_at_least(&h, 6, 11, 999));
EXPECT("at_least: 6.12.5 ≥ 6.13.0 → false (higher minor)",
!skeletonkey_host_kernel_at_least(&h, 6, 13, 0));
EXPECT("at_least: 6.12.5 ≥ 5.0.0 → true (lower major)",
skeletonkey_host_kernel_at_least(&h, 5, 0, 0));
EXPECT("at_least: 6.12.5 ≥ 7.0.0 → false (higher major)",
!skeletonkey_host_kernel_at_least(&h, 7, 0, 0));
/* NULL host → false (don't crash). */
EXPECT("at_least: NULL host → false",
!skeletonkey_host_kernel_at_least(NULL, 5, 0, 0));
/* Unpopulated host (major == 0) → false on any positive threshold:
* a zero kernel version means we never probed; modules should
* fail-safe by treating "unknown" as "below". */
struct skeletonkey_host h_zero = {0};
EXPECT("at_least: zeroed host (major=0) → false on any threshold",
!skeletonkey_host_kernel_at_least(&h_zero, 5, 0, 0));
}
/* ── skeletonkey_host_kernel_in_range ───────────────────────────────── */
static void test_host_kernel_in_range(void)
{
struct skeletonkey_host h = {0};
/* Window [5.8.0, 5.17.0) — the classic mainline introduction/fix
* pattern used by dirty_pipe and several others. */
h.kernel = (struct kernel_version){5, 8, 0, NULL};
EXPECT("in_range: 5.8.0 in [5.8.0, 5.17.0) → true (lo inclusive)",
skeletonkey_host_kernel_in_range(&h, 5, 8, 0, 5, 17, 0));
h.kernel = (struct kernel_version){5, 16, 999, NULL};
EXPECT("in_range: 5.16.999 in [5.8.0, 5.17.0) → true (inside)",
skeletonkey_host_kernel_in_range(&h, 5, 8, 0, 5, 17, 0));
h.kernel = (struct kernel_version){5, 17, 0, NULL};
EXPECT("in_range: 5.17.0 in [5.8.0, 5.17.0) → false (hi exclusive)",
!skeletonkey_host_kernel_in_range(&h, 5, 8, 0, 5, 17, 0));
h.kernel = (struct kernel_version){5, 7, 999, NULL};
EXPECT("in_range: 5.7.999 below 5.8.0 → false",
!skeletonkey_host_kernel_in_range(&h, 5, 8, 0, 5, 17, 0));
h.kernel = (struct kernel_version){6, 0, 0, NULL};
EXPECT("in_range: 6.0.0 above 5.17 → false",
!skeletonkey_host_kernel_in_range(&h, 5, 8, 0, 5, 17, 0));
/* NULL host. */
EXPECT("in_range: NULL host → false",
!skeletonkey_host_kernel_in_range(NULL, 5, 8, 0, 5, 17, 0));
}
int main(void)
{
fprintf(stderr, "=== SKELETONKEY kernel_range unit tests ===\n\n");
test_kernel_range_is_patched();
test_host_kernel_at_least();
test_host_kernel_in_range();
fprintf(stderr, "\n=== RESULTS: %d passed, %d failed ===\n",
g_pass, g_fail);
return g_fail ? 1 : 0;
}