[PATCH v2 1/5] genirq: Synchronize in-flight handlers during NMI teardown

From: Mayank Rungta

Date: Fri Aug 28 2026 - 21:00:59 EST


When freeing an interrupt requested via request_nmi(), __cleanup_nmi()
currently tears down the NMI and frees the irqaction structure without
waiting for in-flight instances of the handler on other CPUs to complete,
which can lead to use-after-free conditions on module unload.

Because NMIs cannot acquire desc->lock or set IRQD_IRQ_INPROGRESS without
risking deadlocks, synchronizing in-flight NMIs on other CPUs during
teardown (__synchronize_hardirq) relies on querying the hardware
controller state via __irq_get_irqchip_state(IRQCHIP_STATE_ACTIVE).

Enforce that any interrupt controller claiming NMI support via
IRQCHIP_SUPPORTS_NMI must implement ->irq_get_irqchip_state(). In
__cleanup_nmi(), shut down the line, call __synchronize_hardirq(desc, true)
to wait for in-flight handlers, and only then tear down the NMI
configuration and deactivate the interrupt domain before freeing the action
structure.

Also add a WARN(in_interrupt()) check in free_nmi() matching __free_irq()
to prevent freeing NMIs from atomic contexts since synchronization and
resource cleanup can sleep and spin.

Assisted-by: Antigravity:gemini
Signed-off-by: Mayank Rungta <mrungta@xxxxxxxxxx>
---
kernel/irq/manage.c | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 2fbff2618a1e..61384925b921 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -1379,6 +1379,14 @@ static bool irq_supports_nmi(struct irq_desc *desc)
if (d->chip->irq_bus_lock || d->chip->irq_bus_sync_unlock)
return false;

+ /*
+ * NMIs cannot set IRQD_IRQ_INPROGRESS because they cannot acquire
+ * spinlocks. Synchronous disable and teardown require querying the
+ * hardware state via ->irq_get_irqchip_state().
+ */
+ if (!d->chip->irq_get_irqchip_state)
+ return false;
+
return d->chip->flags & IRQCHIP_SUPPORTS_NMI;
}

@@ -2035,10 +2043,6 @@ static const void *__cleanup_nmi(unsigned int irq, struct irq_desc *desc)
const char *devname = NULL;

scoped_guard(raw_spinlock_irqsave, &desc->lock) {
- irq_nmi_teardown(desc);
-
- desc->istate &= ~IRQS_NMI;
-
if (!WARN_ON(desc->action == NULL)) {
action = desc->action;
irq_pm_remove_action(desc, action);
@@ -2047,11 +2051,20 @@ static const void *__cleanup_nmi(unsigned int irq, struct irq_desc *desc)
desc->action = NULL;

irq_settings_clr_disable_unlazy(desc);
- irq_shutdown_and_deactivate(desc);
+ irq_shutdown(desc);
}

irq_proc_update_valid(desc);

+ /* Ensure all in-flight NMI handlers on other CPUs complete before freeing action */
+ __synchronize_hardirq(desc, true);
+
+ scoped_guard(raw_spinlock_irqsave, &desc->lock) {
+ irq_nmi_teardown(desc);
+ desc->istate &= ~IRQS_NMI;
+ irq_domain_deactivate_irq(&desc->irq_data);
+ }
+
if (action)
unregister_handler_proc(irq, action);
kfree(action);
@@ -2068,6 +2081,8 @@ const void *free_nmi(unsigned int irq, void *dev_id)
{
struct irq_desc *desc = irq_to_desc(irq);

+ WARN(in_interrupt(), "Trying to free NMI %d from IRQ context!\n", irq);
+
if (!desc || WARN_ON(!irq_is_nmi(desc)))
return NULL;


--
2.55.0.897.gb25b4bd76c-goog