Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7bb9c6e0 authored by Hyunwoo Kim's avatar Hyunwoo Kim Committed by Greg Kroah-Hartman
Browse files

media: dvb-core: Fix use-after-free due on race condition at dvb_net

[ Upstream commit 4172385b0c9ac366dcab78eda48c26814b87ed1a ]

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: default avatarHyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@kernel.org>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 120986e9
Loading
Loading
Loading
Loading
+35 −3
Original line number Diff line number Diff line
@@ -1575,15 +1575,43 @@ static long dvb_net_ioctl(struct file *file,
	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)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_net *dvbnet = dvbdev->priv;

	mutex_lock(&dvbnet->remove_mutex);

	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);
	} else {
		mutex_unlock(&dvbnet->remove_mutex);
	}

	return 0;
}

@@ -1591,7 +1619,7 @@ static int dvb_net_close(struct inode *inode, struct file *file)
static const struct file_operations dvb_net_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = dvb_net_ioctl,
	.open =	dvb_generic_open,
	.open =	locked_dvb_net_open,
	.release = dvb_net_close,
	.llseek = noop_llseek,
};
@@ -1610,7 +1638,10 @@ void dvb_net_release (struct dvb_net *dvbnet)
{
	int i;

	mutex_lock(&dvbnet->remove_mutex);
	dvbnet->exit = 1;
	mutex_unlock(&dvbnet->remove_mutex);

	if (dvbnet->dvbdev->users < 1)
		wait_event(dvbnet->dvbdev->wait_queue,
				dvbnet->dvbdev->users == 1);
@@ -1632,6 +1663,7 @@ int dvb_net_init (struct dvb_adapter *adap, struct dvb_net *dvbnet,
	int i;

	mutex_init(&dvbnet->ioctl_mutex);
	mutex_init(&dvbnet->remove_mutex);
	dvbnet->demux = dmx;

	for (i=0; i<DVB_NET_DEVICES_MAX; i++)
+4 −0
Original line number Diff line number Diff line
@@ -41,6 +41,9 @@
 * @exit:		flag to indicate when the device is being removed.
 * @demux:		pointer to &struct dmx_demux.
 * @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
 * devices.
@@ -53,6 +56,7 @@ struct dvb_net {
	unsigned int exit:1;
	struct dmx_demux *demux;
	struct mutex ioctl_mutex;
	struct mutex remove_mutex;
};

/**