[PATCH] mtd: use refcount to prevent corruption

Tomas Winkler
2021-01-27T20:04:59Z

When underlying device is removed mtd core will crash
in case user space is still holding an open handle to a mtd device node.
A proper refcounting is needed so device is release only when a
partition has no active users. The current simple counter is not
sufficient.

Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
---
drivers/mtd/mtdcore.c | 55 ++++++++++++++++++++++-------------------
drivers/mtd/mtdcore.h | 1 +
drivers/mtd/mtdpart.c | 12 ++++-----
include/linux/mtd/mtd.h | 2 +-
4 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 2d6423d89a17..db5167eacaa4 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -93,9 +93,29 @@ static void mtd_release(struct device *dev)
dev_t index = MTD_DEVT(mtd->index);

/* remove /dev/mtdXro node */
+ if (mtd_is_partition(mtd))
+ release_mtd_partition(mtd);
+
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);
+
+ if (mtd->nvmem) {
+ nvmem_unregister(mtd->nvmem);
+ mtd->nvmem = NULL;
+ }
+
+ idr_remove(&mtd_idr, mtd->index);
+ of_node_put(mtd_get_of_node(mtd));
+
+ device_unregister(&mtd->dev);
+
+ module_put(THIS_MODULE);
+}
+
static ssize_t mtd_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -619,7 +639,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)
@@ -733,23 +753,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 {
- /* Try to remove the NVMEM provider */
- if (mtd->nvmem)
- nvmem_unregister(mtd->nvmem);
-
- device_unregister(&mtd->dev);
-
- idr_remove(&mtd_idr, mtd->index);
- of_node_put(mtd_get_of_node(mtd));
-
- module_put(THIS_MODULE);
- ret = 0;
- }
+ kref_put(&mtd->refcnt, mtd_device_release);
+ ret = 0;

out_error:
mutex_unlock(&mtd_table_mutex);
@@ -984,20 +989,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++;
mtd = mtd->parent;
+ kref_get(&mtd->refcnt);
}

return 0;
@@ -1055,14 +1061,13 @@ void __put_mtd_device(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);

+ kref_put(&mtd->refcnt, mtd_device_release);
+
while (mtd->parent) {
- --mtd->usecount;
- BUG_ON(mtd->usecount < 0);
mtd = mtd->parent;
+ kref_put(&mtd->refcnt, mtd_device_release);
}

- master->usecount--;
-
if (master->_put_device)
master->_put_device(master);

diff --git a/drivers/mtd/mtdcore.h b/drivers/mtd/mtdcore.h
index b5eefeabf310..b014861a06a6 100644
--- a/drivers/mtd/mtdcore.h
+++ b/drivers/mtd/mtdcore.h
@@ -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;

diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index 12ca4f19cb14..8175f6d9c790 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -31,6 +31,12 @@ static inline void free_partition(struct mtd_info *mtd)
kfree(mtd);
}

+void release_mtd_partition(struct mtd_info *mtd)
+{
+ list_del_init(&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)
@@ -313,9 +319,6 @@ static int __mtd_del_partition(struct mtd_info *mtd)
if (err)
return err;

- list_del(&child->part.node);
- free_partition(mtd);
-
return 0;
}

@@ -341,9 +344,6 @@ static int __del_mtd_partitions(struct mtd_info *mtd)
err = ret;
continue;
}
-
- list_del(&child->part.node);
- free_partition(child);
}

return err;
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 157357ec1441..1217c9d8d69d 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -373,7 +373,7 @@ struct mtd_info {

struct module *owner;
struct device dev;
- int usecount;
+ struct kref refcnt;
struct mtd_debug_info dbg;
struct nvmem_device *nvmem;

--
2.26.2