---
id: "CVE-2026-98610"
kind: "vulnerability"
aliases: ["CVE-2026-98610"]
title: "Linux kernel: HFSC packet classify loop can hang CPU forever"
severity: "unknown"
cvss: null
epss: null
exploited: false
status: "open"
category: "runtime"
products: []
ecosystems: []
tags: ["containers", "linux-kernel", "runtime", "self-hosted", "shared-hosts"]
not_affected_if: ["hfsc-not-built", "no-untrusted-netns-users"]
published_at: "2026-10-18T16:17:13+00:00"
first_seen_at: "2026-10-23T21:57:16+00:00"
updated_at: "2026-10-25T05:21:26+00:00"
sources: [{"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-98610", "first_seen_at": "2026-10-23T21:57:16+00:00"}]
---

# Linux kernel: HFSC packet classify loop can hang CPU forever

A crafted set of HFSC traffic-control filters makes the kernel's packet classifier loop endlessly between two classes while holding a lock, causing a soft lockup from a single packet. The sources describe a denial of service; they do not describe code execution.

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

## How to check

- Run `uname -r` and compare against the fixed kernel releases listed in the NVD entry
- Check whether the HFSC scheduler is loaded or built in: `lsmod | grep sch_hfsc` or `grep CONFIG_NET_SCH_HFSC /boot/config-$(uname -r)`
- Check whether unprivileged user namespaces are enabled: `sysctl kernel.unprivileged_userns_clone` or `sysctl user.max_user_namespaces`

## Fix

- Update the Linux kernel to a release that includes the upstream fix for sch_hfsc classify bounding (follows commit 729c4896ab82 for HTB)
- Until patched, avoid creating HFSC qdiscs with u32 filters on untrusted network namespaces: `lsmod | grep sch_hfsc` and unload if not needed

## Timeline

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

## From the sources

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

net/sched: sch_hfsc: bound the classify inner-filter walk with a drift budget

hfsc_classify() applies the "filter may only point downwards" level check
only when the filter result carries no bound class. A filter created with
a flowid gets res.class set once at bind time, so the check never runs for
it during classification. hfsc_adjust_levels() can later raise a class's
level without revalidating existing bindings, leaving two binds that were
each legal at bind time pointing at each other; the classify walk then
bounces between two interior classes forever with the qdisc lock held and
BH disabled — a soft lockup from a single packet. The stuck walk trips
the watchdog:

  watchdog: BUG: soft lockup - CPU#3 stuck for 13s! [ping:444]
  RIP: 0010:u32_classify+0x542/0x17f0
  ...
  tcf_classify+0x66/0xa0
  hfsc_enqueue+0x166/0xdf0

Bound the traversal with a budget of non-descending hops, the only way a
configured walk can move without descending the class tree once levels
drift after bind time. The budget is cumulative over the whole walk and
is deliberately not reset on a descending hop: a chain that alternates a
descent with a lateral hop would return the budget every lap and never
trip. Descending hops never decrement it, so legitimately deep trees are
unaffected and a terminating lateral chain still classifies normally.
Drop the packet with a rate-limited warning once the budget is exhausted,
mirroring the merged HTB fix.

This is a follow-up to commit 729c4896ab82 ("net/sched: sch_htb: limit
htb_classify inner-class filter hops"), which bounded the same classify
loop on the HTB side but left the HFSC walk unbounded.

Conditions to recreate the bug:
- CONFIG_NET_SCHED, CONFIG_NET_SCH_HFSC, CONFIG_NET_CLS_U32,
  CONFIG_LOCKUP_DETECTOR.
- Build a cycle with two legal-at-bind-time flowid binds and a level
  drift: class X 1:1 (child of root) with leaf child 1:10; class Y 1:2
  (sibling of X) with children 1:20 and 1:200; root u32 filter flowid
  1:1; filter on X flowid 1:2 (legal when Y is a leaf); after Y's level
  rises to 2, filter on Y flowid 1:1 (legal then). Send one packet (ping
  on the device). Unfixed kernel: classify spins with the qdisc lock
  held; with softlockup_panic=1 it panics.
- Reachable from unprivileged user via unshare -Urn (CAP_NET_ADMIN).

---

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