mtd: use refcount to prevent corruption
authorTomas Winkler <tomas.winkler@intel.com>
Tue, 20 Jun 2023 13:19:04 +0000 (16:19 +0300)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 12 Jul 2023 11:30:08 +0000 (13:30 +0200)
When underlying device is removed mtd core will crash
in case user space is holding open handle.
Need to use proper refcounting so device is release
only when has no users.

Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20230620131905.648089-2-alexander.usyskin@intel.com
drivers/mtd/mtdcore.c
drivers/mtd/mtdcore.h
drivers/mtd/mtdpart.c
include/linux/mtd/mtd.h

index e00b12aa5ec91c32802177128f9b13be251c64c8..6dbd596a1129360e5dd1c0277d9e44e44f026f6e 100644 (file)
@@ -93,10 +93,33 @@ static void mtd_release(struct device *dev)
        struct mtd_info *mtd = dev_get_drvdata(dev);
        dev_t index = MTD_DEVT(mtd->index);
 
+       if (mtd_is_partition(mtd))
+               release_mtd_partition(mtd);
+
        /* remove /dev/mtdXro node */
        device_destroy(&mtd_class, index + 1);
 }
 
+static void mtd_device_release(struct kref *kref)
+{
+       struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
+
+       debugfs_remove_recursive(mtd->dbg.dfs_dir);
+
+       /* Try to remove the NVMEM provider */
+       nvmem_unregister(mtd->nvmem);
+
+       device_unregister(&mtd->dev);
+
+       /* Clear dev so mtd can be safely re-registered later if desired */
+       memset(&mtd->dev, 0, sizeof(mtd->dev));
+
+       idr_remove(&mtd_idr, mtd->index);
+       of_node_put(mtd_get_of_node(mtd));
+
+       module_put(THIS_MODULE);
+}
+
 #define MTD_DEVICE_ATTR_RO(name) \
 static DEVICE_ATTR(name, 0444, mtd_##name##_show, NULL)
 
@@ -666,7 +689,7 @@ int add_mtd_device(struct mtd_info *mtd)
        }
 
        mtd->index = i;
-       mtd->usecount = 0;
+       kref_init(&mtd->refcnt);
 
        /* default value if not set by driver */
        if (mtd->bitflip_threshold == 0)
@@ -779,7 +802,6 @@ int del_mtd_device(struct mtd_info *mtd)
 {
        int ret;
        struct mtd_notifier *not;
-       struct device_node *mtd_of_node;
 
        mutex_lock(&mtd_table_mutex);
 
@@ -793,28 +815,8 @@ int del_mtd_device(struct mtd_info *mtd)
        list_for_each_entry(not, &mtd_notifiers, list)
                not->remove(mtd);
 
-       if (mtd->usecount) {
-               printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
-                      mtd->index, mtd->name, mtd->usecount);
-               ret = -EBUSY;
-       } else {
-               mtd_of_node = mtd_get_of_node(mtd);
-               debugfs_remove_recursive(mtd->dbg.dfs_dir);
-
-               /* Try to remove the NVMEM provider */
-               nvmem_unregister(mtd->nvmem);
-
-               device_unregister(&mtd->dev);
-
-               /* Clear dev so mtd can be safely re-registered later if desired */
-               memset(&mtd->dev, 0, sizeof(mtd->dev));
-
-               idr_remove(&mtd_idr, mtd->index);
-               of_node_put(mtd_of_node);
-
-               module_put(THIS_MODULE);
-               ret = 0;
-       }
+       kref_put(&mtd->refcnt, mtd_device_release);
+       ret = 0;
 
 out_error:
        mutex_unlock(&mtd_table_mutex);
@@ -1230,19 +1232,21 @@ int __get_mtd_device(struct mtd_info *mtd)
        if (!try_module_get(master->owner))
                return -ENODEV;
 
+       kref_get(&mtd->refcnt);
+
        if (master->_get_device) {
                err = master->_get_device(mtd);
 
                if (err) {
+                       kref_put(&mtd->refcnt, mtd_device_release);
                        module_put(master->owner);
                        return err;
                }
        }
 
-       master->usecount++;
-
        while (mtd->parent) {
-               mtd->usecount++;
+               if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd->parent != master)
+                       kref_get(&mtd->parent->refcnt);
                mtd = mtd->parent;
        }
 
@@ -1329,18 +1333,20 @@ void __put_mtd_device(struct mtd_info *mtd)
 {
        struct mtd_info *master = mtd_get_master(mtd);
 
-       while (mtd->parent) {
-               --mtd->usecount;
-               BUG_ON(mtd->usecount < 0);
-               mtd = mtd->parent;
-       }
+       while (mtd != master) {
+               struct mtd_info *parent = mtd->parent;
 
-       master->usecount--;
+               kref_put(&mtd->refcnt, mtd_device_release);
+               mtd = parent;
+       }
 
        if (master->_put_device)
                master->_put_device(master);
 
        module_put(master->owner);
+
+       if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
+               kref_put(&master->refcnt, mtd_device_release);
 }
 EXPORT_SYMBOL_GPL(__put_mtd_device);
 
index b5eefeabf310d7cb3780d8559e0ab040e549f91c..b014861a06a6f28b50e137274ee93cca8a17f303 100644 (file)
@@ -12,6 +12,7 @@ int __must_check add_mtd_device(struct mtd_info *mtd);
 int del_mtd_device(struct mtd_info *mtd);
 int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
 int del_mtd_partitions(struct mtd_info *);
+void release_mtd_partition(struct mtd_info *mtd);
 
 struct mtd_partitions;
 
index a46affbb037d23ba879d583a2cf9d9fd4b13fab6..23483db8f30c90e17f832c9cb7cab7bdb2610ce6 100644 (file)
@@ -32,6 +32,12 @@ static inline void free_partition(struct mtd_info *mtd)
        kfree(mtd);
 }
 
+void release_mtd_partition(struct mtd_info *mtd)
+{
+       WARN_ON(!list_empty(&mtd->part.node));
+       free_partition(mtd);
+}
+
 static struct mtd_info *allocate_partition(struct mtd_info *parent,
                                           const struct mtd_partition *part,
                                           int partno, uint64_t cur_offset)
@@ -309,13 +315,11 @@ static int __mtd_del_partition(struct mtd_info *mtd)
 
        sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs);
 
+       list_del_init(&mtd->part.node);
        err = del_mtd_device(mtd);
        if (err)
                return err;
 
-       list_del(&mtd->part.node);
-       free_partition(mtd);
-
        return 0;
 }
 
@@ -333,6 +337,7 @@ static int __del_mtd_partitions(struct mtd_info *mtd)
                        __del_mtd_partitions(child);
 
                pr_info("Deleting %s MTD partition\n", child->name);
+               list_del_init(&child->part.node);
                ret = del_mtd_device(child);
                if (ret < 0) {
                        pr_err("Error when deleting partition \"%s\" (%d)\n",
@@ -340,9 +345,6 @@ static int __del_mtd_partitions(struct mtd_info *mtd)
                        err = ret;
                        continue;
                }
-
-               list_del(&child->part.node);
-               free_partition(child);
        }
 
        return err;
index 7c58c44662b87e363cea26e045f9b636f6ea503c..914a9f974baaae5010d473d0e5d8c6e79f88e191 100644 (file)
@@ -379,7 +379,7 @@ struct mtd_info {
 
        struct module *owner;
        struct device dev;
-       int usecount;
+       struct kref refcnt;
        struct mtd_debug_info dbg;
        struct nvmem_device *nvmem;
        struct nvmem_device *otp_user_nvmem;