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

Unified · Split

5 files
  1. fs/proc/stat.c +1 −1
  2. include/linux/kernel_stat.h +1 −0
  3. kernel/irq/internals.h +4 −0
  4. kernel/irq/irqdesc.c +52 −0
  5. kernel/irq/proc.c +21 −1
fs/proc/stat.c+1−1
@@ -159,7 +159,7 @@ static int show_stat(struct seq_file *p, void *v) · 158 unmodified lines
159 159
160 /* sum again ? it could be updated? */160 /* sum again ? it could be updated? */
161 for_each_irq_nr(j)161 for_each_irq_nr(j)
162 seq_put_decimal_ull(p, ' ', kstat_irqs(j));162 seq_put_decimal_ull(p, ' ', kstat_irqs_usr(j));
163 163
164 seq_printf(p,164 seq_printf(p,
165 "\nctxt %llu\n"165 "\nctxt %llu\n"
include/linux/kernel_stat.h+1−0
@@ -68,6 +68,7 @@ static inline unsigned int kstat_softirqs_cpu(unsigned int irq, int cpu) · 67 unmodified lines
68 * Number of interrupts per specific IRQ source, since bootup68 * Number of interrupts per specific IRQ source, since bootup
69 */69 */
70extern unsigned int kstat_irqs(unsigned int irq);70extern unsigned int kstat_irqs(unsigned int irq);
71extern unsigned int kstat_irqs_usr(unsigned int irq);
71 72
72/*73/*
73 * Number of interrupts per cpu, since bootup74 * Number of interrupts per cpu, since bootup
kernel/irq/internals.h+4−0
@@ -78,8 +78,12 @@ extern void unmask_threaded_irq(struct irq_desc *desc); · 77 unmodified lines
78 78
79#ifdef CONFIG_SPARSE_IRQ79#ifdef CONFIG_SPARSE_IRQ
80static inline void irq_mark_irq(unsigned int irq) { }80static inline void irq_mark_irq(unsigned int irq) { }
81extern void irq_lock_sparse(void);
82extern void irq_unlock_sparse(void);
81#else83#else
82extern void irq_mark_irq(unsigned int irq);84extern void irq_mark_irq(unsigned int irq);
85static inline void irq_lock_sparse(void) { }
86static inline void irq_unlock_sparse(void) { }
83#endif87#endif
84 88
85extern void init_kstat_irqs(struct irq_desc *desc, int node, int nr);89extern 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
132static inline void free_masks(struct irq_desc *desc) { }132static inline void free_masks(struct irq_desc *desc) { }
133#endif133#endif
134 134
135void irq_lock_sparse(void)
136{
137 mutex_lock(&sparse_irq_lock);
138}
139
140void irq_unlock_sparse(void)
141{
142 mutex_unlock(&sparse_irq_lock);
143}
144
135static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)145static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
136{146{
137 struct irq_desc *desc;147 struct irq_desc *desc;
@@ -168,6 +178,12 @@ static void free_desc(unsigned int irq) · 30 unmodified lines
168 178
169 unregister_irq_proc(irq, desc);179 unregister_irq_proc(irq, desc);
170 180
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 */
171 mutex_lock(&sparse_irq_lock);187 mutex_lock(&sparse_irq_lock);
172 delete_irq_desc(irq);188 delete_irq_desc(irq);
173 mutex_unlock(&sparse_irq_lock);189 mutex_unlock(&sparse_irq_lock);
@@ -574,6 +590,15 @@ void kstat_incr_irq_this_cpu(unsigned int irq) · 400 unmodified lines
574 kstat_incr_irqs_this_cpu(irq, irq_to_desc(irq));590 kstat_incr_irqs_this_cpu(irq, irq_to_desc(irq));
575}591}
576 592
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 */
577unsigned int kstat_irqs_cpu(unsigned int irq, int cpu)602unsigned int kstat_irqs_cpu(unsigned int irq, int cpu)
578{603{
579 struct irq_desc *desc = irq_to_desc(irq);604 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
582 *per_cpu_ptr(desc->kstat_irqs, cpu) : 0;607 *per_cpu_ptr(desc->kstat_irqs, cpu) : 0;
583}608}
584 609
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 */
585unsigned int kstat_irqs(unsigned int irq)618unsigned int kstat_irqs(unsigned int irq)
586{619{
587 struct irq_desc *desc = irq_to_desc(irq);620 struct irq_desc *desc = irq_to_desc(irq);
@@ -594,3 +627,22 @@ unsigned int kstat_irqs(unsigned int irq) · 6 unmodified lines
594 sum += *per_cpu_ptr(desc->kstat_irqs, cpu);627 sum += *per_cpu_ptr(desc->kstat_irqs, cpu);
595 return sum;628 return sum;
596}629}
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 */
640unsigned 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
15 15
16#include "internals.h"16#include "internals.h"
17 17
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 */
18static struct proc_dir_entry *root_irq_dir;35static struct proc_dir_entry *root_irq_dir;
19 36
20#ifdef CONFIG_SMP37#ifdef CONFIG_SMP
@@ -437,9 +454,10 @@ int show_interrupts(struct seq_file *p, void *v) · 416 unmodified lines
437 seq_putc(p, '\n');454 seq_putc(p, '\n');
438 }455 }
439 456
457 irq_lock_sparse();
440 desc = irq_to_desc(i);458 desc = irq_to_desc(i);
441 if (!desc)459 if (!desc)
442 return 0;460 goto outsparse;
443 461
444 raw_spin_lock_irqsave(&desc->lock, flags);462 raw_spin_lock_irqsave(&desc->lock, flags);
445 for_each_online_cpu(j)463 for_each_online_cpu(j)
@@ -479,6 +497,8 @@ int show_interrupts(struct seq_file *p, void *v) · 33 unmodified lines
479 seq_putc(p, '\n');497 seq_putc(p, '\n');
480out:498out:
481 raw_spin_unlock_irqrestore(&desc->lock, flags);499 raw_spin_unlock_irqrestore(&desc->lock, flags);
500outsparse:
501 irq_unlock_sparse();
482 return 0;502 return 0;
483}503}
484#endif504#endif