Aegis-BPF

AegisBPF Architecture

This document describes the internal architecture of AegisBPF, an eBPF-based runtime security agent.

Overview

AegisBPF uses eBPF (extended Berkeley Packet Filter) to monitor and optionally block process executions at the kernel level. It leverages the BPF LSM (Linux Security Module) hooks for enforcement and tracepoints for audit-only monitoring.

┌─────────────────────────────────────────────────────────────────┐
│                        User Space                               │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                     aegisbpf daemon                      │   │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │   │
│  │  │ Policy  │ │  BPF    │ │ Event   │ │    Metrics      │ │   │
│  │  │ Manager │ │  Ops    │ │ Handler │ │    (Prometheus) │ │   │
│  │  └────┬────┘ └────┬────┘ └────┬────┘ └────────┬────────┘ │   │
│  │       │           │           │               │          │   │
│  │       │      ┌────┴───────────┴───────────────┘          │   │
│  │       │      │                                           │   │
│  │       │      ▼                                           │   │
│  │       │  ┌───────────────────────────────────────────┐   │   │
│  │       └─►│            libbpf                         │   │   │
│  │          └───────────────────────────────────────────┘   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                              │                                  │
│                              │ bpf() syscall                    │
├──────────────────────────────┼──────────────────────────────────┤
│                        Kernel Space                             │
│                              │                                  │
│  ┌───────────────────────────┴───────────────────────────────┐  │
│  │                    BPF Subsystem                          │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐│  │
│  │  │ LSM Hook    │  │ Tracepoint  │  │      BPF Maps       ││  │
│  │  │ file_open   │  │ sched_exec  │  │  ┌───────────────┐  ││  │
│  │  │             │  │             │  │  │ deny_inode    │  ││  │
│  │  │  (enforce)  │  │  (audit)    │  │  │ deny_path     │  ││  │
│  │  │             │  │             │  │  │ allow_cgroup  │  ││  │
│  │  └──────┬──────┘  └──────┬──────┘  │  │ events (ring) │  ││  │
│  │         │                │         │  │ block_stats   │  ││  │
│  │         └────────────────┴─────────┴──┴───────────────┴──┘│  │
│  └───────────────────────────────────────────────────────────┘  │
│                              │                                  │
│                              ▼                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    exec() syscall                         │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Components

Kernel Space (BPF Programs)

bpf/aegis.bpf.c

The BPF program runs in kernel context and implements:

  1. LSM Hook (file_open)
    • Called on file open attempts
    • Can return -EPERM to block access
    • Checks deny_inode and allow_cgroup maps
    • Only active when BPF LSM is enabled
  2. Tracepoints
    • sched_process_exec: emits EXEC events for process executions
    • sys_enter_openat: emits audit-only BLOCK events for deny_path matches
    • Works without BPF LSM (audit-only paths)
  3. BPF Maps
    • deny_inode: Hash map of blocked (dev, inode) pairs
    • deny_path: Hash map of blocked path hashes
    • allow_cgroup: Hash map of allowed cgroup IDs
    • events: Ring buffer for sending events to userspace
    • block_stats: Global counters for blocks and drops
    • deny_cgroup_stats: Per-cgroup block counters
    • deny_inode_stats: Per-inode block counters
    • deny_path_stats: Per-path block counters
    • agent_meta: Agent metadata (layout version)

User Space

src/main.cpp

Entry point and CLI interface:

src/bpf_ops.cpp

BPF operations layer:

Key functions:

src/policy.cpp

Policy file management:

src/events.cpp

Event handling:

src/utils.cpp

Utility functions:

src/sha256.cpp

SHA256 implementation:

src/seccomp.cpp

Seccomp filter:

src/logging.hpp

Structured logging:

src/result.hpp

Error handling:

Data Flow

File Access Blocking (Enforce Mode)

1. Process calls open("/etc/shadow")
           │
           ▼
2. Kernel invokes file_open LSM hook
           │
           ▼
3. BPF program handle_file_open runs
           │
           ├─── Check allow_cgroup map
           │    └─ If cgroup allowed → ALLOW
           │
           └─── Check deny_inode map
                └─ If inode blocked → DENY + emit event
           │
           ▼
4. Return 0 (allow) or -EPERM (deny)
           │
           ▼
5. If denied, ring buffer event sent to userspace
           │
           ▼
6. aegisbpf daemon receives event
           │
           ▼
7. Event logged to journald/stdout

Audit Mode

1. Process executes a binary (execve)
           │
           ▼
2. execve() completes successfully
           │
           ▼
3. Kernel fires sched_process_exec tracepoint
           │
           ▼
4. BPF program handle_execve runs
           │
           ▼
5. EXEC event emitted to ring buffer
           │
           ▼
6. aegisbpf daemon receives event
           │
           ▼
7. Event logged to journald/stdout
1. Process opens a file (open/openat)
           │
           ▼
2. Kernel fires sys_enter_openat tracepoint
           │
           ▼
3. BPF program handle_openat runs
           │
           ▼
4. If path is in deny_path, emit audit-only BLOCK event
           │
           ▼
5. aegisbpf daemon receives event
           │
           ▼
6. Event logged to journald/stdout

BPF Map Pinning

Maps are pinned to /sys/fs/bpf/aegis/ for persistence:

/sys/fs/bpf/aegis/
├── deny_inode          # Blocked inodes
├── deny_path           # Blocked paths
├── allow_cgroup        # Allowed cgroups
├── events              # Ring buffer (not pinned)
├── block_stats         # Global counters
├── deny_cgroup_stats   # Per-cgroup stats
├── deny_inode_stats    # Per-inode stats
├── deny_path_stats     # Per-path stats
└── agent_meta          # Layout version

Pinning allows:

Error Handling Strategy

AegisBPF uses a Result monad pattern:

Result<InodeId> path_to_inode(const std::string& path) {
    struct stat st;
    if (stat(path.c_str(), &st) != 0) {
        return Error::system(errno, "stat failed");
    }
    return InodeId{static_cast<uint32_t>(st.st_dev), st.st_ino};
}

// Usage with TRY macro
Result<void> block_file(const std::string& path) {
    auto inode = TRY(path_to_inode(path));
    TRY(add_to_deny_map(inode));
    return {};
}

Benefits:

Concurrency Model

Security Considerations

  1. Principle of Least Privilege
    • Minimal capability set (SYS_ADMIN, BPF, PERFMON)
    • Seccomp filter restricts syscalls
    • AppArmor/SELinux confine file access
  2. Input Validation
    • All CLI paths validated before use
    • Policy files parsed with strict error handling
    • SHA256 verification for policy integrity
  3. Defense in Depth
    • Multiple security layers (BPF, seccomp, MAC)
    • RAII for resource cleanup
    • Crash-safe persistent state

Performance Characteristics

Kernel Requirements

Feature Minimum Version Notes
BPF CO-RE 5.5 Compile-once, run-everywhere
BPF LSM 5.7 Required for enforce mode
Ring buffer 5.8 More efficient than perf buffer
CAP_BPF 5.8 Dedicated capability
BTF 5.2 Type information