Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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

authored by

Tomas Winkler and committed by
Miquel Raynal
19bfa9eb 06c2afb8

+49 -40
+39 -33
drivers/mtd/mtdcore.c
··· 93 93 struct mtd_info *mtd = dev_get_drvdata(dev); 94 94 dev_t index = MTD_DEVT(mtd->index); 95 95 96 + if (mtd_is_partition(mtd)) 97 + release_mtd_partition(mtd); 98 + 96 99 /* remove /dev/mtdXro node */ 97 100 device_destroy(&mtd_class, index + 1); 101 + } 102 + 103 + static void mtd_device_release(struct kref *kref) 104 + { 105 + struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt); 106 + 107 + debugfs_remove_recursive(mtd->dbg.dfs_dir); 108 + 109 + /* Try to remove the NVMEM provider */ 110 + nvmem_unregister(mtd->nvmem); 111 + 112 + device_unregister(&mtd->dev); 113 + 114 + /* Clear dev so mtd can be safely re-registered later if desired */ 115 + memset(&mtd->dev, 0, sizeof(mtd->dev)); 116 + 117 + idr_remove(&mtd_idr, mtd->index); 118 + of_node_put(mtd_get_of_node(mtd)); 119 + 120 + module_put(THIS_MODULE); 98 121 } 99 122 100 123 #define MTD_DEVICE_ATTR_RO(name) \ ··· 689 666 } 690 667 691 668 mtd->index = i; 692 - mtd->usecount = 0; 669 + kref_init(&mtd->refcnt); 693 670 694 671 /* default value if not set by driver */ 695 672 if (mtd->bitflip_threshold == 0) ··· 802 779 { 803 780 int ret; 804 781 struct mtd_notifier *not; 805 - struct device_node *mtd_of_node; 806 782 807 783 mutex_lock(&mtd_table_mutex); 808 784 ··· 815 793 list_for_each_entry(not, &mtd_notifiers, list) 816 794 not->remove(mtd); 817 795 818 - if (mtd->usecount) { 819 - printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n", 820 - mtd->index, mtd->name, mtd->usecount); 821 - ret = -EBUSY; 822 - } else { 823 - mtd_of_node = mtd_get_of_node(mtd); 824 - debugfs_remove_recursive(mtd->dbg.dfs_dir); 825 - 826 - /* Try to remove the NVMEM provider */ 827 - nvmem_unregister(mtd->nvmem); 828 - 829 - device_unregister(&mtd->dev); 830 - 831 - /* Clear dev so mtd can be safely re-registered later if desired */ 832 - memset(&mtd->dev, 0, sizeof(mtd->dev)); 833 - 834 - idr_remove(&mtd_idr, mtd->index); 835 - of_node_put(mtd_of_node); 836 - 837 - module_put(THIS_MODULE); 838 - ret = 0; 839 - } 796 + kref_put(&mtd->refcnt, mtd_device_release); 797 + ret = 0; 840 798 841 799 out_error: 842 800 mutex_unlock(&mtd_table_mutex); ··· 1232 1230 if (!try_module_get(master->owner)) 1233 1231 return -ENODEV; 1234 1232 1233 + kref_get(&mtd->refcnt); 1234 + 1235 1235 if (master->_get_device) { 1236 1236 err = master->_get_device(mtd); 1237 1237 1238 1238 if (err) { 1239 + kref_put(&mtd->refcnt, mtd_device_release); 1239 1240 module_put(master->owner); 1240 1241 return err; 1241 1242 } 1242 1243 } 1243 1244 1244 - master->usecount++; 1245 - 1246 1245 while (mtd->parent) { 1247 - mtd->usecount++; 1246 + if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd->parent != master) 1247 + kref_get(&mtd->parent->refcnt); 1248 1248 mtd = mtd->parent; 1249 1249 } 1250 1250 ··· 1333 1329 { 1334 1330 struct mtd_info *master = mtd_get_master(mtd); 1335 1331 1336 - while (mtd->parent) { 1337 - --mtd->usecount; 1338 - BUG_ON(mtd->usecount < 0); 1339 - mtd = mtd->parent; 1340 - } 1332 + while (mtd != master) { 1333 + struct mtd_info *parent = mtd->parent; 1341 1334 1342 - master->usecount--; 1335 + kref_put(&mtd->refcnt, mtd_device_release); 1336 + mtd = parent; 1337 + } 1343 1338 1344 1339 if (master->_put_device) 1345 1340 master->_put_device(master); 1346 1341 1347 1342 module_put(master->owner); 1343 + 1344 + if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) 1345 + kref_put(&master->refcnt, mtd_device_release); 1348 1346 } 1349 1347 EXPORT_SYMBOL_GPL(__put_mtd_device); 1350 1348
+1
drivers/mtd/mtdcore.h
··· 12 12 int del_mtd_device(struct mtd_info *mtd); 13 13 int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int); 14 14 int del_mtd_partitions(struct mtd_info *); 15 + void release_mtd_partition(struct mtd_info *mtd); 15 16 16 17 struct mtd_partitions; 17 18
+8 -6
drivers/mtd/mtdpart.c
··· 32 32 kfree(mtd); 33 33 } 34 34 35 + void release_mtd_partition(struct mtd_info *mtd) 36 + { 37 + WARN_ON(!list_empty(&mtd->part.node)); 38 + free_partition(mtd); 39 + } 40 + 35 41 static struct mtd_info *allocate_partition(struct mtd_info *parent, 36 42 const struct mtd_partition *part, 37 43 int partno, uint64_t cur_offset) ··· 315 309 316 310 sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs); 317 311 312 + list_del_init(&mtd->part.node); 318 313 err = del_mtd_device(mtd); 319 314 if (err) 320 315 return err; 321 - 322 - list_del(&mtd->part.node); 323 - free_partition(mtd); 324 316 325 317 return 0; 326 318 } ··· 337 333 __del_mtd_partitions(child); 338 334 339 335 pr_info("Deleting %s MTD partition\n", child->name); 336 + list_del_init(&child->part.node); 340 337 ret = del_mtd_device(child); 341 338 if (ret < 0) { 342 339 pr_err("Error when deleting partition \"%s\" (%d)\n", ··· 345 340 err = ret; 346 341 continue; 347 342 } 348 - 349 - list_del(&child->part.node); 350 - free_partition(child); 351 343 } 352 344 353 345 return err;
+1 -1
include/linux/mtd/mtd.h
··· 379 379 380 380 struct module *owner; 381 381 struct device dev; 382 - int usecount; 382 + struct kref refcnt; 383 383 struct mtd_debug_info dbg; 384 384 struct nvmem_device *nvmem; 385 385 struct nvmem_device *otp_user_nvmem;