qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v2] Do not delete BlockDriverState when deleting


From: Ryan Harper
Subject: Re: [Qemu-devel] [PATCH v2] Do not delete BlockDriverState when deleting the drive
Date: Tue, 22 Mar 2011 20:53:47 -0500
User-agent: Mutt/1.5.6+20040907i

* Markus Armbruster <address@hidden> [2011-03-15 04:48]:
> Sorry for the long delay, I was out of action for a week.
> 
> Ryan Harper <address@hidden> writes:
> 
> > When removing a drive from the host-side via drive_del we currently have the
> > following path:
> >
> > drive_del
> > qemu_aio_flush()
> > bdrv_close()
> > drive_uninit()
> > bdrv_delete()
> >
> > When we bdrv_delete() we end up qemu_free() the BlockDriverState pointer
> > however, the block devices retain a copy of this pointer, see
> > hw/virtio-blk.c:virtio_blk_init() where we s->bs = conf->bs.
> >
> > We now have a use-after-free situation.  If the guest continues to issue IO
> > against the device, and we've reallocated the memory that the 
> > BlockDriverState
> > pointed at, then we will fail the bs->drv checks in the various bdrv_ 
> > methods.
> 
> "we will fail the bs->drv checks" is misleading, in my opinion.  Here's
> what happens:
> 
> 1. bdrv_close(bs) zaps bs->drv, which makes any subsequent I/O get
>    dropped.  Works as designed.
> 
> 2. drive_uninit() frees the bs.  Since the device is still connected to
>    bs, any subsequent I/O is a use-after-free.
> 
>    The value of bs->drv becomes unpredictable on free.  As long as it
>    remains null, I/O still gets dropped.  I/O crashes or worse once that
>    changed.  Could be right on free, could be much later.
> 
> If you respin anyway, please clarify your description.

Sure.  I wasn't planning a new version, but I'll update and send anyhow
as I didn't see it get included in pull from the block branch.
> 
> > To resolve this issue as simply as possible, we can chose to not actually
> > delete the BlockDriverState pointer.  Since bdrv_close() handles setting 
> > the drv
> > pointer to NULL, we just need to remove the BlockDriverState from the QLIST
> > that is used to enumerate the block devices.  This is currently handled 
> > within
> > bdrv_delete, so move this into it's own function, bdrv_remove().
> 
> Why do we remove the BlockDriverState from bdrv_states?  Because we want
> drive_del make its *name* go away.
> 
> Begs the question: is the code prepared for a BlockDriverState object
> that isn't on bdrv_states?  Turns out we're in luck: bdrv_new() already
> creates such objects when the device_name is empty.  This is used for
> internal BlockDriverStates such as COW backing files.  Your code makes
> device_name empty when taking the object off bdrv_states, so we're good.
> 
> Begs yet another question: how does the behavior of a BlockDriverState
> change when it's taken off bdrv_states, and is that the behavior we
> want?  Changes:
> 
> * bdrv_delete() no longer takes it off bdrv_states.  Good.
> 
> * bdrv_close_all(), bdrv_commit_all() and bdrv_flush_all() no longer
>   cover it.  Okay, because bdrv_close(), bdrv_commit() and bdrv_flush()
>   do nothing anyway for closed BlockDriverStates.
> 
> * "info block" and "info blockstats" no longer show it, because
>   bdrv_info() and bdrv_info_stats() no longer see it.  Okay.
> 
> * bdrv_find(), bdrv_next(), bdrv_iterate() no longer see it.  Impact?
>   Please check their uses and report.

   1    664  block-migration.c <<block_load>>
             bs = bdrv_find(device_name);

    - no longer see it.  This is fine since we can't migrate a block
    device that has been removed

   2    562  blockdev.c <<do_commit>>
             bs = bdrv_find(device);

    - do_commit won't see it in either when calling bdrv_commit_all()
    Fine as you mention above.  If user specifies the device name
    we won't find it, that's OK because we can't commit data against
    a closed BlockDriverState.

   3    587  blockdev.c <<do_snapshot_blkdev>>
             bs = bdrv_find(device);

   - OK, cannot take a snapshot against a deleted BlockDriverState

   4    662  blockdev.c <<do_eject>>
             bs = bdrv_find(filename);

   - OK, cannot eject a deleted BlockDriverState; 

   5    676  blockdev.c <<do_block_set_passwd>>
             bs = bdrv_find(qdict_get_str(qdict, "device"));

   - OK, cannot set password a deleted BlockDriverState; 

   6    701  blockdev.c <<do_change_block>>
             bs = bdrv_find(device);

   - OK, cannot change the file/device of a deleted BlockDriverState; 

   7    732  blockdev.c <<do_drive_del>>
             bs = bdrv_find(id);

   - OK, cannot delete an already deleted Drive

   8    783  blockdev.c <<do_block_resize>>
             bs = bdrv_find(device);

   - OK, cannot resize a deleted Drive

   9    312  hw/qdev-properties.c <<parse_drive>>
             bs = bdrv_find(str);

   - Used when invoking qdev_prop_drive .parse method;  parse method is invoked 
via
    qdev_device_add() which calls set_property() which invokes parse.  AFAICT, 
this is OK
    since we won't be going down the device add path worrying about a
    deleted block device.

> 
> > The result is that we can now invoke drive_del, this closes the file 
> > descriptors
> > and sets BlockDriverState->drv to NULL which prevents futher IO to the 
> > device,
> > and since we do not free BlockDriverState, we don't have to worry about the 
> > copy
> > retained in the block devices.
> 
> Yep.  But there's one more question: is the BlockDriverState freed when
> the device using it gets destroyed?
> 
> qdev_free() runs prop->info->free() for all properties.  The drive
> property's free() is free_drive():
> 
>   static void free_drive(DeviceState *dev, Property *prop)
>   {
>       BlockDriverState **ptr = qdev_get_prop_ptr(dev, prop);
> 
>       if (*ptr) {
>           bdrv_detach(*ptr, dev);
>           blockdev_auto_del(*ptr);
>       }
>   }
> 
> This should indeed delete the drive.  But only if the property still
> points to it.  See below.
> 
> > Reported-by: Marcus Armbruster <address@hidden>
> > Signed-off-by: Ryan Harper <address@hidden>
> > ---
> > v1->v2
> >  - NULL bs->device_name after removing from list to prevent
> >    second removal.
> >
> >  block.c    |   12 +++++++++---
> >  block.h    |    1 +
> >  blockdev.c |    2 +-
> >  3 files changed, 11 insertions(+), 4 deletions(-)
> >
> > diff --git a/block.c b/block.c
> > index 1544d81..0df9942 100644
> > --- a/block.c
> > +++ b/block.c
> > @@ -697,14 +697,20 @@ void bdrv_close_all(void)
> >      }
> >  }
> >  
> > +void bdrv_remove(BlockDriverState *bs)
> > +{
> > +    if (bs->device_name[0] != '\0') {
> > +        QTAILQ_REMOVE(&bdrv_states, bs, list);
> > +    }
> > +    bs->device_name[0] = '\0';
> > +}
> > +
> 
> I don't like this name.  What's the difference between "delete" and
> "remove"?
> 
> The function zaps the device name.  bdrv_make_anon()?

bdrv_delist? bdrv_hide?  I'm also fine with make_anon.

> 
> >  void bdrv_delete(BlockDriverState *bs)
> >  {
> >      assert(!bs->peer);
> >  
> >      /* remove from list, if necessary */
> > -    if (bs->device_name[0] != '\0') {
> > -        QTAILQ_REMOVE(&bdrv_states, bs, list);
> > -    }
> > +    bdrv_remove(bs);
> >  
> >      bdrv_close(bs);
> >      if (bs->file != NULL) {
> > diff --git a/block.h b/block.h
> > index 5d78fc0..8447397 100644
> > --- a/block.h
> > +++ b/block.h
> > @@ -66,6 +66,7 @@ int bdrv_create(BlockDriver *drv, const char* filename,
> >      QEMUOptionParameter *options);
> >  int bdrv_create_file(const char* filename, QEMUOptionParameter *options);
> >  BlockDriverState *bdrv_new(const char *device_name);
> > +void bdrv_remove(BlockDriverState *bs);
> >  void bdrv_delete(BlockDriverState *bs);
> >  int bdrv_file_open(BlockDriverState **pbs, const char *filename, int 
> > flags);
> >  int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
> > diff --git a/blockdev.c b/blockdev.c
> > index 0690cc8..1f57b50 100644
> > --- a/blockdev.c
> > +++ b/blockdev.c
> > @@ -760,7 +760,7 @@ int do_drive_del(Monitor *mon, const QDict *qdict, 
> > QObject **ret_data)
> 
> Let me add a bit more context:
> 
>       bdrv_flush(bs);
>       bdrv_close(bs);
> 
>       /* clean up guest state from pointing to host resource by
>        * finding and removing DeviceState "drive" property */
>       if (bs->peer) {
>           for (prop = bs->peer->info->props; prop && prop->name; prop++) {
>               if (prop->info->type == PROP_TYPE_DRIVE) {
>                   ptr = qdev_get_prop_ptr(bs->peer, prop);
>                   if (*ptr == bs) {
>                       bdrv_detach(bs, bs->peer);
>                       *ptr = NULL;
>                       break;
>                   }
>               }
>           }
> >      }
> 
> This zaps the drive property.  A subsequent free_drive() will do
> nothing.  We leak the BlockDriverState on device unplug, I'm afraid.
> 
> Any reason why we still want to zap the drive property?

IIRC, it was to prevent qdev from keeping a ptr around to the bs; but if
we're keeping the bs around anyhow, then I don't think we need to remove
the property.  One last check would be to see what if the device still
shows up in qtree if we don't remove the property.


-- 
Ryan Harper
Software Engineer; Linux Technology Center
IBM Corp., Austin, Tx
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]