Add signed-package verification and anti-rootkit cross-view (v0.7)
Closes the tamper-evidence loop with two trust anchors the attacker can't forge from userland: - pkgdb Layer 2: verify on-disk files against the .MTREE in the *signed* cache package, surviving a rewritten local checksum DB. Rotating-sample cadence keeps it affordable; flags pkg_signature_mismatch (sid 100027) and SigLevel downgrades (sid 100026). Fixes parse_mtree, which required type=file on every line and so matched nothing on real pacman MTREEs (which use a /set type=file default with bare file entries). - rootcheck: anti-rootkit cross-view — hidden processes, modules, ports, and promiscuous interfaces, each caught by diffing two views of the same state (sids 100022-100025). Wired both through config, the daemon (off-loop slow cadence), and CLI (pkgdb-verify, rootcheck). 14 new tests (95 total). Docs + version bump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
34bc09041a
commit
1de5e86fed
10 changed files with 766 additions and 5 deletions
|
|
@ -43,6 +43,13 @@ class Sentinel:
|
|||
self._fim_pkg_thread: threading.Thread | None = None
|
||||
self._last_fim_pkg = 0.0
|
||||
self._last_pkgdb = 0.0
|
||||
# Layer-2 package verification (rotating sample each pass).
|
||||
self._pkgverify_thread: threading.Thread | None = None
|
||||
self._last_pkgverify = 0.0
|
||||
self._pkgverify_offset = 0
|
||||
# Anti-rootkit cross-view sweep (off the loop thread; brute-forces PIDs).
|
||||
self._rootcheck_thread: threading.Thread | None = None
|
||||
self._last_rootcheck = 0.0
|
||||
self._stop = threading.Event()
|
||||
|
||||
# -- baselines ---------------------------------------------------------
|
||||
|
|
@ -158,6 +165,44 @@ class Sentinel:
|
|||
for alert in pacman_verify_alerts():
|
||||
self._on_exec_alert(alert)
|
||||
|
||||
# -- package signature verification (Layer 2, off the loop thread) ------
|
||||
def _maybe_pkgdb_verify(self, now: float) -> None:
|
||||
if not self.cfg.pkgdb_pkgverify:
|
||||
return
|
||||
if self._pkgverify_thread and self._pkgverify_thread.is_alive():
|
||||
return
|
||||
if (now - self._last_pkgverify) < self.cfg.pkgdb_pkgverify_interval:
|
||||
return
|
||||
self._last_pkgverify = now
|
||||
self._pkgverify_thread = threading.Thread(
|
||||
target=self._pkgdb_verify, daemon=True)
|
||||
self._pkgverify_thread.start()
|
||||
|
||||
def _pkgdb_verify(self) -> None:
|
||||
from . import pkgdb
|
||||
for alert in pkgdb.verify_alerts(self.cfg, offset=self._pkgverify_offset):
|
||||
self._on_exec_alert(alert)
|
||||
# Advance the rotating window so the next pass covers a different slice.
|
||||
self._pkgverify_offset += self.cfg.pkgdb_pkgverify_sample
|
||||
|
||||
# -- anti-rootkit cross-view (off the loop thread) ---------------------
|
||||
def _maybe_rootcheck(self, now: float) -> None:
|
||||
if not self.cfg.rootcheck_enabled:
|
||||
return
|
||||
if self._rootcheck_thread and self._rootcheck_thread.is_alive():
|
||||
return
|
||||
if (now - self._last_rootcheck) < self.cfg.rootcheck_interval:
|
||||
return
|
||||
self._last_rootcheck = now
|
||||
self._rootcheck_thread = threading.Thread(
|
||||
target=self._rootcheck, daemon=True)
|
||||
self._rootcheck_thread.start()
|
||||
|
||||
def _rootcheck(self) -> None:
|
||||
from . import rootcheck
|
||||
for alert in rootcheck.run(self.cfg):
|
||||
self._on_exec_alert(alert)
|
||||
|
||||
# -- one sweep ---------------------------------------------------------
|
||||
def sweep(self, *, force_suid: bool = False) -> list[Alert]:
|
||||
now = time.time()
|
||||
|
|
@ -221,6 +266,8 @@ class Sentinel:
|
|||
self._maybe_scan_fim(now)
|
||||
self._maybe_pkg_verify(now)
|
||||
self._maybe_check_pkgdb(now)
|
||||
self._maybe_pkgdb_verify(now)
|
||||
self._maybe_rootcheck(now)
|
||||
alerts = self.sweep()
|
||||
fresh = self.fresh_alerts(alerts, now)
|
||||
if fresh:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue