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

Commit 6d18b44b authored by Lin Ma's avatar Lin Ma Committed by Greg Kroah-Hartman
Browse files

media: dvbdev: adopts refcnt to avoid UAF

[ Upstream commit 0fc044b2b5e2d05a1fa1fb0d7f270367a7855d79 ]

dvb_unregister_device() is known that prone to use-after-free.
That is, the cleanup from dvb_unregister_device() releases the dvb_device
even if there are pointers stored in file->private_data still refer to it.

This patch adds a reference counter into struct dvb_device and delays its
deallocation until no pointer refers to the object.

Link: https://lore.kernel.org/linux-media/20220807145952.10368-1-linma@zju.edu.cn


Signed-off-by: default avatarLin Ma <linma@zju.edu.cn>
Reported-by: default avatarkernel test robot <lkp@intel.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@kernel.org>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 438a4a8d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -157,7 +157,7 @@ static void dvb_ca_private_free(struct dvb_ca_private *ca)
{
	unsigned int i;

	dvb_free_device(ca->dvbdev);
	dvb_device_put(ca->dvbdev);
	for (i = 0; i < ca->slot_count; i++)
		vfree(ca->slot_info[i].rx_buffer.data);

+1 −1
Original line number Diff line number Diff line
@@ -135,7 +135,7 @@ static void __dvb_frontend_free(struct dvb_frontend *fe)
	struct dvb_frontend_private *fepriv = fe->frontend_priv;

	if (fepriv)
		dvb_free_device(fepriv->dvbdev);
		dvb_device_put(fepriv->dvbdev);

	dvb_frontend_invoke_release(fe, fe->ops.release);

+25 −7
Original line number Diff line number Diff line
@@ -107,7 +107,7 @@ static int dvb_device_open(struct inode *inode, struct file *file)
		new_fops = fops_get(dvbdev->fops);
		if (!new_fops)
			goto fail;
		file->private_data = dvbdev;
		file->private_data = dvb_device_get(dvbdev);
		replace_fops(file, new_fops);
		if (file->f_op->open)
			err = file->f_op->open(inode, file);
@@ -171,6 +171,9 @@ int dvb_generic_release(struct inode *inode, struct file *file)
	}

	dvbdev->users++;

	dvb_device_put(dvbdev);

	return 0;
}
EXPORT_SYMBOL(dvb_generic_release);
@@ -487,6 +490,7 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
		return -ENOMEM;
	}

	kref_init(&dvbdev->ref);
	memcpy(dvbdev, template, sizeof(struct dvb_device));
	dvbdev->type = type;
	dvbdev->id = id;
@@ -517,7 +521,7 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
#endif

	dvbdev->minor = minor;
	dvb_minors[minor] = dvbdev;
	dvb_minors[minor] = dvb_device_get(dvbdev);
	up_write(&minor_rwsem);

	ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
@@ -557,6 +561,7 @@ void dvb_remove_device(struct dvb_device *dvbdev)

	down_write(&minor_rwsem);
	dvb_minors[dvbdev->minor] = NULL;
	dvb_device_put(dvbdev);
	up_write(&minor_rwsem);

	dvb_media_device_free(dvbdev);
@@ -568,21 +573,34 @@ void dvb_remove_device(struct dvb_device *dvbdev)
EXPORT_SYMBOL(dvb_remove_device);


void dvb_free_device(struct dvb_device *dvbdev)
static void dvb_free_device(struct kref *ref)
{
	if (!dvbdev)
		return;
	struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref);

	kfree (dvbdev->fops);
	kfree (dvbdev);
}
EXPORT_SYMBOL(dvb_free_device);


struct dvb_device *dvb_device_get(struct dvb_device *dvbdev)
{
	kref_get(&dvbdev->ref);
	return dvbdev;
}
EXPORT_SYMBOL(dvb_device_get);


void dvb_device_put(struct dvb_device *dvbdev)
{
	if (dvbdev)
		kref_put(&dvbdev->ref, dvb_free_device);
}


void dvb_unregister_device(struct dvb_device *dvbdev)
{
	dvb_remove_device(dvbdev);
	dvb_free_device(dvbdev);
	dvb_device_put(dvbdev);
}
EXPORT_SYMBOL(dvb_unregister_device);

+17 −14
Original line number Diff line number Diff line
@@ -156,6 +156,7 @@ struct dvb_adapter {
 */
struct dvb_device {
	struct list_head list_head;
	struct kref ref;
	const struct file_operations *fops;
	struct dvb_adapter *adapter;
	enum dvb_device_type type;
@@ -187,6 +188,20 @@ struct dvb_device {
	void *priv;
};

/**
 * dvb_device_get - Increase dvb_device reference
 *
 * @dvbdev:	pointer to struct dvb_device
 */
struct dvb_device *dvb_device_get(struct dvb_device *dvbdev);

/**
 * dvb_device_get - Decrease dvb_device reference
 *
 * @dvbdev:	pointer to struct dvb_device
 */
void dvb_device_put(struct dvb_device *dvbdev);

/**
 * dvb_register_adapter - Registers a new DVB adapter
 *
@@ -231,29 +246,17 @@ int dvb_register_device(struct dvb_adapter *adap,
/**
 * dvb_remove_device - Remove a registered DVB device
 *
 * This does not free memory.  To do that, call dvb_free_device().
 * This does not free memory. dvb_free_device() will do that when
 * reference counter is empty
 *
 * @dvbdev:	pointer to struct dvb_device
 */
void dvb_remove_device(struct dvb_device *dvbdev);

/**
 * dvb_free_device - Free memory occupied by a DVB device.
 *
 * Call dvb_unregister_device() before calling this function.
 *
 * @dvbdev:	pointer to struct dvb_device
 */
void dvb_free_device(struct dvb_device *dvbdev);

/**
 * dvb_unregister_device - Unregisters a DVB device
 *
 * This is a combination of dvb_remove_device() and dvb_free_device().
 * Using this function is usually a mistake, and is often an indicator
 * for a use-after-free bug (when a userspace process keeps a file
 * handle to a detached device).
 *
 * @dvbdev:	pointer to struct dvb_device
 */
void dvb_unregister_device(struct dvb_device *dvbdev);