[PATCH v3 2/2] mtd: spi-nor: fix use-after-free on unbind with open handles
sashiko-bot at kernel.org
sashiko-bot at kernel.org
Wed Oct 21 21:18:36 PDT 2026
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] mtd: spi-nor: use-after-free in spi_nor_suspend() and spi_nor_resume()
- [High] mtd: spi-nor: use-after-free of nor->dev in dev_dbg() after unregistration
- [High] mtd: spi-nor: use-after-free of devres-backed nor->mtd.name after unbind
- [High] mtd: spi-nor: missing nor->removed check in spi_nor_get_device() after unbind
--
commit de3094832a517827e88d8d0203973cdc91471e95
Author: Tze Yee Ng <tze.yee.ng at altera.com>
mtd: spi-nor: fix use-after-free on unbind with open handles
This commit moves spi_nor and its associated buffers off devres on the
spi-mem probe path and refcounts struct spi_nor across registration and open
MTD handles. During remove(), it unregisters debugfs and the MTD device, marks
nor->removed under the lock to fence subsequent operations with -ENODEV, and
drops the registration reference.
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 8bc117b46e028..76928bd8413ed 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
[ ... ]
> @@ -3420,20 +3449,48 @@ static void spi_nor_resume(struct mtd_info *mtd)
> dev_err(dev, "resume() failed\n");
> }
[Severity: Critical]
What happens if the system suspends and resumes after spi_nor_remove() while
an open file descriptor still holds a reference on the MTD device?
When a partition (or a master device with CONFIG_MTD_PARTITIONED_MASTER
enabled) is open during spi_nor_remove(), del_mtd_device() drops mtd->refcnt
from 2 to 1 without running mtd_device_release() -> device_unregister(), so
mtd->dev remains registered on mtd_class with mtd_cls_pm_ops active:
mtd_cls_suspend() -> mtd_suspend() -> spi_nor_suspend()
mtd_cls_resume() -> mtd_resume() -> spi_nor_resume()
Meanwhile, once spi_nor_remove() returns, devres_release_all(&spi->dev) frees
nor->spimem, nor->dirmap, nor->params->vreg_offset, and nor->params->priv, and
disables the vcc regulator:
drivers/mtd/spi-nor/core.c:spi_nor_suspend() {
struct spi_nor *nor = mtd_to_spi_nor(mtd);
int ret;
/* Disable octal DTR mode if we enabled it. */
ret = spi_nor_set_octal_dtr(nor, false);
...
}
drivers/mtd/spi-nor/core.c:spi_nor_resume() {
struct spi_nor *nor = mtd_to_spi_nor(mtd);
struct device *dev = nor->dev;
int ret;
/* re-initialize the nor chip */
ret = spi_nor_init(nor);
...
}
Since neither spi_nor_suspend() nor spi_nor_resume() acquires nor->lock or
checks nor->removed, won't a suspend/resume cycle execute SPI operations
through the freed nor->spimem and freed params fields?
> -static int spi_nor_get_device(struct mtd_info *mtd)
> +/* Free spi_nor and its buffers; safe on partial init. */
> +static void spi_nor_free(struct spi_nor *nor)
> +{
> + spi_nor_debugfs_unregister(nor);
> + kfree(nor->mtd.eraseregions);
> + kfree(nor->bouncebuf);
> + kfree(nor->params);
> + kfree(nor);
> +}
> +
> +static void spi_nor_release(struct kref *kref)
> +{
> + spi_nor_free(container_of(kref, struct spi_nor, refcnt));
> +}
> +
> +static struct module *spi_nor_controller_owner(struct spi_nor *nor)
> {
> - struct mtd_info *master = mtd_get_master(mtd);
> - struct spi_nor *nor = mtd_to_spi_nor(master);
> struct device *dev;
>
> + /* The controller may be gone on the last put; use the cached owner. */
> + if (nor->refcounted)
> + return nor->controller_module;
> +
> if (nor->spimem)
> dev = nor->spimem->spi->controller->dev.parent;
> else
> dev = nor->dev;
>
> - if (!try_module_get(dev->driver->owner))
> + return dev->driver->owner;
> +}
> +
> +static int spi_nor_get_device(struct mtd_info *mtd)
> +{
> + struct mtd_info *master = mtd_get_master(mtd);
> + struct spi_nor *nor = mtd_to_spi_nor(master);
> +
> + if (!try_module_get(spi_nor_controller_owner(nor)))
> return -ENODEV;
>
> + if (nor->refcounted)
> + kref_get(&nor->refcnt);
> +
> return 0;
> }
[Severity: High]
Should spi_nor_get_device() fail with -ENODEV once nor->removed is set?
When spi_nor_remove() runs while a handle is open on a partition (or master
with CONFIG_MTD_PARTITIONED_MASTER), mtd_device_unregister() only decrements
mtd->refcnt without calling mtd_device_release(), so the MTD device stays in
mtd_idr until the open handle is closed and mtd_release() runs:
mtdchar_open()
-> get_mtd_device()
-> __get_mtd_device()
-> spi_nor_get_device()
Without checking nor->removed (or using kref_get_unless_zero()), new opens of
/dev/mtdX after unbind will still succeed and take new references on
nor->refcnt, allowing userspace to keep struct spi_nor and the controller
module pinned indefinitely after the original pre-unbind handle is closed.
In addition, in __put_mtd_device(), master->_put_device(master) ->
spi_nor_put_device() frees struct spi_nor immediately when the last handle
closes, whereas idr_remove(&mtd_idr, mtd->index) only runs in mtd_release()
when all device references on mtd->dev drop. If anything holds a temporary
reference on mtd->dev across the final close, could a concurrent
get_mtd_device() still find the mtd in mtd_idr and call spi_nor_get_device()
on a freed struct spi_nor?
[ ... ]
> @@ -3788,12 +3870,15 @@ static int spi_nor_probe(struct spi_mem *spimem)
> if (ret)
> return ret;
>
> - nor = devm_kzalloc(dev, sizeof(*nor), GFP_KERNEL);
> + nor = kzalloc_obj(*nor, GFP_KERNEL);
> if (!nor)
> return -ENOMEM;
>
> nor->spimem = spimem;
> nor->dev = dev;
> + nor->controller_module = spi->controller->dev.parent->driver->owner;
> + nor->refcounted = true;
> + kref_init(&nor->refcnt);
[Severity: High]
Does nor->dev need a get_device() reference tied to nor->refcnt, or need to be
avoided before nor->removed is checked?
With CONFIG_MTD_PARTITIONED_MASTER disabled, opening an unpartitioned master
/dev/mtd0 calls __get_mtd_device(), which increments nor->refcnt via
spi_nor_get_device() but does not increment master->refcnt:
drivers/mtd/mtdcore.c:__get_mtd_device() {
...
while (mtd) {
if (mtd != master)
kref_get(&mtd->refcnt);
mtd = mtd->parent;
}
if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
kref_get(&master->refcnt);
...
}
If the SPI controller is unbound or the SPI device is unregistered while
/dev/mtd0 is open, spi_nor_remove() -> mtd_device_unregister() drops
nor->mtd.refcnt to 0 and unregisters nor->mtd.dev immediately, releasing its
parent reference on &spi->dev. Once spi_unregister_device() finishes, struct
spi_device (nor->dev) is freed while struct spi_nor remains alive for the open
descriptor.
A subsequent read, write, or erase on that open descriptor calls dev_dbg() on
nor->dev before spi_nor_prep_and_lock*() checks nor->removed:
drivers/mtd/spi-nor/core.c:spi_nor_read() {
...
dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);
ret = spi_nor_prep_and_lock_rd(nor, from_lock, len_lock);
...
}
The same pattern appears in spi_nor_write(), spi_nor_erase(), and
sst_nor_write(). Could this dereference a freed nor->dev when debug logging is
enabled?
> spi_nor_set_flash_node(nor, dev->of_node);
>
> spi_mem_set_drvdata(spimem, nor);
[Severity: High]
Right below this in spi_nor_probe(), nor->mtd.name is assigned from
spi_mem_get_name(spimem):
drivers/mtd/spi-nor/core.c:spi_nor_probe() {
...
if (data && data->name)
nor->mtd.name = data->name;
if (!nor->mtd.name)
nor->mtd.name = spi_mem_get_name(spimem);
...
}
In spi_mem_probe(), spimem->name is either dev_name(&spi->dev) or a string
allocated via ctlr->mem_ops->get_name(mem), which drivers such as
spi-nxp-fspi, spi-fsl-qspi, spi-nxp-xspi, spi-aspeed-smc, and
spi-cadence-quadspi allocate with devm_kasprintf().
When spi-nor or the SPI controller is unbound while an open handle keeps
nor->mtd alive in mtd_idr and sysfs, devres_release_all() frees that name
buffer. Won't subsequent reads of /proc/mtd (mtd_proc_show()),
/sys/class/mtd/mtdX/name (mtd_name_show()), or lookups via
get_mtd_device_nm() read freed memory from nor->mtd.name unless the string is
duplicated and freed in spi_nor_free()?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022035352.52129-1-tze.yee.ng@altera.com?part=2
More information about the linux-mtd
mailing list