main
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
60d22eb4f6 |
core/host: add meltdown_mitigation passthrough + migrate entrybleed
The kpti_enabled bool in struct skeletonkey_host flattens three
distinct sysfs states into one bit:
/sys/devices/system/cpu/vulnerabilities/meltdown content:
- 'Not affected' → CPU is Meltdown-immune; KPTI off; EntryBleed
doesn't apply (verdict: OK)
- 'Mitigation: PTI' → KPTI on (verdict: VULNERABLE)
- 'Vulnerable' → KPTI off but CPU not hardened (rare;
verdict: VULNERABLE conservatively)
- file unreadable → unknown (verdict: VULNERABLE conservatively)
kpti_enabled=true only captures 'Mitigation: PTI'; kpti_enabled=false
collapses 'Not affected', 'Vulnerable', and 'unreadable' into one
indistinguishable case. That meant entrybleed_detect() had to
re-open the sysfs file to recover the raw string.
Fix by also stashing the raw first line in
ctx->host->meltdown_mitigation[64]. kpti_enabled stays for callers
that only need the simple bool; new code that needs the nuance reads
the string. populate happens once at startup, like every other host
field.
entrybleed migration:
- reads ctx->host->meltdown_mitigation instead of opening sysfs
- removes the file-local read_first_line() helper (now dead code)
- same three-way verdict logic, but driven by a const char *
instead of a fresh fopen() each detect()
Test coverage:
- 3 new test rows on x86_64 fingerprints:
empty mitigation → VULNERABLE (conservative)
'Not affected' → OK
'Mitigation: PTI' → VULNERABLE
- 1 stub-path test row on non-x86_64 fingerprints (PRECOND_FAIL)
- registry coverage report: 30/31 modules now have direct tests
(up from 29/31; copy_fail is the only remaining untested module)
Verification:
- macOS: 33 kernel_range + 1 entrybleed-stub = 34 passes, 0 fails
- Linux (docker gcc:latest): 33 kernel_range + 54 detect = 87
passes, 0 fails. Up from 83 last commit.
|
||
|
|
86812b043d |
core/host: userspace version fingerprint (sudo, polkit)
The host fingerprint now captures sudo + polkit versions at startup
so userspace-LPE modules can consult a single source of truth
instead of each popen-ing the relevant binary themselves on every
scan. Pack2theroot already queries PackageKit version via D-Bus
in-module, so PackageKit stays there for now.
core/host.h:
- new fields: char sudo_version[64], char polkit_version[64].
Empty string when the tool isn't installed or version parse fails;
modules should treat that as PRECOND_FAIL.
- documented next to has_systemd / has_dbus_system in the struct.
core/host.c:
- new populate_userspace_versions(h) called from
skeletonkey_host_get() after the other populators.
- capture_first_line() helper runs a command via popen, grabs first
stdout line, strips newline. Best-effort: failure leaves dst empty.
- extract_version_after_prefix() pulls the version token after a
fixed prefix string ('Sudo version', 'pkexec version'), handling
the colon/space variants.
- skeletonkey_host_print_banner() gained a third line when either
version is non-empty:
[*] userspace: sudo=1.9.17p2 polkit=-
Module migration (graceful fallback pattern — modules still work
without ctx->host populated):
- sudo_samedit detect: if ctx->host->sudo_version is set, skip the
popen and synthesize a 'Sudo version <X>' line for the existing
parser. Falls back to the original find_sudo + popen path if the
host fingerprint didn't capture a version.
- sudoedit_editor detect: same pattern — host fingerprint sudo_version
takes precedence over the local get_sudo_version popen.
tests/test_detect.c additions (2 new cases, 33 → 35):
- h_vuln_sudo fingerprint (sudo_version='1.8.31', kernel 5.15) —
asserts sudo_samedit reports VULNERABLE via the host-provided
version string.
- h_fixed_sudo fingerprint (sudo_version='1.9.13p1', kernel 6.12) —
asserts sudo_samedit reports OK on a patched sudo.
This is the first test pair to cover the *vulnerable* path of a
module rather than just precondition gates — proves the
version-parsing logic itself, not only the short-circuits.
Verification: 35/35 pass on Linux. macOS banner shows
'userspace: sudo=1.9.17p2 polkit=-' as the dev box has Homebrew
sudo but no polkit.
|
||
|
|
2b1e96336e |
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
|
||
|
|
1571b88725 |
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.
|
||
|
|
4f30d00a1c |
core/host: shared host fingerprint refactor
Adds core/host.{h,c} — a single struct skeletonkey_host populated once
at startup and handed to every module callback via ctx->host. Replaces
the per-detect uname / /etc/os-release / sysctl / userns-fork-probe
calls scattered across the corpus with O(1) cached lookups, and gives
the dispatcher one consistent view of the host.
What's in the fingerprint:
- Identity: kernel_version (parsed from uname.release), arch (machine),
nodename, distro_id / distro_version_id / distro_pretty (parsed once
from /etc/os-release).
- Process state: euid, real_uid (defeats userns illusion via
/proc/self/uid_map), egid, username, is_root, is_ssh_session.
- Platform family: is_linux, is_debian_family, is_rpm_family,
is_arch_family, is_suse_family (file-existence checks once).
- Capability gates (Linux): unprivileged_userns_allowed (live
fork+unshare probe), apparmor_restrict_userns,
unprivileged_bpf_disabled, kpti_enabled, kernel_lockdown_active,
selinux_enforcing, yama_ptrace_restricted.
- System services: has_systemd, has_dbus_system.
Wiring:
- core/module.h forward-declares struct skeletonkey_host and adds the
pointer to skeletonkey_ctx. Modules opt-in by including
../../core/host.h.
- core/host.c is fully POD (no heap pointers) — uses a single file-
static instance, returns a stable pointer on every call. Lazily
populated on first skeletonkey_host_get().
- skeletonkey.c calls skeletonkey_host_get() at main() entry, stores
in ctx.host before any register_*() runs.
- cmd_auto's bespoke distro-fingerprint code (was an inline
read_os_release helper) is replaced with skeletonkey_host_print_banner(),
which emits a two-line banner of identity + capability gates.
Migrations:
- dirtydecrypt: kernel_version_current() -> ctx->host->kernel.
- fragnesia: removed local fg_userns_allowed() fork-probe in favour of
ctx->host->unprivileged_userns_allowed (no per-scan fork). Also
pulls kernel from ctx->host. The PRECOND_FAIL message now notes
whether AppArmor restriction is on.
- pack2theroot: access('/etc/debian_version') -> ctx->host->is_debian_family;
also short-circuits when ctx->host->has_dbus_system is false (saves
the GLib g_bus_get_sync attempt on systems without system D-Bus).
- overlayfs: replaced the inline is_ubuntu() /etc/os-release parser
with ctx->host->distro_id comparison. Local helper preserved for
symmetry / standalone builds.
Documentation: docs/ARCHITECTURE.md gains a 'Host fingerprint'
section describing the struct, the opt-in include pattern, and
example detect() usage. ROADMAP --auto accuracy log notes the
landing and flags remaining modules as an incremental follow-up.
Build verification:
- macOS (local): make clean && make -> Mach-O x86_64, 31 modules,
banner prints with distro=?/? (no /etc/os-release).
- Linux (docker gcc:latest + libglib2.0-dev): make clean && make ->
ELF 64-bit, 31 modules. Banner prints with kernel + distro=debian/13
+ 7 capability gates. dirtydecrypt correctly says 'predates the
rxgk code added in 7.0'; fragnesia PRECOND_FAILs with
'(host fingerprint)' annotation; pack2theroot PRECOND_FAILs on
no-DBus; overlayfs reports 'not Ubuntu (distro=debian)'.
|