Skip to content

Commit 9d4af74

Browse files
XueLei1988miquelraynal
authored andcommitted
mtd: fix double free and WARN_ON in add_mtd_device() error paths
When device_register() or mtd_nvmem_add() fails inside add_mtd_device() for a partition, the error handling triggers mtd_release() via put_device() or device_unregister(). mtd_release() calls release_mtd_partition() which frees the mtd_info structure. However, callers such as mtd_add_partition() and add_mtd_partitions() also call free_partition() in their error paths, resulting in a double free. Additionally, release_mtd_partition() hits WARN_ON(!list_empty( &mtd->part.node)) because the partition node is still linked in the parent's partitions list when the release callback fires from the add_mtd_device() error path. Fix this by overriding dev->type and dev->release before put_device() in the error paths, so that device_release() invokes a no-op function instead of mtd_release(). For the mtd_nvmem_add() failure case, device_unregister() is replaced with device_del() to separate the device removal from the final kobject reference drop, allowing the override to take effect before put_device() is called. The callers' error paths (list_del + free_partition) remain the sole owners of mtd_info lifetime on add_mtd_device() failure, which is the expected contract. The normal partition teardown path is not affected: del_mtd_device() goes through kref_put() -> mtd_device_release() -> device_unregister() with dev->type still set to &mtd_devtype, so mtd_release() -> release_mtd_partition() continues to work correctly for the regular removal case. Reported-by: syzbot+e9c76b56dc05023b8117@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e9c76b56dc05023b8117 Fixes: 19bfa9e ("mtd: use refcount to prevent corruption") Signed-off-by: Xue Lei <Xue.Lei@windriver.com> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
1 parent caa0ecb commit 9d4af74

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

drivers/mtd/mtdcore.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ static void mtd_release(struct device *dev)
105105
device_destroy(&mtd_class, index + 1);
106106
}
107107

108+
/*
109+
* No-op device release used in add_mtd_device() error paths.
110+
* Prevents mtd_release() from being called via device_release(),
111+
* which would free the mtd_info that the caller still manages.
112+
*/
113+
static void mtd_dev_release_nop(struct device *dev)
114+
{
115+
}
116+
108117
static void mtd_device_release(struct kref *kref)
109118
{
110119
struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
@@ -799,10 +808,8 @@ int add_mtd_device(struct mtd_info *mtd)
799808
mtd_check_of_node(mtd);
800809
of_node_get(mtd_get_of_node(mtd));
801810
error = device_register(&mtd->dev);
802-
if (error) {
803-
put_device(&mtd->dev);
811+
if (error)
804812
goto fail_added;
805-
}
806813

807814
/* Add the nvmem provider */
808815
error = mtd_nvmem_add(mtd);
@@ -840,8 +847,16 @@ int add_mtd_device(struct mtd_info *mtd)
840847
return 0;
841848

842849
fail_nvmem_add:
843-
device_unregister(&mtd->dev);
850+
device_del(&mtd->dev);
844851
fail_added:
852+
/*
853+
* Clear type and set nop release to prevent mtd_release() ->
854+
* release_mtd_partition() -> free_partition() from freeing mtd.
855+
* The caller handles cleanup on failure.
856+
*/
857+
mtd->dev.type = NULL;
858+
mtd->dev.release = mtd_dev_release_nop;
859+
put_device(&mtd->dev);
845860
of_node_put(mtd_get_of_node(mtd));
846861
fail_devname:
847862
idr_remove(&mtd_idr, i);

0 commit comments

Comments
 (0)