eBPF (extended Berkeley Packet Filter) lets you run sandboxed programs inside the kernel without loading a module or touching source code. For security monitoring, that sounds like a clean win: deep visibility into syscalls, network flows, and file access, all wrapped in a verifier that promises safety. But if you’ve spent any time staring at CPU pipeline diagrams or pulling apart baseband firmware, you know that “safety” is a contract written in sand. The real question isn’t whether eBPF can spot an attack. It’s whether the monitoring infrastructure itself becomes the most reliable pivot point for someone who understands speculative execution, cache coherency, and the gap between what the verifier proves and what the silicon actually does.
This article picks apart eBPF-based security monitoring through the lens of microarchitectural attack surface, kernel memory management, and the compiler toolchains that turn C into verified eBPF bytecode. We’ll look at where the verifier’s formal model diverges from physical reality, how JIT-compiled eBPF programs interact with CPU mitigations, and why the current generation of eBPF security products may be handing attackers the very primitives they claim to detect.
The eBPF Architecture Model vs. Microarchitectural Reality
eBPF programs run inside a lightweight virtual machine in the Linux kernel. Before execution, the verifier performs static analysis to prove termination, memory safety, and the absence of out-of-bounds accesses. Then the program is JIT-compiled to native x86-64 or ARM64 instructions. Security monitoring tools—Falco, Cilium, Tracee, and various commercial EDR products—use eBPF probes attached to kprobes, tracepoints, or syscall entry points to watch system behavior.
The verifier’s safety guarantees rest on a CPU model that excludes speculative execution, cache side channels, and branch prediction state. This is the same class of abstraction failure that gave us Spectre, Meltdown, and their variants. When an eBPF program is JIT-compiled to native code, it emits real x86-64 or ARM64 instructions that interact with the branch predictor, fill cache lines, and occupy entries in the BTB (Branch Target Buffer) and RSB (Return Stack Buffer)—all shared resources on SMT cores.

The Verifier’s Blind Spot: Speculative Execution Paths
The eBPF verifier proves properties about architectural execution—the path the program takes when branch conditions resolve correctly. It does not, and architecturally cannot, reason about speculative execution paths where the CPU guesses a branch direction and executes instructions transiently. A JIT-compiled eBPF program that does a bounds check followed by a memory access looks safe to the verifier. But on a core vulnerable to Spectre v1, an attacker controlling the unchecked input can train the branch predictor to mispredict the bounds check, causing speculative access to out-of-bounds memory.
This isn’t a thought experiment. Researchers have shown that eBPF programs can be used as speculative execution gadgets. The verifier’s own analysis routines run in kernel context and can be influenced by unprivileged users—a fact documented in CVE-2021-33624 and related vulnerabilities. When you deploy an eBPF-based security monitor, you’re adding verified-but-speculatively-unsafe code to a kernel that may already be running with SMT enabled and mitigations turned off for performance.
JIT Compilation and the Transient Execution Window
The eBPF JIT compiler translates verified bytecode into native instructions. On x86-64, that means emitting actual mov, call, and ret instructions that interact with the CPU’s front-end, execution units, and memory subsystem. Each JIT-compiled eBPF program becomes a sequence of native instructions that can:
- Occupy BTB entries, potentially evicting entries used by kernel mitigation code
- Generate speculative loads that fill cache lines, creating measurable timing differences
- Execute transient instructions under misprediction that leave microarchitectural state changes
On ARM64 systems with Pointer Authentication (PAC), the situation gets more tangled. eBPF programs running with PAC enabled must handle signed return addresses. The verifier doesn’t model PAC signing or authentication—it sees abstract eBPF instructions, not the PACIASP and AUTIASP instructions the JIT compiler emits. A JIT-compiled eBPF program that corrupts a signed pointer won’t be caught by the verifier; it’ll be caught by a PAC authentication failure at runtime. But that failure generates a fault that may be observable through timing or other side channels, potentially leaking information about the corrupted pointer’s value.

eBPF Security Monitors as Attack Surface
Security monitoring tools that use eBPF typically load multiple programs into the kernel: syscall probes, network filters, file integrity watchers. Each loaded program is a potential gadget. The more comprehensive the monitoring, the larger the attack surface. This creates an uncomfortable trade-off: the very instrumentation meant to detect attacks may provide the primitives needed to construct them.
Map Leakage and Covert Channels
eBPF maps are shared memory regions between kernel and userspace. Security monitors use them to pass event data to userspace agents. While maps have access controls, the timing of map operations can create covert channels. A compromised userspace process with access to an eBPF map can observe:
- Map update frequency, revealing system call patterns of other processes
- Map lookup latency, potentially exposing kernel data structure states
- Map eviction behavior, leaking information about kernel memory pressure
These channels are low-bandwidth but can be enough to leak cryptographic key material or ASLR randomization bits. The eBPF verifier explicitly permits bounded loops since Linux 5.3, which means timing side channels through loop iteration counts are now verifier-approved in certain configurations.
Kernel Stack Probing via eBPF
eBPF programs can access limited kernel stack data through helper functions like bpf_get_stackid() and bpf_get_stack(). These helpers return stack trace information that includes return addresses—the very values that KASLR (Kernel Address Space Layout Randomization) attempts to hide. While the returned addresses are hashed or masked in some configurations, the raw values may be recoverable through repeated sampling and statistical analysis, especially on kernels with limited entropy in their KASLR implementation.
On ARM64 systems with PAC, stack return addresses are signed. But the eBPF helper returns the raw signed pointer, not the authenticated value. An attacker who can observe these signed pointers and trigger controlled PAC authentication failures can potentially forge valid signed pointers—a technique that has been demonstrated in academic research against userspace PAC but has clear kernel-space implications.
Firmware-Level eBPF: The Hidden Frontier
While most eBPF discussion focuses on the Linux kernel, eBPF is increasingly appearing in firmware contexts. UEFI firmware can include eBPF-based network drivers. Embedded baseband processors in mobile devices are exploring eBPF for packet filtering. These environments lack the mature verifier implementations of mainline Linux and often run on CPUs with different microarchitectural characteristics.
Consider a smartphone baseband processor running a lightweight RTOS with an eBPF interpreter for network packet filtering. The interpreter may not implement all verifier checks, especially those related to speculative execution. A crafted packet that triggers an eBPF program could exploit microarchitectural side channels in the baseband CPU—a CPU that typically has direct DMA access to application processor memory. This isn’t theoretical; baseband-to-AP attacks have been demonstrated using other vectors, and eBPF provides a convenient programmable interface.

Verifier Divergence Across Kernel Versions
The eBPF verifier is not a static specification; it evolves with each kernel release. A program that passes verification on kernel 5.15 may fail on 6.1 due to stricter checks, or—more concerning—a program that fails on 6.1 may pass on a vendor kernel with backported features but incomplete verifier updates. Android OEMs are notorious for shipping kernels with cherry-picked eBPF features that don’t include the corresponding verifier improvements.
This creates a fragmentation problem: security monitoring tools that rely on eBPF must target the lowest-common-denominator verifier, which means they can’t use newer safety features. Or they must maintain per-kernel-version program variants, which increases complexity and the risk of deploying a variant with insufficient safety checks.
Practical Recommendations for the Skeptical Engineer
If you’re deploying eBPF-based security monitoring, here are concrete steps to reduce the risk of your monitoring becoming the attack vector:
- Disable SMT on security-critical hosts. Simultaneous multithreading is the primary enabler of cross-thread microarchitectural attacks. If you’re running eBPF programs that access sensitive kernel data, SMT effectively gives an attacker a co-resident thread on the same physical core.
- Audit eBPF map access patterns. Monitor which userspace processes have read access to eBPF maps. A process with
CAP_BPForCAP_SYS_ADMINthat shouldn’t need map access is a red flag. - Pin eBPF programs to specific cores. If your monitoring workload can be isolated to dedicated cores, you reduce the microarchitectural attack surface. This is especially relevant on ARM64 big.LITTLE systems where core types have different speculative execution behaviors.
- Verify the verifier. Run your eBPF programs through multiple kernel versions’ verifiers in a CI pipeline. Flag any program that passes on an older verifier but fails on a newer one—it may be exploiting a verifier bug.
FAQ
Does eBPF’s verifier guarantee that a program is safe against all attacks?
No. The verifier proves safety within its formal model, which covers memory access bounds, loop termination, and instruction validity. It does not model CPU speculative execution, cache timing, branch predictor state, or other microarchitectural behaviors. A program that passes verification can still be used as a gadget in Spectre-type attacks or leak information through timing side channels. The verifier is a necessary but insufficient safety mechanism.
How does eBPF JIT compilation affect security on ARM64 systems with Pointer Authentication?
On ARM64 systems with PAC enabled, the JIT compiler emits PACIASP and AUTIASP instructions to sign and authenticate return addresses. However, the verifier operates on eBPF bytecode and has no visibility into these PAC instructions. A JIT-compiled eBPF program that corrupts a signed pointer will trigger a PAC authentication fault, which may be observable through timing or other channels. Additionally, eBPF helper functions that return kernel pointers (like bpf_get_stack()) may expose signed pointer values that an attacker can use to forge valid PAC signatures.
Can eBPF-based security monitoring tools be used to bypass CET (Control-flow Enforcement Technology)?
Indirectly, yes. CET’s shadow stack protects return addresses, but eBPF programs that run in kernel context can access and modify memory through helper functions. If an attacker compromises a userspace process that has access to eBPF maps, they may be able to influence kernel execution flow by manipulating map contents that are consumed by eBPF programs. Additionally, eBPF programs themselves are JIT-compiled to native code and placed in kernel memory; if an attacker can locate and modify that memory (through a separate kernel vulnerability), they can bypass CET protections for the eBPF program’s execution.
What are the risks of using eBPF for security monitoring on systems with MTE (Memory Tagging Extension)?
MTE assigns 4-bit tags to memory allocations and checks them on access. eBPF programs that access kernel memory through helpers like bpf_probe_read() may interact with MTE-tagged memory in ways the verifier doesn’t anticipate. If an eBPF program reads MTE-tagged memory and the tag check fails, the resulting fault could be observable from userspace, creating a side channel. Additionally, eBPF maps themselves are kernel allocations that may or may not be MTE-tagged depending on the kernel configuration—inconsistent tagging can create information leaks between eBPF programs and other kernel subsystems.