1. Overview
This component implements BastionGuard’s scheduled Anti-Ransomware scanner, designed to run either as a long-lived daemon or as an on-demand CLI tool. It performs filesystem scans using a consolidated YARA ruleset, applies anti false-positive logic (allowlist + filters), and reduces system impact through CPU throttling, I/O niceness, and rate limiting.
The scanner supports multiple operational modes:
- Daemon mode – continuous scanning loop over configured paths, with optional Samba scans
- Single file scan –
--scan <file> - Single directory scan –
--scan-dir <dir>
Detection events are reported via D-Bus to allow the desktop UI to display real-time alerts.
2. Core Architecture
2.1 Global Runtime State
The scanner uses global state for lifecycle and rules management:
static bool running– main loop control flag (set false by signal handler)static YR_RULES* yara_rules– compiled YARA ruleset cachebool running_as_daemon– controls D-Bus loop handling behavior for CLI mode
Thread safety is ensured with global mutexes for sensitive operations:
yara_mtx– serializes YARA scans against sharedYR_RULES*log_mtx– serializes event-log writes
2.2 ThreadPool for Parallel Scanning
To accelerate traversal across large directory trees, the scanner uses a simple internal ThreadPool based on:
- Worker threads that wait on a condition variable
- A task queue of
std::function<void()> enqueue()accepting lambdas and callable objects
Thread count is bounded:
min(16, hardware_concurrency)with fallback assumptions when concurrency is unknown
The destructor performs a clean stop, wakes all workers, and joins threads.
3. Resource Control and Performance Hardening
3.1 CPU and I/O Priority Reduction
At startup, the daemon lowers scheduling priority to reduce impact on interactive workloads:
setpriority(PRIO_PROCESS, 0, 19)– low CPU scheduling priorityionice -c3 -p <pid>– idle I/O class (best-effort)
3.2 CPU Throttling via /proc Sampling
The CpuThrottle subsystem estimates process CPU usage by comparing:
- System CPU time from
/proc/stat - Process CPU time from
/proc/self/stat(utime + stime)
If the process exceeds the configured target (default 2%), it applies a proportional sleep to reduce CPU consumption. The check interval is configurable (e.g., every 5 seconds in the daemon loop).
3.3 Rate Limiting (Token Bucket)
The scanner uses an explicit token bucket model to cap scanning throughput:
MAX_FILES_PER_SECdefines available tokens per second- Each file scan consumes one token
- Tokens are refilled every 1 second using an atomic timestamp guard
This prevents uncontrolled bursts when scanning large directory trees, particularly when combined with multithreading.
4. Configuration Model
4.1 ScannerConfig Keys
Runtime settings are represented by ScannerConfig, including:
enable_yara– enable/disable YARA scanningenable_sanesecurity– optional DB-based detection toggle (placeholder in this excerpt)scan_path– semicolon-separated scan pathsscan_interval– scan interval (minutes)max_memory– memory budget / stack configurationenable_samba_scan– toggle Samba scanningsamba_path– semicolon-separated Samba paths- Anti false positives:
ignore_paths– glob patternsignore_ext– extension blocklistsuspicious_ext– extension allowlist for “suspicious-only” modesuspicious_only– scan only suspicious extensionsallowlist_file– SHA256 allowlist file path
Configuration is loaded from:
resource("config/scanner.conf")
When absent, the scanner uses safe defaults (scan /home, YARA on, conservative filters enabled).
4.2 Dynamic YARA Rules Directory Selection
If no rules directory is provided, the scanner attempts to locate YARA rules in the following order:
- User rules:
~/.local/share/BastionGuard/data/yara - System rules:
/usr/share/BastionGuard/data/yara - Fallback internal resource directory:
resource("rules")
4.3 Samba Directory Loading
Samba directories are loaded from:
resource("config/samba_dirs.conf")
Non-empty, non-comment lines are treated as paths. If missing, Samba scanning remains possible but uses an empty set until configured.
5. Allowlist (SHA256;Optional Rule)
5.1 Format and Storage
The allowlist supports per-file and per-rule suppression using the format:
SHA256
SHA256;rule_id
Entries are loaded into:
std::unordered_multimap<std::string, std::string> g_allow_sha_rule
Matching logic:
- If the SHA exists with an empty rule value → allow the file globally
- If the SHA exists with a rule id → allow only when that rule matched
6. Debounce and Fingerprinting Cache
6.1 Purpose
To reduce repeated scanning of unchanged files across periodic passes, the scanner maintains an in-memory fingerprint cache:
- Keyed by full path
- Tracks size and last-write ticks
- Optionally stores the last computed SHA256
unchanged(entry) returns true when both size and mtime match the cached values.
7. YARA Ruleset Compilation and Match Aggregation
7.1 Unified Ruleset Compilation
The scanner compiles all .yara rules within the configured directory into a single YR_RULES* using:
yr_compiler_create()yr_compiler_add_file()for each ruleyr_compiler_get_rules()to finalize
7.2 Optional Zero-Day Rule with Integrity Check
An optional “0-day” rule file can be provided via:
--0day-protection <file>
Before loading, the module can validate integrity by comparing the file SHA256 against an expected value. If mismatched:
- Rule loading is suppressed
- A D-Bus alert is generated with
Integrity_Mismatch
7.3 Per-File Hit Accumulator
YARA matches are aggregated per file via a per-thread map:
thread_local std::unordered_map<std::string, HitInfo> g_hits
HitInfo tracks:
- Total match count
- Maximum severity meta value
- High-confidence flag (tag
highor severity ≥ 80) - List of matched rule identifiers
7.4 Callback Logic
The YARA callback:
- Collects rule identifier into
rules - Reads severity from YARA meta (
severity) - Checks tag “high” (compatible with YARA 3.x / 4.x build variations)
- Maintains
count,max_severity, andhigh_conf
Detection decision uses a quorum/threshold model:
- malicious if
count >= 2orhigh_conf == true
8. Detection Workflow (process_one)
8.1 Filtering Pipeline
In daemon mode, each candidate file goes through sequential filters before scanning:
- Path glob ignore list (
ignore_paths_glob) - Extension ignore list (
ignore_exts) - Optional “suspicious-only” extension gating (
suspicious_exts) - Unchanged-file bypass (fingerprint cache)
- Minimum size threshold (
MIN_YARA_SIZE, e.g., 4096 bytes)
This pipeline reduces false positives and dramatically lowers compute cost on benign content.
8.2 YARA Scan and Event Handling
When scanning is enabled and rules are available:
- YARA scan is serialized under
yara_mtxbecause the ruleset pointer is shared - Matches are evaluated for maliciousness using quorum + severity/tag gating
- SHA256 is computed on malicious candidates for allowlist checks
8.3 Allowlist Suppression vs Real Alerts
If the file’s SHA (and optionally matched rules) is allowlisted:
- The event is logged as
SUPPRESSED_ALLOWLIST - No D-Bus alert is emitted
If not allowlisted:
- An event is logged to:
/tmp/BastionGuard-ransomware-events.log - A D-Bus alert is emitted with family:
YARA(local filesystem)YARA_SAMBA(Samba path scan)
When running in CLI mode, a short-lived GLib main loop is created to ensure the D-Bus message is flushed before process exit.
9. D-Bus Alert Transport
9.1 Direct Session-Bus Connection
Alerts are sent using Gio::DBus::Connection::create_for_address_sync(). The address is taken from:
DBUS_SESSION_BUS_ADDRESS
A robust fallback is provided:
unix:path=/run/user/1000/bus
This enables operation when the environment is incomplete (e.g., system service contexts) at the cost of assuming a default UID if not otherwise set by the caller.
9.2 Alert Interface
The D-Bus call is issued to:
- Object path:
/org/BastionGuard/ransomware/alert - Interface:
org.BastionGuard.Ransomware.Alert - Method:
ShowAlert
Payload is a tuple containing:
- File path
- Alert family identifier
10. Operational Modes and CLI
10.1 CLI Options
The scanner supports:
--scan/-s– scan a single file and print results--scan-dir/-d– scan a directory recursively--rules-dir– override YARA rules directory--0day-protection– override the 0-day rule path--help/-h– usage output
A guard prevents conflicting CLI modes (file and directory scan simultaneously).
10.2 Daemon Loop
In daemon mode, the scanner loops while running == true:
- Optionally scans Samba paths (if enabled)
- Scans configured local paths
- Waits for
scan_intervalminutes before repeating
Directory traversal uses recursive iterators with skip_permission_denied to avoid crashes on restricted paths.
11. Dynamic Memory Selection and YARA Configuration
11.1 Total RAM Detection
The scanner reads total system memory from:
/proc/meminfo
and derives a memory budget tier (256MB → 2GB) based on total RAM.
11.2 YARA Configuration Application
The chosen value is applied via:
yr_set_configuration(YR_CONFIG_STACK_SIZE, &cfg.max_memory)
This allows the scanner to adapt to low-memory systems while enabling higher stack sizes on capable hosts.
12. Shutdown and Cleanup
On termination:
- The daemon stops scheduling new work
- The compiled YARA ruleset (if present) is destroyed via
yr_rules_destroy() - YARA is finalized via
yr_finalize()
ThreadPool workers are joined during destruction, ensuring no dangling tasks remain.
13. Runtime and Security Considerations
- Low-impact scanning: priority lowering, CPU throttling, and token bucket limiting collectively reduce system load.
- False-positive resistance: ignore globs, ignore extensions, suspicious-only mode, quorum logic, severity gating, and SHA allowlist combine to reduce noise.
- Ruleset integrity: optional zero-day integrity check enables tamper detection for high-value rules.
- D-Bus assumptions: the fallback session-bus path may be incorrect on multi-user systems; best practice is to resolve the active user bus dynamically.
- Thread safety: serializing YARA scans avoids concurrency issues on shared rulesets; task-level parallelism still benefits directory traversal speed.
- Log placement: event logs are written under
/tmp; for production use, rotation and secure permissions should be considered.