[1/4] genirq/irq_sim: dispose of remaining mappings before removing the domain

Message ID 20230812194457.6432-2-brgl@bgdev.pl
State New
Headers
Series genirq/irq_sim: fix a use-after-free bug + some |

Commit Message

Bartosz Golaszewski Aug. 12, 2023, 7:44 p.m. UTC
  From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>

If the device providing simulated interrupts is unbound (real life
example: gpio-sim is disabled with users that didn't free their irqs)
and removes the simulated domain while interrupts are still requested,
we will hit memory issues when they are eventually freed and the
mappings destroyed in the process.

Specifically we'll access freed memory in __irq_domain_deactivate_irq().

Dispose of all mappings before removing the simulator domain.

Fixes: b19af510e67e ("genirq/irq_sim: Add a simple interrupt simulator framework")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
 kernel/irq/irq_sim.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)
  

Comments

Andy Shevchenko Aug. 15, 2023, 4:53 p.m. UTC | #1
On Tue, Aug 15, 2023 at 09:09:10AM -0700, Yury Norov wrote:
> On Tue, Aug 15, 2023 at 01:38:49PM +0300, Andy Shevchenko wrote:
> > On Sat, Aug 12, 2023 at 09:44:54PM +0200, Bartosz Golaszewski wrote:

...

> > > @@ -16,12 +17,14 @@ struct irq_sim_work_ctx {
> > >  	unsigned int		irq_count;
> > >  	unsigned long		*pending;
> > >  	struct irq_domain	*domain;
> > > +	struct list_head	irqs;
> > >  };
> > >  
> > >  struct irq_sim_irq_ctx {
> > >  	int			irqnum;
> > >  	bool			enabled;
> > >  	struct irq_sim_work_ctx	*work_ctx;
> > 
> > > +	struct list_head	siblings;
> > 
> > You can reduce the code size by moving this to be the first member.
> > Not sure about struct irq_sim_work_ctx, you can play with bloat-o-meter.
> 
> Pahole you meant?

No. I meant bloat-o-meter.

...

> But as Bartosz said in the other email, "it's just good practice
> resulting from years of" kernel coding to have:
>  - members declared strongly according to the logic of the code, and
>    if no strong preference: 

>  - list head be the first element of the structure, to let compiler
>    avoid generating offsets when traversing lists;

Exactly.

>  - put elements of greater size at the beginning, so no holes will be
>    emitted like in the example above.
> 
> So I'd suggest:
> 
>   struct irq_sim_irq_ctx {
>      struct list_head        siblings;
>      struct irq_sim_work_ctx *work_ctx;
>      int                     irqnum;
>      bool                    enabled;
>   }

Yes, I like this.
  

Patch

diff --git a/kernel/irq/irq_sim.c b/kernel/irq/irq_sim.c
index dd76323ea3fd..2c8a9cc1faa6 100644
--- a/kernel/irq/irq_sim.c
+++ b/kernel/irq/irq_sim.c
@@ -4,6 +4,7 @@ 
  * Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com>
  */
 
+#include <linux/list.h>
 #include <linux/irq.h>
 #include <linux/irq_sim.h>
 #include <linux/irq_work.h>
@@ -16,12 +17,14 @@  struct irq_sim_work_ctx {
 	unsigned int		irq_count;
 	unsigned long		*pending;
 	struct irq_domain	*domain;
+	struct list_head	irqs;
 };
 
 struct irq_sim_irq_ctx {
 	int			irqnum;
 	bool			enabled;
 	struct irq_sim_work_ctx	*work_ctx;
+	struct list_head	siblings;
 };
 
 static void irq_sim_irqmask(struct irq_data *data)
@@ -129,6 +132,8 @@  static int irq_sim_domain_map(struct irq_domain *domain,
 	irq_set_handler(virq, handle_simple_irq);
 	irq_modify_status(virq, IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
 	irq_ctx->work_ctx = work_ctx;
+	irq_ctx->irqnum = virq;
+	list_add_tail(&irq_ctx->siblings, &work_ctx->irqs);
 
 	return 0;
 }
@@ -141,6 +146,7 @@  static void irq_sim_domain_unmap(struct irq_domain *domain, unsigned int virq)
 	irqd = irq_domain_get_irq_data(domain, virq);
 	irq_ctx = irq_data_get_irq_chip_data(irqd);
 
+	list_del(&irq_ctx->siblings);
 	irq_set_handler(virq, NULL);
 	irq_domain_reset_irq_data(irqd);
 	kfree(irq_ctx);
@@ -182,6 +188,7 @@  struct irq_domain *irq_domain_create_sim(struct fwnode_handle *fwnode,
 
 	work_ctx->irq_count = num_irqs;
 	work_ctx->work = IRQ_WORK_INIT_HARD(irq_sim_handle_irq);
+	INIT_LIST_HEAD(&work_ctx->irqs);
 
 	return work_ctx->domain;
 
@@ -203,8 +210,13 @@  EXPORT_SYMBOL_GPL(irq_domain_create_sim);
 void irq_domain_remove_sim(struct irq_domain *domain)
 {
 	struct irq_sim_work_ctx *work_ctx = domain->host_data;
+	struct irq_sim_irq_ctx *irq_ctx, *aux;
 
 	irq_work_sync(&work_ctx->work);
+
+	list_for_each_entry_safe(irq_ctx, aux, &work_ctx->irqs, siblings)
+		irq_dispose_mapping(irq_ctx->irqnum);
+
 	bitmap_free(work_ctx->pending);
 	kfree(work_ctx);