eBPF Explained (2026): Verifier, CO-RE, Cilium, Tetragon and sched_ext

Oct 20, 2026 · 11 min · Ajay Kumar

I spend a surprising amount of my week looking at eBPF output without writing any eBPF: Cilium's flow logs, a bpftrace histogram on a slow host, an OpenTelemetry trace that no developer instrumented. That is the point of the technology. eBPF lets you load small, verified programs into a running Linux kernel and attach them to events (a packet arriving, a syscall, a function entry, a scheduler decision) without a kernel module or a reboot. This post explains what eBPF is in 2026, how the verifier and CO-RE make it safe and portable, where it is actually used (Cilium for networking, Tetragon for security, profiling, OBI for zero-code tracing, sched_ext for scheduling), what changed on Windows, and where its limits are.

What is eBPF, in one paragraph?

eBPF is a small virtual instruction set and runtime inside the kernel. You write a program in restricted C (or Rust, or a high-level language like bpftrace's), compile it to BPF bytecode, and ask the kernel to load it through the bpf() syscall. The kernel's verifier checks it, a JIT compiles it to native machine code, and it runs every time its hook fires. Programs keep state and talk to user space through maps: hash tables, arrays, ring buffers and so on. The instruction set is now an IETF standard: RFC 9669, published in October 2024, which also notes that BPF no longer officially stands for anything (it used to be "Berkeley Packet Filter", and the old packet-filter flavour is now called classic BPF).

The governance story is settled too. The Linux Foundation launched the eBPF Foundation on 12 August 2021 with Meta, Google, Isovalent, Microsoft and Netflix as founders. Its site currently lists CrowdStrike, Google, Isovalent, Meta and Netflix as platinum members.

USER SPACE 1. Source C, Rust (Aya), Go (ebpf-go) or bpftrace 2. clang / LLVM BPF bytecode + BTF type info in an ELF 3. Loader (libbpf) CO-RE relocations vs /sys/kernel/btf/vmlinux Agents Cilium, Tetragon, OBI, bpftrace read the maps bpf() syscall KERNEL 4. Verifier 1M insns limit (5.2+) bounded loops (5.3+) no bad pointers, must end 5. JIT native x86-64 or arm64 code, runs at near kernel-function speed 6. Hooks XDP, tc, kprobe, tracepoint, LSM, perf, sched_ext ops Maps hash, array, LRU, ring buffer, per-CPU (shared both ways) Rejected programs never run. Kernel needs CONFIG_DEBUG_INFO_BTF=y for CO-RE; unprivileged bpf() is off by default on kernels built with BPF_UNPRIV_DEFAULT_OFF (sysctl kernel.unprivileged_bpf_disabled = 2).
The path from source to a running eBPF program. Verifier limits are from the eBPF docs verifier page; the BTF and CO-RE requirements are from Andrii Nakryiko's CO-RE write-up; the sysctl default is from the kernel's sysctl documentation.

How does the eBPF verifier keep the kernel safe?

The verifier is the reason eBPF exists as a mainstream tool rather than a kernel-module footgun. Before a program is accepted, it walks every reachable path and proves a list of properties. The eBPF docs summarise them: the program must terminate, it may not read arbitrary memory or packet bytes outside bounds, it may not deadlock or read uninitialised memory, pointers must be null-checked before dereference, helper and kfunc arguments are type-checked, and unreachable code is rejected.

Two numbers explain most verifier pain. Before kernel 5.2 there was a hard 4,096 instruction limit and a 128k complexity limit; since 5.2 both are 1 million, and the complexity count is the number of instructions the verifier inspects across all paths, not the size of your binary. Loops were rejected outright until 5.3 introduced bounded loops, which is why old eBPF code is full of #pragma unroll.

In practice this means eBPF programs are small, and anyone who writes them spends time arguing with the verifier about whether a bounds check is "obvious". That cost is the price of a guarantee a kernel module never gives you: a rejected program never runs, and an accepted one cannot (barring verifier bugs, which do happen) crash the box.

What is CO-RE, and why did it change eBPF tooling?

Kernel structures move between versions. A field in task_struct at offset 1,234 on one kernel is somewhere else on the next, or compiled out entirely. The original answer, used by BCC, was to ship Clang and kernel headers to every host and compile at runtime. It works, but it is heavy and errors surface on the target machine.

CO-RE (Compile Once, Run Everywhere) replaced that. As Andrii Nakryiko's CO-RE write-up lays out, it has four pieces: BTF, a compact type description the kernel exposes at /sys/kernel/btf/vmlinux when built with CONFIG_DEBUG_INFO_BTF=y; Clang built-ins that record "I want field pid of task_struct" as a relocation instead of a fixed offset; libbpf, which patches those relocations against the running kernel's BTF at load time; and a kernel that needs almost nothing special. The result is a single binary that runs across kernel versions, which is why almost every serious eBPF project now ships as a static Go or Rust binary using ebpf-go, Aya or libbpf (now at v1.8.0, released 5 October 2026).

You can check whether a host is CO-RE ready in a few seconds:

# Kernel BTF present? Required for CO-RE programs (Cilium, Tetragon, OBI, libbpf-tools)
ls -l /sys/kernel/btf/vmlinux

# Generate vmlinux.h for writing your own CO-RE programs (bpftool ships with linux-tools)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

# What eBPF programs are loaded right now, and by whom?
sudo bpftool prog show

# Is unprivileged bpf() blocked? 2 = disabled (admin can re-enable), 1 = disabled until reboot
sysctl kernel.unprivileged_bpf_disabled

# bpftrace v0.27.0: count syscalls by process for 5 seconds
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); } interval:s:5 { exit(); }'

Where is eBPF used in 2026? Networking, security, profiling, tracing

The use cases have sorted themselves into a handful of categories, each with a dominant open-source project. Versions below are the latest releases as of 20 October 2026.

Use case Project Latest release What eBPF does there Source
Kubernetes networking Cilium v1.20.3 (15 Oct 2026) CNI datapath, network policy, kube-proxy replacement GitHub releases
Runtime security Tetragon v1.7.1 (25 Aug 2026) Kernel-side filtering and SIGKILL enforcement GitHub releases
Zero-code app tracing OpenTelemetry eBPF Instrumentation (OBI) v0.15.2 (16 Oct 2026) HTTP/gRPC/SQL spans and metrics without SDKs GitHub releases
Ad hoc tracing bpftrace v0.27.0 (10 Sep 2026) One-liners and scripts against any probe GitHub releases
Tracing tool collection BCC v0.37.0 (2 Jul 2026) execsnoop, biolatency, runqlat and friends GitHub releases
CPU scheduling sched_ext schedulers (scx) v1.1.4 (15 Oct 2026) Whole scheduling policies written in BPF GitHub releases
L4 load balancing Katran (Meta) n/a XDP load balancer before the network stack GitHub

Networking with Cilium. Cilium was the first eBPF project to become a default rather than an experiment; the CNCF graduated it on 11 October 2023. Its kube-proxy replacement handles ClusterIP, NodePort, LoadBalancer and externalIPs services with eBPF maps instead of iptables chains, and with kubeProxyReplacement=true the agent refuses to start if the kernel lacks the features it needs, which is the behaviour you want. The current system requirements ask for kernel 5.10 or an equivalent such as RHEL 8.10's 4.18. If you are on ingress-nginx, the Gateway API migration is a good moment to reconsider the CNI too.

Security with Tetragon. Tetragon, a Cilium subproject, hooks kernel functions and LSM points and, per its overview, applies policy and filtering in the kernel so only matching events reach user space. That matters because a security agent that ships every open() to user space either drops events or eats a core. Policies are Kubernetes CRDs; the project's own enforcement example hooks security_file_permission and kills any process that reads /etc/shadow:

# Excerpt from cilium/tetragon examples/quickstart/file_monitoring_enforce.yaml (Tetragon 1.7)
apiVersion: cilium.io/v1alpha1
kind: TracingPolicyNamespaced
metadata:
  name: "file-monitoring-filtered"
spec:
  kprobes:
  - call: "security_file_permission"   # LSM hook, stable across kernels
    syscall: false
    return: true
    args:
    - index: 0
      type: "file"                      # resolved to a path in-kernel
    - index: 1
      type: "int"                       # 4 = MAY_READ, 2 = MAY_WRITE
    returnArg:
      index: 0
      type: "int"
    selectors:
    - matchArgs:
      - index: 0
        operator: "Prefix"
        values: ["/etc/shadow", "/root/.ssh", "/etc/sudoers"]
      - index: 1
        operator: "Equal"
        values: ["4"]
      matchActions:
      - action: Sigkill                 # killed before the read returns data

Falco occupies the detection side of the same space; there is a Falco setup guide and a broader look at runtime security in today's posts.

Zero-code tracing with OBI. Grafana donated Beyla to OpenTelemetry in May 2025, and it now lives as OpenTelemetry eBPF Instrumentation. OBI watches network and TLS library calls from the kernel side and emits HTTP, gRPC and SQL spans and RED metrics for services nobody instrumented. It is pre-1.0, and it cannot see inside your business logic, but for the "we have 40 services and traces in 6 of them" problem it is the cheapest first step. I covered the cost side in Observability in 2026.

Profiling. Continuous profiling is the eBPF use case with the clearest return: sample stacks at a fixed rate across every process on the host, with no restarts. Elastic's agent became the OpenTelemetry eBPF profiler, and the OTel Profiles signal entered public alpha this year with the profiler running as a Collector receiver. Today's continuous profiling post goes deeper.

Scheduling with sched_ext. Linux 6.12, released 17 November 2024, merged sched_ext, which lets you write an entire CPU scheduler as a BPF program. The safety story is the interesting part: per the kernel docs, if the BPF scheduler errors or lets runnable tasks stall, the kernel aborts it and returns every task to the default fair scheduler, and SysRq-S does the same by hand. 6.12 is now a longterm kernel on kernel.org, so this is available on mainstream distributions, not just on a laptop running mainline.

Does eBPF work on Windows?

Partly. Microsoft's eBPF for Windows reuses existing projects rather than forking: the PREVAIL verifier, and uBPF for JIT and interpretation. The preferred path compiles bytecode to a native Windows driver through bpf2c, because with HVCI enabled programs cannot be JIT compiled. The goal is source compatibility for common hooks and helpers, not binary compatibility, and anything that depends on Linux internal structures will not port. It supports Windows 11 and Windows Server 2022 or later. The project ships roughly monthly and every release on GitHub is still marked pre-release, the latest being v1.7.0 on 16 October 2026. Treat it as real but young.

What are eBPF's limitations?

eBPF is not magic, and the marketing around "kernel-level visibility" skips the parts that bite.

  • Kernel version is a feature flag. Program types, helpers and kfuncs arrive in specific kernels. A tool that needs 6.x features will not run on an old enterprise kernel, and backports make version numbers lie. Always check the project's requirements page rather than the kernel number.
  • It needs privilege. Loading most programs requires CAP_BPF plus CAP_PERFMON or CAP_NET_ADMIN, or root. On kernels built with BPF_UNPRIV_DEFAULT_OFF, unprivileged bpf() is off by default per the sysctl docs. An eBPF agent is therefore a privileged DaemonSet with the power to read a lot of the host, and it belongs in your threat model like any other.
  • It is an attack surface. Verifier bugs have produced real privilege escalations: CVE-2021-3490 was a 32-bit bounds-tracking flaw that let a user who could load programs reach root, fixed in 5.13-rc4. Malware uses BPF too; Trend Micro's BPFDoor analysis shows a backdoor hiding behind kernel packet filters. "Verified" means memory-safe, not benign, so bpftool prog show belongs in your incident checklist.
  • Visibility stops at the kernel boundary you are on. Host eBPF sees host syscalls. A container shares that kernel, so you see it; a microVM or gVisor sandbox has its own kernel, so you see the VMM process and its tap device, not the code inside. In PandaStack (my company, a Firecracker microVM cloud for agents) that is a feature for isolation and an inconvenience for observability, and it is one of the trade-offs in choosing agent isolation.
  • Encrypted payloads and managed runtimes are hard. Seeing inside TLS means hooking the user-space crypto library rather than the socket, which only works for libraries the tool knows. JIT-compiled runtimes need extra symbolisation work before their stacks are readable, which is why profiler release notes are full of per-language support items.
  • Overhead is low, not zero. A kprobe on a hot function fires millions of times a second. Kernel-side filtering helps, but a careless bpftrace one-liner on a busy box can still hurt.

FAQ

Is eBPF better than a kernel module?

For observability, networking and security policy, usually yes. The verifier guarantees an eBPF program cannot crash the kernel or loop forever, and CO-RE lets one binary run across kernel versions. Kernel modules remain the only option for things eBPF has no hook or helper for, such as most device drivers.

Do I need to learn to write eBPF code?

Most engineers do not. You will mostly run eBPF-based tools (Cilium, Tetragon, OBI, a profiler) and occasionally a bpftrace one-liner. Writing raw eBPF in C or Rust is worth learning if you build infrastructure products or need a probe no tool offers.

What kernel version do I need for eBPF in 2026?

For the mainstream tools, 5.10 or newer with BTF enabled is the practical floor, and it is what Cilium asks for. sched_ext needs 6.12 or later. Distribution kernels with backports, such as RHEL's, can support features their version number suggests they should not.

Is eBPF safe to run in production?

Yes, it is widely deployed, but treat the agents as privileged software. Pin versions, roll them out like any other node component, and remember that the verifier prevents crashes, not bad intent.

If I were planning this quarter, I would check every node image for /sys/kernel/btf/vmlinux and a 6.x longterm kernel, run OBI against the services that still have no traces, and put a small set of Tetragon policies in observe-only mode on the clusters that run untrusted or agent-generated code before turning on Sigkill. Then I would make sure the on-call rotation can use eBPF when it counts: the 60-second Linux performance checklist and bpftrace follow-ups is where I would start that training.

Related: Observability in 2026: OpenTelemetry Graduated, the Collector Is Still v0.162, eBPF Instrumentation Is v0.14, and Datadog Bills $1.12B a Quarter, Container vs microVM vs gVisor: Choosing Agent Isolation and Kubernetes as the Agent Control Plane in 2026: Agent Sandbox Hit v1.0, 300 Claims a Second, and DRA in Every Supported Release.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related