mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
genirq: Defer kfree(irqaction) until irq_thread_dtor() finishes
When an interrupt thread exits abnormally via do_exit() (e.g. the interrupt
handler oopses) without returning from irq_thread(), a concurrent call to
free_irq() could free the irqaction before the irq_thread_dtor() exit task
work runs.
irq thread (oopsed) free_irq()
------------------- ----------
do_exit()
exit_mm()
complete_vfork_done() -------> kthread_stop() returns
exit_files() ... kfree(action)
exit_task_work()
irq_thread_dtor()
action->... <- use-after-free
Track the pending exit work with the IRQTF_EXIT_WORK thread flag, and have
__free_irq() wait for the bit to clear before kfree().
irq_thread_dtor() only runs on irq_thread abnormal exit. If that dtor also
faults, free_irq() will hang indefinitely, but double IRQ thread faults
require a reboot anyways.
[ tglx: Add missing include ]
Fixes: ed3e694d78 ("move exit_task_work() past exit_files() et.al.")
Signed-off-by: Richard Patel <ripatel@wii.dev>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261009145842.1616117-2-ripatel@wii.dev
This commit is contained in:
parent
feb5c9a244
commit
d04adff686
|
|
@ -36,6 +36,7 @@ extern struct irqaction chained_action;
|
|||
* IRQTF_AFFINITY - irq thread is requested to adjust affinity
|
||||
* IRQTF_FORCED_THREAD - irq action is force threaded
|
||||
* IRQTF_READY - signals that irq thread is ready
|
||||
* IRQTF_EXIT_WORK - irq thread exit task_work is pending
|
||||
*/
|
||||
enum {
|
||||
IRQTF_RUNTHREAD,
|
||||
|
|
@ -43,6 +44,7 @@ enum {
|
|||
IRQTF_AFFINITY,
|
||||
IRQTF_FORCED_THREAD,
|
||||
IRQTF_READY,
|
||||
IRQTF_EXIT_WORK,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <linux/sched/isolation.h>
|
||||
#include <uapi/linux/sched/types.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/wait_bit.h>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
|
|
@ -1176,17 +1177,14 @@ void wake_threads_waitq(struct irq_desc *desc)
|
|||
wake_up(&desc->wait_for_threads);
|
||||
}
|
||||
|
||||
static void irq_thread_dtor(struct callback_head *unused)
|
||||
static void __irq_thread_dtor(struct irqaction *action)
|
||||
{
|
||||
struct task_struct *tsk = current;
|
||||
struct irq_desc *desc;
|
||||
struct irqaction *action;
|
||||
|
||||
if (WARN_ON_ONCE(!(current->flags & PF_EXITING)))
|
||||
return;
|
||||
|
||||
action = kthread_data(tsk);
|
||||
|
||||
pr_err("exiting task \"%s\" (%d) is an active IRQ thread (irq %d)\n",
|
||||
tsk->comm, tsk->pid, action->irq);
|
||||
|
||||
|
|
@ -1203,6 +1201,14 @@ static void irq_thread_dtor(struct callback_head *unused)
|
|||
irq_finalize_oneshot(desc, action);
|
||||
}
|
||||
|
||||
static void irq_thread_dtor(struct callback_head *unused)
|
||||
{
|
||||
struct irqaction *action = kthread_data(current);
|
||||
|
||||
__irq_thread_dtor(action);
|
||||
clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags);
|
||||
}
|
||||
|
||||
static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action)
|
||||
{
|
||||
struct irqaction *secondary = action->secondary;
|
||||
|
|
@ -1263,6 +1269,7 @@ static int irq_thread(void *data)
|
|||
else
|
||||
handler_fn = irq_thread_fn;
|
||||
|
||||
set_bit(IRQTF_EXIT_WORK, &action->thread_flags);
|
||||
init_task_work(&on_exit_work, irq_thread_dtor);
|
||||
task_work_add(current, &on_exit_work, TWA_NONE);
|
||||
|
||||
|
|
@ -1283,6 +1290,7 @@ static int irq_thread(void *data)
|
|||
* oneshot mask bit can be set.
|
||||
*/
|
||||
task_work_cancel_func(current, irq_thread_dtor);
|
||||
clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1853,6 +1861,12 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void irq_stop_thread(struct irqaction *action)
|
||||
{
|
||||
kthread_stop_put(action->thread);
|
||||
wait_on_bit(&action->thread_flags, IRQTF_EXIT_WORK, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal function to unregister an irqaction - used to free
|
||||
* regular and special interrupts that are part of the architecture.
|
||||
|
|
@ -1958,9 +1972,9 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
|
|||
* the same bit to a newly requested action.
|
||||
*/
|
||||
if (action->thread) {
|
||||
kthread_stop_put(action->thread);
|
||||
irq_stop_thread(action);
|
||||
if (action->secondary && action->secondary->thread)
|
||||
kthread_stop_put(action->secondary->thread);
|
||||
irq_stop_thread(action->secondary);
|
||||
}
|
||||
|
||||
/* Last action releases resources */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user