[PATCH 1/4] mtd: ubi: block: don't return on error when removing

Daniel Golle daniel at makrotopia.org
Wed May 24 02:41:24 PDT 2023


On Wed, May 03, 2023 at 09:09:49PM +0800, Zhihao Cheng wrote:
> 在 2023/5/3 0:48, Daniel Golle 写道:
> > There is no point on returning the error from ubiblock_remove in case
> > it is being called due to a volume removal event -- the volume is gone,
> > we should destroy and remove the ubiblock device no matter what.
> > 
> > Introduce a new boolean parameter 'force' to tell ubiblock_remove to go
> > on even in case the ubiblock device is still busy. Use that new option
> > when calling ubiblock_remove due to a UBI_VOLUME_REMOVED event.
> > 
> > Signed-off-by: Daniel Golle <daniel at makrotopia.org>
> > ---
> >   drivers/mtd/ubi/block.c | 6 +++---
> >   drivers/mtd/ubi/cdev.c  | 2 +-
> >   drivers/mtd/ubi/ubi.h   | 4 ++--
> >   3 files changed, 6 insertions(+), 6 deletions(-)
> > 
> > diff --git a/drivers/mtd/ubi/block.c b/drivers/mtd/ubi/block.c
> > index 3711d7f746003..6f5804f4b8f55 100644
> > --- a/drivers/mtd/ubi/block.c
> > +++ b/drivers/mtd/ubi/block.c
> > @@ -457,7 +457,7 @@ static void ubiblock_cleanup(struct ubiblock *dev)
> >   	idr_remove(&ubiblock_minor_idr, dev->gd->first_minor);
> >   }
> > -int ubiblock_remove(struct ubi_volume_info *vi)
> > +int ubiblock_remove(struct ubi_volume_info *vi, bool force)
> >   {
> >   	struct ubiblock *dev;
> >   	int ret;
> > @@ -471,7 +471,7 @@ int ubiblock_remove(struct ubi_volume_info *vi)
> >   	/* Found a device, let's lock it so we can check if it's busy */
> >   	mutex_lock(&dev->dev_mutex);
> > -	if (dev->refcnt > 0) {
> > +	if (dev->refcnt > 0 && !force) {
> >   		ret = -EBUSY;
> >   		goto out_unlock_dev;
> >   	}
> 
> After looking through this series, I think we should pay attention to one
> problem: The lifetime of mtd device and ubi things(ubi device/volume/block
> device). It's difficult to decide whether or not to destroy ubi things when
> mtd driver is removed.
> If we destroy ubi things, one application may have opened an ubi volume
> early, then ubi device and all its volumes are destroyed by
> ubi_notify_remove(), later volume accessing by the application will trigger
> an UAF problem in kernel.
>       App              driver_remove
> fd = ubi_open_volume
>                    ubi_notify_remove
>                     ubi_detach_mtd_dev
>                      vfree(ubi->vtbl)
> ioctl(fd, UBI_IOCVOLUP)
>  ubi_start_update
>   set_update_marker
>    vtbl_rec = ubi->vtbl[vol->vol_id]  // UAF!
> 
> If we reserve ubi things even mtd driver is removed. There exists mtd
> drivers releasing mtd device (eg. phram_remove), then upper application
> could accessing released mtd device by the ubi device, which also triggers
> UAF in kernel.

I agree this is a problem, and I also agree it is not a new problem
introduced by this series, but rather already exists in the kernel for
many years.

An idea to get closer to a good state would be to try dropping the
'anyway' parameter from ubi_detach_mtd_dev which is currently only
used in the module_exit. To avoid this, we should make sure the
module's refcnt is increased/decreased together with ubi->ref_count.

When it comes to the to-be-introduced ubi_notify_remove we still
face another problem, see below...

> 
> After looking at nvme_free_ctrl, I found that nvme_dev is released when
> device refcnt becomes zero, so block device and nvme_dev won't be freed
> immediately when pci driver removed if upper filesystem being mounted on
> nvme device. And the mtd device's refcnt is held by ubi too, we may follow
> this method, but investigating all mtd drivers looks like unrealistic.

A good start would be deciding on and defining the way it should be.
I agree with your suggestion above, however, also note that in case of
MTD (in contrast to block devices) we have only a 'remove' notification
call returning void, see include/linux/mtd/mtd.h

struct mtd_notifier {
        void (*add)(struct mtd_info *mtd);
        void (*remove)(struct mtd_info *mtd);
        struct list_head list;
};

Also see del_mtd_device in drivers/mtd/mtdcore.c:
[...]
        /* No need to get a refcount on the module containing
                the notifier, since we hold the mtd_table_mutex */
        list_for_each_entry(not, &mtd_notifiers, list)
                not->remove(mtd);

        if (mtd->usecount) {
                printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
                       mtd->index, mtd->name, mtd->usecount);
                ret = -EBUSY;
        } else {
[...]

So remove is called despite usecount could still be > 0.

Looks a bit like I've opened a can of worms...


> 
> > @@ -546,7 +546,7 @@ static int ubiblock_notify(struct notifier_block *nb,
> >   		 */
> >   		break;
> >   	case UBI_VOLUME_REMOVED:
> > -		ubiblock_remove(&nt->vi);
> > +		ubiblock_remove(&nt->vi, true);
> >   		break;
> >   	case UBI_VOLUME_RESIZED:
> >   		ubiblock_resize(&nt->vi);
> > diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c
> > index f43430b9c1e65..bb55e863dd296 100644
> > --- a/drivers/mtd/ubi/cdev.c
> > +++ b/drivers/mtd/ubi/cdev.c
> > @@ -572,7 +572,7 @@ static long vol_cdev_ioctl(struct file *file, unsigned int cmd,
> >   		struct ubi_volume_info vi;
> >   		ubi_get_volume_info(desc, &vi);
> > -		err = ubiblock_remove(&vi);
> > +		err = ubiblock_remove(&vi, false);
> >   		break;
> >   	}
> > diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
> > index c8f1bd4fa1008..44c0eeaf1e1b0 100644
> > --- a/drivers/mtd/ubi/ubi.h
> > +++ b/drivers/mtd/ubi/ubi.h
> > @@ -979,7 +979,7 @@ static inline void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol) {}
> >   int ubiblock_init(void);
> >   void ubiblock_exit(void);
> >   int ubiblock_create(struct ubi_volume_info *vi);
> > -int ubiblock_remove(struct ubi_volume_info *vi);
> > +int ubiblock_remove(struct ubi_volume_info *vi, bool force);
> >   #else
> >   static inline int ubiblock_init(void) { return 0; }
> >   static inline void ubiblock_exit(void) {}
> > @@ -987,7 +987,7 @@ static inline int ubiblock_create(struct ubi_volume_info *vi)
> >   {
> >   	return -ENOSYS;
> >   }
> > -static inline int ubiblock_remove(struct ubi_volume_info *vi)
> > +static inline int ubiblock_remove(struct ubi_volume_info *vi, bool force)
> >   {
> >   	return -ENOSYS;
> >   }
> > 
> 



More information about the linux-mtd mailing list