Skip to content

Commit df81dfc

Browse files
ecree-solarflareKAGA-KOKO
authored andcommitted
genirq: Fix reference leaks on irq affinity notifiers
The handling of notify->work did not properly maintain notify->kref in two cases: 1) where the work was already scheduled, another irq_set_affinity_locked() would get the ref and (no-op-ly) schedule the work. Thus when irq_affinity_notify() ran, it would drop the original ref but not the additional one. 2) when cancelling the (old) work in irq_set_affinity_notifier(), if there was outstanding work a ref had been got for it but was never put. Fix both by checking the return values of the work handling functions (schedule_work() for (1) and cancel_work_sync() for (2)) and put the extra ref if the return value indicates preexisting work. Fixes: cd7eab4 ("genirq: Add IRQ affinity notifiers") Fixes: 59c3984 ("genirq: Prevent use-after-free and work list corruption") Signed-off-by: Edward Cree <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Acked-by: Ben Hutchings <[email protected]> Link: https://lkml.kernel.org/r/[email protected]
1 parent 5ad0ec0 commit df81dfc

File tree

1 file changed

+9
-2
lines changed

1 file changed

+9
-2
lines changed

kernel/irq/manage.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,11 @@ int irq_set_affinity_locked(struct irq_data *data, const struct cpumask *mask,
323323

324324
if (desc->affinity_notify) {
325325
kref_get(&desc->affinity_notify->kref);
326-
schedule_work(&desc->affinity_notify->work);
326+
if (!schedule_work(&desc->affinity_notify->work)) {
327+
/* Work was already scheduled, drop our extra ref */
328+
kref_put(&desc->affinity_notify->kref,
329+
desc->affinity_notify->release);
330+
}
327331
}
328332
irqd_set(data, IRQD_AFFINITY_SET);
329333

@@ -423,7 +427,10 @@ irq_set_affinity_notifier(unsigned int irq, struct irq_affinity_notify *notify)
423427
raw_spin_unlock_irqrestore(&desc->lock, flags);
424428

425429
if (old_notify) {
426-
cancel_work_sync(&old_notify->work);
430+
if (cancel_work_sync(&old_notify->work)) {
431+
/* Pending work had a ref, put that one too */
432+
kref_put(&old_notify->kref, old_notify->release);
433+
}
427434
kref_put(&old_notify->kref, old_notify->release);
428435
}
429436

0 commit comments

Comments
 (0)