mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 03:27:30 -04:00
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: 19bfa9ebeb ("mtd: use refcount to prevent corruption")
Signed-off-by: Xue Lei <Xue.Lei@windriver.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
@@ -105,6 +105,15 @@ static void mtd_release(struct device *dev)
|
||||
device_destroy(&mtd_class, index + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* No-op device release used in add_mtd_device() error paths.
|
||||
* Prevents mtd_release() from being called via device_release(),
|
||||
* which would free the mtd_info that the caller still manages.
|
||||
*/
|
||||
static void mtd_dev_release_nop(struct device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static void mtd_device_release(struct kref *kref)
|
||||
{
|
||||
struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
|
||||
@@ -799,10 +808,8 @@ int add_mtd_device(struct mtd_info *mtd)
|
||||
mtd_check_of_node(mtd);
|
||||
of_node_get(mtd_get_of_node(mtd));
|
||||
error = device_register(&mtd->dev);
|
||||
if (error) {
|
||||
put_device(&mtd->dev);
|
||||
if (error)
|
||||
goto fail_added;
|
||||
}
|
||||
|
||||
/* Add the nvmem provider */
|
||||
error = mtd_nvmem_add(mtd);
|
||||
@@ -840,8 +847,16 @@ int add_mtd_device(struct mtd_info *mtd)
|
||||
return 0;
|
||||
|
||||
fail_nvmem_add:
|
||||
device_unregister(&mtd->dev);
|
||||
device_del(&mtd->dev);
|
||||
fail_added:
|
||||
/*
|
||||
* Clear type and set nop release to prevent mtd_release() ->
|
||||
* release_mtd_partition() -> free_partition() from freeing mtd.
|
||||
* The caller handles cleanup on failure.
|
||||
*/
|
||||
mtd->dev.type = NULL;
|
||||
mtd->dev.release = mtd_dev_release_nop;
|
||||
put_device(&mtd->dev);
|
||||
of_node_put(mtd_get_of_node(mtd));
|
||||
fail_devname:
|
||||
idr_remove(&mtd_idr, i);
|
||||
|
||||
Reference in New Issue
Block a user