Skip to main content

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).