| From 64edb885e76b08d63444ff4adbd2ec7a190c4c42 Mon Sep 17 00:00:00 2001 |
| From: Thomas Gleixner <tglx@linutronix.de> |
| Date: Fri, 6 Mar 2020 14:03:43 +0100 |
| Subject: [PATCH] genirq: Add protection against unsafe usage of |
| generic_handle_irq() |
| |
| commit c16816acd08697b02a53f56f8936497a9f6f6e7a upstream. |
| |
| In general calling generic_handle_irq() with interrupts disabled from non |
| interrupt context is harmless. For some interrupt controllers like the x86 |
| trainwrecks this is outright dangerous as it might corrupt state if an |
| interrupt affinity change is pending. |
| |
| Add infrastructure which allows to mark interrupts as unsafe and catch such |
| usage in generic_handle_irq(). |
| |
| Reported-by: sathyanarayanan.kuppuswamy@linux.intel.com |
| Signed-off-by: Thomas Gleixner <tglx@linutronix.de> |
| Acked-by: Marc Zyngier <maz@kernel.org> |
| Link: https://lkml.kernel.org/r/20200306130623.590923677@linutronix.de |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/include/linux/irq.h b/include/linux/irq.h |
| index f8755e5fcd74..b736900394d3 100644 |
| --- a/include/linux/irq.h |
| +++ b/include/linux/irq.h |
| @@ -211,6 +211,8 @@ struct irq_data { |
| * IRQD_CAN_RESERVE - Can use reservation mode |
| * IRQD_MSI_NOMASK_QUIRK - Non-maskable MSI quirk for affinity change |
| * required |
| + * IRQD_HANDLE_ENFORCE_IRQCTX - Enforce that handle_irq_*() is only invoked |
| + * from actual interrupt context. |
| */ |
| enum { |
| IRQD_TRIGGER_MASK = 0xf, |
| @@ -234,6 +236,7 @@ enum { |
| IRQD_DEFAULT_TRIGGER_SET = (1 << 25), |
| IRQD_CAN_RESERVE = (1 << 26), |
| IRQD_MSI_NOMASK_QUIRK = (1 << 27), |
| + IRQD_HANDLE_ENFORCE_IRQCTX = (1 << 28), |
| }; |
| |
| #define __irqd_to_state(d) ACCESS_PRIVATE((d)->common, state_use_accessors) |
| @@ -303,6 +306,16 @@ static inline bool irqd_is_single_target(struct irq_data *d) |
| return __irqd_to_state(d) & IRQD_SINGLE_TARGET; |
| } |
| |
| +static inline void irqd_set_handle_enforce_irqctx(struct irq_data *d) |
| +{ |
| + __irqd_to_state(d) |= IRQD_HANDLE_ENFORCE_IRQCTX; |
| +} |
| + |
| +static inline bool irqd_is_handle_enforce_irqctx(struct irq_data *d) |
| +{ |
| + return __irqd_to_state(d) & IRQD_HANDLE_ENFORCE_IRQCTX; |
| +} |
| + |
| static inline bool irqd_is_wakeup_set(struct irq_data *d) |
| { |
| return __irqd_to_state(d) & IRQD_WAKEUP_STATE; |
| diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h |
| index ed4843df6220..207bac6ad4c3 100644 |
| --- a/kernel/irq/internals.h |
| +++ b/kernel/irq/internals.h |
| @@ -422,6 +422,10 @@ static inline struct cpumask *irq_desc_get_pending_mask(struct irq_desc *desc) |
| { |
| return desc->pending_mask; |
| } |
| +static inline bool handle_enforce_irqctx(struct irq_data *data) |
| +{ |
| + return irqd_is_handle_enforce_irqctx(data); |
| +} |
| bool irq_fixup_move_pending(struct irq_desc *desc, bool force_clear); |
| #else /* CONFIG_GENERIC_PENDING_IRQ */ |
| static inline bool irq_can_move_pcntxt(struct irq_data *data) |
| @@ -448,6 +452,10 @@ static inline bool irq_fixup_move_pending(struct irq_desc *desc, bool fclear) |
| { |
| return false; |
| } |
| +static inline bool handle_enforce_irqctx(struct irq_data *data) |
| +{ |
| + return false; |
| +} |
| #endif /* !CONFIG_GENERIC_PENDING_IRQ */ |
| |
| #if !defined(CONFIG_IRQ_DOMAIN) || !defined(CONFIG_IRQ_DOMAIN_HIERARCHY) |
| diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c |
| index 9be995fc3c5a..6a27603fe1cf 100644 |
| --- a/kernel/irq/irqdesc.c |
| +++ b/kernel/irq/irqdesc.c |
| @@ -638,9 +638,15 @@ void irq_init_desc(unsigned int irq) |
| int generic_handle_irq(unsigned int irq) |
| { |
| struct irq_desc *desc = irq_to_desc(irq); |
| + struct irq_data *data; |
| |
| if (!desc) |
| return -EINVAL; |
| + |
| + data = irq_desc_get_irq_data(desc); |
| + if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data))) |
| + return -EPERM; |
| + |
| generic_handle_irq_desc(desc); |
| return 0; |
| } |
| diff --git a/kernel/irq/resend.c b/kernel/irq/resend.c |
| index 98c04ca5fa43..5064b13b80d6 100644 |
| --- a/kernel/irq/resend.c |
| +++ b/kernel/irq/resend.c |
| @@ -72,8 +72,9 @@ void check_irq_resend(struct irq_desc *desc) |
| desc->istate &= ~IRQS_PENDING; |
| desc->istate |= IRQS_REPLAY; |
| |
| - if (!desc->irq_data.chip->irq_retrigger || |
| - !desc->irq_data.chip->irq_retrigger(&desc->irq_data)) { |
| + if ((!desc->irq_data.chip->irq_retrigger || |
| + !desc->irq_data.chip->irq_retrigger(&desc->irq_data)) && |
| + !handle_enforce_irqctx(&desc->irq_data)) { |
| #ifdef CONFIG_HARDIRQS_SW_RESEND |
| unsigned int irq = irq_desc_get_irq(desc); |
| |
| -- |
| 2.27.0 |
| |