mISDN: fix the races with timers going off just as they are deleted
authorAl Viro <viro@zeniv.linux.org.uk>
Mon, 15 Apr 2013 20:31:13 +0000 (16:31 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Mon, 29 Apr 2013 19:41:47 +0000 (15:41 -0400)
timer callback in timerdev.c both accesses struct mISDNtimer it's
called for *and* moves it to dev->expired.  We need del_timer_sync(),
or we risk kfree() freeing it right under dev_expire_timer() *and*
dev->expired getting corrupted.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
drivers/isdn/mISDN/timerdev.c

index 1094667..5a1a5ca 100644 (file)
@@ -72,14 +72,24 @@ static int
 mISDN_close(struct inode *ino, struct file *filep)
 {
        struct mISDNtimerdev    *dev = filep->private_data;
+       struct list_head        *list = &dev->pending;
        struct mISDNtimer       *timer, *next;
 
        if (*debug & DEBUG_TIMER)
                printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
-       list_for_each_entry_safe(timer, next, &dev->pending, list) {
-               del_timer(&timer->tl);
+
+       spin_lock_irq(&dev->lock);
+       while (!list_empty(list)) {
+               timer = list_first_entry(list, struct mISDNtimer, list);
+               spin_unlock_irq(&dev->lock);
+               del_timer_sync(&timer->tl);
+               spin_lock_irq(&dev->lock);
+               /* it might have been moved to ->expired */
+               list_del(&timer->list);
                kfree(timer);
        }
+       spin_unlock_irq(&dev->lock);
+
        list_for_each_entry_safe(timer, next, &dev->expired, list) {
                kfree(timer);
        }