|
Takashi Iwai |
e391ba |
From 627bb528b086b4136315c25d6a447a98ea9448d3 Mon Sep 17 00:00:00 2001
|
|
Takashi Iwai |
e391ba |
From: Hyunwoo Kim <imv4bel@gmail.com>
|
|
Takashi Iwai |
e391ba |
Date: Thu, 17 Nov 2022 04:59:24 +0000
|
|
Takashi Iwai |
e391ba |
Subject: [PATCH] media: dvb-core: Fix use-after-free due to race at dvb_register_device()
|
|
Takashi Iwai |
e391ba |
Git-commit: 627bb528b086b4136315c25d6a447a98ea9448d3
|
|
Takashi Iwai |
201322 |
Patch-mainline: v6.4-rc3
|
|
Takashi Iwai |
e391ba |
References: CVE-2022-45884 bsc#1205756
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
dvb_register_device() dynamically allocates fops with kmemdup()
|
|
Takashi Iwai |
e391ba |
to set the fops->owner.
|
|
Takashi Iwai |
e391ba |
And these fops are registered in 'file->f_ops' using replace_fops()
|
|
Takashi Iwai |
e391ba |
in the dvb_device_open() process, and kfree()d in dvb_free_device().
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
However, it is not common to use dynamically allocated fops instead
|
|
Takashi Iwai |
e391ba |
of 'static const' fops as an argument of replace_fops(),
|
|
Takashi Iwai |
e391ba |
and UAF may occur.
|
|
Takashi Iwai |
e391ba |
These UAFs can occur on any dvb type using dvb_register_device(),
|
|
Takashi Iwai |
e391ba |
such as dvb_dvr, dvb_demux, dvb_frontend, dvb_net, etc.
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
So, instead of kfree() the fops dynamically allocated in
|
|
Takashi Iwai |
e391ba |
dvb_register_device() in dvb_free_device() called during the
|
|
Takashi Iwai |
e391ba |
.disconnect() process, kfree() it collectively in exit_dvbdev()
|
|
Takashi Iwai |
e391ba |
called when the dvbdev.c module is removed.
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
Link: https://lore.kernel.org/linux-media/20221117045925.14297-4-imv4bel@gmail.com
|
|
Takashi Iwai |
e391ba |
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
|
|
Takashi Iwai |
e391ba |
Reported-by: kernel test robot <lkp@intel.com>
|
|
Takashi Iwai |
e391ba |
Reported-by: Dan Carpenter <error27@gmail.com>
|
|
Takashi Iwai |
e391ba |
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
|
|
Takashi Iwai |
e391ba |
Acked-by: Takashi Iwai <tiwai@suse.de>
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
---
|
|
Takashi Iwai |
e391ba |
drivers/media/dvb-core/dvbdev.c | 84 ++++++++++++++++++++++++++++++----------
|
|
Takashi Iwai |
e391ba |
include/media/dvbdev.h | 15 +++++++
|
|
Takashi Iwai |
e391ba |
2 files changed, 78 insertions(+), 21 deletions(-)
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
--- a/drivers/media/dvb-core/dvbdev.c
|
|
Takashi Iwai |
e391ba |
+++ b/drivers/media/dvb-core/dvbdev.c
|
|
Takashi Iwai |
e391ba |
@@ -37,6 +37,7 @@
|
|
Takashi Iwai |
e391ba |
#include <media/tuner.h>
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
static DEFINE_MUTEX(dvbdev_mutex);
|
|
Takashi Iwai |
e391ba |
+static LIST_HEAD(dvbdevfops_list);
|
|
Takashi Iwai |
e391ba |
static int dvbdev_debug;
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
module_param(dvbdev_debug, int, 0644);
|
|
Takashi Iwai |
e391ba |
@@ -459,14 +460,15 @@ int dvb_register_device(struct dvb_adapt
|
|
Takashi Iwai |
e391ba |
enum dvb_device_type type, int demux_sink_pads)
|
|
Takashi Iwai |
e391ba |
{
|
|
Takashi Iwai |
e391ba |
struct dvb_device *dvbdev;
|
|
Takashi Iwai |
e391ba |
- struct file_operations *dvbdevfops;
|
|
Takashi Iwai |
e391ba |
+ struct file_operations *dvbdevfops = NULL;
|
|
Takashi Iwai |
e391ba |
+ struct dvbdevfops_node *node = NULL, *new_node = NULL;
|
|
Takashi Iwai |
e391ba |
struct device *clsdev;
|
|
Takashi Iwai |
e391ba |
int minor;
|
|
Takashi Iwai |
e391ba |
int id, ret;
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
mutex_lock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
- if ((id = dvbdev_get_free_id (adap, type)) < 0){
|
|
Takashi Iwai |
e391ba |
+ if ((id = dvbdev_get_free_id (adap, type)) < 0) {
|
|
Takashi Iwai |
e391ba |
mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
*pdvbdev = NULL;
|
|
Takashi Iwai |
e391ba |
pr_err("%s: couldn't find free device id\n", __func__);
|
|
Takashi Iwai |
e391ba |
@@ -474,18 +476,45 @@ int dvb_register_device(struct dvb_adapt
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
*pdvbdev = dvbdev = kzalloc(sizeof(*dvbdev), GFP_KERNEL);
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
if (!dvbdev){
|
|
Takashi Iwai |
e391ba |
mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
return -ENOMEM;
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
- dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
|
|
Takashi Iwai |
e391ba |
+ /*
|
|
Takashi Iwai |
e391ba |
+ * When a device of the same type is probe()d more than once,
|
|
Takashi Iwai |
e391ba |
+ * the first allocated fops are used. This prevents memory leaks
|
|
Takashi Iwai |
e391ba |
+ * that can occur when the same device is probe()d repeatedly.
|
|
Takashi Iwai |
e391ba |
+ */
|
|
Takashi Iwai |
e391ba |
+ list_for_each_entry(node, &dvbdevfops_list, list_head) {
|
|
Takashi Iwai |
e391ba |
+ if (node->fops->owner == adap->module &&
|
|
Takashi Iwai |
e391ba |
+ node->type == type &&
|
|
Takashi Iwai |
e391ba |
+ node->template == template) {
|
|
Takashi Iwai |
e391ba |
+ dvbdevfops = node->fops;
|
|
Takashi Iwai |
e391ba |
+ break;
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
- if (!dvbdevfops){
|
|
Takashi Iwai |
e391ba |
- kfree (dvbdev);
|
|
Takashi Iwai |
e391ba |
- mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
- return -ENOMEM;
|
|
Takashi Iwai |
e391ba |
+ if (dvbdevfops == NULL) {
|
|
Takashi Iwai |
e391ba |
+ dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
|
|
Takashi Iwai |
e391ba |
+ if (!dvbdevfops) {
|
|
Takashi Iwai |
e391ba |
+ kfree(dvbdev);
|
|
Takashi Iwai |
e391ba |
+ mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
+ return -ENOMEM;
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
+
|
|
Takashi Iwai |
e391ba |
+ new_node = kzalloc(sizeof(struct dvbdevfops_node), GFP_KERNEL);
|
|
Takashi Iwai |
e391ba |
+ if (!new_node) {
|
|
Takashi Iwai |
e391ba |
+ kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
+ kfree(dvbdev);
|
|
Takashi Iwai |
e391ba |
+ mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
+ return -ENOMEM;
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
+
|
|
Takashi Iwai |
e391ba |
+ new_node->fops = dvbdevfops;
|
|
Takashi Iwai |
e391ba |
+ new_node->type = type;
|
|
Takashi Iwai |
e391ba |
+ new_node->template = template;
|
|
Takashi Iwai |
e391ba |
+ list_add_tail (&new_node->list_head, &dvbdevfops_list);
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
memcpy(dvbdev, template, sizeof(struct dvb_device));
|
|
Takashi Iwai |
e391ba |
@@ -495,20 +524,20 @@ int dvb_register_device(struct dvb_adapt
|
|
Takashi Iwai |
e391ba |
dvbdev->priv = priv;
|
|
Takashi Iwai |
e391ba |
dvbdev->fops = dvbdevfops;
|
|
Takashi Iwai |
e391ba |
init_waitqueue_head (&dvbdev->wait_queue);
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
dvbdevfops->owner = adap->module;
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
list_add_tail (&dvbdev->list_head, &adap->device_list);
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
down_write(&minor_rwsem);
|
|
Takashi Iwai |
e391ba |
#ifdef CONFIG_DVB_DYNAMIC_MINORS
|
|
Takashi Iwai |
e391ba |
for (minor = 0; minor < MAX_DVB_MINORS; minor++)
|
|
Takashi Iwai |
e391ba |
if (dvb_minors[minor] == NULL)
|
|
Takashi Iwai |
e391ba |
break;
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
if (minor == MAX_DVB_MINORS) {
|
|
Takashi Iwai |
e391ba |
+ if (new_node) {
|
|
Takashi Iwai |
e391ba |
+ list_del (&new_node->list_head);
|
|
Takashi Iwai |
e391ba |
+ kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
+ kfree(new_node);
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
list_del (&dvbdev->list_head);
|
|
Takashi Iwai |
e391ba |
- kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
kfree(dvbdev);
|
|
Takashi Iwai |
e391ba |
up_write(&minor_rwsem);
|
|
Takashi Iwai |
e391ba |
mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
@@ -517,41 +546,47 @@ int dvb_register_device(struct dvb_adapt
|
|
Takashi Iwai |
e391ba |
#else
|
|
Takashi Iwai |
e391ba |
minor = nums2minor(adap->num, type, id);
|
|
Takashi Iwai |
e391ba |
#endif
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
dvbdev->minor = minor;
|
|
Takashi Iwai |
e391ba |
dvb_minors[minor] = dvbdev;
|
|
Takashi Iwai |
e391ba |
up_write(&minor_rwsem);
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
|
|
Takashi Iwai |
e391ba |
if (ret) {
|
|
Takashi Iwai |
e391ba |
pr_err("%s: dvb_register_media_device failed to create the mediagraph\n",
|
|
Takashi Iwai |
e391ba |
__func__);
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
+ if (new_node) {
|
|
Takashi Iwai |
e391ba |
+ list_del (&new_node->list_head);
|
|
Takashi Iwai |
e391ba |
+ kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
+ kfree(new_node);
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
dvb_media_device_free(dvbdev);
|
|
Takashi Iwai |
e391ba |
list_del (&dvbdev->list_head);
|
|
Takashi Iwai |
e391ba |
- kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
kfree(dvbdev);
|
|
Takashi Iwai |
e391ba |
mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
return ret;
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
- mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
-
|
|
Takashi Iwai |
e391ba |
clsdev = device_create(dvb_class, adap->device,
|
|
Takashi Iwai |
e391ba |
MKDEV(DVB_MAJOR, minor),
|
|
Takashi Iwai |
e391ba |
dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id);
|
|
Takashi Iwai |
e391ba |
if (IS_ERR(clsdev)) {
|
|
Takashi Iwai |
e391ba |
pr_err("%s: failed to create device dvb%d.%s%d (%ld)\n",
|
|
Takashi Iwai |
e391ba |
__func__, adap->num, dnames[type], id, PTR_ERR(clsdev));
|
|
Takashi Iwai |
e391ba |
+ if (new_node) {
|
|
Takashi Iwai |
e391ba |
+ list_del (&new_node->list_head);
|
|
Takashi Iwai |
e391ba |
+ kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
+ kfree(new_node);
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
dvb_media_device_free(dvbdev);
|
|
Takashi Iwai |
e391ba |
list_del (&dvbdev->list_head);
|
|
Takashi Iwai |
e391ba |
- kfree(dvbdevfops);
|
|
Takashi Iwai |
e391ba |
kfree(dvbdev);
|
|
Takashi Iwai |
e391ba |
+ mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
return PTR_ERR(clsdev);
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
+
|
|
Takashi Iwai |
e391ba |
dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n",
|
|
Takashi Iwai |
e391ba |
adap->num, dnames[type], id, minor, minor);
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
+ mutex_unlock(&dvbdev_register_lock);
|
|
Takashi Iwai |
e391ba |
return 0;
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
EXPORT_SYMBOL(dvb_register_device);
|
|
Takashi Iwai |
e391ba |
@@ -580,7 +615,6 @@ void dvb_free_device(struct dvb_device *
|
|
Takashi Iwai |
e391ba |
if (!dvbdev)
|
|
Takashi Iwai |
e391ba |
return;
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
- kfree (dvbdev->fops);
|
|
Takashi Iwai |
e391ba |
kfree (dvbdev);
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
EXPORT_SYMBOL(dvb_free_device);
|
|
Takashi Iwai |
e391ba |
@@ -1072,9 +1106,17 @@ error:
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
static void __exit exit_dvbdev(void)
|
|
Takashi Iwai |
e391ba |
{
|
|
Takashi Iwai |
e391ba |
+ struct dvbdevfops_node *node, *next;
|
|
Takashi Iwai |
e391ba |
+
|
|
Takashi Iwai |
e391ba |
class_destroy(dvb_class);
|
|
Takashi Iwai |
e391ba |
cdev_del(&dvb_device_cdev);
|
|
Takashi Iwai |
e391ba |
unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS);
|
|
Takashi Iwai |
e391ba |
+
|
|
Takashi Iwai |
e391ba |
+ list_for_each_entry_safe(node, next, &dvbdevfops_list, list_head) {
|
|
Takashi Iwai |
e391ba |
+ list_del (&node->list_head);
|
|
Takashi Iwai |
e391ba |
+ kfree(node->fops);
|
|
Takashi Iwai |
e391ba |
+ kfree(node);
|
|
Takashi Iwai |
e391ba |
+ }
|
|
Takashi Iwai |
e391ba |
}
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
subsys_initcall(init_dvbdev);
|
|
Takashi Iwai |
e391ba |
--- a/include/media/dvbdev.h
|
|
Takashi Iwai |
e391ba |
+++ b/include/media/dvbdev.h
|
|
Takashi Iwai |
e391ba |
@@ -188,6 +188,21 @@ struct dvb_device {
|
|
Takashi Iwai |
e391ba |
};
|
|
Takashi Iwai |
e391ba |
|
|
Takashi Iwai |
e391ba |
/**
|
|
Takashi Iwai |
e391ba |
+ * struct dvbdevfops_node - fops nodes registered in dvbdevfops_list
|
|
Takashi Iwai |
e391ba |
+ *
|
|
Takashi Iwai |
e391ba |
+ * @fops: Dynamically allocated fops for ->owner registration
|
|
Takashi Iwai |
e391ba |
+ * @type: type of dvb_device
|
|
Takashi Iwai |
e391ba |
+ * @template: dvb_device used for registration
|
|
Takashi Iwai |
e391ba |
+ * @list_head: list_head for dvbdevfops_list
|
|
Takashi Iwai |
e391ba |
+ */
|
|
Takashi Iwai |
e391ba |
+struct dvbdevfops_node {
|
|
Takashi Iwai |
e391ba |
+ struct file_operations *fops;
|
|
Takashi Iwai |
e391ba |
+ enum dvb_device_type type;
|
|
Takashi Iwai |
e391ba |
+ const struct dvb_device *template;
|
|
Takashi Iwai |
e391ba |
+ struct list_head list_head;
|
|
Takashi Iwai |
e391ba |
+};
|
|
Takashi Iwai |
e391ba |
+
|
|
Takashi Iwai |
e391ba |
+/**
|
|
Takashi Iwai |
e391ba |
* dvb_register_adapter - Registers a new DVB adapter
|
|
Takashi Iwai |
e391ba |
*
|
|
Takashi Iwai |
e391ba |
* @adap: pointer to struct dvb_adapter
|