Skip to content

eBPF + Rust: Building Kernel-Level Security Monitoring Without Kernel Modules

Published: at 08:15 AM

eBPF + Rust: Building Kernel-Level Security Monitoring Without Kernel Modules

Introduction

Traditional security monitoring operates in user space, creating blind spots and performance bottlenecks. Kernel modules provide deep visibility but require dangerous privileged access and risk system crashes. Enter eBPF (extended Berkeley Packet Filter): a revolutionary technology that allows safe, efficient programs to run in kernel space without modifying kernel source code or loading modules.

This comprehensive guide demonstrates how to build a complete kernel-level security monitoring system using eBPF and Rust. We’ll implement system call monitoring, privilege escalation detection, container security, and intrusion detection—all while maintaining system safety through eBPF’s verification process. By the end, you’ll have a production-ready security monitoring system that operates at kernel speed with user-space safety.

eBPF Fundamentals for Security

eBPF transforms the Linux kernel into a programmable data plane for security monitoring:

Key Advantages:

Security Use Cases:

Setting Up the Rust eBPF Environment

Let’s start by building our eBPF development environment with Aya, a pure Rust eBPF framework:

// Cargo.toml
[dependencies]
aya = { version = "0.12", features = ["async_tokio"] }
aya-log = "0.2"
bytes = "1"
clap = { version = "4", features = ["derive"] }
env_logger = "0.10"
log = "0.4"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
libc = "0.2"
nix = "0.27"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[build-dependencies]
aya-build = "0.12"

// build.rs
fn main() {
    aya_build::build_probe()
        .expect("Failed to build eBPF probe");
}

Implementing System Call Monitoring

Our first eBPF program monitors system calls for suspicious activity:

// src/main.rs - User space control program
use aya::{
    include_bytes_aligned,
    maps::perf::AsyncPerfEventArray,
    programs::TracePoint,
    util::online_cpus,
    Bpf,
};
use aya_log::BpfLogger;
use bytes::BytesMut;
use log::{info, warn};
use tokio::{signal, task};

#[derive(Debug)]
#[repr(C)]
pub struct SyscallEvent {
    pub pid: u32,
    pub ppid: u32,
    pub uid: u32,
    pub gid: u32,
    pub syscall_nr: u32,
    pub timestamp: u64,
    pub comm: [u8; 16],
    pub filename: [u8; 256],
    pub args: [u64; 6],
}

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    env_logger::init();

    // Load the eBPF program
    #[cfg(debug_assertions)]
    let mut bpf = Bpf::load(include_bytes_aligned!(
        "../../target/bpfel-unknown-none/debug/security-monitor"
    ))?;
    #[cfg(not(debug_assertions))]
    let mut bpf = Bpf::load(include_bytes_aligned!(
        "../../target/bpfel-unknown-none/release/security-monitor"
    ))?;

    // Initialize logging
    if let Err(e) = BpfLogger::init(&mut bpf) {
        warn!("failed to initialize eBPF logger: {}", e);
    }

    // Attach to syscall tracepoints
    let program: &mut TracePoint = bpf.program_mut("syscall_monitor").unwrap().try_into()?;
    program.load()?;
    program.attach("syscalls", "sys_enter_openat")?;
    program.attach("syscalls", "sys_enter_execve")?;
    program.attach("syscalls", "sys_enter_connect")?;
    program.attach("syscalls", "sys_enter_clone")?;

    // Set up perf event array for receiving events
    let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut("EVENTS")?)?;

    // Spawn tasks for each CPU
    for cpu_id in online_cpus()? {
        let mut buf = perf_array.open(cpu_id, None)?;

        task::spawn(async move {
            let mut buffers = (0..10)
                .map(|_| BytesMut::with_capacity(1024))
                .collect::<Vec<_>>();

            loop {
                let events = buf.read_events(&mut buffers).await.unwrap();

                for buf in buffers.iter().take(events.read) {
                    let ptr = buf.as_ptr() as *const SyscallEvent;
                    let event = unsafe { ptr.read_unaligned() };

                    process_syscall_event(event).await;
                }
            }
        });
    }

    info!("eBPF security monitor started. Press Ctrl-C to exit.");
    signal::ctrl_c().await?;
    info!("Shutting down...");

    Ok(())
}

async fn process_syscall_event(event: SyscallEvent) {
    let comm = std::str::from_utf8(&event.comm)
        .unwrap_or("unknown")
        .trim_end_matches('\0');

    let filename = std::str::from_utf8(&event.filename)
        .unwrap_or("")
        .trim_end_matches('\0');

    // Security analysis
    let mut alerts = Vec::new();

    // Check for suspicious file access
    if is_suspicious_file_access(&event, filename) {
        alerts.push(SecurityAlert {
            alert_type: AlertType::SuspiciousFileAccess,
            severity: Severity::High,
            description: format!(
                "Process {} (PID: {}) accessing sensitive file: {}",
                comm, event.pid, filename
            ),
            mitre_technique: Some("T1005".to_string()), // Data from Local System
        });
    }

    // Check for privilege escalation attempts
    if is_privilege_escalation(&event) {
        alerts.push(SecurityAlert {
            alert_type: AlertType::PrivilegeEscalation,
            severity: Severity::Critical,
            description: format!(
                "Potential privilege escalation: {} (PID: {}) UID: {} -> GID: {}",
                comm, event.pid, event.uid, event.gid
            ),
            mitre_technique: Some("T1068".to_string()), // Exploitation for Privilege Escalation
        });
    }

    // Check for suspicious network connections
    if event.syscall_nr == libc::SYS_connect as u32 {
        if let Some(alert) = analyze_network_connection(&event, comm).await {
            alerts.push(alert);
        }
    }

    // Process alerts
    for alert in alerts {
        handle_security_alert(alert).await;
    }

    info!(
        "SYSCALL: {} [{}] PID:{} UID:{} File:{}",
        syscall_name(event.syscall_nr),
        comm,
        event.pid,
        event.uid,
        filename
    );
}

Now let’s implement the kernel-space eBPF program:

// src/bpf/security_monitor.rs - eBPF kernel program
#![no_std]
#![no_main]

use aya_bpf::{
    macros::{map, tracepoint},
    maps::PerfEventArray,
    programs::TracePointContext,
    BpfContext,
};
use aya_log_ebpf::info;

// Event structure (must match userspace)
#[repr(C)]
pub struct SyscallEvent {
    pub pid: u32,
    pub ppid: u32,
    pub uid: u32,
    pub gid: u32,
    pub syscall_nr: u32,
    pub timestamp: u64,
    pub comm: [u8; 16],
    pub filename: [u8; 256],
    pub args: [u64; 6],
}

// Perf event array for sending events to userspace
#[map(name = "EVENTS")]
static mut EVENTS: PerfEventArray<SyscallEvent> = PerfEventArray::new(0);

// Tracepoint for system call monitoring
#[tracepoint(name = "syscall_monitor")]
pub fn syscall_monitor(ctx: TracePointContext) -> u32 {
    match try_syscall_monitor(ctx) {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

fn try_syscall_monitor(ctx: TracePointContext) -> Result<u32, u32> {
    // Get syscall information
    let syscall_nr: u32 = unsafe { ctx.read_at(8)? };

    // Get process information
    let tgid = (ctx.tgid() >> 32) as u32;
    let pid = ctx.pid();

    // Skip kernel threads
    if tgid == 0 {
        return Ok(0);
    }

    // Get task struct information
    let uid = ctx.uid();
    let gid = ctx.gid();

    // Get process name
    let mut comm = [0u8; 16];
    let comm_slice = ctx.command()?;
    let len = comm_slice.len().min(15);
    comm[..len].copy_from_slice(&comm_slice[..len]);

    // Extract filename for file operations
    let mut filename = [0u8; 256];
    if is_file_syscall(syscall_nr) {
        if let Ok(fname) = extract_filename(&ctx) {
            let len = fname.len().min(255);
            filename[..len].copy_from_slice(&fname[..len]);
        }
    }

    // Extract syscall arguments
    let mut args = [0u64; 6];
    for i in 0..6 {
        args[i] = unsafe { ctx.read_at(16 + i * 8).unwrap_or(0) };
    }

    // Create event
    let event = SyscallEvent {
        pid,
        ppid: get_ppid()?,
        uid,
        gid,
        syscall_nr,
        timestamp: ctx.timestamp(),
        comm,
        filename,
        args,
    };

    // Filter events based on security rules
    if should_report_event(&event) {
        // Send event to userspace
        unsafe {
            EVENTS.output(&ctx, &event, 0);
        }

        info!(&ctx, "Security event: syscall={} pid={} uid={}",
              syscall_nr, pid, uid);
    }

    Ok(0)
}

// Security filtering logic
fn should_report_event(event: &SyscallEvent) -> bool {
    // Always report privileged operations
    if event.uid == 0 {
        return true;
    }

    // Report suspicious syscalls
    match event.syscall_nr {
        // Process creation
        libc::SYS_clone | libc::SYS_fork | libc::SYS_vfork => true,

        // Execution
        libc::SYS_execve | libc::SYS_execveat => true,

        // Network operations
        libc::SYS_socket | libc::SYS_connect | libc::SYS_bind => true,

        // File operations on sensitive paths
        libc::SYS_open | libc::SYS_openat => {
            is_sensitive_path(&event.filename)
        }

        // Memory operations
        libc::SYS_mmap | libc::SYS_mprotect => {
            // Check for executable memory allocation
            has_exec_flag(event.args[2])
        }

        _ => false,
    }
}

fn is_sensitive_path(filename: &[u8; 256]) -> bool {
    let path = match core::str::from_utf8(filename) {
        Ok(s) => s.trim_end_matches('\0'),
        Err(_) => return false,
    };

    // Sensitive system paths
    const SENSITIVE_PATHS: &[&str] = &[
        "/etc/passwd",
        "/etc/shadow",
        "/etc/sudoers",
        "/root/",
        "/var/log/",
        "/proc/",
        "/sys/",
        "/dev/mem",
        "/dev/kmem",
    ];

    for &sensitive in SENSITIVE_PATHS {
        if path.starts_with(sensitive) {
            return true;
        }
    }

    false
}

fn has_exec_flag(prot: u64) -> bool {
    const PROT_EXEC: u64 = 0x4;
    (prot & PROT_EXEC) != 0
}

fn get_ppid() -> Result<u32, u32> {
    // Implementation would use BPF helpers to get parent PID
    // This is simplified for demonstration
    Ok(1)
}

fn is_file_syscall(syscall_nr: u32) -> bool {
    matches!(
        syscall_nr,
        libc::SYS_open | libc::SYS_openat | libc::SYS_creat
    )
}

fn extract_filename(ctx: &TracePointContext) -> Result<&[u8], u32> {
    // Extract filename from syscall arguments
    // Implementation depends on specific syscall
    Ok(b"")
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Advanced Container Security Monitoring

Let’s extend our system to detect container escapes and suspicious container behavior:

// Container-aware security monitoring
use aya_bpf::{
    helpers::{bpf_get_current_cgroup_id, bpf_probe_read_kernel_str},
    macros::kprobe,
    programs::ProbeContext,
};

#[repr(C)]
pub struct ContainerEvent {
    pub pid: u32,
    pub ppid: u32,
    pub uid: u32,
    pub cgroup_id: u64,
    pub event_type: u32,
    pub timestamp: u64,
    pub comm: [u8; 16],
    pub namespace_info: NamespaceInfo,
    pub security_context: SecurityContext,
}

#[repr(C)]
pub struct NamespaceInfo {
    pub mnt_ns: u64,
    pub pid_ns: u64,
    pub net_ns: u64,
    pub user_ns: u64,
    pub uts_ns: u64,
    pub ipc_ns: u64,
}

#[repr(C)]
pub struct SecurityContext {
    pub selinux_context: [u8; 64],
    pub apparmor_profile: [u8; 64],
    pub capabilities: u64,
    pub seccomp_mode: u32,
}

#[kprobe(name = "container_monitor")]
pub fn container_monitor(ctx: ProbeContext) -> u32 {
    match try_container_monitor(ctx) {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

fn try_container_monitor(ctx: ProbeContext) -> Result<u32, u32> {
    let pid = ctx.pid();
    let uid = ctx.uid();
    let cgroup_id = unsafe { bpf_get_current_cgroup_id() };

    // Check if we're in a container
    if !is_in_container(cgroup_id) {
        return Ok(0);
    }

    // Collect namespace information
    let ns_info = collect_namespace_info(&ctx)?;

    // Check for container escape attempts
    if detect_container_escape(&ctx, &ns_info) {
        let event = ContainerEvent {
            pid,
            ppid: get_ppid()?,
            uid,
            cgroup_id,
            event_type: 1, // CONTAINER_ESCAPE
            timestamp: ctx.timestamp(),
            comm: get_comm(&ctx)?,
            namespace_info: ns_info,
            security_context: collect_security_context(&ctx)?,
        };

        unsafe {
            CONTAINER_EVENTS.output(&ctx, &event, 0);
        }

        info!(&ctx, "Container escape detected: PID={} UID={}", pid, uid);
    }

    Ok(0)
}

fn is_in_container(cgroup_id: u64) -> bool {
    // Check if cgroup ID indicates container environment
    // Docker containers typically have specific cgroup patterns
    cgroup_id != 0 && (cgroup_id & 0xFF00000000000000) != 0
}

fn detect_container_escape(ctx: &ProbeContext, ns_info: &NamespaceInfo) -> bool {
    // Detect attempts to break out of container namespaces

    // Check for suspicious namespace operations
    if is_namespace_escape_attempt(ns_info) {
        return true;
    }

    // Check for privileged filesystem access
    if is_host_filesystem_access(ctx) {
        return true;
    }

    // Check for kernel module loading
    if is_kernel_module_operation(ctx) {
        return true;
    }

    false
}

fn is_namespace_escape_attempt(ns_info: &NamespaceInfo) -> bool {
    // Look for attempts to join host namespaces
    // This is simplified - real implementation would track initial namespaces
    ns_info.mnt_ns == 0 || ns_info.pid_ns == 0 || ns_info.net_ns == 0
}

Process Injection and Malware Detection

Implement detection for advanced attack techniques:

// Process injection detection
#[map(name = "PROCESS_MAP")]
static mut PROCESS_MAP: HashMap<u32, ProcessInfo> = HashMap::new(0);

#[repr(C)]
pub struct ProcessInfo {
    pub pid: u32,
    pub ppid: u32,
    pub start_time: u64,
    pub binary_hash: [u8; 32],
    pub memory_regions: u32,
    pub suspicious_score: u32,
}

#[kprobe(name = "process_injection_monitor")]
pub fn process_injection_monitor(ctx: ProbeContext) -> u32 {
    match try_process_injection_monitor(ctx) {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

fn try_process_injection_monitor(ctx: ProbeContext) -> Result<u32, u32> {
    let pid = ctx.pid();

    // Monitor memory operations that could indicate injection
    if is_memory_injection_syscall(&ctx) {
        let target_pid = get_target_pid(&ctx)?;

        // Check if injecting into different process
        if target_pid != pid && target_pid != 0 {
            // Analyze injection pattern
            if is_suspicious_injection(&ctx, pid, target_pid) {
                report_process_injection(&ctx, pid, target_pid)?;
            }
        }
    }

    // Monitor for hollow process creation
    if is_process_hollowing(&ctx) {
        report_process_hollowing(&ctx)?;
    }

    Ok(0)
}

fn is_memory_injection_syscall(ctx: &ProbeContext) -> bool {
    // Check for syscalls commonly used in process injection
    let syscall_nr = ctx.arg::<u32>(0).unwrap_or(0);

    matches!(
        syscall_nr,
        libc::SYS_ptrace |     // Process tracing
        libc::SYS_process_vm_writev | // Cross-process memory write
        libc::SYS_process_vm_readv |  // Cross-process memory read
        libc::SYS_mmap |       // Memory mapping
        libc::SYS_mprotect     // Memory protection changes
    )
}

fn is_suspicious_injection(ctx: &ProbeContext, injector_pid: u32, target_pid: u32) -> bool {
    // Analyze injection characteristics

    // Check if target is privileged process
    if is_privileged_process(target_pid) {
        return true;
    }

    // Check injection patterns
    let flags = ctx.arg::<u64>(2).unwrap_or(0);

    // Executable memory allocation in foreign process
    if (flags & libc::PROT_EXEC as u64) != 0 {
        return true;
    }

    // Frequent small allocations (shellcode pattern)
    if is_shellcode_pattern(ctx, injector_pid) {
        return true;
    }

    false
}

fn is_shellcode_pattern(ctx: &ProbeContext, pid: u32) -> bool {
    // Look for patterns typical of shellcode injection
    let size = ctx.arg::<usize>(1).unwrap_or(0);

    // Small allocations (typical shellcode size)
    if size < 4096 && size > 100 {
        // Check allocation frequency
        if get_recent_allocation_count(pid) > 10 {
            return true;
        }
    }

    false
}

// Network connection monitoring for C2 detection
#[kprobe(name = "network_monitor")]
pub fn network_monitor(ctx: ProbeContext) -> u32 {
    match try_network_monitor(ctx) {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

fn try_network_monitor(ctx: ProbeContext) -> Result<u32, u32> {
    let pid = ctx.pid();
    let syscall_nr = ctx.arg::<u32>(0).unwrap_or(0);

    match syscall_nr {
        libc::SYS_connect => {
            let sockaddr = ctx.arg::<*const libc::sockaddr>(1).ok_or(1u32)?;

            // Extract connection details safely
            if let Ok(conn_info) = extract_connection_info(sockaddr) {
                // Check against threat intelligence
                if is_malicious_destination(&conn_info) {
                    report_malicious_connection(&ctx, pid, &conn_info)?;
                }

                // Check for beaconing patterns
                if is_c2_beaconing(pid, &conn_info) {
                    report_c2_beaconing(&ctx, pid, &conn_info)?;
                }
            }
        }

        libc::SYS_socket => {
            let domain = ctx.arg::<i32>(0).unwrap_or(0);
            let socket_type = ctx.arg::<i32>(1).unwrap_or(0);

            // Monitor for suspicious socket creation
            if is_suspicious_socket(domain, socket_type) {
                report_suspicious_socket(&ctx, pid, domain, socket_type)?;
            }
        }

        _ => {}
    }

    Ok(0)
}

#[repr(C)]
pub struct ConnectionInfo {
    pub family: u16,
    pub port: u16,
    pub addr: [u8; 16], // IPv4/IPv6 address
}

fn extract_connection_info(sockaddr: *const libc::sockaddr) -> Result<ConnectionInfo, u32> {
    // Safely read socket address structure
    let mut conn_info = ConnectionInfo {
        family: 0,
        port: 0,
        addr: [0; 16],
    };

    // Read address family
    unsafe {
        if bpf_probe_read_kernel(&mut conn_info.family as *mut _ as *mut c_void,
                               2, sockaddr as *const c_void).is_err() {
            return Err(1);
        }
    }

    match conn_info.family as u32 {
        libc::AF_INET => {
            // IPv4 address
            unsafe {
                let sin = sockaddr as *const libc::sockaddr_in;
                if bpf_probe_read_kernel(&mut conn_info.port as *mut _ as *mut c_void,
                                       2, &(*sin).sin_port as *const _ as *const c_void).is_err() {
                    return Err(1);
                }
                if bpf_probe_read_kernel(&mut conn_info.addr[0] as *mut _ as *mut c_void,
                                       4, &(*sin).sin_addr as *const _ as *const c_void).is_err() {
                    return Err(1);
                }
            }
        }

        libc::AF_INET6 => {
            // IPv6 address
            unsafe {
                let sin6 = sockaddr as *const libc::sockaddr_in6;
                if bpf_probe_read_kernel(&mut conn_info.port as *mut _ as *mut c_void,
                                       2, &(*sin6).sin6_port as *const _ as *const c_void).is_err() {
                    return Err(1);
                }
                if bpf_probe_read_kernel(&mut conn_info.addr[0] as *mut _ as *mut c_void,
                                       16, &(*sin6).sin6_addr as *const _ as *const c_void).is_err() {
                    return Err(1);
                }
            }
        }

        _ => return Err(1),
    }

    Ok(conn_info)
}

User-Space Analysis Engine

Now let’s implement the sophisticated user-space analysis engine:

// src/analyzer.rs - Security analysis engine
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityAlert {
    pub alert_type: AlertType,
    pub severity: Severity,
    pub description: String,
    pub mitre_technique: Option<String>,
    pub timestamp: u64,
    pub process_info: ProcessContext,
    pub evidence: Vec<Evidence>,
    pub confidence_score: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AlertType {
    SuspiciousFileAccess,
    PrivilegeEscalation,
    ProcessInjection,
    ContainerEscape,
    MaliciousNetwork,
    C2Beaconing,
    Cryptomining,
    RansomwareActivity,
    DataExfiltration,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Severity {
    Low,
    Medium,
    High,
    Critical,
}

pub struct SecurityAnalyzer {
    // Process tracking
    processes: RwLock<HashMap<u32, ProcessTracker>>,

    // Network analysis
    network_tracker: RwLock<NetworkTracker>,

    // Behavioral baselines
    behavioral_baselines: RwLock<BehavioralBaselines>,

    // Threat intelligence
    threat_intel: RwLock<ThreatIntelligence>,

    // Alert correlation
    alert_correlator: AlertCorrelator,
}

#[derive(Debug, Clone)]
struct ProcessTracker {
    pid: u32,
    ppid: u32,
    start_time: Instant,
    command: String,
    uid: u32,
    gid: u32,

    // Behavioral tracking
    syscalls: VecDeque<SyscallRecord>,
    files_accessed: HashMap<String, u32>,
    network_connections: Vec<ConnectionRecord>,
    memory_allocations: Vec<MemoryAllocation>,

    // Risk scoring
    risk_score: f32,
    anomaly_flags: Vec<AnomalyFlag>,
}

impl SecurityAnalyzer {
    pub fn new() -> Self {
        Self {
            processes: RwLock::new(HashMap::new()),
            network_tracker: RwLock::new(NetworkTracker::new()),
            behavioral_baselines: RwLock::new(BehavioralBaselines::new()),
            threat_intel: RwLock::new(ThreatIntelligence::new()),
            alert_correlator: AlertCorrelator::new(),
        }
    }

    pub async fn analyze_syscall_event(&self, event: SyscallEvent) -> Option<SecurityAlert> {
        // Update process tracking
        self.update_process_tracking(event.clone()).await;

        // Analyze individual event
        let mut alerts = Vec::new();

        // File access analysis
        if let Some(alert) = self.analyze_file_access(&event).await {
            alerts.push(alert);
        }

        // Process creation analysis
        if event.syscall_nr == libc::SYS_execve as u32 {
            if let Some(alert) = self.analyze_process_creation(&event).await {
                alerts.push(alert);
            }
        }

        // Network connection analysis
        if event.syscall_nr == libc::SYS_connect as u32 {
            if let Some(alert) = self.analyze_network_connection(&event).await {
                alerts.push(alert);
            }
        }

        // Behavioral analysis
        if let Some(alert) = self.analyze_behavioral_anomaly(&event).await {
            alerts.push(alert);
        }

        // Return highest severity alert
        alerts.into_iter().max_by_key(|a| match a.severity {
            Severity::Critical => 4,
            Severity::High => 3,
            Severity::Medium => 2,
            Severity::Low => 1,
        })
    }

    async fn analyze_file_access(&self, event: &SyscallEvent) -> Option<SecurityAlert> {
        let filename = std::str::from_utf8(&event.filename)
            .ok()?
            .trim_end_matches('\0');

        if filename.is_empty() {
            return None;
        }

        // Check against sensitive files
        let sensitive_files = [
            "/etc/passwd",
            "/etc/shadow",
            "/etc/sudoers",
            "/root/.ssh/",
            "/home/*/.ssh/",
            "/var/log/auth.log",
            "/var/log/secure",
        ];

        for pattern in &sensitive_files {
            if filename_matches(filename, pattern) {
                // Check if access is legitimate
                if !is_legitimate_access(event, filename).await {
                    return Some(SecurityAlert {
                        alert_type: AlertType::SuspiciousFileAccess,
                        severity: Severity::High,
                        description: format!(
                            "Suspicious access to sensitive file {} by process {} (PID: {})",
                            filename,
                            get_process_name(&event.comm),
                            event.pid
                        ),
                        mitre_technique: Some("T1005".to_string()),
                        timestamp: event.timestamp,
                        process_info: ProcessContext::from_event(event),
                        evidence: vec![
                            Evidence::FileAccess {
                                path: filename.to_string(),
                                action: "open".to_string(),
                            }
                        ],
                        confidence_score: 0.85,
                    });
                }
            }
        }

        // Check for ransomware file patterns
        if is_ransomware_pattern(filename) {
            return Some(SecurityAlert {
                alert_type: AlertType::RansomwareActivity,
                severity: Severity::Critical,
                description: format!(
                    "Potential ransomware activity: {} creating/accessing {}",
                    get_process_name(&event.comm),
                    filename
                ),
                mitre_technique: Some("T1486".to_string()),
                timestamp: event.timestamp,
                process_info: ProcessContext::from_event(event),
                evidence: vec![
                    Evidence::FileAccess {
                        path: filename.to_string(),
                        action: "encrypt".to_string(),
                    }
                ],
                confidence_score: 0.95,
            });
        }

        None
    }

    async fn analyze_process_creation(&self, event: &SyscallEvent) -> Option<SecurityAlert> {
        let comm = get_process_name(&event.comm);

        // Check for suspicious process spawning patterns
        let processes = self.processes.read().await;

        if let Some(parent) = processes.get(&event.ppid) {
            // Check for process injection via parent analysis
            if is_process_injection_pattern(parent, comm) {
                return Some(SecurityAlert {
                    alert_type: AlertType::ProcessInjection,
                    severity: Severity::High,
                    description: format!(
                        "Potential process injection: {} spawned unusual child process {}",
                        parent.command, comm
                    ),
                    mitre_technique: Some("T1055".to_string()),
                    timestamp: event.timestamp,
                    process_info: ProcessContext::from_event(event),
                    evidence: vec![
                        Evidence::ProcessCreation {
                            parent_pid: event.ppid,
                            child_pid: event.pid,
                            command: comm.to_string(),
                        }
                    ],
                    confidence_score: 0.75,
                });
            }

            // Check for privilege escalation
            if event.uid == 0 && parent.uid != 0 {
                return Some(SecurityAlert {
                    alert_type: AlertType::PrivilegeEscalation,
                    severity: Severity::Critical,
                    description: format!(
                        "Privilege escalation detected: {} (UID: {}) spawned {} (UID: 0)",
                        parent.command, parent.uid, comm
                    ),
                    mitre_technique: Some("T1068".to_string()),
                    timestamp: event.timestamp,
                    process_info: ProcessContext::from_event(event),
                    evidence: vec![
                        Evidence::PrivilegeChange {
                            from_uid: parent.uid,
                            to_uid: event.uid,
                        }
                    ],
                    confidence_score: 0.90,
                });
            }
        }

        // Check for suspicious binaries
        if is_suspicious_binary(comm) {
            return Some(SecurityAlert {
                alert_type: AlertType::SuspiciousFileAccess,
                severity: Severity::Medium,
                description: format!("Execution of suspicious binary: {}", comm),
                mitre_technique: Some("T1059".to_string()),
                timestamp: event.timestamp,
                process_info: ProcessContext::from_event(event),
                evidence: vec![
                    Evidence::ProcessCreation {
                        parent_pid: event.ppid,
                        child_pid: event.pid,
                        command: comm.to_string(),
                    }
                ],
                confidence_score: 0.65,
            });
        }

        None
    }

    async fn analyze_network_connection(&self, event: &SyscallEvent) -> Option<SecurityAlert> {
        // Extract connection details from syscall arguments
        let dest_ip = parse_ip_from_args(&event.args)?;
        let dest_port = parse_port_from_args(&event.args)?;

        // Check against threat intelligence
        let threat_intel = self.threat_intel.read().await;

        if threat_intel.is_malicious_ip(&dest_ip) {
            return Some(SecurityAlert {
                alert_type: AlertType::MaliciousNetwork,
                severity: Severity::High,
                description: format!(
                    "Connection to known malicious IP {} by process {} (PID: {})",
                    dest_ip,
                    get_process_name(&event.comm),
                    event.pid
                ),
                mitre_technique: Some("T1071".to_string()),
                timestamp: event.timestamp,
                process_info: ProcessContext::from_event(event),
                evidence: vec![
                    Evidence::NetworkConnection {
                        destination_ip: dest_ip.clone(),
                        destination_port: dest_port,
                        protocol: "TCP".to_string(),
                    }
                ],
                confidence_score: 0.90,
            });
        }

        // Check for C2 beaconing patterns
        let mut network_tracker = self.network_tracker.write().await;

        if network_tracker.is_beaconing_pattern(event.pid, &dest_ip, dest_port) {
            return Some(SecurityAlert {
                alert_type: AlertType::C2Beaconing,
                severity: Severity::Critical,
                description: format!(
                    "C2 beaconing detected: {} making regular connections to {}:{}",
                    get_process_name(&event.comm),
                    dest_ip,
                    dest_port
                ),
                mitre_technique: Some("T1071.001".to_string()),
                timestamp: event.timestamp,
                process_info: ProcessContext::from_event(event),
                evidence: vec![
                    Evidence::BeaconingPattern {
                        destination_ip: dest_ip,
                        destination_port: dest_port,
                        connection_count: network_tracker.get_connection_count(event.pid),
                        interval_pattern: network_tracker.get_interval_pattern(event.pid),
                    }
                ],
                confidence_score: 0.95,
            });
        }

        None
    }
}

// Helper functions for analysis
fn filename_matches(filename: &str, pattern: &str) -> bool {
    if pattern.contains('*') {
        // Simple glob matching
        let prefix = pattern.split('*').next().unwrap_or("");
        filename.starts_with(prefix)
    } else {
        filename.starts_with(pattern)
    }
}

fn is_ransomware_pattern(filename: &str) -> bool {
    // Check for common ransomware file extensions
    let ransomware_extensions = [
        ".encrypted", ".locked", ".crypto", ".crypt",
        ".vault", ".petya", ".cerber", ".locky",
        ".zepto", ".thor", ".sage", ".spora",
    ];

    for ext in &ransomware_extensions {
        if filename.ends_with(ext) {
            return true;
        }
    }

    // Check for ransom note files
    let ransom_notes = [
        "README_DECRYPT",
        "DECRYPT_INSTRUCTION",
        "HOW_TO_RECOVER",
        "FILES_ENCRYPTED",
    ];

    for note in &ransom_notes {
        if filename.contains(note) {
            return true;
        }
    }

    false
}

fn is_suspicious_binary(command: &str) -> bool {
    // Known malicious or suspicious binaries
    let suspicious_binaries = [
        "nc", "netcat", "ncat",           // Network tools
        "wget", "curl",                    // Download tools
        "python", "python3", "perl",      // Scripting (context-dependent)
        "base64", "xxd", "hexdump",       // Encoding tools
        "dd", "memdump",                   // Memory/disk tools
        "tcpdump", "wireshark", "tshark", // Network sniffing
        "metasploit", "meterpreter",      // Penetration testing
    ];

    for binary in &suspicious_binaries {
        if command.contains(binary) {
            return true;
        }
    }

    false
}

async fn is_legitimate_access(event: &SyscallEvent, filename: &str) -> bool {
    // Implement whitelist logic for legitimate access patterns

    // System processes accessing system files
    if event.uid == 0 && is_system_process(&event.comm) {
        return true;
    }

    // Known applications accessing their config files
    if is_application_config_access(&event.comm, filename) {
        return true;
    }

    false
}

fn is_system_process(comm: &[u8; 16]) -> bool {
    let process_name = std::str::from_utf8(comm)
        .unwrap_or("")
        .trim_end_matches('\0');

    let system_processes = [
        "systemd", "init", "kthreadd", "sshd",
        "rsyslog", "dbus", "NetworkManager",
    ];

    system_processes.contains(&process_name)
}

Performance Optimization

Let’s optimize our eBPF programs for production deployment:

// Performance-optimized eBPF maps and helpers

// Use per-CPU arrays for better performance
#[map(name = "STATS")]
static mut STATS: PerCpuArray<ProcessStats> = PerCpuArray::with_max_entries(1024, 0);

// Use LRU hash map for automatic cleanup
#[map(name = "PROCESS_CACHE")]
static mut PROCESS_CACHE: LruHashMap<u32, CachedProcessInfo> =
    LruHashMap::with_max_entries(65536, 0);

// Efficient string comparison for path matching
#[inline(always)]
fn fast_string_match(haystack: &[u8], needle: &[u8]) -> bool {
    if needle.len() > haystack.len() {
        return false;
    }

    // Unrolled comparison for common cases
    match needle.len() {
        1 => haystack[0] == needle[0],
        2 => haystack[0] == needle[0] && haystack[1] == needle[1],
        3 => haystack[0] == needle[0] &&
             haystack[1] == needle[1] &&
             haystack[2] == needle[2],
        _ => {
            // Use BPF helper for longer strings
            unsafe {
                bpf_strncmp(
                    haystack.as_ptr() as *const c_char,
                    needle.as_ptr() as *const c_char,
                    needle.len() as u32
                ) == 0
            }
        }
    }
}

// Optimized event filtering with bloom filter
#[map(name = "BLOOM_FILTER")]
static mut BLOOM_FILTER: Array<u64> = Array::with_max_entries(1024, 0);

fn bloom_test(item: &[u8]) -> bool {
    let hash1 = hash_fnv1a(item, 0x811c9dc5);
    let hash2 = hash_fnv1a(item, 0x01000193);

    let idx1 = (hash1 % 1024) as u32;
    let idx2 = (hash2 % 1024) as u32;

    unsafe {
        let bit1 = BLOOM_FILTER.get(idx1 / 64).map(|v| *v & (1u64 << (idx1 % 64))).unwrap_or(0);
        let bit2 = BLOOM_FILTER.get(idx2 / 64).map(|v| *v & (1u64 << (idx2 % 64))).unwrap_or(0);

        bit1 != 0 && bit2 != 0
    }
}

#[inline(always)]
fn hash_fnv1a(data: &[u8], basis: u32) -> u32 {
    let mut hash = basis;

    for &byte in data.iter().take(64) { // Limit for eBPF
        hash ^= byte as u32;
        hash = hash.wrapping_mul(0x01000193);
    }

    hash
}

// Rate limiting using token bucket
#[map(name = "RATE_LIMIT")]
static mut RATE_LIMIT: HashMap<u32, TokenBucket> = HashMap::with_max_entries(10000, 0);

#[repr(C)]
struct TokenBucket {
    tokens: u32,
    last_refill: u64,
    max_tokens: u32,
    refill_rate: u32, // tokens per second
}

fn rate_limit_check(pid: u32, current_time: u64) -> bool {
    let bucket = unsafe {
        RATE_LIMIT.get_ptr_mut(&pid)
            .map(|ptr| &mut *ptr)
            .unwrap_or_else(|| {
                let new_bucket = TokenBucket {
                    tokens: 10,
                    last_refill: current_time,
                    max_tokens: 10,
                    refill_rate: 1,
                };
                RATE_LIMIT.insert(&pid, &new_bucket, 0).ok();
                RATE_LIMIT.get_ptr_mut(&pid).map(|ptr| &mut *ptr).unwrap()
            })
    };

    // Refill tokens based on elapsed time
    let elapsed = current_time - bucket.last_refill;
    let new_tokens = (elapsed / 1_000_000_000) as u32 * bucket.refill_rate; // Convert ns to seconds

    if new_tokens > 0 {
        bucket.tokens = (bucket.tokens + new_tokens).min(bucket.max_tokens);
        bucket.last_refill = current_time;
    }

    // Consume token if available
    if bucket.tokens > 0 {
        bucket.tokens -= 1;
        true
    } else {
        false
    }
}

Real-World Deployment and Monitoring

Finally, let’s implement comprehensive monitoring and deployment:

// src/deployment.rs - Production deployment utilities
use prometheus::{Counter, Histogram, Gauge, Encoder, TextEncoder};
use std::sync::Arc;
use tokio::sync::RwLock;

pub struct EbpfMonitor {
    // Performance metrics
    events_processed: Counter,
    event_processing_duration: Histogram,
    memory_usage: Gauge,
    cpu_usage: Gauge,

    // Security metrics
    alerts_generated: Counter,
    false_positive_rate: Gauge,
    detection_latency: Histogram,

    // System health
    ebpf_program_status: Gauge,
    kernel_version_compatibility: Gauge,
}

impl EbpfMonitor {
    pub fn new() -> Self {
        let events_processed = Counter::new(
            "ebpf_events_processed_total",
            "Total number of eBPF events processed"
        ).unwrap();

        let event_processing_duration = Histogram::with_opts(
            prometheus::HistogramOpts::new(
                "ebpf_event_processing_duration_seconds",
                "Time spent processing eBPF events"
            ).buckets(vec![0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1])
        ).unwrap();

        // Register metrics...
        prometheus::register(Box::new(events_processed.clone())).unwrap();
        prometheus::register(Box::new(event_processing_duration.clone())).unwrap();

        Self {
            events_processed,
            event_processing_duration,
            // ... other metrics
        }
    }

    pub fn record_event_processed(&self, duration: std::time::Duration) {
        self.events_processed.inc();
        self.event_processing_duration.observe(duration.as_secs_f64());
    }

    pub async fn collect_metrics(&self) -> String {
        let encoder = TextEncoder::new();
        let metric_families = prometheus::gather();
        encoder.encode_to_string(&metric_families).unwrap()
    }
}

// Health checking for eBPF programs
pub struct HealthChecker {
    bpf: Arc<RwLock<Bpf>>,
    last_event_time: Arc<RwLock<std::time::Instant>>,
}

impl HealthChecker {
    pub async fn check_health(&self) -> HealthStatus {
        let mut issues = Vec::new();

        // Check if eBPF programs are still loaded
        if !self.check_programs_loaded().await {
            issues.push("eBPF programs not loaded".to_string());
        }

        // Check event processing
        let last_event = *self.last_event_time.read().await;
        if last_event.elapsed() > std::time::Duration::from_secs(60) {
            issues.push("No events received in last 60 seconds".to_string());
        }

        // Check kernel compatibility
        if !self.check_kernel_compatibility().await {
            issues.push("Kernel version incompatibility detected".to_string());
        }

        if issues.is_empty() {
            HealthStatus::Healthy
        } else {
            HealthStatus::Unhealthy(issues)
        }
    }

    async fn check_programs_loaded(&self) -> bool {
        let bpf = self.bpf.read().await;

        // Check each attached program
        for (name, program) in bpf.programs() {
            if let Ok(tracepoint) = program.try_into() as Result<&TracePoint, _> {
                // Check if program is still attached
                if !self.is_program_attached(tracepoint) {
                    log::warn!("Program {} is not attached", name);
                    return false;
                }
            }
        }

        true
    }
}

#[derive(Debug)]
pub enum HealthStatus {
    Healthy,
    Degraded(Vec<String>),
    Unhealthy(Vec<String>),
}

// Automatic recovery mechanisms
pub struct RecoveryManager {
    bpf: Arc<RwLock<Bpf>>,
    health_checker: HealthChecker,
    recovery_attempts: Arc<RwLock<u32>>,
}

impl RecoveryManager {
    pub async fn start_monitoring(&self) {
        let mut interval = tokio::time::interval(std::time::Duration::from_secs(30));

        loop {
            interval.tick().await;

            match self.health_checker.check_health().await {
                HealthStatus::Healthy => {
                    // Reset recovery attempts counter
                    *self.recovery_attempts.write().await = 0;
                }

                HealthStatus::Degraded(issues) => {
                    log::warn!("System degraded: {:?}", issues);
                    // Attempt soft recovery
                    self.attempt_soft_recovery().await;
                }

                HealthStatus::Unhealthy(issues) => {
                    log::error!("System unhealthy: {:?}", issues);

                    let attempts = *self.recovery_attempts.read().await;
                    if attempts < 3 {
                        self.attempt_hard_recovery().await;
                        *self.recovery_attempts.write().await += 1;
                    } else {
                        log::error!("Max recovery attempts reached, manual intervention required");
                        // Send alert to operations team
                        self.send_critical_alert(issues).await;
                    }
                }
            }
        }
    }

    async fn attempt_soft_recovery(&self) {
        log::info!("Attempting soft recovery...");

        // Restart event processing
        // Clear internal state
        // Reload configuration
    }

    async fn attempt_hard_recovery(&self) {
        log::info!("Attempting hard recovery...");

        // Detach all programs
        // Reload eBPF programs
        // Reattach to tracepoints
        // Reset all state
    }
}

Conclusion

eBPF with Rust provides unprecedented capabilities for kernel-level security monitoring:

Our implementation demonstrates:

Key benefits achieved:

The complete implementation is available on GitHub, including Docker containers for easy deployment and integration examples for popular SIEM platforms.

Next Steps

eBPF represents the future of kernel-level security monitoring—and with Rust, we can build it safely and efficiently.