diff options
author | Mauro Carvalho Chehab <mchehab@osg.samsung.com> | 2016-04-27 18:28:26 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@s-opensource.com> | 2016-06-15 16:57:24 -0400 |
commit | a087ce704b802becbb4b0f2a20f2cb3f6911802e (patch) | |
tree | 6cfa3f4ac4ef03bc7f7cc5fe4180a956f470213b | |
parent | 163f1e93e995048b894c5fc86a6034d16beed740 (diff) |
[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: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
-rw-r--r-- | drivers/media/media-device.c | 44 | ||||
-rw-r--r-- | drivers/media/media-devnode.c | 7 | ||||
-rw-r--r-- | drivers/media/usb/au0828/au0828-core.c | 4 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_driver.c | 2 | ||||
-rw-r--r-- | include/media/media-device.h | 5 | ||||
-rw-r--r-- | include/media/media-devnode.h | 13 |
6 files changed, 52 insertions, 23 deletions
diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index a1cd50f331f1..e61fa66d95df 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c | |||
@@ -423,7 +423,7 @@ static long media_device_ioctl(struct file *filp, unsigned int cmd, | |||
423 | unsigned long arg) | 423 | unsigned long arg) |
424 | { | 424 | { |
425 | struct media_devnode *devnode = media_devnode_data(filp); | 425 | struct media_devnode *devnode = media_devnode_data(filp); |
426 | struct media_device *dev = to_media_device(devnode); | 426 | struct media_device *dev = devnode->media_dev; |
427 | long ret; | 427 | long ret; |
428 | 428 | ||
429 | mutex_lock(&dev->graph_mutex); | 429 | mutex_lock(&dev->graph_mutex); |
@@ -495,7 +495,7 @@ static long media_device_compat_ioctl(struct file *filp, unsigned int cmd, | |||
495 | unsigned long arg) | 495 | unsigned long arg) |
496 | { | 496 | { |
497 | struct media_devnode *devnode = media_devnode_data(filp); | 497 | struct media_devnode *devnode = media_devnode_data(filp); |
498 | struct media_device *dev = to_media_device(devnode); | 498 | struct media_device *dev = devnode->media_dev; |
499 | long ret; | 499 | long ret; |
500 | 500 | ||
501 | switch (cmd) { | 501 | switch (cmd) { |
@@ -531,7 +531,8 @@ static const struct media_file_operations media_device_fops = { | |||
531 | static ssize_t show_model(struct device *cd, | 531 | static ssize_t show_model(struct device *cd, |
532 | struct device_attribute *attr, char *buf) | 532 | struct device_attribute *attr, char *buf) |
533 | { | 533 | { |
534 | struct media_device *mdev = to_media_device(to_media_devnode(cd)); | 534 | struct media_devnode *devnode = to_media_devnode(cd); |
535 | struct media_device *mdev = devnode->media_dev; | ||
535 | 536 | ||
536 | return sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model); | 537 | return sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model); |
537 | } | 538 | } |
@@ -704,23 +705,34 @@ EXPORT_SYMBOL_GPL(media_device_cleanup); | |||
704 | int __must_check __media_device_register(struct media_device *mdev, | 705 | int __must_check __media_device_register(struct media_device *mdev, |
705 | struct module *owner) | 706 | struct module *owner) |
706 | { | 707 | { |
708 | struct media_devnode *devnode; | ||
707 | int ret; | 709 | int ret; |
708 | 710 | ||
711 | devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); | ||
712 | if (!devnode) | ||
713 | return -ENOMEM; | ||
714 | |||
709 | /* Register the device node. */ | 715 | /* Register the device node. */ |
710 | mdev->devnode.fops = &media_device_fops; | 716 | mdev->devnode = devnode; |
711 | mdev->devnode.parent = mdev->dev; | 717 | devnode->fops = &media_device_fops; |
712 | mdev->devnode.release = media_device_release; | 718 | devnode->parent = mdev->dev; |
719 | devnode->release = media_device_release; | ||
713 | 720 | ||
714 | /* Set version 0 to indicate user-space that the graph is static */ | 721 | /* Set version 0 to indicate user-space that the graph is static */ |
715 | mdev->topology_version = 0; | 722 | mdev->topology_version = 0; |
716 | 723 | ||
717 | ret = media_devnode_register(&mdev->devnode, owner); | 724 | ret = media_devnode_register(mdev, devnode, owner); |
718 | if (ret < 0) | 725 | if (ret < 0) { |
726 | mdev->devnode = NULL; | ||
727 | kfree(devnode); | ||
719 | return ret; | 728 | return ret; |
729 | } | ||
720 | 730 | ||
721 | ret = device_create_file(&mdev->devnode.dev, &dev_attr_model); | 731 | ret = device_create_file(&devnode->dev, &dev_attr_model); |
722 | if (ret < 0) { | 732 | if (ret < 0) { |
723 | media_devnode_unregister(&mdev->devnode); | 733 | mdev->devnode = NULL; |
734 | media_devnode_unregister(devnode); | ||
735 | kfree(devnode); | ||
724 | return ret; | 736 | return ret; |
725 | } | 737 | } |
726 | 738 | ||
@@ -771,7 +783,7 @@ void media_device_unregister(struct media_device *mdev) | |||
771 | mutex_lock(&mdev->graph_mutex); | 783 | mutex_lock(&mdev->graph_mutex); |
772 | 784 | ||
773 | /* Check if mdev was ever registered at all */ | 785 | /* Check if mdev was ever registered at all */ |
774 | if (!media_devnode_is_registered(&mdev->devnode)) { | 786 | if (!media_devnode_is_registered(mdev->devnode)) { |
775 | mutex_unlock(&mdev->graph_mutex); | 787 | mutex_unlock(&mdev->graph_mutex); |
776 | return; | 788 | return; |
777 | } | 789 | } |
@@ -794,9 +806,13 @@ void media_device_unregister(struct media_device *mdev) | |||
794 | 806 | ||
795 | mutex_unlock(&mdev->graph_mutex); | 807 | mutex_unlock(&mdev->graph_mutex); |
796 | 808 | ||
797 | device_remove_file(&mdev->devnode.dev, &dev_attr_model); | 809 | dev_dbg(mdev->dev, "Media device unregistered\n"); |
798 | dev_dbg(mdev->dev, "Media device unregistering\n"); | 810 | |
799 | media_devnode_unregister(&mdev->devnode); | 811 | /* Check if mdev devnode was registered */ |
812 | if (media_devnode_is_registered(mdev->devnode)) { | ||
813 | device_remove_file(&mdev->devnode->dev, &dev_attr_model); | ||
814 | media_devnode_unregister(mdev->devnode); | ||
815 | } | ||
800 | } | 816 | } |
801 | EXPORT_SYMBOL_GPL(media_device_unregister); | 817 | EXPORT_SYMBOL_GPL(media_device_unregister); |
802 | 818 | ||
diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c index 7481c9610945..ecdc02d6ed83 100644 --- a/drivers/media/media-devnode.c +++ b/drivers/media/media-devnode.c | |||
@@ -44,6 +44,7 @@ | |||
44 | #include <linux/uaccess.h> | 44 | #include <linux/uaccess.h> |
45 | 45 | ||
46 | #include <media/media-devnode.h> | 46 | #include <media/media-devnode.h> |
47 | #include <media/media-device.h> | ||
47 | 48 | ||
48 | #define MEDIA_NUM_DEVICES 256 | 49 | #define MEDIA_NUM_DEVICES 256 |
49 | #define MEDIA_NAME "media" | 50 | #define MEDIA_NAME "media" |
@@ -74,6 +75,8 @@ static void media_devnode_release(struct device *cd) | |||
74 | /* Release media_devnode and perform other cleanups as needed. */ | 75 | /* Release media_devnode and perform other cleanups as needed. */ |
75 | if (devnode->release) | 76 | if (devnode->release) |
76 | devnode->release(devnode); | 77 | devnode->release(devnode); |
78 | |||
79 | kfree(devnode); | ||
77 | } | 80 | } |
78 | 81 | ||
79 | static struct bus_type media_bus_type = { | 82 | static struct bus_type media_bus_type = { |
@@ -219,7 +222,8 @@ static const struct file_operations media_devnode_fops = { | |||
219 | .llseek = no_llseek, | 222 | .llseek = no_llseek, |
220 | }; | 223 | }; |
221 | 224 | ||
222 | int __must_check media_devnode_register(struct media_devnode *devnode, | 225 | int __must_check media_devnode_register(struct media_device *mdev, |
226 | struct media_devnode *devnode, | ||
223 | struct module *owner) | 227 | struct module *owner) |
224 | { | 228 | { |
225 | int minor; | 229 | int minor; |
@@ -238,6 +242,7 @@ int __must_check media_devnode_register(struct media_devnode *devnode, | |||
238 | mutex_unlock(&media_devnode_lock); | 242 | mutex_unlock(&media_devnode_lock); |
239 | 243 | ||
240 | devnode->minor = minor; | 244 | devnode->minor = minor; |
245 | devnode->media_dev = mdev; | ||
241 | 246 | ||
242 | /* Part 2: Initialize and register the character device */ | 247 | /* Part 2: Initialize and register the character device */ |
243 | cdev_init(&devnode->cdev, &media_devnode_fops); | 248 | cdev_init(&devnode->cdev, &media_devnode_fops); |
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 321ea5cf1329..bf53553d2624 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c | |||
@@ -142,7 +142,7 @@ static void au0828_unregister_media_device(struct au0828_dev *dev) | |||
142 | struct media_device *mdev = dev->media_dev; | 142 | struct media_device *mdev = dev->media_dev; |
143 | struct media_entity_notify *notify, *nextp; | 143 | struct media_entity_notify *notify, *nextp; |
144 | 144 | ||
145 | if (!mdev || !media_devnode_is_registered(&mdev->devnode)) | 145 | if (!mdev || !media_devnode_is_registered(mdev->devnode)) |
146 | return; | 146 | return; |
147 | 147 | ||
148 | /* Remove au0828 entity_notify callbacks */ | 148 | /* Remove au0828 entity_notify callbacks */ |
@@ -482,7 +482,7 @@ static int au0828_media_device_register(struct au0828_dev *dev, | |||
482 | if (!dev->media_dev) | 482 | if (!dev->media_dev) |
483 | return 0; | 483 | return 0; |
484 | 484 | ||
485 | if (!media_devnode_is_registered(&dev->media_dev->devnode)) { | 485 | if (!media_devnode_is_registered(dev->media_dev->devnode)) { |
486 | 486 | ||
487 | /* register media device */ | 487 | /* register media device */ |
488 | ret = media_device_register(dev->media_dev); | 488 | ret = media_device_register(dev->media_dev); |
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 451e84e962e2..302e284a95eb 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c | |||
@@ -1674,7 +1674,7 @@ static void uvc_delete(struct uvc_device *dev) | |||
1674 | if (dev->vdev.dev) | 1674 | if (dev->vdev.dev) |
1675 | v4l2_device_unregister(&dev->vdev); | 1675 | v4l2_device_unregister(&dev->vdev); |
1676 | #ifdef CONFIG_MEDIA_CONTROLLER | 1676 | #ifdef CONFIG_MEDIA_CONTROLLER |
1677 | if (media_devnode_is_registered(&dev->mdev.devnode)) | 1677 | if (media_devnode_is_registered(dev->mdev.devnode)) |
1678 | media_device_unregister(&dev->mdev); | 1678 | media_device_unregister(&dev->mdev); |
1679 | media_device_cleanup(&dev->mdev); | 1679 | media_device_cleanup(&dev->mdev); |
1680 | #endif | 1680 | #endif |
diff --git a/include/media/media-device.h b/include/media/media-device.h index a9b33c47310d..f743ae2210ee 100644 --- a/include/media/media-device.h +++ b/include/media/media-device.h | |||
@@ -347,7 +347,7 @@ struct media_entity_notify { | |||
347 | struct media_device { | 347 | struct media_device { |
348 | /* dev->driver_data points to this struct. */ | 348 | /* dev->driver_data points to this struct. */ |
349 | struct device *dev; | 349 | struct device *dev; |
350 | struct media_devnode devnode; | 350 | struct media_devnode *devnode; |
351 | 351 | ||
352 | char model[32]; | 352 | char model[32]; |
353 | char driver_name[32]; | 353 | char driver_name[32]; |
@@ -393,9 +393,6 @@ struct usb_device; | |||
393 | #define MEDIA_DEV_NOTIFY_PRE_LINK_CH 0 | 393 | #define MEDIA_DEV_NOTIFY_PRE_LINK_CH 0 |
394 | #define MEDIA_DEV_NOTIFY_POST_LINK_CH 1 | 394 | #define MEDIA_DEV_NOTIFY_POST_LINK_CH 1 |
395 | 395 | ||
396 | /* media_devnode to media_device */ | ||
397 | #define to_media_device(node) container_of(node, struct media_device, devnode) | ||
398 | |||
399 | /** | 396 | /** |
400 | * media_entity_enum_init - Initialise an entity enumeration | 397 | * media_entity_enum_init - Initialise an entity enumeration |
401 | * | 398 | * |
diff --git a/include/media/media-devnode.h b/include/media/media-devnode.h index e1d5af077adb..5bb3b0e86d73 100644 --- a/include/media/media-devnode.h +++ b/include/media/media-devnode.h | |||
@@ -33,6 +33,8 @@ | |||
33 | #include <linux/device.h> | 33 | #include <linux/device.h> |
34 | #include <linux/cdev.h> | 34 | #include <linux/cdev.h> |
35 | 35 | ||
36 | struct media_device; | ||
37 | |||
36 | /* | 38 | /* |
37 | * Flag to mark the media_devnode struct as registered. Drivers must not touch | 39 | * Flag to mark the media_devnode struct as registered. Drivers must not touch |
38 | * this flag directly, it will be set and cleared by media_devnode_register and | 40 | * this flag directly, it will be set and cleared by media_devnode_register and |
@@ -81,6 +83,8 @@ struct media_file_operations { | |||
81 | * before registering the node. | 83 | * before registering the node. |
82 | */ | 84 | */ |
83 | struct media_devnode { | 85 | struct media_devnode { |
86 | struct media_device *media_dev; | ||
87 | |||
84 | /* device ops */ | 88 | /* device ops */ |
85 | const struct media_file_operations *fops; | 89 | const struct media_file_operations *fops; |
86 | 90 | ||
@@ -103,6 +107,7 @@ struct media_devnode { | |||
103 | /** | 107 | /** |
104 | * media_devnode_register - register a media device node | 108 | * media_devnode_register - register a media device node |
105 | * | 109 | * |
110 | * @media_dev: struct media_device we want to register a device node | ||
106 | * @devnode: media device node structure we want to register | 111 | * @devnode: media device node structure we want to register |
107 | * @owner: should be filled with %THIS_MODULE | 112 | * @owner: should be filled with %THIS_MODULE |
108 | * | 113 | * |
@@ -116,7 +121,8 @@ struct media_devnode { | |||
116 | * the media_devnode structure is *not* called, so the caller is responsible for | 121 | * the media_devnode structure is *not* called, so the caller is responsible for |
117 | * freeing any data. | 122 | * freeing any data. |
118 | */ | 123 | */ |
119 | int __must_check media_devnode_register(struct media_devnode *devnode, | 124 | int __must_check media_devnode_register(struct media_device *mdev, |
125 | struct media_devnode *devnode, | ||
120 | struct module *owner); | 126 | struct module *owner); |
121 | 127 | ||
122 | /** | 128 | /** |
@@ -146,9 +152,14 @@ static inline struct media_devnode *media_devnode_data(struct file *filp) | |||
146 | * false otherwise. | 152 | * false otherwise. |
147 | * | 153 | * |
148 | * @devnode: pointer to struct &media_devnode. | 154 | * @devnode: pointer to struct &media_devnode. |
155 | * | ||
156 | * Note: If mdev is NULL, it also returns false. | ||
149 | */ | 157 | */ |
150 | static inline int media_devnode_is_registered(struct media_devnode *devnode) | 158 | static inline int media_devnode_is_registered(struct media_devnode *devnode) |
151 | { | 159 | { |
160 | if (!devnode) | ||
161 | return false; | ||
162 | |||
152 | return test_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); | 163 | return test_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); |
153 | } | 164 | } |
154 | 165 | ||