Security Monitoring with eBPF: LSM Hooks and Beyond¶
eBPF enables powerful security monitoring capabilities by intercepting security-relevant events at the kernel level. This guide covers Linux Security Module (LSM) hooks, security event correlation, and building comprehensive security monitoring tools.
🛡️ Linux Security Modules (LSM) Overview¶
LSM provides a framework for implementing security policies in the Linux kernel. eBPF programs can attach to LSM hooks to monitor and potentially block security-relevant operations.
LSM Hook Points¶
graph TD
A[User Application] --> B[System Call]
B --> C[Kernel Function]
C --> D{LSM Hook}
D -->|Allow| E[Continue Operation]
D -->|Deny| F[Return Error]
D --> G[eBPF Security Program]
G --> H[Log/Alert/Block]
style D fill:#f3e5f5
style G fill:#e8f5e8
style H fill:#fff3e0
Common LSM Hooks¶
| Hook | Purpose | Use Cases |
|---|---|---|
file_open |
File access control | Monitor sensitive file access |
file_permission |
File permission checks | Detect privilege escalation |
task_alloc |
Process creation | Track process lineage |
socket_create |
Network socket creation | Monitor network activity |
ptrace_access_check |
Debugging access | Detect debugging attempts |
kernel_module_request |
Module loading | Monitor kernel modifications |
🔍 Complete Security Monitoring Examples¶
1. File Access Monitor with LSM¶
// lsm_file_monitor.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#define MAX_FILENAME_LEN 256
#define MAX_COMM_LEN 16
struct file_access_event {
u32 pid;
u32 tid;
u32 uid;
u32 gid;
char comm[MAX_COMM_LEN];
char filename[MAX_FILENAME_LEN];
u32 mode; // Access mode (read/write/execute)
u64 timestamp;
u32 action; // 0=allow, 1=deny
u32 inode;
u32 dev;
};
// Event ring buffer
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} security_events SEC(".maps");
// Sensitive paths to monitor
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, char[MAX_FILENAME_LEN]);
__type(value, u32); // sensitivity level
__uint(max_entries, 1000);
} sensitive_paths SEC(".maps");
// Process whitelist
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, char[MAX_COMM_LEN]);
__type(value, u32); // privilege level
__uint(max_entries, 100);
} process_whitelist SEC(".maps");
static inline int is_sensitive_path(const char *path) {
// Check if path matches sensitive patterns
u32 *sensitivity = bpf_map_lookup_elem(&sensitive_paths, path);
return sensitivity ? *sensitivity : 0;
}
static inline int is_whitelisted_process(const char *comm) {
u32 *privilege = bpf_map_lookup_elem(&process_whitelist, comm);
return privilege ? *privilege : 0;
}
static inline void log_file_access(struct file *file, u32 mode, u32 action) {
struct file_access_event *event;
struct task_struct *task;
const struct cred *cred;
event = bpf_ringbuf_reserve(&security_events, sizeof(*event), 0);
if (!event)
return;
task = (struct task_struct *)bpf_get_current_task();
cred = BPF_CORE_READ(task, cred);
// Fill basic process information
event->pid = bpf_get_current_pid_tgid() >> 32;
event->tid = bpf_get_current_pid_tgid() & 0xFFFFFFFF;
event->uid = BPF_CORE_READ(cred, uid.val);
event->gid = BPF_CORE_READ(cred, gid.val);
event->timestamp = bpf_ktime_get_ns();
event->mode = mode;
event->action = action;
bpf_get_current_comm(&event->comm, sizeof(event->comm));
// Get file information
if (file) {
struct dentry *dentry = BPF_CORE_READ(file, f_path.dentry);
struct inode *inode = BPF_CORE_READ(dentry, d_inode);
if (inode) {
event->inode = BPF_CORE_READ(inode, i_ino);
event->dev = BPF_CORE_READ(inode, i_sb, s_dev);
}
// Get filename
BPF_CORE_READ_STR_INTO(&event->filename, dentry, d_name.name);
}
bpf_ringbuf_submit(event, 0);
}
SEC("lsm/file_open")
int BPF_PROG(lsm_file_open, struct file *file) {
char filename[MAX_FILENAME_LEN];
char comm[MAX_COMM_LEN];
if (!file)
return 0;
// Get current process name
bpf_get_current_comm(&comm, sizeof(comm));
// Get filename
struct dentry *dentry = BPF_CORE_READ(file, f_path.dentry);
BPF_CORE_READ_STR_INTO(&filename, dentry, d_name.name);
// Check if this is a sensitive path
int sensitivity = is_sensitive_path(filename);
if (sensitivity == 0)
return 0; // Not sensitive, allow
// Check if process is whitelisted
int privilege = is_whitelisted_process(comm);
// Log the access attempt
log_file_access(file, 0, 0); // mode=0 (open), action=0 (allow)
// Security policy: block non-whitelisted processes from sensitive files
if (sensitivity > privilege) {
log_file_access(file, 0, 1); // action=1 (deny)
return -EPERM; // Deny access
}
return 0; // Allow access
}
SEC("lsm/file_permission")
int BPF_PROG(lsm_file_permission, struct file *file, int mask) {
char filename[MAX_FILENAME_LEN];
char comm[MAX_COMM_LEN];
if (!file)
return 0;
bpf_get_current_comm(&comm, sizeof(comm));
struct dentry *dentry = BPF_CORE_READ(file, f_path.dentry);
BPF_CORE_READ_STR_INTO(&filename, dentry, d_name.name);
// Monitor write/execute permissions to sensitive files
if (mask & (MAY_WRITE | MAY_EXEC)) {
int sensitivity = is_sensitive_path(filename);
if (sensitivity > 0) {
log_file_access(file, mask, 0);
}
}
return 0;
}
char _license[] SEC("license") = "GPL";
2. Process Execution Security Monitor¶
// lsm_process_monitor.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
struct process_exec_event {
u32 pid;
u32 ppid;
u32 uid;
u32 gid;
char comm[16];
char parent_comm[16];
char filename[256];
char args[512];
u64 timestamp;
u32 suspicious_score;
u32 action; // 0=allow, 1=block
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} process_events SEC(".maps");
// Suspicious process patterns
struct suspicious_pattern {
char name_pattern[32];
u32 score;
u32 enabled;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, u32);
__type(value, struct suspicious_pattern);
__uint(max_entries, 100);
} suspicious_patterns SEC(".maps");
// Process execution statistics
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32); // UID
__type(value, u64); // execution count
__uint(max_entries, 1000);
} user_exec_stats SEC(".maps");
static inline u32 calculate_suspicion_score(const char *filename, const char *comm, u32 uid) {
u32 score = 0;
// Check for suspicious patterns
#pragma unroll
for (u32 i = 0; i < 20; i++) { // Check first 20 patterns
struct suspicious_pattern *pattern = bpf_map_lookup_elem(&suspicious_patterns, &i);
if (!pattern || !pattern->enabled)
continue;
// Simple substring check (simplified for eBPF)
if (bpf_strncmp(comm, pattern->name_pattern, 16) == 0) {
score += pattern->score;
}
}
// Unusual execution patterns
u64 *exec_count = bpf_map_lookup_elem(&user_exec_stats, &uid);
if (!exec_count) {
// First execution by this user - slightly suspicious
score += 10;
u64 initial_count = 1;
bpf_map_update_elem(&user_exec_stats, &uid, &initial_count, BPF_ANY);
} else {
(*exec_count)++;
bpf_map_update_elem(&user_exec_stats, &uid, exec_count, BPF_EXIST);
}
// Root execution by non-root user (privilege escalation)
if (uid == 0) {
score += 50;
}
// Execution from unusual directories
if (bpf_strncmp(filename, "/tmp/", 5) == 0 ||
bpf_strncmp(filename, "/dev/shm/", 9) == 0) {
score += 30;
}
return score;
}
SEC("lsm/bprm_check_security")
int BPF_PROG(lsm_process_exec, struct linux_binprm *bprm) {
struct process_exec_event *event;
struct task_struct *task, *parent;
const struct cred *cred;
struct file *file;
char *filename;
event = bpf_ringbuf_reserve(&process_events, sizeof(*event), 0);
if (!event)
return 0;
task = (struct task_struct *)bpf_get_current_task();
cred = BPF_CORE_READ(task, cred);
parent = BPF_CORE_READ(task, real_parent);
file = BPF_CORE_READ(bprm, file);
// Fill event data
event->pid = bpf_get_current_pid_tgid() >> 32;
event->ppid = BPF_CORE_READ(parent, tgid);
event->uid = BPF_CORE_READ(cred, uid.val);
event->gid = BPF_CORE_READ(cred, gid.val);
event->timestamp = bpf_ktime_get_ns();
bpf_get_current_comm(&event->comm, sizeof(event->comm));
BPF_CORE_READ_STR_INTO(&event->parent_comm, parent, comm);
// Get filename
if (file) {
struct dentry *dentry = BPF_CORE_READ(file, f_path.dentry);
BPF_CORE_READ_STR_INTO(&event->filename, dentry, d_name.name);
}
// Get command line arguments (simplified)
filename = BPF_CORE_READ(bprm, filename);
if (filename) {
bpf_probe_read_str(&event->args, sizeof(event->args), filename);
}
// Calculate suspicion score
event->suspicious_score = calculate_suspicion_score(event->filename, event->comm, event->uid);
// Security policy: block highly suspicious executions
if (event->suspicious_score > 100) {
event->action = 1; // Block
bpf_ringbuf_submit(event, 0);
return -EPERM;
}
event->action = 0; // Allow
bpf_ringbuf_submit(event, 0);
return 0;
}
char _license[] SEC("license") = "GPL";
3. Network Security Monitor¶
// lsm_network_monitor.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
struct network_security_event {
u32 pid;
u32 uid;
char comm[16];
u16 family; // AF_INET, AF_INET6
u16 type; // SOCK_STREAM, SOCK_DGRAM
u16 protocol; // IPPROTO_TCP, IPPROTO_UDP
u32 local_ip;
u16 local_port;
u32 remote_ip;
u16 remote_port;
u64 timestamp;
u32 action; // 0=allow, 1=deny
u32 risk_score;
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} network_events SEC(".maps");
// Blocked networks (IP ranges)
struct network_rule {
u32 network;
u32 mask;
u16 port_min;
u16 port_max;
u32 action; // 0=allow, 1=block
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, u32);
__type(value, struct network_rule);
__uint(max_entries, 100);
} network_rules SEC(".maps");
// Process network privileges
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, char[16]); // process name
__type(value, u32); // allowed operations bitmask
__uint(max_entries, 100);
} process_network_policy SEC(".maps");
static inline u32 calculate_network_risk(u16 family, u16 type, u32 remote_ip, u16 remote_port, const char *comm) {
u32 risk = 0;
// Raw sockets are high risk
if (type == SOCK_RAW) {
risk += 80;
}
// Outbound connections to unusual ports
if (remote_port < 1024 && remote_port != 22 && remote_port != 80 && remote_port != 443) {
risk += 30;
}
// Connections to private/internal networks from certain processes
if ((remote_ip & 0xFF000000) == 0x0A000000 || // 10.x.x.x
(remote_ip & 0xFFF00000) == 0xAC100000 || // 172.16.x.x
(remote_ip & 0xFFFF0000) == 0xC0A80000) { // 192.168.x.x
// Some processes shouldn't access internal networks
if (bpf_strncmp(comm, "wget", 4) == 0 ||
bpf_strncmp(comm, "curl", 4) == 0) {
risk += 40;
}
}
return risk;
}
static inline int check_network_policy(u32 remote_ip, u16 remote_port) {
#pragma unroll
for (u32 i = 0; i < 50; i++) { // Check first 50 rules
struct network_rule *rule = bpf_map_lookup_elem(&network_rules, &i);
if (!rule)
continue;
// Check if IP matches network/mask
if ((remote_ip & rule->mask) == rule->network) {
// Check port range
if (remote_port >= rule->port_min && remote_port <= rule->port_max) {
return rule->action; // 0=allow, 1=block
}
}
}
return 0; // Default allow
}
SEC("lsm/socket_create")
int BPF_PROG(lsm_socket_create, int family, int type, int protocol, int kern) {
struct network_security_event *event;
struct task_struct *task;
const struct cred *cred;
// Only monitor user-space sockets
if (kern)
return 0;
// Only monitor IPv4/IPv6
if (family != AF_INET && family != AF_INET6)
return 0;
event = bpf_ringbuf_reserve(&network_events, sizeof(*event), 0);
if (!event)
return 0;
task = (struct task_struct *)bpf_get_current_task();
cred = BPF_CORE_READ(task, cred);
event->pid = bpf_get_current_pid_tgid() >> 32;
event->uid = BPF_CORE_READ(cred, uid.val);
event->family = family;
event->type = type;
event->protocol = protocol;
event->timestamp = bpf_ktime_get_ns();
bpf_get_current_comm(&event->comm, sizeof(event->comm));
// Calculate risk score
event->risk_score = calculate_network_risk(family, type, 0, 0, event->comm);
// Check process privileges
u32 *privileges = bpf_map_lookup_elem(&process_network_policy, &event->comm);
if (!privileges && type == SOCK_RAW) {
// Raw socket without privileges - block
event->action = 1;
bpf_ringbuf_submit(event, 0);
return -EPERM;
}
event->action = 0;
bpf_ringbuf_submit(event, 0);
return 0;
}
SEC("lsm/socket_connect")
int BPF_PROG(lsm_socket_connect, struct socket *sock, struct sockaddr *address, int addrlen) {
struct network_security_event *event;
struct sockaddr_in *addr_in;
if (!address || address->sa_family != AF_INET)
return 0;
addr_in = (struct sockaddr_in *)address;
event = bpf_ringbuf_reserve(&network_events, sizeof(*event), 0);
if (!event)
return 0;
event->pid = bpf_get_current_pid_tgid() >> 32;
event->remote_ip = addr_in->sin_addr.s_addr;
event->remote_port = bpf_ntohs(addr_in->sin_port);
event->timestamp = bpf_ktime_get_ns();
bpf_get_current_comm(&event->comm, sizeof(event->comm));
// Check network policy
int policy_result = check_network_policy(event->remote_ip, event->remote_port);
event->risk_score = calculate_network_risk(AF_INET, 0, event->remote_ip, event->remote_port, event->comm);
if (policy_result == 1 || event->risk_score > 100) {
event->action = 1;
bpf_ringbuf_submit(event, 0);
return -EPERM;
}
event->action = 0;
bpf_ringbuf_submit(event, 0);
return 0;
}
char _license[] SEC("license") = "GPL";
🔧 Security Event Correlation Engine¶
Event Processing and Correlation¶
// security_correlator.go
package main
import (
"encoding/binary"
"fmt"
"log"
"sync"
"time"
)
type SecurityEvent struct {
Type string
Timestamp time.Time
PID uint32
UID uint32
ProcessName string
Details map[string]interface{}
RiskScore uint32
}
type SecurityRule struct {
ID string
Name string
Description string
Pattern []EventPattern
Action string
Severity string
Enabled bool
}
type EventPattern struct {
EventType string
TimeWindow time.Duration
MinOccurrence int
Conditions map[string]interface{}
}
type SecurityCorrelator struct {
events []SecurityEvent
rules []SecurityRule
eventsMutex sync.RWMutex
alerts chan SecurityAlert
}
type SecurityAlert struct {
RuleID string
RuleName string
Severity string
Timestamp time.Time
Events []SecurityEvent
Description string
Action string
}
func NewSecurityCorrelator() *SecurityCorrelator {
sc := &SecurityCorrelator{
events: make([]SecurityEvent, 0, 10000),
alerts: make(chan SecurityAlert, 1000),
}
// Initialize default security rules
sc.initializeRules()
return sc
}
func (sc *SecurityCorrelator) initializeRules() {
sc.rules = []SecurityRule{
{
ID: "PRIV_ESC_1",
Name: "Privilege Escalation Attempt",
Description: "Process running as root after execution from unusual location",
Pattern: []EventPattern{
{
EventType: "process_exec",
TimeWindow: 30 * time.Second,
MinOccurrence: 1,
Conditions: map[string]interface{}{
"uid": uint32(0), // root
"filename": []string{"/tmp/", "/dev/shm/"},
},
},
},
Action: "alert",
Severity: "high",
Enabled: true,
},
{
ID: "LATERAL_MOVEMENT_1",
Name: "Lateral Movement Detection",
Description: "Multiple network connections to internal hosts",
Pattern: []EventPattern{
{
EventType: "network_connect",
TimeWindow: 60 * time.Second,
MinOccurrence: 5,
Conditions: map[string]interface{}{
"remote_ip_range": "10.0.0.0/8",
},
},
},
Action: "alert",
Severity: "medium",
Enabled: true,
},
{
ID: "SENSITIVE_FILE_ACCESS",
Name: "Sensitive File Access",
Description: "Access to sensitive system files",
Pattern: []EventPattern{
{
EventType: "file_access",
TimeWindow: 10 * time.Second,
MinOccurrence: 1,
Conditions: map[string]interface{}{
"filename": []string{"/etc/shadow", "/etc/passwd", "/etc/sudoers"},
"mode": "write",
},
},
},
Action: "block",
Severity: "critical",
Enabled: true,
},
}
}
func (sc *SecurityCorrelator) AddEvent(event SecurityEvent) {
sc.eventsMutex.Lock()
defer sc.eventsMutex.Unlock()
// Add event to history
sc.events = append(sc.events, event)
// Cleanup old events (keep last 10000)
if len(sc.events) > 10000 {
sc.events = sc.events[1000:]
}
// Check all rules against this event
go sc.evaluateRules(event)
}
func (sc *SecurityCorrelator) evaluateRules(triggerEvent SecurityEvent) {
for _, rule := range sc.rules {
if !rule.Enabled {
continue
}
if sc.checkRulePattern(rule, triggerEvent) {
alert := SecurityAlert{
RuleID: rule.ID,
RuleName: rule.Name,
Severity: rule.Severity,
Timestamp: time.Now(),
Description: rule.Description,
Action: rule.Action,
}
// Find related events
alert.Events = sc.findRelatedEvents(rule, triggerEvent)
select {
case sc.alerts <- alert:
default:
log.Printf("Alert queue full, dropping alert: %s", rule.Name)
}
}
}
}
func (sc *SecurityCorrelator) checkRulePattern(rule SecurityRule, event SecurityEvent) bool {
for _, pattern := range rule.Pattern {
if event.Type != pattern.EventType {
continue
}
// Check conditions
if !sc.matchesConditions(event, pattern.Conditions) {
continue
}
// Check time window and occurrence count
relatedEvents := sc.findEventsInTimeWindow(pattern.EventType, pattern.TimeWindow)
matchingEvents := 0
for _, relEvent := range relatedEvents {
if sc.matchesConditions(relEvent, pattern.Conditions) {
matchingEvents++
}
}
if matchingEvents >= pattern.MinOccurrence {
return true
}
}
return false
}
func (sc *SecurityCorrelator) matchesConditions(event SecurityEvent, conditions map[string]interface{}) bool {
for key, expected := range conditions {
switch key {
case "uid":
if event.UID != expected.(uint32) {
return false
}
case "filename":
filename, ok := event.Details["filename"].(string)
if !ok {
return false
}
patterns := expected.([]string)
found := false
for _, pattern := range patterns {
if len(filename) >= len(pattern) && filename[:len(pattern)] == pattern {
found = true
break
}
}
if !found {
return false
}
case "mode":
mode, ok := event.Details["mode"].(string)
if !ok || mode != expected.(string) {
return false
}
}
}
return true
}
func (sc *SecurityCorrelator) findEventsInTimeWindow(eventType string, window time.Duration) []SecurityEvent {
sc.eventsMutex.RLock()
defer sc.eventsMutex.RUnlock()
cutoff := time.Now().Add(-window)
var events []SecurityEvent
for i := len(sc.events) - 1; i >= 0; i-- {
event := sc.events[i]
if event.Timestamp.Before(cutoff) {
break
}
if event.Type == eventType {
events = append(events, event)
}
}
return events
}
func (sc *SecurityCorrelator) findRelatedEvents(rule SecurityRule, triggerEvent SecurityEvent) []SecurityEvent {
events := []SecurityEvent{triggerEvent}
// Find events from same process
sc.eventsMutex.RLock()
defer sc.eventsMutex.RUnlock()
for i := len(sc.events) - 1; i >= 0; i-- {
event := sc.events[i]
if event.PID == triggerEvent.PID &&
event.Timestamp.After(triggerEvent.Timestamp.Add(-5*time.Minute)) {
events = append(events, event)
}
}
return events
}
func (sc *SecurityCorrelator) GetAlerts() <-chan SecurityAlert {
return sc.alerts
}
// Security monitor integration
func main() {
correlator := NewSecurityCorrelator()
// Process LSM events
go func() {
for {
// This would read from your eBPF ring buffer
// Example: processing file access events
event := SecurityEvent{
Type: "file_access",
Timestamp: time.Now(),
PID: 1234,
UID: 0,
ProcessName: "suspicious_app",
Details: map[string]interface{}{
"filename": "/etc/shadow",
"mode": "write",
},
RiskScore: 90,
}
correlator.AddEvent(event)
time.Sleep(100 * time.Millisecond)
}
}()
// Process alerts
for alert := range correlator.GetAlerts() {
log.Printf("SECURITY ALERT: %s - %s (%s)",
alert.Severity, alert.RuleName, alert.Description)
if alert.Action == "block" {
log.Printf("Action: Blocking process PID %d", alert.Events[0].PID)
// Implement blocking logic
}
}
}
🛠️ Building and Deploying Security Tools¶
1. Makefile for Security Programs¶
# Build LSM programs
build_security:
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \
-I./bpf/headers \
-c bpf/lsm_file_monitor.c \
-o bpf/lsm_file_monitor.o
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \
-I./bpf/headers \
-c bpf/lsm_process_monitor.c \
-o bpf/lsm_process_monitor.o
# Load LSM programs (requires CONFIG_BPF_LSM=y)
load_security:
sudo bpftool prog load bpf/lsm_file_monitor.o /sys/fs/bpf/file_monitor type lsm
sudo bpftool prog load bpf/lsm_process_monitor.o /sys/fs/bpf/process_monitor type lsm
# Configure LSM (requires kernel parameter: lsm=...,bpf)
configure_lsm:
@echo "Add 'lsm=capability,yama,apparmor,bpf' to kernel parameters"
@echo "Reboot required after kernel parameter change"
# Check LSM status
check_lsm:
cat /sys/kernel/security/lsm
ls -la /sys/fs/bpf/
2. Security Configuration Management¶
// security_config.go
type SecurityConfig struct {
SensitivePaths []PathRule `json:"sensitive_paths"`
ProcessWhitelist []ProcessRule `json:"process_whitelist"`
NetworkRules []NetworkRule `json:"network_rules"`
AlertRules []AlertRule `json:"alert_rules"`
}
type PathRule struct {
Path string `json:"path"`
Sensitivity int `json:"sensitivity"`
Actions []string `json:"actions"`
}
func LoadSecurityConfig(filename string) (*SecurityConfig, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var config SecurityConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func (sc *SecurityConfig) UpdateEBPFMaps(maps map[string]*ebpf.Map) error {
// Update sensitive paths map
for _, rule := range sc.SensitivePaths {
key := rule.Path
value := uint32(rule.Sensitivity)
if err := maps["sensitive_paths"].Update(&key, &value, ebpf.UpdateAny); err != nil {
return fmt.Errorf("updating sensitive paths: %w", err)
}
}
// Update process whitelist
for _, rule := range sc.ProcessWhitelist {
key := rule.ProcessName
value := uint32(rule.PrivilegeLevel)
if err := maps["process_whitelist"].Update(&key, &value, ebpf.UpdateAny); err != nil {
return fmt.Errorf("updating process whitelist: %w", err)
}
}
return nil
}
📊 Security Metrics and Dashboards¶
Real-time Security Dashboard¶
// security_dashboard.go
type SecurityDashboard struct {
metrics map[string]uint64
mutex sync.RWMutex
}
func (sd *SecurityDashboard) UpdateMetrics(event SecurityEvent) {
sd.mutex.Lock()
defer sd.mutex.Unlock()
sd.metrics["total_events"]++
sd.metrics[event.Type+"_events"]++
if event.RiskScore > 50 {
sd.metrics["high_risk_events"]++
}
if event.UID == 0 {
sd.metrics["root_events"]++
}
}
func (sd *SecurityDashboard) GetMetrics() map[string]uint64 {
sd.mutex.RLock()
defer sd.mutex.RUnlock()
result := make(map[string]uint64)
for k, v := range sd.metrics {
result[k] = v
}
return result
}
⚠️ Security Considerations¶
1. LSM Program Security¶
// Secure LSM program patterns
SEC("lsm/file_open")
int secure_file_monitor(struct file *file) {
// Always validate pointers
if (!file)
return 0;
// Bounds check for all memory access
struct dentry *dentry = BPF_CORE_READ(file, f_path.dentry);
if (!dentry)
return 0;
// Use safe string operations
char filename[256];
int ret = BPF_CORE_READ_STR_INTO(&filename, dentry, d_name.name);
if (ret < 0)
return 0;
// Validate security decisions
// Never allow by default in security-critical paths
return check_security_policy(filename);
}
2. Performance Impact¶
#!/bin/bash
# Monitor LSM performance impact
echo "=== LSM Program Performance ==="
bpftool prog show type lsm
perf stat -e cycles,instructions,cache-misses sudo ./security_monitor
echo -e "\n=== System Call Latency ==="
strace -c -T -S calls ls > /dev/null
echo -e "\n=== Security Event Rate ==="
bpftool map dump id <events_map_id> | wc -l
Security monitoring with eBPF provides unprecedented visibility into system behavior while maintaining excellent performance. Use these patterns to build comprehensive security tools! 🔒