What Is eBPF? How Can Linux Safely Run Programs Inside the Kernel?
Linux can take a program written in user space, analyze it, compile it, and run it inside the kernel. That sentence deserves a second read. User space and kernel space are supposed to be separated. Kernel code runs wit
Linux can take a program written in user space, analyze it, compile it, and run it inside the kernel.
That sentence deserves a second read.
User space and kernel space are supposed to be separated. Kernel code runs with enormous privilege. A bug in kernel code can crash the system, corrupt memory, or break security boundaries in ways that a bug in an ordinary application cannot. The whole point of the user space / kernel space divide is to keep untrusted code far away from that level of the system.
So how is Linux allowing code supplied from outside the kernel to run inside it?
The answer is eBPF, and the interesting part isn't the feature. It's how Linux keeps that boundary from collapsing entirely.
The Boundary That eBPF Crosses
To understand why this is unusual, it helps to be clear about what the boundary normally looks like.
When an application wants to do something privileged, it asks the kernel through a system call. The application doesn't execute inside the kernel. It makes a request, the kernel handles it, and control returns to user space. The separation is deliberate.
User Space
↓
System Call
↓
Kernel
↓
Hardware / Resources
Kernel code itself is trusted by definition. Operating system developers wrote it. It was compiled into the kernel image. It went through code review and extensive testing. When the kernel runs something, it isn't asking permission.
An eBPF program comes from somewhere else entirely. A developer writes it, compiles it to eBPF bytecode, and asks the kernel to load it. That request goes through a very different path than anything the kernel normally trusts.
User Space
↓
eBPF Program Submitted
↓
Kernel Verifier
↓
JIT Compilation
↓
Attached to Kernel Hook
↓
Executes When Event Fires
The pipeline between "submitted" and "executes" is where the security model lives. Skip any step and the whole thing breaks.
What eBPF Actually Is
eBPF (extended Berkeley Packet Filter, though the name is now largely historical) is a kernel subsystem that allows sandboxed programs to run at specific points inside the Linux kernel. These attachment points include system calls, network events, kernel functions, scheduler events, and more.
The important thing isn't the list. It's the constraint: an eBPF program doesn't execute wherever it wants. The kernel decides where it can attach and what context it receives there. The program runs when that specific event fires, with access to only what that context provides.
That controlled attachment is the first layer of restriction. But it's not the important one.
The Verifier: Where Linux Decides Whether to Trust the Program
Before an eBPF program runs a single instruction, the kernel's verifier analyzes it.
The verifier is a static analysis engine. It reads the eBPF bytecode and analyzes possible execution paths before the program runs, tracking things like register states, pointer types, and memory-access bounds at each point. It doesn't execute the program. It reasons about what the program could do across all reachable paths and determines whether those possibilities satisfy a defined set of safety constraints.
This includes:
Control flow. The verifier checks that execution can't jump to arbitrary instructions, that every path terminates, and that the program doesn't loop indefinitely. eBPF programs must have bounded execution.
Memory access. The verifier tracks what each register holds at every point. It knows whether a register contains a valid pointer, what kind, what memory region it addresses, and what bounds apply. If the program tries to dereference a pointer the verifier can't confirm is in-bounds, the program is rejected.
Pointer types. A pointer into an eBPF map has different rules from a pointer to packet data. The verifier enforces these distinctions. A program can't take an arbitrary integer, cast it to a pointer, and dereference it.
Helper calls. eBPF programs can't call arbitrary kernel functions. Only a defined set of helper functions per program type is allowed. The verifier checks every call.
To make pointer tracking concrete: a networking program might load with a register pointing to packet data and another holding the packet length. The verifier asks whether every memory access using that pointer stays within the available data on every possible execution path. If it can't establish that, the program is rejected before it runs.
r1 → pointer to packet data
r2 → packet length
↓
Verifier asks:
Is r1 a valid pointer to packet data?
Is this offset within bounds for all code paths?
Can this access become invalid on any reachable path?
↓
All constraints satisfied → accepted
Any constraint fails → rejected
The security model isn't "trust the developer." It's closer to: "the program must satisfy a defined set of safety constraints before it touches the kernel."
Helpers: A Controlled Interface Into the Kernel
Instead of letting eBPF programs call arbitrary kernel functions, the kernel exposes a set of approved helper functions for each program type.
A networking eBPF program might have helpers for reading packet data, modifying it, or redirecting it. A tracing program might have helpers for recording events. All of these are controlled interfaces. The eBPF program can't reach past them into arbitrary kernel internals.
eBPF Program
↓
Helper Function
↓
Kernel-managed operation
Helpers are defined per program type. A networking hook doesn't get the helpers meant for a tracing hook. The scope is intentionally narrow.
Maps: Shared State Between Kernel and User Space
eBPF programs use maps: structured key-value stores accessible by both the in-kernel eBPF program and user-space applications. This is how security monitors and observability tools get data out of kernel-attached programs without needing a persistent kernel connection. The program writes events into the map; user space reads them.
JIT Compilation: Performance, Not Security
Once verified, the kernel can JIT-compile the eBPF bytecode into native machine instructions. This matters because eBPF programs can run in extremely performance-sensitive paths like network packet processing.
JIT compilation is not the security step. The verifier is. By the time JIT runs, the kernel has already decided the program is acceptable. Compilation is an optimization, not a trust decision.
eBPF vs. Kernel Modules
A kernel module is the traditional way to extend the kernel. A loaded module executes with kernel privileges and can interact directly with kernel APIs and memory, giving it a much broader capability set than a verified eBPF program. Kernel modules are powerful, but the trust model is simple: you either trust the module completely or you don't load it.
eBPF operates differently. The kernel doesn't trust the submitted program. It verifies it. The program executes in a restricted environment with controlled helpers, bounded execution, and verified memory access. The restriction is the point.
This doesn't mean eBPF programs are less capable at the specific things they're designed to do. It means the capability they have is defined and constrained rather than open-ended.
eBPF As a Security Tool (And a Security Boundary)
Security teams use eBPF because it can observe the system at a layer that application-level logging can't reach. A system-call monitoring tool built with eBPF can attach to kernel instrumentation points and observe system-call activity across processes, with coverage determined by the attachment points used and the permissions the tool holds. That kind of visibility is genuinely difficult to achieve from user space.
The flip side: because eBPF operates inside the kernel, vulnerabilities in the verifier, JIT compiler, or helper implementations are kernel-level vulnerabilities. Bugs in the verifier have historically allowed privilege escalation. The security benefit comes with a significant responsibility: the components that implement eBPF's safety model are themselves security-critical.
"eBPF has a verifier" and "eBPF can never have security vulnerabilities" are not the same statement.
Who Can Load eBPF Programs?
Not everyone. Loading eBPF programs requires appropriate Linux capabilities, and the specific requirements depend on the program type, kernel version, and system configuration. The security model isn't just the verifier: it's capability controls plus verification plus restricted execution plus controlled helpers, all together. Removing any layer changes the security properties.
The Answer to the Original Question
How can Linux allow a program supplied from user space to execute inside the kernel without simply giving that program unrestricted access?
By making the boundary programmable but not removing it.
The kernel doesn't simply trust the submitted program. It analyzes the bytecode and requires it to satisfy a set of safety constraints before allowing it to execute anywhere near kernel memory. The verifier enforces those constraints. The helper interface constrains what the program can do even after verification. The attachment model constrains where it runs. Capability requirements constrain who can load it.
What eBPF offers isn't unrestricted kernel access dressed up with a safety label. It's a constrained execution model where the kernel decides what "safe enough for kernel execution" means, and then enforces that definition before a single instruction runs.
The boundary still exists. eBPF just made it programmable.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.