History

genirq: Prevent proc race against freeing of irq descriptors

Since the rework of the sparse interrupt code to actually free the unused interrupt descriptors there exists a race between the /proc interfaces to the irq subsystem and the code which frees the interrupt descriptor. CPU0 CPU1 show_interrupts() desc = irq_to_desc(X); free_desc(desc) remove_from_radix_tree(); kfree(desc); raw_spinlock_irq(&desc->lock); /proc/interrupts is the only interface which can actively corrupt kernel memory via the lock access. /proc/stat can only read from freed memory. Extremly hard to trigger, but possible. The interfaces in /proc/irq/N/ are not affected by this because the removal of the proc file is serialized in procfs against concurrent readers/writers. The removal happens before the descriptor is freed. For architectures which have CONFIG_SPARSE_IRQ=n this is a non issue as the descriptor is never freed. It's merely cleared out with the irq descriptor lock held. So any concurrent proc access will either see the old correct value or the cleared out ones. Protect the lookup and access to the irq descriptor in show_interrupts() with the sparse_irq_lock. Provide kstat_irqs_usr() which is protecting the lookup and access with sparse_irq_lock and switch /proc/stat to use it. Document the existing kstat_irqs interfaces so it's clear that the caller needs to take care about protection. The users of these interfaces are either not affected due to SPARSE_IRQ=n or already protected against removal.

Thomas Gleixner committed 11 years ago

Commit c291ee61 parent 3a5dc1f5 files · +79 −2Browse files

Showing the changes to kernel (3 of 5 files). Show all

Unified · Split

3 files
  1. kernel/irq/internals.h +4 −0
  2. kernel/irq/irqdesc.c +52 −0
  3. kernel/irq/proc.c +21 −1
kernel/irq/internals.h+4−0
@@ -78,8 +78,12 @@ extern void unmask_threaded_irq(struct irq_desc *desc); · 77 unmodified lines
7878
7979 #ifdef CONFIG_SPARSE_IRQ
8080 static inline void irq_mark_irq(unsigned int irq) { }
81+extern void irq_lock_sparse(void);
82+extern void irq_unlock_sparse(void);
8183 #else
8284 extern void irq_mark_irq(unsigned int irq);
85+static inline void irq_lock_sparse(void) { }
86+static inline void irq_unlock_sparse(void) { }
8387 #endif
8488
8589 extern void init_kstat_irqs(struct irq_desc *desc, int node, int nr);
kernel/irq/irqdesc.c+52−0
@@ -132,6 +132,16 @@ static void free_masks(struct irq_desc *desc) · 131 unmodified lines
132132 static inline void free_masks(struct irq_desc *desc) { }
133133 #endif
134134
135+void irq_lock_sparse(void)
136+{
137+ mutex_lock(&sparse_irq_lock);
138+}
139+
140+void irq_unlock_sparse(void)
141+{
142+ mutex_unlock(&sparse_irq_lock);
143+}
144+
135145 static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
136146 {
137147 struct irq_desc *desc;
@@ -168,6 +178,12 @@ static void free_desc(unsigned int irq) · 30 unmodified lines
168178
169179 unregister_irq_proc(irq, desc);
170180
181+ /*
182+ * sparse_irq_lock protects also show_interrupts() and
183+ * kstat_irq_usr(). Once we deleted the descriptor from the
184+ * sparse tree we can free it. Access in proc will fail to
185+ * lookup the descriptor.
186+ */
171187 mutex_lock(&sparse_irq_lock);
172188 delete_irq_desc(irq);
173189 mutex_unlock(&sparse_irq_lock);
@@ -574,6 +590,15 @@ void kstat_incr_irq_this_cpu(unsigned int irq) · 400 unmodified lines
574590 kstat_incr_irqs_this_cpu(irq, irq_to_desc(irq));
575591 }
576592
593+/**
594+ * kstat_irqs_cpu - Get the statistics for an interrupt on a cpu
595+ * @irq: The interrupt number
596+ * @cpu: The cpu number
597+ *
598+ * Returns the sum of interrupt counts on @cpu since boot for
599+ * @irq. The caller must ensure that the interrupt is not removed
600+ * concurrently.
601+ */
577602 unsigned int kstat_irqs_cpu(unsigned int irq, int cpu)
578603 {
579604 struct irq_desc *desc = irq_to_desc(irq);
@@ -582,6 +607,14 @@ unsigned int kstat_irqs_cpu(unsigned int irq, int cpu) · 2 unmodified lines
582607 *per_cpu_ptr(desc->kstat_irqs, cpu) : 0;
583608 }
584609
610+/**
611+ * kstat_irqs - Get the statistics for an interrupt
612+ * @irq: The interrupt number
613+ *
614+ * Returns the sum of interrupt counts on all cpus since boot for
615+ * @irq. The caller must ensure that the interrupt is not removed
616+ * concurrently.
617+ */
585618 unsigned int kstat_irqs(unsigned int irq)
586619 {
587620 struct irq_desc *desc = irq_to_desc(irq);
@@ -594,3 +627,22 @@ unsigned int kstat_irqs(unsigned int irq) · 6 unmodified lines
594627 sum += *per_cpu_ptr(desc->kstat_irqs, cpu);
595628 return sum;
596629 }
630+
631+/**
632+ * kstat_irqs_usr - Get the statistics for an interrupt
633+ * @irq: The interrupt number
634+ *
635+ * Returns the sum of interrupt counts on all cpus since boot for
636+ * @irq. Contrary to kstat_irqs() this can be called from any
637+ * preemptible context. It's protected against concurrent removal of
638+ * an interrupt descriptor when sparse irqs are enabled.
639+ */
640+unsigned int kstat_irqs_usr(unsigned int irq)
641+{
642+ int sum;
643+
644+ irq_lock_sparse();
645+ sum = kstat_irqs(irq);
646+ irq_unlock_sparse();
647+ return sum;
648+}
kernel/irq/proc.c+21−1
@@ -15,6 +15,23 @@ · 14 unmodified lines
1515
1616 #include "internals.h"
1717
18+/*
19+ * Access rules:
20+ *
21+ * procfs protects read/write of /proc/irq/N/ files against a
22+ * concurrent free of the interrupt descriptor. remove_proc_entry()
23+ * immediately prevents new read/writes to happen and waits for
24+ * already running read/write functions to complete.
25+ *
26+ * We remove the proc entries first and then delete the interrupt
27+ * descriptor from the radix tree and free it. So it is guaranteed
28+ * that irq_to_desc(N) is valid as long as the read/writes are
29+ * permitted by procfs.
30+ *
31+ * The read from /proc/interrupts is a different problem because there
32+ * is no protection. So the lookup and the access to irqdesc
33+ * information must be protected by sparse_irq_lock.
34+ */
1835 static struct proc_dir_entry *root_irq_dir;
1936
2037 #ifdef CONFIG_SMP
@@ -437,9 +454,10 @@ int show_interrupts(struct seq_file *p, void *v) · 416 unmodified lines
437454 seq_putc(p, '\n');
438455 }
439456
457+ irq_lock_sparse();
440458 desc = irq_to_desc(i);
441459 if (!desc)
442− return 0;
460+ goto outsparse;
443461
444462 raw_spin_lock_irqsave(&desc->lock, flags);
445463 for_each_online_cpu(j)
@@ -479,6 +497,8 @@ int show_interrupts(struct seq_file *p, void *v) · 33 unmodified lines
479497 seq_putc(p, '\n');
480498 out:
481499 raw_spin_unlock_irqrestore(&desc->lock, flags);
500+outsparse:
501+ irq_unlock_sparse();
482502 return 0;
483503 }
484504 #endif