genirq: Add protection against unsafe usage of generic_handle_irq()
authorThomas Gleixner <tglx@linutronix.de>
Fri, 6 Mar 2020 13:03:43 +0000 (14:03 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Sun, 8 Mar 2020 10:06:40 +0000 (11:06 +0100)
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
include/linux/irq.h
kernel/irq/internals.h
kernel/irq/irqdesc.c
kernel/irq/resend.c

index 3ed5a055b5f4d72987fb09595b553f7b316c7db7..9315fbb87db37ff67413cf4c7ff0bc03f8c27fc4 100644 (file)
@@ -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;
index c9d8eb7f5c029244b4db5f68cae4463412064077..5be382fc9415989ce5199f84b81e472f5aa0d5d7 100644 (file)
@@ -425,6 +425,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)
@@ -451,6 +455,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)
index 98a5f10d19002f049d11e031472c97460d5fe00c..1a7723604399c09496a77433b1685604680edc5e 100644 (file)
@@ -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;
 }
index 98c04ca5fa43d6a4515fff52a2d956c250777083..5064b13b80d603735783336de564bca1267f9068 100644 (file)
@@ -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);