summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2013-04-16 01:18:17 +0400
committerAl Viro <viro@zeniv.linux.org.uk>2013-04-29 23:41:49 +0400
commitebb06be16bc9a1e66a010ca50c75c5128bafb4b1 (patch)
tree3a9212bb7f267519242b3f66420cb98527edee41
parent1678ec00a632f8b9204e28e5c506128881171604 (diff)
downloadlinux-ebb06be16bc9a1e66a010ca50c75c5128bafb4b1.tar.xz
mISDN: fix mISDN_read()/mISDN_read() race
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r--drivers/isdn/mISDN/timerdev.c23
1 files changed, 14 insertions, 9 deletions
diff --git a/drivers/isdn/mISDN/timerdev.c b/drivers/isdn/mISDN/timerdev.c
index ddb8adcd5fbb..da2aa376a3a3 100644
--- a/drivers/isdn/mISDN/timerdev.c
+++ b/drivers/isdn/mISDN/timerdev.c
@@ -102,36 +102,41 @@ static ssize_t
mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
{
struct mISDNtimerdev *dev = filep->private_data;
+ struct list_head *list = &dev->expired;
struct mISDNtimer *timer;
- u_long flags;
int ret = 0;
if (*debug & DEBUG_TIMER)
printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
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)
return -EAGAIN;
wait_event_interruptible(dev->wait, (dev->work ||
- !list_empty(&dev->expired)));
+ !list_empty(list)));
if (signal_pending(current))
return -ERESTARTSYS;
+ spin_lock_irq(&dev->lock);
}
- if (count < sizeof(int))
- return -ENOSPC;
if (dev->work)
dev->work = 0;
- if (!list_empty(&dev->expired)) {
- spin_lock_irqsave(&dev->lock, flags);
- timer = (struct mISDNtimer *)dev->expired.next;
+ if (!list_empty(list)) {
+ timer = list_first_entry(list, struct mISDNtimer, list);
list_del(&timer->list);
- spin_unlock_irqrestore(&dev->lock, flags);
+ spin_unlock_irq(&dev->lock);
if (put_user(timer->id, (int __user *)buf))
ret = -EFAULT;
else
ret = sizeof(int);
kfree(timer);
+ } else {
+ spin_unlock_irq(&dev->lock);
}
return ret;
}