# 0024: First-use malware check

> Files are checked the first time they run, instead of a crawler that reads the whole disk.

Source: https://wolos.pages.dev/manual/adr-0024

## Context

WolOS needs a layer of protection against known malware at almost no cost. A scanner that walks the disk spends the machine's time on files that never change, and the sealed system needs no scanning at all. Files become interesting at one moment: when they arrive and first open.

## Decision

- Check at first use. `crates/xguard` holds fanotify permission marks only on the mounts where new programs arrive, and on files tagged as downloads. It never scans the sealed root.
- The kernel is the verdict cache. A clean file gets an evictable ignore mark, which the kernel drops when the file changes. No kernel patch: measurement did not ask for one. eBPF LSM was not tried for the same reason.
- Verdicts survive restarts in a mapped store, keyed by file identity and sha256, or by fs-verity digest.
- Fail open. Only a signature match stops a file.
- An own matcher in a flat, mapped, shared file. Rule sets (YARA-X) run only in a short-lived unprivileged worker, because they cost 28 to 51 MB resident.
- The signature file format is ours (`.xgdb`), built from public signature lists at release time and delivered signed, like other updates.
- libclamav is not linked: 998 MB resident and 7 s to load the full database. Anything it reads that we do not is listed in `archicture/modules/security.md`.
- No file, hash or verdict leaves the computer. Sample submission does not exist.
- A small stable scan API for installers: `libxguard.so.1` and `wol-xguard scan --json`. Archives are read in memory.

Measured (16 vCPU virtual machine, btrfs, 99 MB signature file, guard off and on in alternating rounds):

| What | Off | On |
|---|---|---|
| Matcher, 96,371 patterns (others: 49 and 68 MB/s) | | 450 MB/s, 7 MB file |
| Cached exec | 182.6 us | 182.8 us (within noise) |
| `open` in scope | 1044 ns | 1065 ns |
| `stat` | 590 ns | 590 ns |
| Game asset load, 30,000 files, 7.5 GB, cold | 11.52 s | 11.52 s |
| Kernel compile, cold caches, 3 rounds | 98.67 s | 99.64 s (+0.99%) |
| Idle memory, zero wakeups in 60 s | | 8.2 MB resident |

A first run pays once: about 4 ms for a small static binary, 140 ms for 32 MB, 1 s for 256 MB. Up to 58 MB of the mapped file is resident for the 50 seconds after a scan. App launch was not measured on its own; a launch from cache is the cached exec above.

## Consequences

- Signatures miss new malware. This is a layer of protection, and the copy never says more.
- Interpreters, `dlopen`, `memfd` and packed Windows programs get past the first-use check.
- Every open in scope pays about 20 ns while the guard runs.
- Redistribution terms of the signature data need checking before any public list ships in an update. Until then a system ships with its test signatures only.
