Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

Consider the design concepts through flowcharts below:

  • architecture flowchart
  • event transformation flowchart

Architecture Flowchart

---
config:
  layout: elk
  look: handDrawn
---
flowchart TB
    A["Config"] -- configure --> B["Detectors"]
    B -- manage --> C["eBPF probes"]
    B -- manage --> D["eBPF maps"]
    A -- configure --> E["Transmuters"]
    A -- configure --> F["Transmitter"]
    G["Monitor"] -- use --> E
    G["Monitor"] -- use --> F
    G["Monitor"] -- read --> H["RingBuffer"]
    E -- manage --> I["Process cache"]
    C -- send --> H
    A -- configure --> J["Pod watcher"]
    J -- update --> K["Pod index"]
    I -- lookup --> K
    J -- watch pods --> L["kube-apiserver"]

    A@{ shape: docs}
    B@{ shape: processes}
    C@{ shape: processes}
    D@{ shape: cyl}
    E@{ shape: processes}
    H@{ shape: das}
    I@{ shape: cyl}
    J@{ shape: processes}
    K@{ shape: cyl}
    L@{ shape: cyl}

Detector

Detector provides a common interface for loading / unloading eBPF programs, initializing maps and attaching programs to hook points. EBPF part of detectors is located here. User mode part is here. Detectors can share information between each other storing it in eBPF maps. Some parts of detectors can be reused across different detectors. Detectors submit events to user space using ring buffer. Detectors use YAML config files for initialization. Detectors are stored in Registry.

Transmuter

Transmuter provides a common interface to convert (transmute) low kernel events into serializable data structures (e.g. json). Transmuters can enrich kernel events with some user mode data and implement different types of caching. Transmuters are stored in TransmuterRegistry. One Detector can have many types of Transmuters, but usually it has only one.

Transmitter

Transmitter sends serialized events (byte arrays) to different sources (unix socket, stdout, file, etc).

Monitor

Monitor observes new low level kernel events (messages) and extracts them from ring buffer. According to event type, it fetches corresponding transmuter to convert and enrich eBPF event. Further transmuted events are send to destination with transmitter.

Config

Config provides all information about Bombini setup. It also holds options of bombini cli and config for each detector to be loaded. Detector’s configs are also provided for corresponding transmuters.

Event Transformation Flowchart

---
config:
  layout: dagre
  look: handDrawn
---
flowchart LR
    A["Kernel Events"] -- collect & apply rules --> B["eBPF probes"]
    B <-- store/update --> C["eBPF maps"]
    B -- send --> D["RingBuffer"]
    D --enrich & transform --> E["Transmuters"]
    E -- serialize --> F["Transmitter"]
    F -- send --> G["Collector"]
    E <-- store/update --> I["Process cache"]

    A@{ shape: docs}
    B@{ shape: processes}
    C@{ shape: cyl}
    D@{ shape: das}
    E@{ shape: processes}
    G@{ shape: cyl}
    I@{ shape: cyl}

Rule Engine

The rule engine executes entirely within the eBPF layer using a custom Reverse Polish Notation (RPN) interpreter. Rules are parsed using the LALRPOP framework, which generates an AST that undergoes optimization passes before being serialized into a binary format. These serialized rules are stored in dedicated eBPF maps, while in operations trigger lookups against attribute-specific eBPF maps (e.g., binary_path, ipv4_dst, etc.).

Process Execution Detection

eBPF

Process information (ProcInfo) is stored in PROCMON_PROC_MAP which is shared across all detectors. Map entries are updated with tracepoints: sched_process_exec, sched_process_fork, sched_process_exit, and security_bprm_comitting_creds LSM BPF hook. This hook is used to collect binary_path with bpf_d_path helper and IMA binary hash collection. sched_process_fork - creates an entry and sends ProcessClone event, entries are created only for thread leaders (pid == tgid). It means that Bombini doesn’t track thread creation, but in tracks events (e.g. file open, etc.) in threads. In this case, event will hold process information about its thread leader. sched_process_exec - updates entries and sends ProcessExec. sched_process_exit - mark entry as “exited” for the garbage collector and sends ProcessExit event.

User Space

In Bombini there are three event types related to process execution: ProcessExec, ProcessClone, and ProcessExit. These events are provided by corresponding Transmuters. Also, they maintain a ProcessCache to hold serializable Process structures that are used in all event types. ProcessClone event creates an entry in ProcessCache. ProcessExit event marks entry as “exited” for cache garbage collection. ProcessExec event marks as “exited” Process cache entry, related to previous clone() or exec() calls and puts new Process entry in cache.

Kubernetes Pod Enrichment

The pod watcher keeps a PodIndex, a container id to pod mapping built from the pod status of the node the agent runs on. ProcessCache looks a process up in it by the container id extracted in eBPF and stores the result in the Process structure, so pod information is emitted in every event type without touching the eBPF part.

The index is read on the event hot path and written by the watcher only, so the mapping is published as a whole snapshot behind ArcSwap: a lookup is a lock free atomic load. A container id appears in the pod status only after the container has started, so processes that were not resolved are kept aside and re-resolved when the index changes. To avoid rescanning them on every kubelet status update, the index holds a generation counter that is bumped only when the set of container ids actually changes. Configuration is described in the Kubernetes chapter.