إنتقل إلى المحتوى الرئيسي

Tamper resistance and self-update

What this page is

The hardening measures the sensor applies to its own process at startup, the binary integrity attestation it computes, and the crash-loop-rollback state machine that swaps it back to a known- good version when the current binary cannot stay alive.

Why it exists this way

Architecture Section 7.2 and 7.4 are explicit: the sensor must resist tampering and must not become a permanent regression when a bad update ships. None of the measures here defeat a local-root attacker, but they raise the bar against casual tampering and surface observable signals (failed seccomp, mismatched binary hash) that the central server can act on.

How it works

Two modules collaborate:

edr_linux::guard applies four measures at startup, all entered from a single apply_hardening call:

  • prctl(PR_SET_DUMPABLE, 0), kernel refuses to write a core dump that could leak eBPF map contents, the mTLS client key, or analyst-collected forensic data on crash.
  • prctl(PR_SET_NO_NEW_PRIVS, 1), setuid/setgid bits and file capabilities cannot take effect for any child the sensor spawns. Precondition for unprivileged seccomp.
  • Capability bounding-set trimmed to {CAP_BPF, CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW} via capset; every other capability is dropped.
  • Seccomp-bpf filter installed in kill-process mode with an allow-list of approximately 100 syscalls needed for sensor operation. Filter is encoded with raw BPF instructions rather than libseccomp to keep the crate's dependency surface in Rust.

compute_binary_hash returns the SHA-256 of /proc/self/exe so the daemon's startup log can attest the binary identity. An attacker rewriting the binary cannot avoid this report without patching the hash computation itself.

edr_linux::update is the state machine that recovers from a bad update.

  • UpdateState cycles Idle → Available → Downloading → Verifying → Ready → Applying, with the terminal RolledBack state. State is persisted to a small JSON file so a crash between phases does not lose progress.
  • CrashLoopDetector records crash timestamps in a bounded ring (last sixteen). Three crashes inside ten minutes is the rollback threshold per the architecture document.
  • RollbackManager stages the current binary as the rollback target before applying an update. A failed apply reverts by rename, atomic on POSIX filesystems.
  • evaluate_startup is the entry-point the daemon calls before initialising the runtime. It returns a typed CrashOutcome: Healthy, CrashLoopDetected (rollback requested), or CrashLoopDetectedNoRollback (no previous version on disk; the init system surfaces the failure).

What goes wrong

  • Hardening on systems without the syscalls the allow-list expects (older kernels, containerised hosts with restricted seccomp), HardeningError surfaces with the specific measure that failed. The daemon refuses to start under hardening failure rather than silently running unprotected.
  • Rollback impossible because RollbackManager.previous_path is None, the sensor exits and lets the init system surface the failure. Masking the crash by ignoring it would hide the regression from the fleet.
  • The binary hash on a freshly built sensor differs from the one the Collector last saw, the central server raises a binary_drift alert. Causes range from legitimate updates (expected) to silent tampering (worth investigating).