dahdi: Be more tolerant of surprise removal of channels.

Enable DAHDI to detect if an operation on a file handle refers to a
channel that may have been unregistered. This can occur, for example,
when a board driver is hot-swapped out in a live system.

This patch ensures that file->private_data is always properly set for
any open channel, and it's set back to NULL when a channel is
unregistered.  This way file->private_data can be used to check whether
it's valid to perform an operation on the channel.  (NOTE:  There is
still a race condition here if the driver was unbound on one processor
during the window of time between when file->private_data was checked
and the system call finishes).

Also, since DAHDI should only return -ENODEV on read or write when there
was a surprise device removal on a running system this sleep can prevent
the system from becoming unresponsive if the userspace application does
not check for the -ENODEV error and constantly tries to call read with
elevated privileges.

(issue #17669)
Reported by: tzafrir
Tested by: sruffell

Review: https://reviewboard.asterisk.org/r/905/

Signed-off-by: Shaun Ruffell <sruffell@digium.com>

git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9353 a0bf4364-ded3-4de4-8d8a-66a801d63aff
This commit is contained in:
Shaun Ruffell 2010-09-20 20:32:29 +00:00
parent 078b3b7bbc
commit 5514b84e06

View File

@ -47,6 +47,7 @@
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/delay.h>
#ifdef HAVE_UNLOCKED_IOCTL
#include <linux/smp_lock.h>
@ -1971,6 +1972,15 @@ static void dahdi_chan_unreg(struct dahdi_chan *chan)
might_sleep();
/* In the case of surprise removal of hardware, make sure any open
* file handles to this channel are disassociated with the actual
* dahdi_chan. */
if (chan->file) {
chan->file->private_data = NULL;
if (chan->span)
module_put(chan->span->ops->owner);
}
release_echocan(chan->ec_factory);
#ifdef CONFIG_DAHDI_NET
@ -2024,9 +2034,10 @@ static void dahdi_chan_unreg(struct dahdi_chan *chan)
write_unlock_irqrestore(&chan_lock, flags);
}
static ssize_t dahdi_chan_read(struct file *file, char __user *usrbuf, size_t count, int unit)
static ssize_t dahdi_chan_read(struct file *file, char __user *usrbuf,
size_t count)
{
struct dahdi_chan *chan = chans[unit];
struct dahdi_chan *chan = file->private_data;
int amnt;
int res, rv;
int oldbuf,x;
@ -2035,10 +2046,19 @@ static ssize_t dahdi_chan_read(struct file *file, char __user *usrbuf, size_t co
/* Make sure count never exceeds 65k, and make sure it's unsigned */
count &= 0xffff;
if (!chan)
return -EINVAL;
if (unlikely(!chan)) {
/* We would typically be here because of surprise hardware
* removal or driver unbinding while a user space application
* has a channel open. Most telephony applications are run at
* elevated priorities so this sleep can prevent the high
* priority threads from consuming the CPU if they're not
* expecting surprise device removal.
*/
msleep(5);
return -ENODEV;
}
if (count < 1)
if (unlikely(count < 1))
return -EINVAL;
for (;;) {
@ -2150,22 +2170,31 @@ static int num_filled_bufs(struct dahdi_chan *chan)
return range1 + range2;
}
static ssize_t dahdi_chan_write(struct file *file, const char __user *usrbuf, size_t count, int unit)
static ssize_t dahdi_chan_write(struct file *file, const char __user *usrbuf,
size_t count)
{
unsigned long flags;
struct dahdi_chan *chan = chans[unit];
struct dahdi_chan *chan = file->private_data;
int res, amnt, oldbuf, rv, x;
/* Make sure count never exceeds 65k, and make sure it's unsigned */
count &= 0xffff;
if (!chan)
return -EINVAL;
if (count < 1) {
return -EINVAL;
if (unlikely(!chan)) {
/* We would typically be here because of surprise hardware
* removal or driver unbinding while a user space application
* has a channel open. Most telephony applications are run at
* elevated priorities so this sleep can prevent the high
* priority threads from consuming the CPU if they're not
* expecting surprise device removal.
*/
msleep(5);
return -ENODEV;
}
if (unlikely(count < 1))
return -EINVAL;
for (;;) {
spin_lock_irqsave(&chan->lock, flags);
if ((chan->curtone || chan->pdialcount) && !(chan->flags & DAHDI_FLAG_PSEUDO)) {
@ -2748,6 +2777,7 @@ static int dahdi_specchan_open(struct file *file, int unit)
}
if (!res) {
chan->file = file;
file->private_data = chan;
spin_unlock_irqrestore(&chan->lock, flags);
} else {
spin_unlock_irqrestore(&chan->lock, flags);
@ -2912,7 +2942,7 @@ static ssize_t dahdi_read(struct file *file, char __user *usrbuf, size_t count,
chan = file->private_data;
if (!chan)
return -EINVAL;
return dahdi_chan_read(file, usrbuf, count, chan->channo);
return dahdi_chan_read(file, usrbuf, count);
}
if (unit == 255) {
@ -2921,12 +2951,12 @@ static ssize_t dahdi_read(struct file *file, char __user *usrbuf, size_t count,
module_printk(KERN_NOTICE, "No pseudo channel structure to read?\n");
return -EINVAL;
}
return dahdi_chan_read(file, usrbuf, count, chan->channo);
return dahdi_chan_read(file, usrbuf, count);
}
if (count < 0)
return -EINVAL;
return dahdi_chan_read(file, usrbuf, count, unit);
return dahdi_chan_read(file, usrbuf, count);
}
static ssize_t dahdi_write(struct file *file, const char __user *usrbuf, size_t count, loff_t *ppos)
@ -2944,7 +2974,7 @@ static ssize_t dahdi_write(struct file *file, const char __user *usrbuf, size_t
chan = file->private_data;
if (!chan)
return -EINVAL;
return dahdi_chan_write(file, usrbuf, count, chan->channo);
return dahdi_chan_write(file, usrbuf, count);
}
if (unit == 255) {
chan = file->private_data;
@ -2952,9 +2982,9 @@ static ssize_t dahdi_write(struct file *file, const char __user *usrbuf, size_t
module_printk(KERN_NOTICE, "No pseudo channel structure to read?\n");
return -EINVAL;
}
return dahdi_chan_write(file, usrbuf, count, chan->channo);
return dahdi_chan_write(file, usrbuf, count);
}
return dahdi_chan_write(file, usrbuf, count, unit);
return dahdi_chan_write(file, usrbuf, count);
}
@ -5891,6 +5921,12 @@ static int dahdi_ioctl(struct inode *inode, struct file *file,
ret = dahdi_chanandpseudo_ioctl(file, cmd, data, chan->channo);
goto unlock_exit;
}
if (!file->private_data) {
ret = -ENXIO;
goto unlock_exit;
}
ret = dahdi_chan_ioctl(file, cmd, data, unit);
unlock_exit: