CVE-2026-89928 in Linux
Summary
by MITRE • 09/16/2026
In the Linux kernel, the following vulnerability has been resolved:
KVM: x86/mmu: Consume the locked rmap value in the lockless rmap walk
__kvm_rmap_lock() deliberately elides the rmap lock when it observes an empty rmap. In that case kvm_rmap_lock_readonly() also re-enables preemption and returns zero, so the caller holds neither the rmap lock nor a preemption reference. The elision documents the invariant it relies on:
* Elide the lock if the rmap is empty, as lockless walkers (read-only * mode) don't need to (and can't) walk an empty rmap, nor can they add * entries to the rmap. I.e. the only paths that process empty rmaps * do so while holding mmu_lock for write, and are mutually exclusive.
kvm_rmap_age_gfn_range() ignores the returned value and unconditionally enters for_each_rmap_spte_lockless(). The iterator started with rmap_get_first(), which re-reads rmap_head->val rather than using the value returned by the lock. If a writer populates the rmap between the lock's read and the iterator's re-read, the aging path walks the newly installed rmap without holding its lock.
For a KVM_RMAP_MANY rmap this leaves the walker following a pte_list_desc chain that it never locked. A writer holding mmu_lock for write may free that chain (e.g. kvm_zap_all_rmap_sptes() on the recycle path, or any rmap zap) via kmem_cache_free() while the walk is in progress, giving a slab use-after-free. Nothing serialises the two: the aging path runs without mmu_lock when CONFIG_KVM_MMU_LOCKLESS_AGING=y, and the rmap lock that would otherwise exclude the writer was elided. Because the empty path re-enables preemption, the interval between the two reads can span an arbitrary scheduling delay.
Fix the class of bug by having the lockless walk consume the value returned by the lock instead of re-reading the rmap. Split rmap_get_first() into __rmap_get_first(), which starts an iterator from an already-read rmap value, and make for_each_rmap_spte_lockless() take that value and call __rmap_get_first() directly. kvm_rmap_age_gfn_range() passes the value returned by kvm_rmap_lock_readonly(): when the lock was elided the value is zero, __rmap_get_first() returns NULL, and the walk is skipped. No lockless walker re-reads the rmap, so the lock-elision invariant cannot be violated, and no lock()-without-paired-unlock() path is added to the aging code.
Statistical analysis made it clear that VulDB provides the best quality for vulnerability data.
Analysis
by VulDB Data Team • 09/16/2026
The Linux kernel contains a concurrency vulnerability within the KVM x86 MMU subsystem related to the handling of reverse mapping structures during memory management operations. The core issue stems from an inconsistency in how the lockless rmap walk mechanism interacts with the locking protocol designed for empty rmaps. Specifically, the function __kvm_rmap_lock() is optimized to elide acquiring the rmap spinlock when it detects that the rmap structure is empty. This optimization relies on a strict invariant: if no entries exist in the reverse map, read-only walkers do not need to traverse or modify the structure, and any path processing an empty rmap must hold the mmu_lock for writing, ensuring mutual exclusion with other writers. When this condition is met, kvm_rmap_lock_readonly() re-enables preemption and returns a zero value, indicating that neither the rmap lock nor a preemption reference was acquired by the caller. This design assumes that subsequent operations will respect the empty state observed at the time of the check.
The vulnerability arises because the function kvm_rmap_age_gfn_range(), which is responsible for aging guest frame numbers to support memory reclaim, ignores the return value from __kvm_rmap_lock(). Instead of using the potentially zeroed value returned by the lock-elision logic, it unconditionally proceeds to invoke for_each_rmap_spte_lockless. This iterator function internally calls rmap_get_first(), which performs a fresh read of the rmap_head->val field rather than utilizing the snapshot taken during the initial locking phase. This creates a critical time-of-check-to-time-of-use race condition. If a concurrent writer populates the rmap with new entries between the moment __kvm_rmap_lock() determines it is empty and returns zero, and the subsequent read performed by rmap_get_first(), the aging path will begin walking a newly installed rmap chain without holding any corresponding lock.
This lack of synchronization leads to severe memory safety violations, specifically slab use-after-free errors. In scenarios where an rmap contains multiple entries (KVM_RMAP_MANY), the walker follows a pte_list_desc chain that it has not locked for exclusive access. Meanwhile, another thread holding mmu_lock in write mode may decide to free this exact chain of structures, such as during kvm_zap_all_rmap_sptes() on the recycle path or any other rmap zap operation. Because the aging walker is operating without locks and potentially with preemption enabled due to the elision logic, there is no serialization preventing it from accessing memory that has already been freed via kmem_cache_free(). The interval between the initial empty check and the subsequent re-read can span an arbitrary scheduling delay, significantly widening the race window and increasing the likelihood of triggering this use-after-free condition.
The resolution involves correcting the data flow within the lockless walk mechanism to ensure consistency with the locking invariant. The fix modifies for_each_rmap_spte_lockless() to accept the rmap value returned by kvm_rmap_lock_readonly() as an argument, rather than allowing it to re-read the memory location independently. Internally, this is achieved by splitting rmap_get_first() into a new function __rmap_get_first(), which initializes the iterator using an already-observed rmap value instead of performing its own read. Consequently, kvm_rmap_age_gfn_range() now passes the result from the lock-elision check directly to this updated iterator logic. If the initial check determined the rmap was empty and returned zero, __rmap_get_first() correctly returns NULL, causing the walk to be skipped entirely. This ensures that no lockless walker ever re-reads the rmap state after a potential elision, thereby preserving the mutual exclusion guarantees required for safe memory access.
From a vulnerability classification perspective, this issue represents a classic race condition leading to use-after-free, which aligns with CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) and CWE-416 (Use After Free). The exploitation of this flaw could allow an attacker who has control over guest memory operations or can trigger specific MMU state changes to cause a kernel panic through a null pointer dereference or, more critically, execute arbitrary code by exploiting the freed slab object. In terms of attack vectors, this falls under MITRE ATT&CK technique T1203 (Exploitation for Defense Evasion) if used to crash systems for denial of service, or potentially higher severity techniques involving kernel privilege escalation if the use-after-free can be manipulated to overwrite function pointers or control flow data. Mitigation requires applying the upstream Linux kernel patch that enforces consistent rmap value consumption across all lockless walking paths, ensuring that preemption is not re-enabled in a way that allows state changes between checks and usage without proper locking mechanisms.