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

From: Mayank Rungta

Date: Wed Sep 02 2026 - 22:00:08 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(), serialize teardown under desc->request_mutex, shut down
the line, call __synchronize_hardirq(desc, true) while keeping desc->action
valid to avoid racing with lockless in-flight handlers, and only then clear
desc->action, tear down the NMI configuration, deactivate the interrupt
domain, and update proc status 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 | 31 +++++++++++++++++++++++++------
1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 2fbff2618a1e..a9973b61163a 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;
}

@@ -2034,11 +2042,20 @@ static const void *__cleanup_nmi(unsigned int irq, struct irq_desc *desc)
struct irqaction *action = NULL;
const char *devname = NULL;

+ guard(mutex)(&desc->request_mutex);
+
scoped_guard(raw_spinlock_irqsave, &desc->lock) {
- irq_nmi_teardown(desc);
+ irq_settings_clr_disable_unlazy(desc);
+ irq_shutdown(desc);
+ }

- desc->istate &= ~IRQS_NMI;
+ /*
+ * Ensure all in-flight NMI handlers on other CPUs complete before
+ * clearing desc->action or tearing down NMI state.
+ */
+ __synchronize_hardirq(desc, true);

+ scoped_guard(raw_spinlock_irqsave, &desc->lock) {
if (!WARN_ON(desc->action == NULL)) {
action = desc->action;
irq_pm_remove_action(desc, action);
@@ -2046,12 +2063,12 @@ 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_nmi_teardown(desc);
+ desc->istate &= ~IRQS_NMI;
+ irq_domain_deactivate_irq(&desc->irq_data);
+ irq_proc_update_valid(desc);
}

- irq_proc_update_valid(desc);
-
if (action)
unregister_handler_proc(irq, action);
kfree(action);
@@ -2068,6 +2085,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.970.g62bdec98f9-goog