media: dvb-core: Fix use-after-free due on race condition at dvb_net
[ Upstream commit 4172385b0c
]
A race condition may occur between the .disconnect function, which
is called when the device is disconnected, and the dvb_device_open()
function, which is called when the device node is open()ed.
This results in several types of UAFs.
The root cause of this is that you use the dvb_device_open() function,
which does not implement a conditional statement
that checks 'dvbnet->exit'.
So, add 'remove_mutex` to protect 'dvbnet->exit' and use
locked_dvb_net_open() function to check 'dvbnet->exit'.
[mchehab: fix a checkpatch warning]
Link: https://lore.kernel.org/linux-media/20221117045925.14297-3-imv4bel@gmail.com
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Родитель
9f74fec18f
Коммит
50831747cb
|
@ -1564,15 +1564,43 @@ static long dvb_net_ioctl(struct file *file,
|
||||||
return dvb_usercopy(file, cmd, arg, dvb_net_do_ioctl);
|
return dvb_usercopy(file, cmd, arg, dvb_net_do_ioctl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int locked_dvb_net_open(struct inode *inode, struct file *file)
|
||||||
|
{
|
||||||
|
struct dvb_device *dvbdev = file->private_data;
|
||||||
|
struct dvb_net *dvbnet = dvbdev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (mutex_lock_interruptible(&dvbnet->remove_mutex))
|
||||||
|
return -ERESTARTSYS;
|
||||||
|
|
||||||
|
if (dvbnet->exit) {
|
||||||
|
mutex_unlock(&dvbnet->remove_mutex);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = dvb_generic_open(inode, file);
|
||||||
|
|
||||||
|
mutex_unlock(&dvbnet->remove_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int dvb_net_close(struct inode *inode, struct file *file)
|
static int dvb_net_close(struct inode *inode, struct file *file)
|
||||||
{
|
{
|
||||||
struct dvb_device *dvbdev = file->private_data;
|
struct dvb_device *dvbdev = file->private_data;
|
||||||
struct dvb_net *dvbnet = dvbdev->priv;
|
struct dvb_net *dvbnet = dvbdev->priv;
|
||||||
|
|
||||||
|
mutex_lock(&dvbnet->remove_mutex);
|
||||||
|
|
||||||
dvb_generic_release(inode, file);
|
dvb_generic_release(inode, file);
|
||||||
|
|
||||||
if(dvbdev->users == 1 && dvbnet->exit == 1)
|
if (dvbdev->users == 1 && dvbnet->exit == 1) {
|
||||||
|
mutex_unlock(&dvbnet->remove_mutex);
|
||||||
wake_up(&dvbdev->wait_queue);
|
wake_up(&dvbdev->wait_queue);
|
||||||
|
} else {
|
||||||
|
mutex_unlock(&dvbnet->remove_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1580,7 +1608,7 @@ static int dvb_net_close(struct inode *inode, struct file *file)
|
||||||
static const struct file_operations dvb_net_fops = {
|
static const struct file_operations dvb_net_fops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.unlocked_ioctl = dvb_net_ioctl,
|
.unlocked_ioctl = dvb_net_ioctl,
|
||||||
.open = dvb_generic_open,
|
.open = locked_dvb_net_open,
|
||||||
.release = dvb_net_close,
|
.release = dvb_net_close,
|
||||||
.llseek = noop_llseek,
|
.llseek = noop_llseek,
|
||||||
};
|
};
|
||||||
|
@ -1599,10 +1627,13 @@ void dvb_net_release (struct dvb_net *dvbnet)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
mutex_lock(&dvbnet->remove_mutex);
|
||||||
dvbnet->exit = 1;
|
dvbnet->exit = 1;
|
||||||
|
mutex_unlock(&dvbnet->remove_mutex);
|
||||||
|
|
||||||
if (dvbnet->dvbdev->users < 1)
|
if (dvbnet->dvbdev->users < 1)
|
||||||
wait_event(dvbnet->dvbdev->wait_queue,
|
wait_event(dvbnet->dvbdev->wait_queue,
|
||||||
dvbnet->dvbdev->users==1);
|
dvbnet->dvbdev->users == 1);
|
||||||
|
|
||||||
dvb_unregister_device(dvbnet->dvbdev);
|
dvb_unregister_device(dvbnet->dvbdev);
|
||||||
|
|
||||||
|
@ -1621,6 +1652,7 @@ int dvb_net_init (struct dvb_adapter *adap, struct dvb_net *dvbnet,
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
mutex_init(&dvbnet->ioctl_mutex);
|
mutex_init(&dvbnet->ioctl_mutex);
|
||||||
|
mutex_init(&dvbnet->remove_mutex);
|
||||||
dvbnet->demux = dmx;
|
dvbnet->demux = dmx;
|
||||||
|
|
||||||
for (i=0; i<DVB_NET_DEVICES_MAX; i++)
|
for (i=0; i<DVB_NET_DEVICES_MAX; i++)
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
* @exit: flag to indicate when the device is being removed.
|
* @exit: flag to indicate when the device is being removed.
|
||||||
* @demux: pointer to &struct dmx_demux.
|
* @demux: pointer to &struct dmx_demux.
|
||||||
* @ioctl_mutex: protect access to this struct.
|
* @ioctl_mutex: protect access to this struct.
|
||||||
|
* @remove_mutex: mutex that avoids a race condition between a callback
|
||||||
|
* called when the hardware is disconnected and the
|
||||||
|
* file_operations of dvb_net.
|
||||||
*
|
*
|
||||||
* Currently, the core supports up to %DVB_NET_DEVICES_MAX (10) network
|
* Currently, the core supports up to %DVB_NET_DEVICES_MAX (10) network
|
||||||
* devices.
|
* devices.
|
||||||
|
@ -53,6 +56,7 @@ struct dvb_net {
|
||||||
unsigned int exit:1;
|
unsigned int exit:1;
|
||||||
struct dmx_demux *demux;
|
struct dmx_demux *demux;
|
||||||
struct mutex ioctl_mutex;
|
struct mutex ioctl_mutex;
|
||||||
|
struct mutex remove_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Загрузка…
Ссылка в новой задаче