[PATCH v2 1/2] mtd: core: add release hook and removed flag for unbind with open handles

tze.yee.ng at altera.com tze.yee.ng at altera.com
Fri Oct 9 00:35:54 PDT 2026


From: Tze Yee Ng <tze.yee.ng at altera.com>

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 at altera.com>
---
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




More information about the linux-mtd mailing list