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:
parent
078b3b7bbc
commit
5514b84e06
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user