CVE-2026-89589: acpi/apei/ghes: Use raw_spinlock_t for CXL CPER work locks
In the Linux kernel, the following vulnerability has been resolved:
acpi/apei/ghes: Use rawspinlockt for CXL CPER work locks
The CXL CPER work registration and unregistration helpers acquire cxlcperworklock and cxlcperproterrworklock with a spinlock guard(), which leaves local interrupts enabled. The corresponding post paths (cxlcperpostevent(), cxlcperpostproterr()) execute in hard IRQ context (they are called from the GHES error notification path) and acquire the same locks with an irqsave guard().
If a CPU is holding one of these locks via a spinlock guard() when a GHES interrupt arrives on the same CPU, the IRQ handler spins on the held lock waiting for it to release, while the lock holder is preempted by the IRQ. The result is a deadlock.
Convert both locks from spinlockt to rawspinlockt and use guard() at all call sites. On PREEMPTRT kernels spinlockt is backed by rtmutex and sleeping from hard IRQ context is not permitted; rawspinlockt is safe in both contexts.
Add WARNONCE to both register functions to surface double-registration bugs at runtime.
Restructure both unregister functions to clear the global work pointer under the lock before calling cancelworksync(), closing the window where a CPER interrupt could schedule work on a pointer about to be freed. Add kfiforeset() after cancelworksync() so stale entries are not replayed on next module load.
Both kfifos are single-consumer: only one workstruct is registered at a time, enforced by the WARNONCE guard in the register functions. kfiforeset() is safe outside the lock because cancelworksync() has already quiesced the consumer, and no new consumer can register until the current module exit completes and a fresh module init runs.
Remove the redundant cancelworksync() call from cxlrasexit() and cxlpcidriverexit(). The CPER unregister functions now quiesce the work internally.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Configuration
Change both locks cxl_cper_work_lock and cxl_cper_prot_err_work_lock from spinlock_t to raw_spinlock_t.
Linux kernel (cxl/CPER work registration helpers; acpi/apei/ghes) Use raw_spinlock_t for cxl_cper_work_lock and cxl_cper_prot_err_work_lock (instead of spinlock_t) = raw_spinlock_t - Configuration
In all call sites from both the GHES error notification path (IRQ/CPER context) and normal contexts, acquire the CXL CPER work locks using guard() with irqsave.
Linux kernel (cxl/CPER work registration helpers; acpi/apei/ghes) Lock acquisition at IRQ context = irqsave guard() - Configuration
Add WARN_ONCE to both CPER work register functions to surface double-registration (enforced by WARN_ONCE guard in the register functions).
Linux kernel (CXL CPER work unregister/register helpers) register helper double-registration detection = Add WARN_ONCE to both register functions - Configuration
In both unregister paths, call kfifo_reset() after cancel_work_sync() so stale entries are cleared.
Linux kernel (CXL CPER work unregister helpers) kfifo_reset after cancel_work_sync = Call kfifo_reset() after cancel_work_sync() - Configuration
Remove the redundant cancel_work_sync() call from cxl_ras_exit().
Linux kernel (cxl/CPER work unregistration) Remove redundant cancel_work_sync() call = Remove extra cancel_work_sync() in cxl_ras_exit() - Configuration
Restructure both unregister functions to clear the global work pointer; the CPER unregister functions should quiesce and clear global work pointers so consumers can’t schedule work on pointers about to be freed.
Linux kernel (CXL CPER unregister helpers) Restructure unregister functions to clear global work pointer = Clear global work pointer during CPER unregister - Compensating control
Ensure unregister quiesces the consumer so no new consumer can register until module exit completes and a fresh module init can run (prevents replay/scheduling bugs at runtime).
Event History
Frequently Asked Questions
Which systems are exposed to this deadlock condition?
Systems using the Linux kernel's CXL CPER work registration paths and receiving GHES error notifications are relevant. The deadlock requires a GHES interrupt to arrive on a CPU while that CPU holds one of the affected CXL CPER work locks.
What is required to trigger the failure?
A CPU must hold cxl_cper_work_lock or cxl_cper_prot_err_work_lock with local interrupts enabled, then receive a GHES interrupt on that same CPU. The hard-IRQ handler attempts to acquire the same lock and can spin indefinitely while preempting the lock holder.
Why are PREEMPT_RT kernels specifically relevant?
On PREEMPT_RT kernels, spinlock_t is backed by an rt_mutex, which cannot safely be used from hard IRQ context because it may sleep. The resolution changes the affected locks to raw_spinlock_t so they are safe in both normal and hard-IRQ contexts.
Is there an indication of duplicate CXL CPER work registration after the fix?
The resolved code adds WARN_ONCE checks to both registration functions. These warnings are intended to expose double-registration bugs at runtime.