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

Commit a087ce70 authored by Mauro Carvalho Chehab's avatar Mauro Carvalho Chehab
Browse files

[media] media-device: dynamically allocate struct media_devnode



struct media_devnode is currently embedded at struct media_device.

While this works fine during normal usage, it leads to a race
condition during devnode unregister. the problem is that drivers
assume that, after calling media_device_unregister(), the struct
that contains media_device can be freed. This is not true, as it
can't be freed until userspace closes all opened /dev/media devnodes.

In other words, if the media devnode is still open, and media_device
gets freed, any call to an ioctl will make the core to try to access
struct media_device, with will cause an use-after-free and even GPF.

Fix this by dynamically allocating the struct media_devnode and only
freeing it when it is safe.

Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@osg.samsung.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@s-opensource.com>
parent 163f1e93
Loading
Loading
Loading
Loading
+30 −14
Original line number Diff line number Diff line
@@ -423,7 +423,7 @@ static long media_device_ioctl(struct file *filp, unsigned int cmd,
			       unsigned long arg)
{
	struct media_devnode *devnode = media_devnode_data(filp);
	struct media_device *dev = to_media_device(devnode);
	struct media_device *dev = devnode->media_dev;
	long ret;

	mutex_lock(&dev->graph_mutex);
@@ -495,7 +495,7 @@ static long media_device_compat_ioctl(struct file *filp, unsigned int cmd,
				      unsigned long arg)
{
	struct media_devnode *devnode = media_devnode_data(filp);
	struct media_device *dev = to_media_device(devnode);
	struct media_device *dev = devnode->media_dev;
	long ret;

	switch (cmd) {
@@ -531,7 +531,8 @@ static const struct media_file_operations media_device_fops = {
static ssize_t show_model(struct device *cd,
			  struct device_attribute *attr, char *buf)
{
	struct media_device *mdev = to_media_device(to_media_devnode(cd));
	struct media_devnode *devnode = to_media_devnode(cd);
	struct media_device *mdev = devnode->media_dev;

	return sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model);
}
@@ -704,23 +705,34 @@ EXPORT_SYMBOL_GPL(media_device_cleanup);
int __must_check __media_device_register(struct media_device *mdev,
					 struct module *owner)
{
	struct media_devnode *devnode;
	int ret;

	devnode = kzalloc(sizeof(*devnode), GFP_KERNEL);
	if (!devnode)
		return -ENOMEM;

	/* Register the device node. */
	mdev->devnode.fops = &media_device_fops;
	mdev->devnode.parent = mdev->dev;
	mdev->devnode.release = media_device_release;
	mdev->devnode = devnode;
	devnode->fops = &media_device_fops;
	devnode->parent = mdev->dev;
	devnode->release = media_device_release;

	/* Set version 0 to indicate user-space that the graph is static */
	mdev->topology_version = 0;

	ret = media_devnode_register(&mdev->devnode, owner);
	if (ret < 0)
	ret = media_devnode_register(mdev, devnode, owner);
	if (ret < 0) {
		mdev->devnode = NULL;
		kfree(devnode);
		return ret;
	}

	ret = device_create_file(&mdev->devnode.dev, &dev_attr_model);
	ret = device_create_file(&devnode->dev, &dev_attr_model);
	if (ret < 0) {
		media_devnode_unregister(&mdev->devnode);
		mdev->devnode = NULL;
		media_devnode_unregister(devnode);
		kfree(devnode);
		return ret;
	}

@@ -771,7 +783,7 @@ void media_device_unregister(struct media_device *mdev)
	mutex_lock(&mdev->graph_mutex);

	/* Check if mdev was ever registered at all */
	if (!media_devnode_is_registered(&mdev->devnode)) {
	if (!media_devnode_is_registered(mdev->devnode)) {
		mutex_unlock(&mdev->graph_mutex);
		return;
	}
@@ -794,9 +806,13 @@ void media_device_unregister(struct media_device *mdev)

	mutex_unlock(&mdev->graph_mutex);

	device_remove_file(&mdev->devnode.dev, &dev_attr_model);
	dev_dbg(mdev->dev, "Media device unregistering\n");
	media_devnode_unregister(&mdev->devnode);
	dev_dbg(mdev->dev, "Media device unregistered\n");

	/* Check if mdev devnode was registered */
	if (media_devnode_is_registered(mdev->devnode)) {
		device_remove_file(&mdev->devnode->dev, &dev_attr_model);
		media_devnode_unregister(mdev->devnode);
	}
}
EXPORT_SYMBOL_GPL(media_device_unregister);

+6 −1
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@
#include <linux/uaccess.h>

#include <media/media-devnode.h>
#include <media/media-device.h>

#define MEDIA_NUM_DEVICES	256
#define MEDIA_NAME		"media"
@@ -74,6 +75,8 @@ static void media_devnode_release(struct device *cd)
	/* Release media_devnode and perform other cleanups as needed. */
	if (devnode->release)
		devnode->release(devnode);

	kfree(devnode);
}

static struct bus_type media_bus_type = {
@@ -219,7 +222,8 @@ static const struct file_operations media_devnode_fops = {
	.llseek = no_llseek,
};

int __must_check media_devnode_register(struct media_devnode *devnode,
int __must_check media_devnode_register(struct media_device *mdev,
					struct media_devnode *devnode,
					struct module *owner)
{
	int minor;
@@ -238,6 +242,7 @@ int __must_check media_devnode_register(struct media_devnode *devnode,
	mutex_unlock(&media_devnode_lock);

	devnode->minor = minor;
	devnode->media_dev = mdev;

	/* Part 2: Initialize and register the character device */
	cdev_init(&devnode->cdev, &media_devnode_fops);
+2 −2
Original line number Diff line number Diff line
@@ -142,7 +142,7 @@ static void au0828_unregister_media_device(struct au0828_dev *dev)
	struct media_device *mdev = dev->media_dev;
	struct media_entity_notify *notify, *nextp;

	if (!mdev || !media_devnode_is_registered(&mdev->devnode))
	if (!mdev || !media_devnode_is_registered(mdev->devnode))
		return;

	/* Remove au0828 entity_notify callbacks */
@@ -482,7 +482,7 @@ static int au0828_media_device_register(struct au0828_dev *dev,
	if (!dev->media_dev)
		return 0;

	if (!media_devnode_is_registered(&dev->media_dev->devnode)) {
	if (!media_devnode_is_registered(dev->media_dev->devnode)) {

		/* register media device */
		ret = media_device_register(dev->media_dev);
+1 −1
Original line number Diff line number Diff line
@@ -1674,7 +1674,7 @@ static void uvc_delete(struct uvc_device *dev)
	if (dev->vdev.dev)
		v4l2_device_unregister(&dev->vdev);
#ifdef CONFIG_MEDIA_CONTROLLER
	if (media_devnode_is_registered(&dev->mdev.devnode))
	if (media_devnode_is_registered(dev->mdev.devnode))
		media_device_unregister(&dev->mdev);
	media_device_cleanup(&dev->mdev);
#endif
+1 −4
Original line number Diff line number Diff line
@@ -347,7 +347,7 @@ struct media_entity_notify {
struct media_device {
	/* dev->driver_data points to this struct. */
	struct device *dev;
	struct media_devnode devnode;
	struct media_devnode *devnode;

	char model[32];
	char driver_name[32];
@@ -393,9 +393,6 @@ struct usb_device;
#define MEDIA_DEV_NOTIFY_PRE_LINK_CH	0
#define MEDIA_DEV_NOTIFY_POST_LINK_CH	1

/* media_devnode to media_device */
#define to_media_device(node) container_of(node, struct media_device, devnode)

/**
 * media_entity_enum_init - Initialise an entity enumeration
 *
Loading