Our Blog

Network Operators Should Be Doing This

The Linux TCP handshake exploit (CVE-2026-98432) is triggered by one packet with a recognizable shape. doxx.net drops it on the fabric before it reaches a customer. Here is the exact policy, why it is safe, and how any operator can run it today.

Barrett Lyon in a backwards doxx.net cap, one finger raised, in front of a wall of hex.
On this page

Most of October's advice was patch and pray. For CVE-2026-98432 a lot of people can't patch yet: the fix is still in distribution testing for several Ubuntu releases, appliances ship kernels nobody will ever update, and half the fleet you are responsible for belongs to customers who don't read advisories. So we did what I've done since Prolexic: stop it in the network.

This post is the exact policy we run, written for the people who sit at the right vantage point to copy it. That is ISPs and hosting providers, and it is just as much every VPN and mesh operator: if you terminate a tunnel and forward packets into it, you are the last hop in front of your users' kernels, and a port forward or an exit node delivers this SYN exactly like a public IP does. If you carry traffic toward Linux hosts, you can make this exploit undeliverable to every one of them in an afternoon, with no customer action, and no legitimate traffic lost.

The bug, in one paragraph

CVE-2026-98432 is a use-after-free in the Linux TCP handshake (CVSS 9.8, kernels 4.4 through 6.12.112). When a connection request comes in, the kernel saves the IP options carried on that SYN and hands them to the child socket it builds for the connection, without copying them. A retransmitted SYN racing the child's teardown reads that saved state after it was freed. The demonstrated remote-root path needs an MPTCP listener on the target, and since Go 1.24 made MPTCP the default for ListenConfig on systems that support it, that surface is wider than anyone intended. The crash and kernel-memory-leak variants need nothing but the options.

The trigger is always an inbound SYN: the first packet of a connection someone opens toward the victim. That single fact is what makes this filterable. The attacker has to send a packet with a recognizable shape, through your network, to a host that did not ask for it.

Two ways we take it off the table

Exposure. A doxx.net server with the outside world turned off has no path from the public internet at all. Only the devices its owner allows in can reach it, so the SYN that starts this never arrives. Most of our customers' servers sit here, and for them the CVE is a headline, not an event.

Filtering. For servers that do take internet traffic on a doxx.net address, the fabric drops the trigger before it reaches them. That covers more hosts than it might sound like. Every doxx.net customer gets a real, routable IPv6 address on our space, not a NAT'd placeholder, and anyone can lease a dedicated public IPv4 address and point it at a VPS, a home server or a whole home network behind the mesh. The address starts closed, you open the ports you want, and whatever you expose is reached only through the doxx.net backbone. So hosting on our addresses rides this fix automatically: the SYN that triggers CVE-2026-98432 is dropped on our side of the tunnel, before it reaches a kernel you may not have been able to patch yet. That is also the part operators can copy, so here it is in full.

The policy

Four rules. All of them apply only to traffic flowing toward a host you protect, and only to packets with SYN set and ACK clear. Nothing a host initiates is ever touched, and no established flow is ever touched.

#MatchActionWhy it is safe
1IPv4, TCP SYN, IP header carries Record Route (7), Timestamp (68), Loose Source Route (131) or Strict Source Route (137)dropThese are the options the kernel saves on the request sock. A client opening a connection to a server has no use for any of them, and RFC 7126 (BCP 186) already recommends dropping all four.
2IPv6, TCP SYN, any extension header between the IPv6 header and TCP (Hop-by-Hop, Destination Options, Routing, Fragment, Authentication Header)dropSame shape as rule 1: the kernel can stash SYN extension headers on the request (pktopts) for the child. An unsolicited SYN never needs them. Routing Header Type 0 is deprecated outright (RFC 5095).
3IPv6 first fragment that does not contain the full TCP headerdropA legitimate SYN is never fragmented, and RFC 7112 requires the whole header chain in the first fragment. A tiny first fragment is the classic trick for hiding a TCP header from inspection.
4TCP SYN carrying MPTCP MP_CAPABLE (option kind 30, subtype 0)overwrite the option with NOPs, recompute the TCP checksum, forwardRemoves the MPTCP attack surface entirely. The connection completes as plain TCP; RFC 8684 is designed to tolerate exactly this middlebox behavior. It also closes an evasion where an extension header in front of TCP smuggled MP_CAPABLE past a filter that only looked at the immediate next header.

We deliberately do not drop IPv4 options wholesale. Router Alert (148) is not the vector and passes. We do not touch SYN/ACKs, so a customer's own outbound MPTCP connections, which arrive as inbound SYN/ACKs, keep working. We do not act on non-SYN TCP, UDP, ICMP, or anything with a header chain we cannot parse: a chain the receiving kernel cannot parse completes no handshake either, so fail-open costs nothing.

Rule 4 is optional for a transit operator. It is a modification, not a drop, and some operators have a policy against touching TCP options in flight. Rules 1 through 3 on their own make the demonstrated exploit undeliverable. We run all four because we also own the receiving side of the tunnel.

What it costs

On the doxx.net server fast path this is a single byte compare per packet: the IPv4 header-length nibble, or the IPv6 next-header byte. The option or extension-header walk only runs when that compare says there is something to walk, which on real traffic is almost never. We measured it at one to three nanoseconds with zero allocations, against the one to two microseconds of encryption that follow it. You will not see it on a graph.

Both the IPv4 and the IPv6 filters were built, tested and live on every site before we said a word about them on November 3, for every doxx.net user with a port open to the internet. The Security screen in the app shows every attempt we dropped, with the CVE, the vector and where it came from, so customers can watch it work instead of taking our word for it. The counts are rate-limited to one event per source per minute with the packet count attached, so a fifty-minute retransmit storm is a handful of events, not thousands.

If you want the options

A few people legitimately need these packets delivered: you run an MPTCP server behind a port forward, or you are doing record-route or source-routed diagnostics against your own box. So the filter is a per-tunnel setting, ip_options_filter, on by default for every account, and anyone on any plan can turn it off and back on. It is one call to the same API your agent already uses:

# off
curl -s -X POST https://config.doxx.net/v1/ -d token=$DOXX_TOKEN \
  -d update_tunnel=1 -d tunnel_token=$TUNNEL -d ip_options_filter=0

# on again
curl -s -X POST https://config.doxx.net/v1/ -d token=$DOXX_TOKEN \
  -d update_tunnel=1 -d tunnel_token=$TUNNEL -d ip_options_filter=1

list_tunnels=1 shows the current value on every tunnel. The change takes effect on the live tunnel within milliseconds, no reconnect, and it is a PATCH: nothing else on the tunnel moves. One switch covers all three mechanisms (the IPv4 option drop, the IPv6 extension-header drop and the MP_CAPABLE strip), because the people who need one of them off need them all off. Turning it off removes the protection for that tunnel only; if the box behind it is an unpatched kernel, patch it first.

How to run it on your edge

Put it on the last hop toward the customer, not in the core. That is where the direction is unambiguous: anything leaving the interface toward the customer is inbound to them. On a Linux-based edge, BNG, or CPE, this is the whole thing:

sudo nft -f - <<'EOF'
table inet cve_2026_98432 {
    chain toward_customers {
        type filter hook forward priority -150; policy accept;

        # Rule 1. IPv4 SYN with IP options (header longer than 20 bytes).
        # nft can name lsrr/ssrr/rr but has no keyword for Timestamp, so the
        # header-length test is the one that actually closes the hole. The
        # only collateral is Router Alert on an inbound SYN, which nothing
        # legitimate sends.
        oifname "cust*" ip hdrlength > 5 tcp flags & (syn|ack) == syn counter drop

        # Rule 2. IPv6 SYN with any extension header in front of TCP.
        # "ip6 nexthdr" is the first next-header field; if the kernel had to
        # walk past something to reach TCP, that something is an extension header.
        oifname "cust*" meta l4proto tcp tcp flags & (syn|ack) == syn ip6 nexthdr != tcp counter drop

        # Rule 3. IPv6 first fragment too short to carry the TCP header.
        # Fragment header (8) plus a full TCP header (20) is 28 bytes of
        # payload; a first fragment shorter than that has nothing for
        # "tcp flags" above to see. RFC 7112 says drop it.
        oifname "cust*" exthdr frag exists meta l4proto tcp ip6 length < 28 counter drop
    }
}
EOF

Swap oifname "cust*" for whatever identifies your customer-facing interfaces, or match on destination prefix if that is cleaner in your topology. The priority -150 hook runs before the usual filter chain, so it layers on top of whatever policy you already have. For line-rate edges, the same four tests are a short XDP program; everything needed is in the first 60 bytes of the packet.

Prove it before you trust it. From outside, hping3 -S -p 443 --rroute toward a host behind the filter should make the rule 1 counter move and the host's SYN backlog stay silent; a normal curl to the same host should be unaffected, because a normal SYN carries no options.

On hardware routers

If your edge is a Juniper, Cisco or Arista box rather than a Linux host, the first thing to know is that the packet in question is probably already on your slow path. Most forwarding ASICs do not route an IPv4 packet with options at line rate; they trap it to the CPU, because the options have to be parsed and the header rewritten. The same is true of IPv6 packets with an extension-header chain on many platforms. So this filter is not a new cost on your hardware. It is a decision about what the CPU does with a packet it was going to see anyway, and the right decision for an inbound SYN is to drop it before it goes any further.

The match you want is the blunt one: "TCP SYN, not ACK, with any IP option present" for IPv4, and "TCP SYN, not ACK, with any extension header before the TCP header" for IPv6. Junos and IOS can both express the IPv4 form in a single ACL term, and Junos can express the IPv6 form. Matching the four specific option kinds the way our dataplane does is possible on some platforms, but on the ones we checked it forces the packet through the control plane for parsing, which is the opposite of what you want under a flood; "any option on a SYN" is the version that stays in hardware, and the only legitimate thing it catches is Router Alert on an inbound SYN, which nothing sends. Arista EOS has no options match in its ACL grammar at all; there it is either a user-defined TCAM field on the first header byte or a platform-level drop of all IPv4-options traffic, which on the R-series is simply dropping the packets the ASIC was already trapping.

Two of our four rules do not translate to router ACLs, and we would rather say so than hand you a configuration that quietly does less than the post describes. Rewriting MP_CAPABLE out of a SYN is a modification, not a match; no router ACL does it, and you do not need it, because rules 1 and 2 alone make the demonstrated exploit undeliverable. The tiny-first-fragment rule has partial analogs ("undetermined transport" on Cisco, first-fragment as a building block on Junos) but nothing as precise as "first fragment too short to hold the TCP header"; RFC 9288 is the current guidance on extension-header filtering at transit routers if you want to go further.

We are not publishing vendor snippets here, deliberately. Every router ACL that matches on IP options is evaluated differently depending on the ASIC generation and the release, and a copy-pasted term that silently punts to the CPU is worse than no term. Build it from the two match descriptions above, test it with the same hping3 --rroute probe, and watch where the counter increments: if it is a control-plane policer and not the interface filter, you have found out something about your platform worth knowing before the next CVE.

What this does not do

It doesn't patch the kernel. It buys your customers the time to, and it means the ones who never will are no longer the softest targets on your network. The SYN-flood and memory-leak variants still touch code paths you want fixed, so keep the advisory open.

And while you are in the sysctl file on your own Linux routers and servers: net.mptcp.enabled = 0 unless you have a reason for it to be on. We found it enabled on hosts of ours that had no business running multipath TCP. Yours probably has it too.

Why we can do this, and why we will keep doing it

Most networks cannot ship a policy like this in a day. The filter lives in a vendor's firmware, the edge is a dozen platforms from three acquisitions, and the change window is next quarter. VPN and mesh products have the opposite problem: they are software, but they treat themselves as pipes and decline to look at what they carry, so the SYN goes through untouched to a kernel that trusts it.

doxx.net is engineered differently, on purpose. We own the servers, the IP space, the backbone between sites, the DNS, the certificate authority and the code on every tunnel endpoint. The place this filter runs is a Go function in our own WireGuard server, in the path every inbound packet already takes before it is encrypted toward a customer. Writing it, testing it against the exploit's packet shapes, wiring the telemetry into the app and deploying it to every site was done, for IPv4 and IPv6 both, before we announced it on day one. Customers were covered before they knew there was something to be covered from. There was no vendor to wait for and no change board to convince.

That is the point of running a parallel internet on infrastructure we control: when the next CVE has a shape on the wire, we can take it off the table for every customer at once, at the network layer, without asking them to do anything. Expect us to be proactive about this across the backbone. October will not be the last month like October.

The internet has a lot of filtering in it that we argue about: ports blocked because of a worm in 2003, ICMP dropped because somebody read a bad blog post, extension headers thrown away on principle. This is not that. This is four precise tests on the first packet of a connection, matching shapes that have no legitimate use toward a server and that RFCs have told us to drop for a decade. The difference in October was that the shape also became a root shell. Network operators should be doing this. VPN and mesh operators especially: you already have the software in the path, so you are one function away.

Get started at doxx.net, or point your agent at config.doxx.net. Join the community at doxx.net/discord.

Barrett Lyon is founder and CEO of doxx.net. He founded Prolexic Technologies (acquired by Akamai), BitGravity (acquired by Tata Communications), XDN (acquired by Fortinet), and Defense.net (acquired by F5), and has worked with law enforcement to track extortion crews across continents.

Ask an AI assistant

Share this article

Private Everywhere

Stop giving the internet everything

Keep your browsing, messages, files, and agents private.