[PATCH v2 1/2] mtd: core: add release hook and removed flag for unbind with open handles
From: tze . yee . ng
Date: Fri Oct 09 2026 - 03:40:29 EST
From: Tze Yee Ng <tze.yee.ng@xxxxxxxxxx>
An open /dev/mtdX can outlive unbind. refcnt defers device teardown, but
the driver-owned object embedding mtd_info (and its bus resources) may
already be gone when remove() returns — UAF for parent-devm allocations.
Add optional core hooks:
- mtd->_free(): called from kref release so the backing object lives until
the last reference drops
- mtd->removed: set after draining ops; core returns -ENODEV for hw ops,
new openers, and suspend/resume
Signed-off-by: Tze Yee Ng <tze.yee.ng@xxxxxxxxxx>
---
Changes in v2:
- New.
- Drop the spi-nor-local kref and nor->removed.
- Add mtd->_free and mtd->removed in the core so the backing
object outlives unbind and later ops return -ENODEV.
---
drivers/mtd/mtdcore.c | 20 ++++++++++++++++++++
include/linux/mtd/mtd.h | 13 +++++++++++++
2 files changed, 33 insertions(+)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 491a27d0315f..59133894b5e5 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -118,6 +118,7 @@ static void mtd_device_release(struct kref *kref)
{
struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
bool is_partition = mtd_is_partition(mtd);
+ void (*mtd_free)(struct mtd_info *mtd) = mtd->_free;
debugfs_remove_recursive(mtd->dbg.dfs_dir);
@@ -135,6 +136,9 @@ static void mtd_device_release(struct kref *kref)
memset(&mtd->dev, 0, sizeof(mtd->dev));
module_put(THIS_MODULE);
+
+ if (mtd_free)
+ mtd_free(mtd);
}
#define MTD_DEVICE_ATTR_RO(name) \
@@ -1332,6 +1336,9 @@ int __get_mtd_device(struct mtd_info *mtd)
struct mtd_info *master = mtd_get_master(mtd);
int err;
+ if (master->removed)
+ return -ENODEV;
+
if (master->_get_device) {
err = master->_get_device(master);
if (err)
@@ -1471,6 +1478,9 @@ int mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
adjinstr = *instr;
+ if (master->removed)
+ return -ENODEV;
+
if (!mtd->erasesize || !master->_erase)
return -ENOTSUPP;
@@ -1792,6 +1802,9 @@ int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
ops->retlen = ops->oobretlen = 0;
+ if (master->removed)
+ return -ENODEV;
+
ret_code = mtd_check_oob_ops(mtd, from, ops);
if (ret_code)
return ret_code;
@@ -1836,6 +1849,9 @@ int mtd_write_oob(struct mtd_info *mtd, loff_t to,
ops->retlen = ops->oobretlen = 0;
+ if (master->removed)
+ return -ENODEV;
+
if (!(mtd->flags & MTD_WRITEABLE))
return -EROFS;
@@ -2331,6 +2347,8 @@ int mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
struct mtd_info *master = mtd_get_master(mtd);
+ if (master->removed)
+ return -ENODEV;
if (!master->_lock)
return -EOPNOTSUPP;
if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs)
@@ -2351,6 +2369,8 @@ int mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
struct mtd_info *master = mtd_get_master(mtd);
+ if (master->removed)
+ return -ENODEV;
if (!master->_unlock)
return -EOPNOTSUPP;
if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs)
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 8d10d9d2e830..28fd98c4193c 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -362,6 +362,9 @@ struct mtd_info {
int (*_get_device) (struct mtd_info *mtd);
void (*_put_device) (struct mtd_info *mtd);
+ /* Optional free of the embedding object; called from kref release. */
+ void (*_free) (struct mtd_info *mtd);
+
/*
* flag indicates a panic write, low level drivers can take appropriate
* action if required to ensure writes go through
@@ -380,6 +383,10 @@ struct mtd_info {
struct module *owner;
struct device dev;
struct kref refcnt;
+
+ /* Set on unbind while refs held; core returns -ENODEV for hw ops. */
+ bool removed;
+
struct mtd_debug_info dbg;
struct nvmem_device *nvmem;
struct nvmem_device *otp_user_nvmem;
@@ -548,6 +555,9 @@ static inline int mtd_suspend(struct mtd_info *mtd)
struct mtd_info *master = mtd_get_master(mtd);
int ret;
+ if (master->removed)
+ return 0;
+
if (master->master.suspended)
return 0;
@@ -563,6 +573,9 @@ static inline void mtd_resume(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);
+ if (master->removed)
+ return;
+
if (!master->master.suspended)
return;
--
2.43.7