media: dvbdev: adopts refcnt to avoid UAF
authorLin Ma <linma@zju.edu.cn>
Sun, 7 Aug 2022 14:59:52 +0000 (15:59 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 18 Jan 2023 08:26:32 +0000 (09:26 +0100)
[ 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: Lin Ma <linma@zju.edu.cn>
Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/media/dvb-core/dvb_ca_en50221.c
drivers/media/dvb-core/dvb_frontend.c
drivers/media/dvb-core/dvbdev.c
drivers/media/dvb-core/dvbdev.h

index 95b3723282f4623b725e1e1c22214f653fa65c74..56114d85510f52303a560c5b6ed2caa4d330a1cf 100644 (file)
@@ -167,7 +167,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);
 
index f7d4ec37fdbc13cad62cc2437f5012bae3dce52f..e3a4a4688c2ec4bc5aec8237b99b045c19589aa4 100644 (file)
@@ -146,7 +146,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);
 
index 1628cbdd20d73d59414a7974310ef8315d7f264c..4c89c37713bdf26f63b2fa5347eecf0664aece42 100644 (file)
@@ -84,7 +84,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);
@@ -148,6 +148,9 @@ int dvb_generic_release(struct inode *inode, struct file *file)
        }
 
        dvbdev->users++;
+
+       dvb_device_put(dvbdev);
+
        return 0;
 }
 EXPORT_SYMBOL(dvb_generic_release);
@@ -462,6 +465,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;
@@ -493,7 +497,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);
@@ -534,6 +538,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);
@@ -545,21 +550,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);
 
index 49189392cf3b9c22907ff9ff17c610a4b86a4ff3..5f6a6e7cda61774e8e4a7c562865bc65f7697e77 100644 (file)
@@ -133,6 +133,7 @@ struct dvb_adapter {
  */
 struct dvb_device {
        struct list_head list_head;
+       struct kref ref;
        const struct file_operations *fops;
        struct dvb_adapter *adapter;
        int type;
@@ -164,6 +165,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
  *
@@ -210,29 +225,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);