
eBPF (XDP/tc) mitigation for CVE-2026-98432 / RHSB-2026-016 - draft
An eBPF implementation of the published network mitigation for CVE-2026-98432 ([RHSB-2026-016]): a remotely triggerable use-after-free in the Linux kernel's IPv4 TCP connection-request handling. Red Hat rates it Critical.
[!WARNING] Early release: CI-tested, not yet field-tested. Every push runs end-to-end tests on Ubuntu 24.04, in AlmaLinux 8.10, 9.8 and 10.2 VMs (RHEL-compatible kernels 4.18, 5.14 and 6.12), and on a 2-node Kubernetes (kind) cluster running Cilium, all with virtual interfaces. It has not yet been tested on physical NICs, bonds or VLANs, on OpenShift, or on actual RHEL (see Tested platforms). Try it on a non-production host first. Reports of results, good or bad, are very welcome.
The program drops inbound IPv4 TCP packets that carry IP options (IP header length > 5 words). It is the same filter as the vendor's nftables rule:
ip protocol tcp ip hdrlength > 5 counter drop
It applies the same check to the inner packet of VXLAN (UDP 4789, 8472) and Geneve (UDP 6081) overlays. That way, Kubernetes pod-to-pod traffic between nodes is dropped on the physical interface, before a CNI plugin such as Cilium can deliver it to a pod. It runs as XDP where the driver supports it, and as tc ingress everywhere else.
There are two ways to run it, with the same filter and the same settings:
cve-2026-98432-ebpf), which uses ip and tc
and is driven by a systemd service and timer. It's the simplest to audit.cve-2026-98432-ebpfd), a single static binary. It
covers new interfaces immediately, runs ahead of CNI programs on tc, and
keeps the filter attached if it crashes. See Shell loader or Go
daemon?[!IMPORTANT] This is a stop-gap that reduces exposure, it does not fix the bug. The only complete remediation is a fixed kernel. Remove this mitigation once every host runs one (see Removal).
This is an independent community project. It is not affiliated with or endorsed by Red Hat or any Linux distributor. Check the [bulletin][RHSB-2026-016] for the current guidance.
The nftables rule works, and if it suits your environment, use it. This project is for environments where it doesn't:
| nftables rule | this project | |
|---|---|---|
| Where packets are dropped | netfilter input hook (after skb allocation, IP defrag and routing) | XDP: in the driver, before an skb exists. tc: before netfilter |
| Coverage | Locally addressed traffic only | Also routed and forwarded traffic, e.g. packets to Kubernetes pods, including inside VXLAN/Geneve overlays |
| Interaction with firewall managers | A separate table that has to be persisted and survive tools that rewrite the ruleset | Doesn't touch netfilter at all |
| Requires | nftables | iproute2 built with libbpf |
| Per-interface | No (global) | Yes, and the loader handles that (see below) |
The latest release has:
-daemon RPM (Go daemon) and
the source RPMcve-2026-98432-ebpf-<version>.tar.gzSHA256SUMSThe RPM installs the loader, the BPF objects, the config file and the systemd
units, and pulls in iproute-tc. The service is not enabled
automatically. Pick the RPM matching your major version (el8, el9 or
el10):
sha256sum -c SHA256SUMS --ignore-missing # verify what you downloaded
sudo dnf install ./cve-2026-98432-ebpf-0.1.1-1.el9.x86_64.rpm
sudo systemctl enable --now cve-2026-98432-ebpf.service cve-2026-98432-ebpf-reconcile.timer
sudo cve-2026-98432-ebpf status
Or, for the Go daemon (use one or the other, not both; their units conflict):
sudo dnf install ./cve-2026-98432-ebpf-daemon-0.1.1-1.el9.x86_64.rpm
sudo systemctl enable --now cve-2026-98432-ebpfd.service
sudo cve-2026-98432-ebpfd status
On Debian and Ubuntu, use the .debs (next section). On other distributions, build and install from the source tarball (see Quick start).
Upgrading is dnf install of a newer RPM. The service restarts so the new
BPF objects are attached, and your edits to /etc/cve-2026-98432-ebpf.conf
are kept.
To build the RPM yourself, see the comment at the top of packaging/rpm/build-rpm.sh. On every push, CI builds the RPMs in AlmaLinux containers, installs each one in an AlmaLinux VM and tests it, including the systemd units.
The release also has two amd64 .debs, which work on every supported release: the BPF objects contain no host code and the daemon is a static binary. They hold the same files as the RPMs, and they also don't enable the service:
sha256sum -c SHA256SUMS --ignore-missing
sudo apt install ./cve-2026-98432-ebpf_0.1.1-1_amd64.deb
sudo systemctl enable --now cve-2026-98432-ebpf.service cve-2026-98432-ebpf-reconcile.timer
sudo cve-2026-98432-ebpf status
Or the Go daemon, ./cve-2026-98432-ebpf-daemon_0.1.1-1_amd64.deb, enabled
as in the RPM section. Debian and Ubuntu ship tc in iproute2 itself,
built with libbpf.
To build them yourself, run
packaging/deb/build-deb.sh. On every push, CI
builds the .debs, installs them on Ubuntu 24.04 and runs the end-to-end and
systemd tests against the installed files. It also installs them on Debian
12 and 13 and Ubuntu 22.04 and 24.04 and runs check there.
Use a git checkout or the source tarball from a GitHub release.
To build, you need clang, llvm, libbpf-devel / libbpf-dev and the
kernel UAPI headers:
make
sudo make install
sudo cve-2026-98432-ebpf check # verify prerequisites
sudo systemctl enable --now cve-2026-98432-ebpf.service cve-2026-98432-ebpf-reconcile.timer
sudo cve-2026-98432-ebpf status
status lists each protected interface and how it is attached, plus the
number of dropped packets (the counter needs bpftool):
eth0 XDP
wg0 tc
packets dropped: 3
Without systemd, run sudo cve-2026-98432-ebpf apply and remove directly.
To build the Go daemon from source, you need Go ≥ 1.22, clang and the libbpf headers:
cd go && go generate ./... && CGO_ENABLED=0 go build -o cve-2026-98432-ebpfd .
sudo ./cve-2026-98432-ebpfd run --mode auto # SIGTERM detaches
ip -V must mention libbpf. check
tests for it. Verified in CI on AlmaLinux 8.10, 9.8 and 10.2 (iproute2 6.2
to 6.17) and Ubuntu 24.04.tc: on RHEL, AlmaLinux, Rocky and Fedora it ships separately, in the
iproute-tc package (dnf install iproute-tc).CAP_NET_ADMIN + CAP_BPF/CAP_SYS_ADMIN).On every push, CI checks that an IPv4 TCP connection carrying IP options is dropped, that a plain one still connects, and that removal restores normal behaviour. It runs these checks in XDP, tc and tunnel (L3) modes on veth, ipip and dummy interfaces, plus native XDP on virtio-net in the VMs. It also runs every eBPF program on 17 hand-made packets. See docs/TESTING.md for how it all works without a second host: