mISDN: fix mISDN_read()/mISDN_read() race

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Al Viro 2013-04-15 17:18:17 -04:00
parent 1678ec00a6
commit ebb06be16b
1 changed files with 14 additions and 9 deletions

View File

@ -102,36 +102,41 @@ static ssize_t
mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off) mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
{ {
struct mISDNtimerdev *dev = filep->private_data; struct mISDNtimerdev *dev = filep->private_data;
struct list_head *list = &dev->expired;
struct mISDNtimer *timer; struct mISDNtimer *timer;
u_long flags;
int ret = 0; int ret = 0;
if (*debug & DEBUG_TIMER) if (*debug & DEBUG_TIMER)
printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__, printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
filep, buf, (int)count, off); filep, buf, (int)count, off);
if (list_empty(&dev->expired) && (dev->work == 0)) { if (count < sizeof(int))
return -ENOSPC;
spin_lock_irq(&dev->lock);
while (list_empty(list) && (dev->work == 0)) {
spin_unlock_irq(&dev->lock);
if (filep->f_flags & O_NONBLOCK) if (filep->f_flags & O_NONBLOCK)
return -EAGAIN; return -EAGAIN;
wait_event_interruptible(dev->wait, (dev->work || wait_event_interruptible(dev->wait, (dev->work ||
!list_empty(&dev->expired))); !list_empty(list)));
if (signal_pending(current)) if (signal_pending(current))
return -ERESTARTSYS; return -ERESTARTSYS;
spin_lock_irq(&dev->lock);
} }
if (count < sizeof(int))
return -ENOSPC;
if (dev->work) if (dev->work)
dev->work = 0; dev->work = 0;
if (!list_empty(&dev->expired)) { if (!list_empty(list)) {
spin_lock_irqsave(&dev->lock, flags); timer = list_first_entry(list, struct mISDNtimer, list);
timer = (struct mISDNtimer *)dev->expired.next;
list_del(&timer->list); list_del(&timer->list);
spin_unlock_irqrestore(&dev->lock, flags); spin_unlock_irq(&dev->lock);
if (put_user(timer->id, (int __user *)buf)) if (put_user(timer->id, (int __user *)buf))
ret = -EFAULT; ret = -EFAULT;
else else
ret = sizeof(int); ret = sizeof(int);
kfree(timer); kfree(timer);
} else {
spin_unlock_irq(&dev->lock);
} }
return ret; return ret;
} }