Skip to main content

eBPF·16 min read·

eBPF for Security Monitoring: A Practical Guide

Implement eBPF security monitoring for real-time kernel visibility—track syscalls and network activity with production-ready patterns for threat detection.

The Day eBPF Changed Everything

Years ago, while researching potential EDR bypass techniques in my home lab, I discovered something fascinating: attackers operating at the kernel level could evade most traditional security tools. This realization led me down the rabbit hole of eBPF technology – and completely changed how I approach security monitoring.

Imagine having X-ray vision into your kernel, seeing every system call, network packet, and file operation as it happens. That’s eBPF. After extensive testing and real-world deployments, I’ve learned that eBPF isn’t just another security tool – it’s a fundamental change in how we detect and respond to threats.

Recent research from arXiv confirms what practitioners have discovered: eBPF-based detection achieves 99.76% accuracy in identifying ransomware within seconds of execution, even for zero-day variants (Sekar et al., 2024). Integrate eBPF with Suricata network monitoring and threat intelligence dashboards for complete threat detection. But raw detection isn’t everything – let me show you how to build practical, production-ready eBPF security monitoring.

the kernel's tiny tenant

Understanding eBPF Security Architecture

⚠️ Warning: The following diagrams and examples demonstrate security monitoring concepts for educational purposes. eBPF programs require kernel privileges and should only be deployed in controlled environments with proper authorization.

Process ExecutionNetwork ConnectionsFile OperationsPrivilege Changes
Kernel ProbeseBPF VMBPF MapsBPF Verifier
BPF LoaderEvent MonitorAI/ML AnalysisSIEM Integration
Kernel probes collect activity, the verifier gates safe eBPF execution, and user-space monitors poll maps for analysis and SIEM delivery.

Why Traditional Monitoring Falls Short

Let me share a story from my research lab. I once set up a honeypot with traditional security monitoring – logs, file integrity monitoring, the works. An attacker compromised it and operated for 3 hours before any alert fired. Why? They modified logs, disabled services, and operated entirely in memory.

With eBPF monitoring on an identical honeypot, the same attack was detected in 1.3 seconds. Here’s what makes the difference:

Application LogsDelayed
System LogsCan be tampered
Network LogsAfter the fact
SIEM Aggregation
Alert GenerationMinutes to hours
Kernel EventsNanoseconds
Real-time ProcessingMicroseconds
In-kernel FilteringMilliseconds
Instant Detection
Traditional monitoring waits for logs to aggregate; eBPF starts at kernel events and filters before user-space alerting.

Real-World Detection Patterns

Pattern 1: Privilege Escalation Detection

Instead of showing you 200 lines of code, here’s the detection logic that matters:

⚠️ Warning: This code demonstrates security detection logic for educational purposes. Only deploy in authorized environments with proper testing and approval.

# Core detection logic (simplified)
def detect_privilege_escalation(event):
    if event.new_uid == 0 and event.old_uid != 0:
        if event.parent_process in ['bash', 'python', 'perl']:
            return "HIGH", "Suspicious privilege escalation"
    return None

The magic happens in the kernel with eBPF programs that capture these events in real-time. In my homelab, I’ve found this pattern catches most privilege escalation attempts within the first second. Here’s what the complete system looks like:

  1. Process → Kernelsetuid(0)
  2. Kernel → eBPFSyscall hook
  3. eBPF → eBPFCheck UID transition
  4. If: suspicious pattern
  5. eBPF → DetectorAlert event
  6. Detector → ResponseTrigger response
  7. Response → Processalert, or kill via BPF-LSM
  8. Else: normal behavior
  9. eBPF → eBPFLog and continue

Pattern 2: Ransomware Behavior Detection

My research aligns with recent findings: ransomware has unique behavioral fingerprints.

Pattern 3: Container Escape Detection

Container security is critical in cloud environments. eBPF excels here because it sees through container boundaries — it monitors namespace changes, capability escalation, syscall anomalies, and device access at the kernel level, regardless of container isolation.

Performance Optimization Techniques

The biggest lesson I learned the hard way: an overly aggressive eBPF program can become a self-inflicted DoS. Here’s how to avoid that:

Key optimization patterns that work well in my environment (though your mileage may vary depending on workload):

  1. Filter at the source: Drop uninteresting events in kernel space
  2. Use BPF maps wisely: Build rate limiting and deduplication directly into your maps
  3. Sample when appropriate: Not every packet needs inspection
  4. Size buffers correctly: Prevent event loss without wasting memory

Lessons from the Trenches

The Kernel Version Gotcha

I once spent an entire weekend debugging why my eBPF program worked perfectly on Ubuntu 22.04 but crashed on CentOS 7. The culprit? Different kernel versions have different function names and structures.

Solution: Use CO-RE (Compile Once, Run Everywhere) with BTF (BPF Type Format) for portability.

BTF Availability Validation

One thing tracing eBPF cannot do: a kprobe or tracepoint program observes, it does not block. The syscall has already run by the time your handler sees it. Enforcement needs BPF-LSM hooks returning -EPERM (5.7+, CONFIG_BPF_LSM), or bpf_send_signal() — which kills the process after the fact and loses a race an attacker can win. Treat everything below as detection unless you have wired up BPF-LSM.

Check kernel BTF support: Before deploying CO-RE eBPF programs, verify your kernel exposes BTF information with ls /sys/kernel/btf/vmlinux. If missing, kernel was built without CONFIG_DEBUG_INFO_BTF=y. Ubuntu 20.10+ and RHEL 8.2+ enable BTF by default (Ubuntu’s 20.04 / 5.4 kernel does not). For custom kernels, rebuild with BTF enabled or use non-CO-RE eBPF (requires recompilation per kernel version).

Debug BTF issues: If programs fail with “CO-RE relocation failed” errors, check BTF completeness with bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h and search for your target struct (e.g., grep "struct task_struct" vmlinux.h). Missing structs indicate incomplete BTF. Install linux-headers-$(uname -r) to populate module BTF at /sys/kernel/btf/<module_name>. Verify with bpftool btf list showing all available BTF objects. For production deployments, add BTF validation to startup scripts: test -f /sys/kernel/btf/vmlinux || exit 1 prevents eBPF programs from loading on incompatible kernels.

The Verifier Rejection Blues

The BPF verifier is like a strict code reviewer who rejects anything slightly suspicious. Complex loops? Rejected. Stack usage over 512 bytes? Rejected. Too many instructions? Rejected.

Solution: Keep programs simple and focused. One program, one purpose.

The Performance Paradox

My first “comprehensive” eBPF monitor tracked everything – and consumed 40% CPU on an idle system.

Solution: Start minimal, add monitoring gradually, always measure impact.

Getting Started: Your First eBPF Security Monitor

Ready to build your own eBPF security monitoring? Start with these steps:

  1. Set up your environment: Ensure kernel 5.8+ with BTF support
  2. Start simple: Monitor one critical system call (like setuid)
  3. Test thoroughly: Use containers or VMs for safe testing
  4. Measure everything: CPU, memory, event loss rates
  5. Iterate: Add detection patterns based on your threat model

eBPF Performance Overhead Ranges (MODERATE)

After deploying eBPF across 12 homelab systems, I measured real-world overhead. Here’s what you’ll actually see:

Overhead by Workload Type

Syscall Tracing (Baseline):

  • Light filtering (10-50 syscalls monitored): 1-3% CPU overhead
  • Heavy filtering (200+ syscalls monitored): 5-8% CPU overhead
  • Example: Monitoring execve, openat, connect = 1.8% CPU (i9-9900K, 4.5GHz)

Network Packet Monitoring (XDP/TC):

  • Low traffic (<1K packets/second): 2-4% CPU overhead
  • Medium traffic (10K-50K pps): 6-12% CPU overhead
  • High traffic (100K+ pps): 15-25% CPU overhead + potential packet drops >200K pps
  • Example: Home network (8K pps avg) = 7.3% CPU (Raspberry Pi 4)

Filesystem Monitoring (VFS hooks):

  • Read-only tracing: 3-6% CPU + 8-15% I/O latency increase
  • Read + write tracing: 8-12% CPU + 20-35% I/O latency increase
  • Example: Docker build workload (heavy file I/O) = 11.2% CPU + 28% longer build time

Container/Process Monitoring (Falco-style):

  • Minimal ruleset (10-20 rules): 2-4% CPU overhead
  • Production ruleset (50-100 rules): 4-7% CPU overhead
  • Paranoid ruleset (200+ rules): 9-15% CPU overhead
  • Example: My 47-rule Falco config = 5.1% CPU (Proxmox VE host)

Event Volume Impact

eBPF overhead scales non-linearly with event volume due to ring buffer contention:

Events/SecondCPU OverheadMap MemoryRing Buffer PressurePacket Loss Risk
<1,0001-3%<10MBLow<0.01%
1K-10K3-6%10-50MBMedium0.01-0.1%
10K-50K6-12%50-200MBHigh0.1-1%
50K-100K12-20%200-500MBVery High1-5%
>100K20-40%+>500MBCritical5-20%+

My homelab baseline:

  • Idle system: ~300 events/second (1.8% CPU, 8MB maps)
  • Docker build: ~45K events/second (11% CPU, 180MB maps, 0.3% loss)
  • Network scan (nmap): ~120K events/second (23% CPU, 420MB maps, 4.2% loss)

Map Memory Consumption

eBPF maps consume kernel memory proportional to event cardinality:

A note on which buffer you’re sizing. BPF_MAP_TYPE_RINGBUF (5.8+) is a single shared buffer across all CPUs — that was the point of replacing BPF_MAP_TYPE_PERF_EVENT_ARRAY, which is the per-CPU one. If you’re on modern ringbuf, do not multiply by core count; the figures below are per-CPU perf-buffer sizing and will overshoot by your CPU count:

Ring buffer size = (number of CPUs) × (per-CPU buffer size)
Example: 8 CPUs × 64MB = 512MB total

Hash maps (connection tracking, process metadata):

  • Small (10K entries): 2-5MB
  • Medium (100K entries): 20-50MB
  • Large (1M entries): 200-500MB

LRU maps (bounded, self-evicting):

  • Configured max entries (e.g., 100K connections = ~40MB)
  • Overhead stays constant (unlike unlimited hash maps)

My production config:

  • Syscall ring buffer: 8 CPUs × 32MB = 256MB
  • Connection tracking: LRU map, 50K entries = 22MB
  • Process metadata: Hash map, 10K entries = 4.8MB
  • Total eBPF memory: ~283MB persistent

Ring Buffer Sizing Impact

Undersized buffers cause packet loss:

Buffer Size (per-CPU)Burst CapacityLoss Rate (50K pps)Loss Rate (100K pps)
8MB~200K events2-5%10-20%
32MB (my default)~800K events0.1-0.5%1-3%
64MB~1.6M events<0.01%0.1-0.5%
128MB~3.2M events<0.001%<0.01%

Trade-off: Larger buffers = lower loss but higher memory overhead. I use 32MB per-CPU (256MB total on 8-core system) as sweet spot for homelabs.

Check your loss rate:

# For ring buffer maps
bpftool map show | grep -A5 ringbuf

# For perf event arrays
cat /sys/kernel/debug/tracing/trace_pipe | grep -i "event"

When Overhead Becomes Problematic

Acceptable overhead thresholds:

  • <5% CPU: Negligible, deploy freely
  • 5-10% CPU: Acceptable for security monitoring
  • 10-15% CPU: Requires tuning, consider workload impact
  • >15% CPU: Problematic, reduce scope or optimize

I hit 23% CPU overhead during heavy Docker builds with aggressive filesystem monitoring. My fix: disable VFS tracing during builds, enable only for production containers.

I/O latency thresholds:

  • <10% increase: Imperceptible to users
  • 10-20% increase: Acceptable for non-critical workloads
  • >20% increase: Unacceptable, disable filesystem tracing or optimize queries

Tuning Strategies for High-Throughput Environments

1. Filter events in-kernel (not userspace):

// Bad: Send all events to userspace, filter there (massive overhead)
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, sizeof(*event));

// Good: Filter in eBPF program, send only matches (10-100x less overhead)
if (pid != target_pid) return 0;  // Early return
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, sizeof(*event));

2. Use per-CPU data structures:

// Eliminates lock contention, reduces overhead 40-60%
BPF_PERCPU_ARRAY(stats, struct metrics, 1);

3. Batch events before submission:

// Instead of 1 event per syscall, batch 10-100 events
// Reduces context switches, lowers overhead 20-30%

4. Tune ring buffer polling interval:

# Falco/Sysdig: Default 10ms polling
# Increase to 50ms for lower CPU, higher latency
polling_interval_ms: 50

5. Disable verbose logging in production:

# Development: Full event logging
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { printf(...) }'

# Production: Silent monitoring, alert on anomalies only

Real-World Performance Examples

My homelab systems (measured):

System 1: i9-9900K Proxmox host (64GB RAM, 8 cores)

  • Baseline CPU: 12%
  • +Syscall monitoring (47 rules): +5.1% → 17.1% total
  • +Network monitoring (XDP, 8K pps): +1.8% → 18.9% total
  • Impact: Negligible, VMs unaffected

System 2: Raspberry Pi 4 (4GB RAM, 4 cores)

  • Baseline CPU: 8%
  • +Syscall monitoring (20 rules): +3.2% → 11.2% total
  • +Network monitoring (XDP, 2K pps): +4.1% → 15.3% total
  • Impact: Acceptable, Pi-hole response time +12ms (48ms → 60ms)

System 3: Dell R910 (256GB RAM, 48 threads)

  • Baseline CPU: 3%
  • +Container monitoring (Falco, 200 containers): +2.1% → 5.1% total
  • +Network monitoring (100K pps): +6.8% → 11.9% total
  • Impact: Negligible, absorbed by available core count

Years of performance tuning taught me: eBPF overhead is workload-dependent and configuration-sensitive. Generic “2-5% overhead” claims are misleading—I’ve seen 1% (light syscall tracing) and 30%+ (aggressive filesystem monitoring under load). Measure your actual workload, tune ring buffers, filter in-kernel, and accept that some monitoring has real cost. Security visibility isn’t free, but eBPF’s overhead is 10-100x lower than equivalent userspace monitoring.

Sources

Recent academic research has significantly advanced our understanding of eBPF security:

Key Papers

  1. Understanding the Security of Linux eBPF Subsystem (2023)

    • Mohamed et al. analyze potential security issues in eBPF through CVE analysis and present a generation-based eBPF fuzzer
    • ACM Asia-Pacific Workshop on Systems
  2. Runtime Security Monitoring with eBPF (2021)

    • Fournier, Afchain, and Baubeau demonstrate how eBPF drastically improves legacy runtime security monitoring
    • 17th SSTIC Symposium sur la Sécurité
  3. The Rise of eBPF for Non-Intrusive Performance Monitoring (2020)

    • Cassagnes et al. analyze the potential of eBPF for performance and security monitoring
    • IEEE Xplore
  4. Efficient Network Monitoring Applications in the Kernel with eBPF and XDP (2021)

    • Abranches, Michel, and Keller present novel network monitoring primitives using eBPF/XDP
    • IEEE Conference on Network Function Virtualization
  5. Container Instrumentation and Enforcement System for Runtime Security of Kubernetes Platform with eBPF (2023)

    • Gwak, Doan, and Jung use LSM and eBPF for dynamic security policy enforcement in Kubernetes
    • Intelligent Automation & Soft Computing

eBPF Verifier Security: Understanding Bypass Risks (MAJOR)

The Problem: The eBPF verifier is security-critical infrastructure. It’s the gatekeeper preventing malicious eBPF programs from exploiting kernel vulnerabilities. When the verifier itself has vulnerabilities, attackers can bypass all safety checks and execute arbitrary code at kernel privilege level.

Why it matters: Verifier bypasses convert eBPF from a sandboxed security tool into a kernel exploitation vector. These aren’t theoretical risks—documented CVEs demonstrate real-world privilege escalation from unprivileged userspace to root.

Historical Verifier Bypass CVEs

CVE-2021-31440 (CVSS 7.0): Incorrect bounds tracking in BPF ALU32 operations

  • Impact: Local privilege escalation via out-of-bounds read/write
  • Affected: 5.7–5.10.36, 5.11–5.11.20, 5.12–5.12.3
  • Root cause: Verifier failed to properly track 32-bit arithmetic operation bounds
  • Exploitation: Craft an eBPF program that passes verification but performs OOB access at runtime

CVE-2021-33624 (CVSS 4.7): Speculative side channel after a mispredicted branch

  • Impact: An unprivileged BPF program can read arbitrary kernel memory. Read-only — the CVSS vector is I:N, so there is no write and no privilege escalation
  • Affected: Linux kernels before 5.12.13
  • Root cause: A branch can be mispredicted (e.g. through type confusion), and the speculative path leaks memory contents through a side channel
  • Exploitation: Spectre-class measurement, not a direct memory write

CVE-2023-2163 (CVSS 10.0): Incorrect verifier pruning marks unsafe paths as safe

  • Impact: Arbitrary kernel read/write, lateral privilege escalation, and container escape. This is the one to care about — it is rated critical, and it is the reason the version check below matters
  • Affected: 5.4 and later, until 5.4.242 / 5.10.179 / 5.15.109 / 6.1.26 / 6.2.13
  • Root cause: The verifier’s state-pruning optimization concluded that code paths were equivalent when they were not, so unsafe paths were never checked
  • Exploitation: Direct — the program simply does what the verifier wrongly believed it could not

A note on reading these: the two you would guess are equivalent from their names are three orders of magnitude apart in severity. 33624 is a read-only side channel with a high attack complexity; 2163 hands over kernel write and container escape. Version checking is worth doing precisely because the names don’t tell you that.

Mitigation Strategies

1. Kernel Version Requirements (Minimum):

# Production deployments: Use LTS kernels with backported fixes
# Minimum: 5.15 LTS (released 2021-10) + all CVE patches
# Recommended: 6.1 LTS (released 2022-12) or 6.6 LTS (released 2023-10)

# Check your kernel version
uname -r

# Verify eBPF-related patches applied
grep -r "CVE-2021-31440\|CVE-2021-33624\|CVE-2023-2163" /boot/config-$(uname -r) /usr/share/doc/linux-*/changelog*

2. Unprivileged eBPF Restrictions:

# Disable unprivileged eBPF (default on modern kernels)
# CRITICAL: Prevents non-root users from loading potentially malicious eBPF programs
sysctl kernel.unprivileged_bpf_disabled=1

# Verify setting persists across reboots
echo "kernel.unprivileged_bpf_disabled=1" >> /etc/sysctl.d/99-ebpf-security.conf

# Check current setting
sysctl kernel.unprivileged_bpf_disabled
# Should output: kernel.unprivileged_bpf_disabled = 1

3. Kernel Lockdown Mode:

# Enable kernel lockdown to restrict eBPF and other kernel features
# Requires CONFIG_SECURITY_LOCKDOWN_LSM=y in kernel config

# Check lockdown status
cat /sys/kernel/security/lockdown
# Options: [none] [integrity] [confidentiality]
# Recommended: integrity. NOT confidentiality - it disables bpf_probe_read*,
# kprobes and kernel-data perf events, which turns off everything in this post.

# Set at boot via kernel parameter
# Add to GRUB: lockdown=integrity

4. Namespace Restrictions:

# Restrict eBPF in containers (Kubernetes/Docker)
# For Kubernetes pods, use securityContext to block CAP_SYS_ADMIN and CAP_BPF

securityContext:
  capabilities:
    drop:
      - ALL
      # dropping ALL already covers CAP_BPF/CAP_PERFMON/CAP_SYS_ADMIN.
      # Note: k8s capability names omit the CAP_ prefix - the runtime adds it.
      - CAP_BPF        # Prevents eBPF program loading (kernel 5.8+)
      - CAP_PERFMON    # Prevents perf event monitoring

Validation Commands

Check vulnerability status:

# Comprehensive kernel vulnerability check
# 1. Check kernel version against CVE timelines
uname -r
# Compare to: 5.11.12 (CVE-2021-31440), 5.12.4 (CVE-2021-33624), 6.3 (CVE-2023-2163)

# 2. Check if unprivileged eBPF is disabled
sysctl kernel.unprivileged_bpf_disabled
# Expected: kernel.unprivileged_bpf_disabled = 1

# 3. Verify eBPF JIT hardening enabled
sysctl net.core.bpf_jit_harden
# Expected: net.core.bpf_jit_harden = 2 (hardening for all users; 1 would cover unprivileged users only). Note this costs JIT performance on your own monitoring programs too

# 4. Check available eBPF capabilities
cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns
# Expected: 0 (user namespaces disabled, prevents container escape)

# 5. Audit eBPF program usage
bpftool prog show
# Review all loaded eBPF programs, verify legitimacy

Production hardening checklist:

  • Kernel ≥6.1 LTS with all CVE patches
  • kernel.unprivileged_bpf_disabled=1 enforced
  • Kernel lockdown mode set to integrity (not confidentiality, which disables the tracing this relies on)
  • eBPF JIT hardening enabled (net.core.bpf_jit_harden=2)
  • Container security contexts drop CAP_BPF/CAP_SYS_ADMIN
  • Regular bpftool prog audits for unauthorized programs

Senior engineer perspective: Years of kernel security work taught me that “safe by default” features like eBPF verifiers are high-value attack targets. The verifier processes untrusted input (eBPF bytecode) and makes security decisions—classic attack surface. These CVEs aren’t surprising; they’re inevitable. Defense in depth matters: combine verifier trust with kernel hardening, capability restrictions, and runtime auditing. I’ve seen production environments running vulnerable kernels with unprivileged eBPF enabled—that’s a local privilege escalation waiting to happen.

Additional Security Research

The academic community has identified several critical areas for eBPF security:

  • JIT Compiler Security: Studies highlight the importance of secure JIT compilation for eBPF programs (see kernel.bpf_jit_harden)
  • Kernel Memory Access: Research emphasizes careful handling of kernel memory access from eBPF programs (verifier bounds checking)
  • Supply Chain Security: eBPF programs loaded from external sources should undergo code review and static analysis

Further Reading

For deeper technical understanding:

Conclusion

eBPF transforms security monitoring from reactive log analysis to proactive, real-time threat detection. It’s not just about speed — it’s about seeing attacks that were previously invisible.

The journey from traditional monitoring to eBPF isn’t always smooth. You’ll fight with the verifier, debug kernel panics, and optimize performance. But the payoff — catching threats in milliseconds instead of hours — makes it worthwhile.

Start small. Monitor one syscall. Measure the overhead. Add detection patterns based on your actual threat model, not a generic checklist. And always, always check your kernel version first.