qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Fam Zheng <famz@redhat.com>
To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>,
	uobergfe@redhat.com, Stefan Hajnoczi <stefanha@redhat.com>
Subject: [Qemu-devel] [PATCH] aio: Fix use-after-free in cancellation path
Date: Mon, 19 May 2014 20:18:39 +0800	[thread overview]
Message-ID: <1400501919-7643-1-git-send-email-famz@redhat.com> (raw)

The current flow of canceling a thread from THREAD_ACTIVE state is:

  1) Caller wants to cancel a request, so it calls thread_pool_cancel.

  2) thread_pool_cancel waits on the conditional variable
     elem->check_cancel.

  3) The worker thread changes state to THREAD_DONE once the task is
     done, and notifies elem->check_cancel to allow thread_pool_cancel
     to continue execution, and signals the notifier (pool->notifier) to
     allow callback function to be called later. But because of the
     global mutex, the notifier won't get processed until step 4) and 5)
     are done.

  4) thread_pool_cancel continues, leaving the notifier signaled, it
     just returns to caller.

  5) Caller thinks the request is already canceled successfully, so it
     releases any related data, such as freeing elem->common.opaque.

  6) In the next main loop iteration, the notifier handler,
     event_notifier_ready, is called. It finds the canceled thread in
     THREAD_DONE state, so calls elem->common.cb, with an (likely)
     dangling opaque pointer. This is a use-after-free.

Fix it by calling elem->common.cb right before thread_pool_cancel
returns.

We leave the notifier signaled to allow any other acbs to be processed.

Reported-by: Ulrich Obergfell <uobergfe@redhat.com>
Suggested-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Fam Zheng <famz@redhat.com>
---
 thread-pool.c | 40 ++++++++++++++++++++++++++++++----------
 1 file changed, 30 insertions(+), 10 deletions(-)

diff --git a/thread-pool.c b/thread-pool.c
index fbdd3ff..cc1c9f1 100644
--- a/thread-pool.c
+++ b/thread-pool.c
@@ -168,6 +168,24 @@ static void spawn_thread(ThreadPool *pool)
     }
 }
 
+/* Complete one thread and call common.cb if it has returned.
+ * Returns true if commmon.cb is called.
+ */
+static bool thread_pool_complete_one(ThreadPoolElement *elem)
+{
+    bool ret = false;
+
+    QLIST_REMOVE(elem, all);
+    if (elem->state == THREAD_DONE && elem->common.cb) {
+        /* Read state before ret.  */
+        smp_rmb();
+        elem->common.cb(elem->common.opaque, elem->ret);
+        ret = true;
+    }
+    qemu_aio_release(elem);
+    return ret;
+}
+
 static void event_notifier_ready(EventNotifier *notifier)
 {
     ThreadPool *pool = container_of(notifier, ThreadPool, notifier);
@@ -183,23 +201,15 @@ restart:
             trace_thread_pool_complete(pool, elem, elem->common.opaque,
                                        elem->ret);
         }
-        if (elem->state == THREAD_DONE && elem->common.cb) {
-            QLIST_REMOVE(elem, all);
-            /* Read state before ret.  */
-            smp_rmb();
-            elem->common.cb(elem->common.opaque, elem->ret);
-            qemu_aio_release(elem);
+        if (thread_pool_complete_one(elem)) {
             goto restart;
-        } else {
-            /* remove the request */
-            QLIST_REMOVE(elem, all);
-            qemu_aio_release(elem);
         }
     }
 }
 
 static void thread_pool_cancel(BlockDriverAIOCB *acb)
 {
+    ThreadPoolElement *next;
     ThreadPoolElement *elem = (ThreadPoolElement *)acb;
     ThreadPool *pool = elem->pool;
 
@@ -222,6 +232,16 @@ static void thread_pool_cancel(BlockDriverAIOCB *acb)
             qemu_cond_wait(&pool->check_cancel, &pool->lock);
         }
         pool->pending_cancellations--;
+
+        /* In the case of THREAD_ACTIVE -> THREAD_DONE, we need to call
+         * callback.  Make sure we do it before returning to caller.
+         */
+        QLIST_FOREACH_SAFE(elem, &pool->head, all, next) {
+            if (acb == &elem->common) {
+                thread_pool_complete_one(elem);
+                break;
+            }
+        }
     }
     qemu_mutex_unlock(&pool->lock);
 }
-- 
1.9.2

                 reply	other threads:[~2014-05-19 12:18 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=1400501919-7643-1-git-send-email-famz@redhat.com \
    --to=famz@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    --cc=uobergfe@redhat.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).