[Qemu-devel] [PATCH v2 10/17] block-backend: Fix potential double blk_delete()

Kevin Wolf posted 17 patches 7 years, 1 month ago
There is a newer version of this series
[Qemu-devel] [PATCH v2 10/17] block-backend: Fix potential double blk_delete()
Posted by Kevin Wolf 7 years, 1 month ago
blk_unref() first decreases the refcount of the BlockBackend and calls
blk_delete() if the refcount reaches zero. Requests can still be in
flight at this point, they are only drained during blk_delete():

At this point, arbitrary callbacks can run. If any callback takes a
temporary BlockBackend reference, it will first increase the refcount to
1 and then decrease it to 0 again, triggering another blk_delete(). This
will cause a use-after-free crash in the outer blk_delete().

Fix it by draining the BlockBackend before decreasing to refcount to 0.
Assert in blk_ref() that it never takes the first refcount (which would
mean that the BlockBackend is already being deleted).

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Fam Zheng <famz@redhat.com>
---
 block/block-backend.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index efa8d8011c..1b2d7a6ff5 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -435,6 +435,7 @@ int blk_get_refcnt(BlockBackend *blk)
  */
 void blk_ref(BlockBackend *blk)
 {
+    assert(blk->refcnt > 0);
     blk->refcnt++;
 }
 
@@ -447,7 +448,11 @@ void blk_unref(BlockBackend *blk)
 {
     if (blk) {
         assert(blk->refcnt > 0);
-        if (!--blk->refcnt) {
+        if (blk->refcnt > 1) {
+            blk->refcnt--;
+        } else {
+            blk_drain(blk);
+            blk->refcnt = 0;
             blk_delete(blk);
         }
     }
-- 
2.13.6


Re: [Qemu-devel] [PATCH v2 10/17] block-backend: Fix potential double blk_delete()
Posted by Paolo Bonzini 7 years, 1 month ago
On 13/09/2018 14:52, Kevin Wolf wrote:
> blk_unref() first decreases the refcount of the BlockBackend and calls
> blk_delete() if the refcount reaches zero. Requests can still be in
> flight at this point, they are only drained during blk_delete():
> 
> At this point, arbitrary callbacks can run. If any callback takes a
> temporary BlockBackend reference, it will first increase the refcount to
> 1 and then decrease it to 0 again, triggering another blk_delete(). This
> will cause a use-after-free crash in the outer blk_delete().
> 
> Fix it by draining the BlockBackend before decreasing to refcount to 0.
> Assert in blk_ref() that it never takes the first refcount (which would
> mean that the BlockBackend is already being deleted).
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Fam Zheng <famz@redhat.com>
> ---
>  block/block-backend.c | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/block/block-backend.c b/block/block-backend.c
> index efa8d8011c..1b2d7a6ff5 100644
> --- a/block/block-backend.c
> +++ b/block/block-backend.c
> @@ -435,6 +435,7 @@ int blk_get_refcnt(BlockBackend *blk)
>   */
>  void blk_ref(BlockBackend *blk)
>  {
> +    assert(blk->refcnt > 0);
>      blk->refcnt++;
>  }
>  
> @@ -447,7 +448,11 @@ void blk_unref(BlockBackend *blk)
>  {
>      if (blk) {
>          assert(blk->refcnt > 0);
> -        if (!--blk->refcnt) {
> +        if (blk->refcnt > 1) {
> +            blk->refcnt--;
> +        } else {
> +            blk_drain(blk);

Maybe "assert(blk->refcnt == 1);" here to ensure that blk_drain() cannot
resurrect the block device, or alternatively

+    if (blk->refcnt == 1) {
+        blk_drain(blk);
+        assert(blk->refcnt == 1);
+    }
     if (!--blk->refcnt) {
         blk_delete(blk);
     }

Paolo

Re: [Qemu-devel] [PATCH v2 10/17] block-backend: Fix potential double blk_delete()
Posted by Max Reitz 7 years, 1 month ago
On 13.09.18 14:52, Kevin Wolf wrote:
> blk_unref() first decreases the refcount of the BlockBackend and calls
> blk_delete() if the refcount reaches zero. Requests can still be in
> flight at this point, they are only drained during blk_delete():
> 
> At this point, arbitrary callbacks can run. If any callback takes a
> temporary BlockBackend reference, it will first increase the refcount to
> 1 and then decrease it to 0 again, triggering another blk_delete(). This
> will cause a use-after-free crash in the outer blk_delete().
> 
> Fix it by draining the BlockBackend before decreasing to refcount to 0.
> Assert in blk_ref() that it never takes the first refcount (which would
> mean that the BlockBackend is already being deleted).
> 
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> Reviewed-by: Fam Zheng <famz@redhat.com>
> ---
>  block/block-backend.c | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)

Reviewed-by: Max Reitz <mreitz@redhat.com>