All of lore.kernel.org
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
	qemu-devel@nongnu.org, Max Reitz <mreitz@redhat.com>
Subject: [PATCH 2/2] curl: Disconnect sockets from CURLState
Date: Tue,  9 Mar 2021 14:05:41 +0100	[thread overview]
Message-ID: <20210309130541.37540-3-mreitz@redhat.com> (raw)
In-Reply-To: <20210309130541.37540-1-mreitz@redhat.com>

When a curl transfer is finished, that does not mean that CURL lets go
of all the sockets it used for it.  We therefore must not free a
CURLSocket object before CURL has invoked curl_sock_cb() to tell us to
remove it.  Otherwise, we may get a use-after-free, as described in this
bug report: https://bugs.launchpad.net/qemu/+bug/1916501

(Reproducer from that report:
  $ qemu-img convert -f qcow2 -O raw \
  https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img \
  out.img
)

(Alternatively, it might seem logical to force-drop all sockets that
have been used for a state when the respective transfer is done, kind of
like it is done now, but including unsetting the AIO handlers.
Unfortunately, doing so makes the driver just hang instead of crashing,
which seems to evidence that CURL still uses those sockets.)

Make the CURLSocket object independent of "its" CURLState by putting all
sockets into a hash table belonging to the BDRVCURLState instead of a
list that belongs to a CURLState.  Do not touch any sockets in
curl_clean_state().

Testing, it seems like all sockets are indeed gone by the time the curl
BDS is closed, so it seems like there really was no point in freeing any
socket just because a transfer is done.  libcurl does invoke
curl_sock_cb() with CURL_POLL_REMOVE for every socket it has.

Buglink: https://bugs.launchpad.net/qemu/+bug/1916501
Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 block/curl.c | 42 ++++++++++++++++++++++++------------------
 1 file changed, 24 insertions(+), 18 deletions(-)

diff --git a/block/curl.c b/block/curl.c
index 43c79bcf36..50e741a0d7 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -79,7 +79,6 @@ typedef struct CURLAIOCB {
 typedef struct CURLSocket {
     int fd;
     struct BDRVCURLState *s;
-    QLIST_ENTRY(CURLSocket) next;
 } CURLSocket;
 
 typedef struct CURLState
@@ -87,7 +86,6 @@ typedef struct CURLState
     struct BDRVCURLState *s;
     CURLAIOCB *acb[CURL_NUM_ACB];
     CURL *curl;
-    QLIST_HEAD(, CURLSocket) sockets;
     char *orig_buf;
     uint64_t buf_start;
     size_t buf_off;
@@ -102,6 +100,7 @@ typedef struct BDRVCURLState {
     QEMUTimer timer;
     uint64_t len;
     CURLState states[CURL_NUM_STATES];
+    GHashTable *sockets; /* GINT_TO_POINTER(fd) -> socket */
     char *url;
     size_t readahead_size;
     bool sslverify;
@@ -120,6 +119,21 @@ typedef struct BDRVCURLState {
 static void curl_clean_state(CURLState *s);
 static void curl_multi_do(void *arg);
 
+static gboolean curl_drop_socket(void *key, void *value, void *opaque)
+{
+    CURLSocket *socket = value;
+    BDRVCURLState *s = socket->s;
+
+    aio_set_fd_handler(s->aio_context, socket->fd, false,
+                       NULL, NULL, NULL, NULL);
+    return true;
+}
+
+static void curl_drop_all_sockets(GHashTable *sockets)
+{
+    g_hash_table_foreach_remove(sockets, curl_drop_socket, NULL);
+}
+
 /* Called from curl_multi_do_locked, with s->mutex held.  */
 static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
 {
@@ -147,16 +161,12 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
     curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&state);
     s = state->s;
 
-    QLIST_FOREACH(socket, &state->sockets, next) {
-        if (socket->fd == fd) {
-            break;
-        }
-    }
+    socket = g_hash_table_lookup(s->sockets, GINT_TO_POINTER(fd));
     if (!socket) {
         socket = g_new0(CURLSocket, 1);
         socket->fd = fd;
         socket->s = s;
-        QLIST_INSERT_HEAD(&state->sockets, socket, next);
+        g_hash_table_insert(s->sockets, GINT_TO_POINTER(fd), socket);
     }
 
     trace_curl_sock_cb(action, (int)fd);
@@ -180,8 +190,7 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
     }
 
     if (action == CURL_POLL_REMOVE) {
-        QLIST_REMOVE(socket, next);
-        g_free(socket);
+        g_hash_table_remove(s->sockets, GINT_TO_POINTER(fd));
     }
 
     return 0;
@@ -498,7 +507,6 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
 #endif
     }
 
-    QLIST_INIT(&state->sockets);
     state->s = s;
 
     return 0;
@@ -515,13 +523,6 @@ static void curl_clean_state(CURLState *s)
     if (s->s->multi)
         curl_multi_remove_handle(s->s->multi, s->curl);
 
-    while (!QLIST_EMPTY(&s->sockets)) {
-        CURLSocket *socket = QLIST_FIRST(&s->sockets);
-
-        QLIST_REMOVE(socket, next);
-        g_free(socket);
-    }
-
     s->in_use = 0;
 
     qemu_co_enter_next(&s->s->free_state_waitq, &s->s->mutex);
@@ -539,6 +540,7 @@ static void curl_detach_aio_context(BlockDriverState *bs)
     int i;
 
     WITH_QEMU_LOCK_GUARD(&s->mutex) {
+        curl_drop_all_sockets(s->sockets);
         for (i = 0; i < CURL_NUM_STATES; i++) {
             if (s->states[i].in_use) {
                 curl_clean_state(&s->states[i]);
@@ -745,6 +747,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
     qemu_co_queue_init(&s->free_state_waitq);
     s->aio_context = bdrv_get_aio_context(bs);
     s->url = g_strdup(file);
+    s->sockets = g_hash_table_new_full(NULL, NULL, NULL, g_free);
     qemu_mutex_lock(&s->mutex);
     state = curl_find_state(s);
     qemu_mutex_unlock(&s->mutex);
@@ -818,6 +821,8 @@ out_noclean:
     g_free(s->username);
     g_free(s->proxyusername);
     g_free(s->proxypassword);
+    curl_drop_all_sockets(s->sockets);
+    g_hash_table_destroy(s->sockets);
     qemu_opts_del(opts);
     return -EINVAL;
 }
@@ -916,6 +921,7 @@ static void curl_close(BlockDriverState *bs)
     curl_detach_aio_context(bs);
     qemu_mutex_destroy(&s->mutex);
 
+    g_hash_table_destroy(s->sockets);
     g_free(s->cookie);
     g_free(s->url);
     g_free(s->username);
-- 
2.29.2



  parent reply	other threads:[~2021-03-09 13:14 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-09 13:05 [PATCH 0/2] block/curl: Disconnect sockets from CURLState Max Reitz
2021-03-09 13:05 ` [PATCH 1/2] curl: Store BDRVCURLState pointer in CURLSocket Max Reitz
2021-03-09 13:13   ` Philippe Mathieu-Daudé
2021-03-09 13:05 ` Max Reitz [this message]
2021-03-10 11:51 ` [PATCH 0/2] block/curl: Disconnect sockets from CURLState Kevin Wolf

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=20210309130541.37540-3-mreitz@redhat.com \
    --to=mreitz@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /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.