All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yuqiang Sun" <izaiahsun@gmail.com>,
	miquel.raynal@bootlin.com, richard@nod.at, vigneshr@ti.com
Cc: amit.kumar-mahapatra@amd.com, luca.ceresoli@bootlin.com,
	harshit.m.mogalapalli@oracle.com, linux-mtd@lists.infradead.org
Subject: Re: [PATCH v2] mtd: virt-concat: Tear the concat down when its master goes away
Date: Tue, 03 Nov 2026 08:29:23 +0000	[thread overview]
Message-ID: <sashiko-outbox-194829@kernel.org> (raw)
In-Reply-To: <20261103080327.90193-1-izaiahsun@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] mtd: virt-concat: use-after-free in mtd_virt_concat_destroy() on 2nd unbind
- [Medium] mtd: virt-concat: partition leak in mtd_virt_concat_destroy_joins()
--

commit 9b28247f2646ce546494e78445d5bc76ddee956a
Author: Yuqiang Sun <izaiahsun@gmail.com>

mtd: virt-concat: Tear the concat down when its master goes away

This patch updates mtd_virt_concat_destroy() to tear down concatenated MTD
devices by matching the master of their component partitions, keeping each
partition in the slot of its DT node so the concatenation can be re-created
if a master is rebound. It also registers the mtd_info returned by
mtd_concat_create() directly and reference-counts the concatenated device via
struct mtd_virt_concat_dev so departing partitions are freed when the last
user closes the device.

> diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
> index 40b300021ddaa..64b0dd434ee24 100644
> --- a/drivers/mtd/mtd_virt_concat.c
> +++ b/drivers/mtd/mtd_virt_concat.c
[ ... ]
> +static struct mtd_virt_concat_dev *
> +mtd_virt_concat_unregister(struct mtd_virt_concat_node *item)
> +{
> +	struct mtd_virt_concat_dev *vdev = item->vdev;
> +
> +	if (vdev) {
> +		item->vdev = NULL;
> +		del_mtd_device(vdev->mtd);
> +	}
> +	return vdev;
>  }
>  
>  void mtd_virt_concat_destroy_joins(void)
>  {
> -	struct mtd_virt_concat_node *item, *tmp;
> -	struct mtd_info *mtd;
> +	struct mtd_virt_concat_node *item;
> +	struct mtd_virt_concat_dev *vdev;
>  
> -	list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
> -		mtd = &item->concat->mtd;
> -		if (item->concat) {
> -			mtd_device_unregister(mtd);
> -			kfree(mtd->name);
> -			mtd_virt_concat_put_mtd_devices(item->concat);
> -			mtd_concat_destroy(mtd);
> -		}
> +	list_for_each_entry(item, &concat_node_list, head) {
> +		vdev = mtd_virt_concat_unregister(item);
> +		if (vdev)
> +			kref_put(&vdev->refs, mtd_virt_concat_release);
> +		kfree(item->concat);
> +		item->concat = NULL;
>  	}
>  }

[Severity: Medium]
Does mtd_virt_concat_destroy_joins() leak the struct mtd_info partition
objects and their name strings still stored in item->concat->subdev[]?

In mtd_device_parse_register(), add_mtd_partitions() passes concat partitions
to mtd_virt_concat_add(), which stores them in item->concat->subdev[idx]. If
a subsequent partition in add_mtd_partitions() fails or if
mtd_virt_concat_create_join() fails, the error path in
mtd_device_parse_register() calls del_mtd_device(mtd) directly without
calling mtd_virt_concat_destroy(mtd):

drivers/mtd/mtdcore.c:mtd_device_parse_register() {
	...
	if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
		ret = mtd_virt_concat_create_join();
		if (ret < 0)
			goto out;
	}
	...
out:
	...
	if (ret && device_is_registered(&mtd->dev)) {
		err = del_mtd_device(mtd);
		if (err)
			pr_err("Error when deleting MTD device (%d)\n", err);
	}
}

For drivers that do not call mtd_device_unregister() when
mtd_device_parse_register() fails (such as spi_nor_probe() or
brcmnand_init_cs()), those partitions remain in item->concat->subdev[idx].
When cleanup_mtd() later calls mtd_virt_concat_destroy_joins() on module
unload, kfree(item->concat) frees the container without freeing the
partitions in item->concat->subdev[], and mtd_virt_concat_release() only
frees partitions that were moved into vdev->gone[].

>  /**
> - * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object
> + * mtd_virt_concat_destroy - Destroy the concats that include partitions of
> + *                           an MTD device
>   * @mtd:        pointer to 'mtd_info'
>   *
> - * Return: 0 on success, -error otherwise.
> + * Called when the master of @mtd is unregistered. Partitions of that master
> + * that are part of a concatenation were never registered on their own, so
> + * unregister every concat that includes one of them, and free them once that
> + * concat is no longer in use. The other parts stay with the concat item,
> + * which is joined again once all its parts are back.
> + *
> + * Return: 0.
>   */
>  int mtd_virt_concat_destroy(struct mtd_info *mtd)
>  {
> -	struct mtd_info *child, *master = mtd_get_master(mtd);
> -	struct mtd_virt_concat_node *item, *tmp;
> +	struct mtd_info *part, *master = mtd_get_master(mtd);
> +	struct mtd_virt_concat_node *item;
> +	struct mtd_virt_concat_dev *vdev;
>  	struct mtd_concat *concat;
> -	int idx, ret = 0;
> -	bool is_mtd_found;
> -
> -	list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
> -		is_mtd_found = false;
> +	int idx;
>  
> -		/* Find the concat item that hold the mtd device */
> -		for (idx = 0; idx < item->count; idx++) {
> -			if (item->nodes[idx] == mtd->dev.of_node) {
> -				is_mtd_found = true;
> -				break;
> -			}
> -		}
> -		if (!is_mtd_found)
> -			continue;
> +	list_for_each_entry(item, &concat_node_list, head) {
>  		concat = item->concat;
> +		vdev = NULL;
>  
> -		/*
> -		 * Since this concatenated device is being removed, retrieve
> -		 * all MTD devices that are part of it and register them
> -		 * individually.
> -		 */
> -		for (idx = 0; idx < concat->num_subdev; idx++) {
> -			child = concat->subdev[idx];
> -			if (child->dev.of_node != mtd->dev.of_node) {
> -				ret = add_mtd_device(child);
> -				if (ret)
> -					goto out;
> -			}
> -		}
> -		/* Destroy the concat */
> -		if (concat->mtd.name) {
> -			del_mtd_device(&concat->mtd);
> -			kfree(concat->mtd.name);
> -			mtd_virt_concat_put_mtd_devices(item->concat);
> -			mtd_concat_destroy(&concat->mtd);
> +		for (idx = 0; idx < item->count; idx++) {
> +			part = concat->subdev[idx];
> +			if (!part || mtd_get_master(part) != master)
> +				continue;
> +
> +			if (!vdev)
> +				vdev = mtd_virt_concat_unregister(item);
> +			concat->subdev[idx] = NULL;
> +			concat->num_subdev--;
> +			if (vdev)
> +				vdev->gone[vdev->num_gone++] = part;
> +			else
> +				mtd_virt_concat_free_part(part);
>  		}
> -
> -		list_del(&item->head);
> -		for (idx = 0; idx < item->count; idx++)
> -			of_node_put(item->nodes[idx]);
> -
> -		kfree(item);
> +		if (vdev)
> +			kref_put(&vdev->refs, mtd_virt_concat_release);
>  	}
> -	return 0;
> -out:
> -	mutex_lock(&master->master.partitions_lock);
> -	list_del(&child->part.node);
> -	mutex_unlock(&master->master.partitions_lock);
> -	kfree(mtd->name);
> -	kfree(mtd);
>  
> -	return ret;
> +	return 0;
>  }

[Severity: Critical]
As reported by Sashiko on v1 [1], can mtd_virt_concat_destroy() free a
partition while an open concatenated device still holds a pointer to it in
CONCAT(vdev->mtd)->subdev[]?

Consider a concatenated device vdev1 joining part0 on master0 and part1 on
master1 while a user holds vdev1->mtd open (so vdev1->refs is 2).

When master0 is unregistered first, mtd_virt_concat_destroy(master0) calls
mtd_virt_concat_unregister(item), which clears item->vdev = NULL and unregisters
vdev1->mtd. It places part0 into vdev1->gone[0] and drops the registration
reference on vdev1->refs from 2 to 1. Because vdev1->mtd is still open, vdev1
remains alive and CONCAT(vdev1->mtd)->subdev[1] still points to part1.

If master1 is then unregistered while vdev1->mtd is still held open,
mtd_virt_concat_destroy(master1) calls mtd_virt_concat_unregister(item), which
sees item->vdev == NULL and returns NULL. As a result,
mtd_virt_concat_destroy(master1) takes the else branch and immediately calls
mtd_virt_concat_free_part(part1).

Similarly, if master0 is bound again while vdev1->mtd is still open,
mtd_virt_concat_create_join() creates a new vdev2 that shares part1 in
concat->subdev[1] with vdev1 without per-partition reference counting. If
master1 is then unregistered while vdev2 is not open, part1 is placed into
vdev2->gone[] and immediately freed by mtd_virt_concat_release() when vdev2 is
put.

In either case, when the user later accesses or closes vdev1->mtd (for
example, mtdchar_close() -> mtd_sync() -> concat_sync() -> mtd_sync(subdev[1])),
won't it dereference the freed part1 structure?

[1] https://lore.kernel.org/r/sashiko-outbox-193135@kernel.org

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103080327.90193-1-izaiahsun@gmail.com?part=1

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

      reply	other threads:[~2026-11-03  8:29 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03  8:03 [PATCH v2] mtd: virt-concat: Tear the concat down when its master goes away Yuqiang Sun
2026-11-03  8:29 ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-194829@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=amit.kumar-mahapatra@amd.com \
    --cc=harshit.m.mogalapalli@oracle.com \
    --cc=izaiahsun@gmail.com \
    --cc=linux-mtd@lists.infradead.org \
    --cc=luca.ceresoli@bootlin.com \
    --cc=miquel.raynal@bootlin.com \
    --cc=richard@nod.at \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.