mtd: use refcount to prevent corruption

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
This commit is contained in:
Tomas Winkler 2023-06-20 16:19:04 +03:00 committed by Miquel Raynal
parent 06c2afb862
commit 19bfa9ebeb
4 changed files with 49 additions and 40 deletions

View File

@ -93,10 +93,33 @@ static void mtd_release(struct device *dev)
struct mtd_info *mtd = dev_get_drvdata(dev); struct mtd_info *mtd = dev_get_drvdata(dev);
dev_t index = MTD_DEVT(mtd->index); dev_t index = MTD_DEVT(mtd->index);
if (mtd_is_partition(mtd))
release_mtd_partition(mtd);
/* remove /dev/mtdXro node */ /* remove /dev/mtdXro node */
device_destroy(&mtd_class, index + 1); 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) \ #define MTD_DEVICE_ATTR_RO(name) \
static DEVICE_ATTR(name, 0444, mtd_##name##_show, NULL) 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->index = i;
mtd->usecount = 0; kref_init(&mtd->refcnt);
/* default value if not set by driver */ /* default value if not set by driver */
if (mtd->bitflip_threshold == 0) if (mtd->bitflip_threshold == 0)
@ -779,7 +802,6 @@ int del_mtd_device(struct mtd_info *mtd)
{ {
int ret; int ret;
struct mtd_notifier *not; struct mtd_notifier *not;
struct device_node *mtd_of_node;
mutex_lock(&mtd_table_mutex); 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) list_for_each_entry(not, &mtd_notifiers, list)
not->remove(mtd); not->remove(mtd);
if (mtd->usecount) { kref_put(&mtd->refcnt, mtd_device_release);
printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n", ret = 0;
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;
}
out_error: out_error:
mutex_unlock(&mtd_table_mutex); mutex_unlock(&mtd_table_mutex);
@ -1230,19 +1232,21 @@ int __get_mtd_device(struct mtd_info *mtd)
if (!try_module_get(master->owner)) if (!try_module_get(master->owner))
return -ENODEV; return -ENODEV;
kref_get(&mtd->refcnt);
if (master->_get_device) { if (master->_get_device) {
err = master->_get_device(mtd); err = master->_get_device(mtd);
if (err) { if (err) {
kref_put(&mtd->refcnt, mtd_device_release);
module_put(master->owner); module_put(master->owner);
return err; return err;
} }
} }
master->usecount++;
while (mtd->parent) { while (mtd->parent) {
mtd->usecount++; if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd->parent != master)
kref_get(&mtd->parent->refcnt);
mtd = mtd->parent; mtd = mtd->parent;
} }
@ -1329,18 +1333,20 @@ void __put_mtd_device(struct mtd_info *mtd)
{ {
struct mtd_info *master = mtd_get_master(mtd); struct mtd_info *master = mtd_get_master(mtd);
while (mtd->parent) { while (mtd != master) {
--mtd->usecount; struct mtd_info *parent = mtd->parent;
BUG_ON(mtd->usecount < 0);
mtd = mtd->parent;
}
master->usecount--; kref_put(&mtd->refcnt, mtd_device_release);
mtd = parent;
}
if (master->_put_device) if (master->_put_device)
master->_put_device(master); master->_put_device(master);
module_put(master->owner); module_put(master->owner);
if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
kref_put(&master->refcnt, mtd_device_release);
} }
EXPORT_SYMBOL_GPL(__put_mtd_device); EXPORT_SYMBOL_GPL(__put_mtd_device);

View File

@ -12,6 +12,7 @@ int __must_check add_mtd_device(struct mtd_info *mtd);
int del_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 add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
int del_mtd_partitions(struct mtd_info *); int del_mtd_partitions(struct mtd_info *);
void release_mtd_partition(struct mtd_info *mtd);
struct mtd_partitions; struct mtd_partitions;

View File

@ -32,6 +32,12 @@ static inline void free_partition(struct mtd_info *mtd)
kfree(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, static struct mtd_info *allocate_partition(struct mtd_info *parent,
const struct mtd_partition *part, const struct mtd_partition *part,
int partno, uint64_t cur_offset) 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); sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs);
list_del_init(&mtd->part.node);
err = del_mtd_device(mtd); err = del_mtd_device(mtd);
if (err) if (err)
return err; return err;
list_del(&mtd->part.node);
free_partition(mtd);
return 0; return 0;
} }
@ -333,6 +337,7 @@ static int __del_mtd_partitions(struct mtd_info *mtd)
__del_mtd_partitions(child); __del_mtd_partitions(child);
pr_info("Deleting %s MTD partition\n", child->name); pr_info("Deleting %s MTD partition\n", child->name);
list_del_init(&child->part.node);
ret = del_mtd_device(child); ret = del_mtd_device(child);
if (ret < 0) { if (ret < 0) {
pr_err("Error when deleting partition \"%s\" (%d)\n", pr_err("Error when deleting partition \"%s\" (%d)\n",
@ -340,9 +345,6 @@ static int __del_mtd_partitions(struct mtd_info *mtd)
err = ret; err = ret;
continue; continue;
} }
list_del(&child->part.node);
free_partition(child);
} }
return err; return err;

View File

@ -379,7 +379,7 @@ struct mtd_info {
struct module *owner; struct module *owner;
struct device dev; struct device dev;
int usecount; struct kref refcnt;
struct mtd_debug_info dbg; struct mtd_debug_info dbg;
struct nvmem_device *nvmem; struct nvmem_device *nvmem;
struct nvmem_device *otp_user_nvmem; struct nvmem_device *otp_user_nvmem;