---
id: "CVE-2026-98598"
kind: "vulnerability"
aliases: ["CVE-2026-98598"]
title: "Linux kernel IPv6: stale route exceptions can leak and stall netdev unregistration"
severity: "low"
cvss: null
epss: null
exploited: false
status: "open"
category: "infrastructure"
products: []
ecosystems: []
tags: ["infrastructure", "ipv6", "linux-kernel", "self-hosted"]
not_affected_if: ["no-ipv6-routing", "no-tunnel-devices"]
published_at: "2026-10-18T16:17:12+00:00"
first_seen_at: "2026-10-23T21:57:16+00:00"
updated_at: "2026-10-25T05:21:36+00:00"
sources: [{"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-98598", "first_seen_at": "2026-10-23T21:57:16+00:00"}]
---

# Linux kernel IPv6: stale route exceptions can leak and stall netdev unregistration

When an IPv6 route is being removed, the kernel can still add a new route exception to it, creating a reference cycle. The leaked route and its network device are never freed, so unregistering the device hangs.

_Summary, check and fix written by a model from the sources; labelled as such._

## How to check

- `uname -r` and compare against the fixed kernel builds in the distribution's advisory
- Look for kernel log lines `unregister_netdevice: waiting for ... to become free`

## Fix

- Install the kernel build from your distribution that includes the upstream fix for this CVE
- Reboot into the patched kernel; a leaked device cannot be cleared without a reboot

## Timeline

- 2026-10-23T21:57:16+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-98598

## From the sources

In the Linux kernel, the following vulnerability has been resolved:

ipv6: Prevent rt6_insert_exception() for dying fib6_info.

Before the cited commit, fib6_nh_flush_exceptions() always set
from->exception_bucket_flushed = 1 under rt6_exception_lock to
prevent rt6_insert_exception() from inserting a new exception
for a dying fib6_info.

The flag was replaced with the FIB6_EXCEPTION_BUCKET_FLUSHED
bit stored in nh->rt6i_exception_bucket.

The problem is that now the bit is only set when the bucket
is not NULL and fib6_nh_flush_exceptions() is called from
fib6_nh_release() after fib6_ref has already reached zero.

If rt6_insert_exception() is called while the target fib6_info
is being removed via fib6_purge_rt(), a new exception could be
created successfully because rt6_flush_exceptions() no longer
sets the bit.

This creates a reference cycle between the fib6_info and the
exception route, leaking the fib6_info, its nexthop device,
and all per-CPU routes in fib6_nh->rt6i_pcpu, which stalls netdev
unregistration.

[   34.680602] unregister_netdevice: waiting for gre6 to become free. Usage count = 68
[   44.920675] unregister_netdevice: waiting for gre6 to become free. Usage count = 68
[   55.176582] unregister_netdevice: waiting for gre6 to become free. Usage count = 68

Let's call fib6_drop_pcpu_from() before rt6_flush_exceptions(),
to set fib6_destroying before rt6_exception_lock, and check
f6i->fib6_destroying in rt6_insert_exception().

Note that FIB6_EXCEPTION_BUCKET_FLUSHED logic is dead and
we can clean it up in net-next.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
