hrtimer: Don't dereference the hrtimer pointer after the callback
authorSebastian Andrzej Siewior <bigeasy@linutronix.de>
Tue, 31 Mar 2020 20:18:49 +0000 (22:18 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Wed, 1 Apr 2020 11:20:14 +0000 (13:20 +0200)
A hrtimer can be released in its callback, but lockdep_hrtimer_exit()
dereferences the pointer after the callback returns, i.e. a potential use
after free.

Retrieve the context in which the hrtimer expires before the callback is
invoked and use it in lockdep_hrtimer_exit().

Fixes: 40db173965c0 ("lockdep: Add hrtimer context tracing bits")
Reported-by: syzbot+62c155c276e580cfb606@syzkaller.appspotmail.com
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lkml.kernel.org/r/20200331201849.fkp2siy3vcdqvqlz@linutronix.de
include/linux/irqflags.h
kernel/time/hrtimer.c

index ceca42d..61a9ced 100644 (file)
@@ -58,16 +58,21 @@ do {                                                \
 } while (0)
 
 # define lockdep_hrtimer_enter(__hrtimer)              \
-         do {                                          \
-                 if (!__hrtimer->is_hard)              \
-                       current->irq_config = 1;        \
-         } while (0)
-
-# define lockdep_hrtimer_exit(__hrtimer)               \
-         do {                                          \
-                 if (!__hrtimer->is_hard)              \
+({                                                     \
+       bool __expires_hardirq = true;                  \
+                                                       \
+       if (!__hrtimer->is_hard) {                      \
+               current->irq_config = 1;                \
+               __expires_hardirq = false;              \
+       }                                               \
+       __expires_hardirq;                              \
+})
+
+# define lockdep_hrtimer_exit(__expires_hardirq)       \
+       do {                                            \
+               if (!__expires_hardirq)                 \
                        current->irq_config = 0;        \
-         } while (0)
+       } while (0)
 
 # define lockdep_posixtimer_enter()                            \
          do {                                                  \
@@ -102,8 +107,8 @@ do {                                                \
 # define lockdep_hardirq_exit()                do { } while (0)
 # define lockdep_softirq_enter()       do { } while (0)
 # define lockdep_softirq_exit()                do { } while (0)
-# define lockdep_hrtimer_enter(__hrtimer)              do { } while (0)
-# define lockdep_hrtimer_exit(__hrtimer)               do { } while (0)
+# define lockdep_hrtimer_enter(__hrtimer)      false
+# define lockdep_hrtimer_exit(__context)       do { } while (0)
 # define lockdep_posixtimer_enter()            do { } while (0)
 # define lockdep_posixtimer_exit()             do { } while (0)
 # define lockdep_irq_work_enter(__work)                do { } while (0)
index d0a5ba3..d89da1c 100644 (file)
@@ -1480,6 +1480,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base,
                          unsigned long flags) __must_hold(&cpu_base->lock)
 {
        enum hrtimer_restart (*fn)(struct hrtimer *);
+       bool expires_in_hardirq;
        int restart;
 
        lockdep_assert_held(&cpu_base->lock);
@@ -1514,11 +1515,11 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base,
         */
        raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
        trace_hrtimer_expire_entry(timer, now);
-       lockdep_hrtimer_enter(timer);
+       expires_in_hardirq = lockdep_hrtimer_enter(timer);
 
        restart = fn(timer);
 
-       lockdep_hrtimer_exit(timer);
+       lockdep_hrtimer_exit(expires_in_hardirq);
        trace_hrtimer_expire_exit(timer);
        raw_spin_lock_irq(&cpu_base->lock);