CVE-2026-98538
NoneCVSS
—
EPSS
0.2%
Published
Oct 18, 2026
CISA due date
—
Cloudanix analysis
A Linux kernel bug can make the whole system freeze for many seconds when a program creates and then removes a huge number of files inside certain resource groups.
- Exploitation
- Local Privilege
- Attacker needs
- Local User
- Complexity
- Moderate
- Impact
- Denial Of Service
- Component
- OS kernel
- Class
- Denial of service
- Remediation
- Upgrade
- Effort
- Medium
- Patch risk
- Low
What it actually does
cleanup_offline_cgwb repeatedly rescans the same prefix of wb->b_attached and wb->b_dirty_time under wb->list_lock; with millions of inodes the repeated full-list walks produce quadratic CPU time that can hold a CPU for tens of seconds and trigger soft lockups.
Why it matters to the business
Unpatched kernels may experience prolonged system unresponsiveness or watchdog-induced crashes during writeback cgroup teardown when a large number of inodes are present, disrupting workloads that create or delete many files under memory or I/O cgroups.
How an attack unfolds
An attacker with the ability to create and dirty a very large number of inodes attached to a memory cgroup triggers destruction of that cgroup's writeback structure; the kernel then performs the expensive repeated scans inside cleanup_offline_cgwb, consuming CPU for an extended period and potentially causing soft lockups before the linear-scan fix is applied.
How to fix it
Apply the upstream patch that walks both b_attached and b_dirty_time from the oldest end and rotates each scanned inode to the newest end so that subsequent passes resume where the previous pass stopped; rebuild and boot the fixed kernel on all affected systems.
How to confirm the fix worked
Confirm the commit that bounds cleanup_offline_cgwb rescans by rotating scanned inodes is present in the running kernel; exercise cgroup teardown under load with a large inode count and verify that CPU usage remains linear rather than quadratic and that soft lockups no longer occur.
Typical attack path
- Create or inherit a memory cgroup and attach many inodes to its writeback instance
- Dirty the inodes and then destroy the cgroup so that cleanup_offline_cgwb is invoked repeatedly
- Trigger the quadratic rescans that hold wb->list_lock for tens of seconds
- Cause soft lockup warnings or system stalls on affected CPUs
How to detect it
Search kernel logs for soft lockup or hung task messages referencing wb->list_lock, cleanup_offline_cgwb, or writeback during cgroup teardown; correlate with processes that rapidly create or delete large numbers of inodes inside memory cgroups.
What your security team should do
- Prioritize patching production kernels that host workloads creating large numbers of inodes inside cgroups
- Monitor for soft lockup reports mentioning wb->list_lock or cleanup_offline_cgwb after any cgroup destruction events
- Test the fix in staging environments that replicate high-inode-count cgroup teardown before broad rollout
The record
In the Linux kernel, the following vulnerability has been resolved: writeback: bound cleanup_offline_cgwb() rescans by rotating scanned inodes cleanup_offline_cgwb() prepares at most WB_MAX_INODES_PER_ISW inodes per call and is called again until the dying wb is drained, but every call walks wb->b_attached and then wb->b_dirty_time from the same end. Inodes already prepared (they stay on the list with I_WB_SWITCH set until the switch worker runs) and inodes that cannot be switched (I_FREEING, I_WILL_FREE, !SB_ACTIVE, DAX, already on the target wb) stay where they are, so each pass rescans a growing run of them under wb->list_lock and a full drain is quadratic in the number of inodes on the list. With ~17M inodes attached to one dying cgwb we saw this end in soft lockups, with CPUs reported stuck for 21-48s. Walk both lists from the oldest end and move every scanned inode to the newest end, so the next pass starts where the previous one stopped and the drain becomes linear. b_attached is unordered, so nobody sees the reorder there. b_dirty_time is ordered by dirtied_when, but the oldest unscanned inode stays at the end move_expired_inodes() picks from, sync takes the whole list regardless of order, and prepared inodes leave the list as soon as the switch work runs and get a new dirtied_time_when on the new wb anyway, so the only inodes left out of order are the ones that can never switch (DAX), and only on the dying wb.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|