1. Overview
The RootGuardEngine.cpp implementation is the central userspace control plane for RootGuard’s eBPF/LSM enforcement. It loads and attaches the BPF skeleton, resolves policy paths into filesystem identities, performs a two-phase map reload, enforces audit-only global metadata surveillance, polls and enriches kernel events, recognizes trusted application ancestry, filters ignored noise, and performs periodic userspace integrity checks against protected baselines.
The file integrates the following components, APIs, or runtime concepts:
- libbpf – BPF skeleton, maps, ring buffer, attachment
- FileIdentityResolver – device/inode identities and metadata baselines
- Policy – trusted, denied, protected, ignored, burst, and enforcement settings
- IEventSink – event output/automatic response pipeline
- /proc – process executable, parent ancestry, FD, and CWD path enrichment
2. BPF Startup and Ownership
start() enables strict libbpf mode, opens/loads the generated skeleton, applies policy, attaches BPF LSM programs, and creates a ring buffer on the events map. Custom unique-pointer deleters destroy the skeleton and ring buffer automatically.
3. Policy Pre-Resolution
applyPolicy() resolves all trusted applications, privilege-trusted executables, denied executables, and protected paths before changing active BPF maps. Optional missing paths become warnings unless the rule is required or allowMissingPaths is false.
Recursive protected directories are walked with permission errors optionally downgraded to warnings. Symlinks are not added as protected identities; regular files and directories are resolved through FileIdentityResolver.
4. Trusted and Denied Identity Validation
Trusted and denied rules are compared by resolved device/inode key, not only by path. The same inode cannot be both trusted and denied. Enforcement that blocks unknown/credential elevation is refused if no privilege-trusted executable was successfully loaded.
5. Protected Identity Preparation
Protected identities combine explicit protected paths and, when enabled, privilege-trusted executables. Duplicate identities merge flags and rule IDs.
When blockUserHome is false, protected identities under /home, /root, or /var/home receive RG_PROTECT_AUDIT_ONLY. The engine enforces a safety limit of 8,000 protected inodes.
6. Two-Phase Policy Reload
All path resolution and validation occurs while the previous policy remains active. Only immediately before map replacement does the engine switch kernel configuration to audit and disable global metadata burst detection, clear stale metadata-domain state, and clear trusted/denied/protected maps.
The prepared maps are then repopulated. If any update fails, the catch path restores a conservative all-audit kernel configuration before rethrowing.
7. Kernel Configuration
setKernelConfig() writes a single rg_kernel_config entry containing ABI version, mode, unknown/metadata/credential/global actions, trusted logging, global metadata monitoring, /run suppression, burst detection, sample limit, threshold, window, and cooldown.
8. Global Metadata Enforcement Rule
Even when applying a normal policy, the engine explicitly passes Action::Audit for global metadata. Broad metadata surveillance is therefore audit-only; immediate blocking requires an explicit protected identity with enough context to enforce safely.
9. Integrity Baselines
For every prepared protected identity, the engine stores canonical path, device/inode, rule ID, mode, UID, and GID. runIntegrityCheckIfDue() detects missing objects, symlink/replacement identity drift, mode mismatch, and owner mismatch.
Changes generate RG_EVENT_INTEGRITY_DRIFT with alert verdict; recovery back to the baseline generates RG_EVENT_INTEGRITY_RESTORED. Repeated identical drift is suppressed until reasons change or the object recovers.
10. Authorization Tokens
authorizeProcess() creates a per-TGID token in the auth_tokens BPF map containing monotonic expiry, target EUID, and issuer PID. TTL must be positive and no greater than 30 seconds.
11. Actor and Parent Enrichment
When the BPF event lacks executable identity/path, enrichActorExecutable() uses /proc/<pid>/exe and /proc/<ppid>/exe as fallback, also reconstructing device/inode identity from those procfs links where missing.
12. Trusted Application Classification
Trusted applications affect only unprotected global-surveillance/global-metadata events (rule_id == 0). Matching can occur by captured actor/parent identity, canonical executable path, or process ancestry.
The ancestry walk follows PPid through /proc/<pid>/status for at most 12 generations. Explicitly protected identities are excluded from this compatibility trust path and therefore cannot be bypassed by trusted desktop/browser ancestry.
13. Affected Path Enrichment
Path resolution follows a confidence-oriented order: protected baseline identity; absolute path supplied by the kernel; matching file descriptor under /proc/<pid>/fd; matching relative filename under process CWD; otherwise basename-only.
Every successful method writes the corresponding path-resolution enum into the event so downstream UI can decide whether path-based recovery is safe.
14. Ignored Event Filtering
Ignored absolute paths honor exact/recursive policy rules. Unprotected global metadata under /run can be suppressed because it is high-churn runtime state. For basename-only global events, exact ignored filenames are used as a fallback only when rule_id == 0, ensuring an explicitly protected object is never hidden by basename matching.
15. Ring Buffer Callback
handleEvent() copies the kernel event, enriches actor and affected path, marks compatible trusted-application global events as trusted, optionally suppresses trusted logging, applies ignored-path filtering, and finally forwards the event to the configured sink. All exceptions are contained inside the C callback boundary.
16. Runtime and Security Considerations
- Two-phase fail-safe reload: validation precedes map mutation and the mutation window is forced into audit.
- Global surveillance is audit-only: explicit protected identities are required for immediate metadata blocking.
- Protected inode bound: recursive configuration cannot load more than 8,000 protected identities.
- Home safety default: user-home protected identities become audit-only unless policy explicitly enables blocking.
- Identity-based conflict detection: trusted and denied overlap is checked after filesystem resolution.
- Path confidence tracking: downstream components know whether a path came from baseline, kernel, proc FD/CWD, or basename only.
- Trusted app scope restriction: desktop/browser compatibility trust cannot override explicit protected identities.
