Antivirus/dist/HOUND-VERIFY-README.txt
Hound 778236822b houndd: the persistence ledger
Completes Phase 5. Half of a Linux compromise is not a file on disk, it
is a line added to a startup file — a curl in a shell profile, a systemd
unit with a dull name, one extra key in authorized_keys. The payload is
often unremarkable; what makes it an incident is that it survives a
reboot and nobody reads those files from one year to the next.

So this is not a scanner but an inventory with a memory. It records
systemd units (system and per-user), cron in all its locations,
autostart entries, shell profiles, authorized_keys and ld.so.preload,
then reports what CHANGED.

Three decisions, all of which are the difference between a report people
read and one they turn off:

* Content is hashed, not stat'd. An mtime can be set backwards with one
  touch, and someone editing a startup file is exactly the person who
  would. Verified: a backdated edit is still caught.

* A first run reports no changes and says so. Everything would be a
  change, and a first-run report full of alarms is one nobody reads.
  What a first run can honestly say is how many entries no package
  claims — 97 of 1,026 on this machine — because that is true
  regardless of history.

* Writing the baseline is an explicit act (`--accept`, or
  update_baseline on the wire). A plain check must never quietly record
  whatever is currently installed as normal; that is how a compromise
  becomes the new baseline.

Package ownership decides what is ordinary: a unit that arrived with a
package is the system working, the same unit unowned is somebody's
decision. Reuses the merged-/usr-aware index from the rootkit rewrite,
with a test asserting most units resolve to a package — if that ratio
collapses, ownership lookup has broken and the whole report is noise.

Exercised end to end against this machine: baseline of 1,026 items,
a planted user unit caught as ADDED, an in-place edit with a backdated
mtime caught as CHANGED, and its deletion caught as REMOVED. The test
artifact was removed afterwards.

Cross-distro verification of the rootkit rewrite is now MET. Henry ran
the suite on the Ubuntu box (26.04, glibc 2.43): 20/20, including both
unowned_setuid_does_not_fire_on_a_healthy_system and
no_false_positives_on_system_binaries.

214 tests pass across the workspace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 06:50:35 -05:00

58 lines
2.1 KiB
Text

hound-verify — cross-distro verification for Hound Antivirus
============================================================
What this is
------------
The Hound test suite, compiled. It is READ-ONLY: it installs nothing,
starts no daemon, needs no root, and does not modify system state. It
reads /usr/bin, /bin, /usr/sbin, /proc and dpkg's package index.
Requirements
------------
x86_64, glibc 2.39 or newer — Ubuntu 24.04 and up. (The floor comes from
Rust's standard library, not from Hound.) Check with: ldd --version
How to run
----------
chmod +x hound-verify
# The two checks that matter, together:
./hound-verify rootkit rules
# Or everything (~30s, mostly the goodware scan):
./hound-verify
What is actually being verified
-------------------------------
1. rootkit::tests::unowned_setuid_does_not_fire_on_a_healthy_system
Hound flags setuid binaries that no installed package claims. It asks
dpkg. Ubuntu's merged-/usr layout means every binary has two names
(/bin/sudo and /usr/bin/sudo), and dpkg records some packages under
one and some under the other. Getting this wrong makes Hound alarm on
a clean machine.
FAILURE OUTPUT names each binary it wrongly flagged. That is the
useful part — please paste it.
2. rules::tests::no_false_positives_on_system_binaries
Every Hound detection rule is scanned against every binary in
/usr/bin, /bin and /usr/sbin. A single hit fails the build. Ubuntu
ships binaries Linux Mint does not, so this is genuinely new ground.
FAILURE OUTPUT names the binary and the rule. A hit means I delete
that rule rather than tune it — a rule that flags a system binary is
worse than no rule.
3. Everything else in the suite comes along for the ride and is a bonus
signal: the fanotify policy tests, the capability arithmetic, the
supply-chain detectors.
What to send back
-----------------
Either "all passed" (which is a real result — it closes the last open
criterion on the rootkit rewrite), or the assertion text of anything
that failed. The messages are written to be self-explanatory.
Thanks — Hound