Files
SKELETONKEY/.github/workflows/build.yml
T
leviathan ea1744e6f0 tests: detect() unit harness with mocked ctx->host
Adds tests/test_detect.c — a standalone harness that constructs
synthetic struct skeletonkey_host fingerprints (vulnerable / patched /
specific-gate-closed) and asserts each migrated module's detect()
returns the expected verdict. First real test coverage for the corpus;
catches regressions in the host-fingerprint-consuming logic.

Initial coverage — 8 deterministic cases across the 4 modules that
already consume ctx->host:
- dirtydecrypt: 3 cases verifying 'kernel < 7.0 -> predates the bug'
  short-circuit on synthetic 6.12 / 6.14 / 6.8 hosts.
- fragnesia: unprivileged_userns_allowed=false -> PRECOND_FAIL.
- pack2theroot: is_debian_family=false -> PRECOND_FAIL.
- pack2theroot: has_dbus_system=false -> PRECOND_FAIL.
- overlayfs: distro=debian / distro=fedora -> 'not Ubuntu' -> OK.

Coverage grows automatically as more modules migrate to ctx->host
(task #12 below adds them). Each new module that consults the host
fingerprint can have its precondition gates tested with a one-line
EXPECT_DETECT call against a pre-built fingerprint.

Wiring:
- Makefile: new MODULE_OBJS var consolidates the module .o list so
  both the main binary and the test binary can share it without
  duplication. New TEST_BIN := skeletonkey-test target. 'make test'
  builds and runs the suite.
- .github/workflows/build.yml: install libglib2.0-dev + pkg-config so
  pack2theroot builds with GLib in CI (was previously stub-compiling).
  New 'tests — detect() unit suite' step runs 'make test' as a
  non-root user so modules' 'already root' gates don't short-circuit
  before the synthetic host checks fire.
- Test harness compiles cross-platform but assertions are #ifdef
  __linux__ guarded (on non-Linux all module detect() bodies stub-out
  to PRECOND_FAIL, making assertions tautological); macOS dev build
  reports 'skipped'.

Module change:
- pack2theroot p2tr_detect now consults ctx->host->is_root (with a
  geteuid() fallback when ctx->host is null) instead of calling
  geteuid() directly. Production behaviour is identical
  (host->is_root is populated from geteuid() at startup); tests can
  now construct non-root fingerprints regardless of the test
  process's actual euid. Exposed a real consistency issue worth
  fixing.

Verified in docker as non-root: 8/8 pass on Linux. macOS reports
'skipped' as designed.
2026-05-22 23:32:12 -04:00

106 lines
3.5 KiB
YAML

name: build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang]
flavor: [default, debug]
name: build (${{ matrix.cc }} / ${{ matrix.flavor }})
steps:
- uses: actions/checkout@v4
- name: install build deps
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
build-essential clang make linux-libc-dev \
libglib2.0-dev pkg-config
- name: show compiler
run: ${{ matrix.cc }} --version
- name: build
env:
CC: ${{ matrix.cc }}
run: |
if [ "${{ matrix.flavor }}" = "debug" ]; then
make debug
else
make
fi
- name: sanity — skeletonkey --version
run: ./skeletonkey --version
- name: sanity — skeletonkey --list
run: ./skeletonkey --list
- name: sanity — skeletonkey --scan (no exploit; just detect)
run: ./skeletonkey --scan --no-color || true
# exit code may be nonzero (vulnerable host = exit 2, missing
# precond = exit 4) — that's diagnostic data, not CI failure
- name: sanity — --detect-rules auditd
run: ./skeletonkey --detect-rules --format=auditd | head -50
- name: sanity — --detect-rules sigma
run: ./skeletonkey --detect-rules --format=sigma | head -50
- name: tests — detect() unit suite
env:
CC: ${{ matrix.cc }}
run: |
# Run as a non-root user so modules' "already root" gates do
# not short-circuit before the synthetic host-fingerprint
# checks fire. The test binary itself is platform-agnostic;
# the assertions are #ifdef __linux__ guarded.
sudo useradd -m -s /bin/bash skeletonkeyci 2>/dev/null || true
sudo chown -R skeletonkeyci .
sudo -u skeletonkeyci make test
# Static build job: ensures the project links cleanly when -static is
# requested. Useful for deployment to minimal containers / fleet scans
# where shared-libc availability isn't guaranteed.
static-build:
runs-on: ubuntu-latest
name: static-build
steps:
- uses: actions/checkout@v4
- name: install build deps
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
build-essential make linux-libc-dev libc6-dev \
libglib2.0-dev pkg-config
- name: make static
# Glibc static linking pulls in NSS at runtime which breaks
# getpwnam; the legacy DIRTYFAIL Makefile noted this. For now,
# we allow this job to fail loudly so we know if a regression
# makes the regular dynamic build also break, but we don't
# gate the merge on it. Migrate to musl-gcc when we want a
# truly portable static binary.
continue-on-error: true
run: make static && ls -la skeletonkey
# Phase 4 followup (placeholder): kernel-VM matrix. Each entry runs
# the binary against a VM running a specific (vulnerable or patched)
# kernel and asserts the correct detect() verdict + exploit behavior.
# Requires self-hosted runners or a paid VM service; not enabled yet.
#
# kernel-vm-matrix:
# strategy:
# matrix:
# distro: [ubuntu-22.04, debian-11, alma-9, fedora-40]
# kernel: [5.10.50, 5.13.0, 5.15.30, 6.1.x, 6.12.x]
# runs-on: [self-hosted, kvm-host]
# ...