[evolution-data-server] [IMAPx] Crash in imapx_free_capability()



commit efd024c93df0632261bde161c6db952743f9238b
Author: Milan Crha <mcrha redhat com>
Date:   Mon Feb 1 15:06:13 2016 +0100

    [IMAPx] Crash in imapx_free_capability()
    
    It seems the imapx_disconnect() can be called from multiple threads,
    eventually causing use-after-free on is->priv->cinfo. Adding a lock
    around the private cinfo structure should avoid the crash.
    
    Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1293028

 camel/providers/imapx/camel-imapx-command.c |    2 +-
 camel/providers/imapx/camel-imapx-server.c  |  153 ++++++++++++++++++++++++---
 camel/providers/imapx/camel-imapx-server.h  |    6 +
 camel/providers/imapx/camel-imapx-store.c   |    4 +-
 4 files changed, 145 insertions(+), 20 deletions(-)
---
diff --git a/camel/providers/imapx/camel-imapx-command.c b/camel/providers/imapx/camel-imapx-command.c
index 0388c7d..e56dd6a 100644
--- a/camel/providers/imapx/camel-imapx-command.c
+++ b/camel/providers/imapx/camel-imapx-command.c
@@ -422,7 +422,7 @@ camel_imapx_command_add_part (CamelIMAPXCommand *ic,
        if (type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) {
                g_string_append_c (buffer, '{');
                g_string_append_printf (buffer, "%u", ob_size);
-               if (CAMEL_IMAPX_HAVE_CAPABILITY (camel_imapx_server_get_capability_info (ic->is), 
LITERALPLUS)) {
+               if (camel_imapx_server_have_capability (ic->is, IMAPX_CAPABILITY_LITERALPLUS)) {
                        g_string_append_c (buffer, '+');
                } else {
                        type &= ~CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
diff --git a/camel/providers/imapx/camel-imapx-server.c b/camel/providers/imapx/camel-imapx-server.c
index 78b4ef3..ab6824a 100644
--- a/camel/providers/imapx/camel-imapx-server.c
+++ b/camel/providers/imapx/camel-imapx-server.c
@@ -291,7 +291,7 @@ struct _CamelIMAPXServerPrivate {
 
        gboolean is_cyrus;
 
-       /* Info about the current connection */
+       /* Info about the current connection; guarded by priv->stream_lock */
        struct _capability_info *cinfo;
 
        GRecMutex command_lock;
@@ -782,21 +782,36 @@ imapx_untagged_capability (CamelIMAPXServer *is,
                            GCancellable *cancellable,
                            GError **error)
 {
+       struct _capability_info *cinfo;
+
        g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
 
-       if (is->priv->cinfo != NULL)
+       g_mutex_lock (&is->priv->stream_lock);
+
+       if (is->priv->cinfo != NULL) {
                imapx_free_capability (is->priv->cinfo);
+               is->priv->cinfo = NULL;
+       }
 
-       is->priv->cinfo = imapx_parse_capability (
-               CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+       g_mutex_unlock (&is->priv->stream_lock);
 
-       if (is->priv->cinfo == NULL)
+       cinfo = imapx_parse_capability (CAMEL_IMAPX_INPUT_STREAM (input_stream), cancellable, error);
+
+       if (!cinfo)
                return FALSE;
 
+       g_mutex_lock (&is->priv->stream_lock);
+
+       if (is->priv->cinfo != NULL)
+               imapx_free_capability (is->priv->cinfo);
+       is->priv->cinfo = cinfo;
+
        c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo->capa);
 
        imapx_server_stash_command_arguments (is);
 
+       g_mutex_unlock (&is->priv->stream_lock);
+
        return TRUE;
 }
 
@@ -1792,7 +1807,11 @@ imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
                break;
        case IMAPX_CAPABILITY:
                if (is->priv->context->sinfo->u.cinfo) {
-                       struct _capability_info *cinfo = is->priv->cinfo;
+                       struct _capability_info *cinfo;
+
+                       g_mutex_lock (&is->priv->stream_lock);
+
+                       cinfo = is->priv->cinfo;
                        is->priv->cinfo = is->priv->context->sinfo->u.cinfo;
                        is->priv->context->sinfo->u.cinfo = NULL;
                        if (cinfo)
@@ -1809,7 +1828,10 @@ imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
                                        is->priv->cinfo->capa &= ~list_extended;
                                }
                        }
+
                        imapx_server_stash_command_arguments (is);
+
+                       g_mutex_unlock (&is->priv->stream_lock);
                }
                break;
        case IMAPX_COPYUID:
@@ -2713,7 +2735,11 @@ connected:
                        goto exit;
        }
 
+       g_mutex_lock (&is->priv->stream_lock);
+
        if (!is->priv->cinfo) {
+               g_mutex_unlock (&is->priv->stream_lock);
+
                ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CAPABILITY, "CAPABILITY");
 
                success = camel_imapx_server_process_command_sync (is, ic, _("Failed to get capabilities"), 
cancellable, error);
@@ -2722,17 +2748,24 @@ connected:
 
                if (!success)
                        goto exit;
+       } else {
+               g_mutex_unlock (&is->priv->stream_lock);
        }
 
        if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
 
+               g_mutex_lock (&is->priv->stream_lock);
+
                if (CAMEL_IMAPX_LACK_CAPABILITY (is->priv->cinfo, STARTTLS)) {
+                       g_mutex_unlock (&is->priv->stream_lock);
                        g_set_error (
                                &local_error, CAMEL_ERROR,
                                CAMEL_ERROR_GENERIC,
                                _("Failed to connect to IMAP server %s in secure mode: %s"),
                                host, _("STARTTLS not supported"));
                        goto exit;
+               } else {
+                       g_mutex_unlock (&is->priv->stream_lock);
                }
 
                ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_STARTTLS, "STARTTLS");
@@ -2740,6 +2773,8 @@ connected:
                success = camel_imapx_server_process_command_sync (is, ic, _("Failed to issue STARTTLS"), 
cancellable, error);
 
                if (success) {
+                       g_mutex_lock (&is->priv->stream_lock);
+
                        /* See if we got new capabilities
                         * in the STARTTLS response. */
                        imapx_free_capability (is->priv->cinfo);
@@ -2750,6 +2785,8 @@ connected:
                                c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo ? 
is->priv->cinfo->capa : 0xFFFFFFFF);
                                imapx_server_stash_command_arguments (is);
                        }
+
+                       g_mutex_unlock (&is->priv->stream_lock);
                }
 
                camel_imapx_command_unref (ic);
@@ -2788,13 +2825,17 @@ connected:
                }
 
                /* Get new capabilities if they weren't already given */
+               g_mutex_lock (&is->priv->stream_lock);
                if (is->priv->cinfo == NULL) {
+                       g_mutex_unlock (&is->priv->stream_lock);
                        ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CAPABILITY, "CAPABILITY");
                        success = camel_imapx_server_process_command_sync (is, ic, _("Failed to get 
capabilities"), cancellable, error);
                        camel_imapx_command_unref (ic);
 
                        if (!success)
                                goto exit;
+               } else {
+                       g_mutex_unlock (&is->priv->stream_lock);
                }
        }
 
@@ -2863,8 +2904,11 @@ camel_imapx_server_authenticate_sync (CamelIMAPXServer *is,
        g_object_unref (settings);
 
        if (mechanism != NULL) {
+               g_mutex_lock (&is->priv->stream_lock);
+
                if (is->priv->cinfo && !g_hash_table_lookup (is->priv->cinfo->auth_types, mechanism) && (
                    !g_str_equal (mechanism, "Google") || !g_hash_table_lookup (is->priv->cinfo->auth_types, 
"XOAUTH2"))) {
+                       g_mutex_unlock (&is->priv->stream_lock);
                        g_set_error (
                                error, CAMEL_SERVICE_ERROR,
                                CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
@@ -2872,6 +2916,8 @@ camel_imapx_server_authenticate_sync (CamelIMAPXServer *is,
                                "authentication"), host, mechanism);
                        result = CAMEL_AUTHENTICATION_ERROR;
                        goto exit;
+               } else {
+                       g_mutex_unlock (&is->priv->stream_lock);
                }
 
                sasl = camel_sasl_new ("imap", mechanism, service);
@@ -2958,6 +3004,8 @@ camel_imapx_server_authenticate_sync (CamelIMAPXServer *is,
 
        /* Forget old capabilities after login. */
        if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
+               g_mutex_lock (&is->priv->stream_lock);
+
                if (is->priv->cinfo) {
                        imapx_free_capability (is->priv->cinfo);
                        is->priv->cinfo = NULL;
@@ -2969,6 +3017,8 @@ camel_imapx_server_authenticate_sync (CamelIMAPXServer *is,
                        c (is->priv->tagprefix, "got capability flags %08x\n", is->priv->cinfo ? 
is->priv->cinfo->capa : 0xFFFFFFFF);
                        imapx_server_stash_command_arguments (is);
                }
+
+               g_mutex_unlock (&is->priv->stream_lock);
        }
 
        camel_imapx_command_unref (ic);
@@ -3033,9 +3083,12 @@ imapx_reconnect (CamelIMAPXServer *is,
                goto exception;
 
        /* After login we re-capa unless the server already told us. */
+       g_mutex_lock (&is->priv->stream_lock);
        if (is->priv->cinfo == NULL) {
                GError *local_error = NULL;
 
+               g_mutex_unlock (&is->priv->stream_lock);
+
                ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_CAPABILITY, "CAPABILITY");
                camel_imapx_server_process_command_sync (is, ic, _("Failed to get capabilities"), 
cancellable, &local_error);
                camel_imapx_command_unref (ic);
@@ -3044,15 +3097,20 @@ imapx_reconnect (CamelIMAPXServer *is,
                        g_propagate_error (error, local_error);
                        goto exception;
                }
+       } else {
+               g_mutex_unlock (&is->priv->stream_lock);
        }
 
        is->priv->state = IMAPX_AUTHENTICATED;
 
 preauthed:
        /* Fetch namespaces (if supported). */
+       g_mutex_lock (&is->priv->stream_lock);
        if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NAMESPACE)) {
                GError *local_error = NULL;
 
+               g_mutex_unlock (&is->priv->stream_lock);
+
                ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_NAMESPACE, "NAMESPACE");
                camel_imapx_server_process_command_sync (is, ic, _("Failed to issue NAMESPACE"), cancellable, 
&local_error);
                camel_imapx_command_unref (ic);
@@ -3061,12 +3119,16 @@ preauthed:
                        g_propagate_error (error, local_error);
                        goto exception;
                }
+
+               g_mutex_lock (&is->priv->stream_lock);
        }
 
        /* Enable quick mailbox resynchronization (if supported). */
        if (use_qresync && CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, QRESYNC)) {
                GError *local_error = NULL;
 
+               g_mutex_unlock (&is->priv->stream_lock);
+
                ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_ENABLE, "ENABLE CONDSTORE QRESYNC");
                camel_imapx_server_process_command_sync (is, ic, _("Failed to enable QResync"), cancellable, 
&local_error);
                camel_imapx_command_unref (ic);
@@ -3076,6 +3138,8 @@ preauthed:
                        goto exception;
                }
 
+               g_mutex_lock (&is->priv->stream_lock);
+
                is->priv->use_qresync = TRUE;
        } else {
                is->priv->use_qresync = FALSE;
@@ -3085,6 +3149,8 @@ preauthed:
        if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NOTIFY)) {
                GError *local_error = NULL;
 
+               g_mutex_unlock (&is->priv->stream_lock);
+
                /* XXX The list of FETCH attributes is negotiable. */
                ic = camel_imapx_command_new (is, CAMEL_IMAPX_JOB_NOTIFY, "NOTIFY SET "
                        "(selected "
@@ -3103,8 +3169,12 @@ preauthed:
                        g_propagate_error (error, local_error);
                        goto exception;
                }
+
+               g_mutex_lock (&is->priv->stream_lock);
        }
 
+       g_mutex_unlock (&is->priv->stream_lock);
+
        is->priv->state = IMAPX_INITIALISED;
 
        success = TRUE;
@@ -3114,11 +3184,6 @@ preauthed:
 exception:
        imapx_disconnect (is);
 
-       if (is->priv->cinfo) {
-               imapx_free_capability (is->priv->cinfo);
-               is->priv->cinfo = NULL;
-       }
-
 exit:
        g_free (mechanism);
 
@@ -3775,6 +3840,11 @@ imapx_disconnect (CamelIMAPXServer *is)
        g_clear_object (&is->priv->connection);
        g_clear_object (&is->priv->subprocess);
 
+       if (is->priv->cinfo) {
+               imapx_free_capability (is->priv->cinfo);
+               is->priv->cinfo = NULL;
+       }
+
        g_mutex_unlock (&is->priv->stream_lock);
 
        g_mutex_lock (&is->priv->select_lock);
@@ -3782,11 +3852,6 @@ imapx_disconnect (CamelIMAPXServer *is)
        g_weak_ref_set (&is->priv->select_pending, NULL);
        g_mutex_unlock (&is->priv->select_lock);
 
-       if (is->priv->cinfo) {
-               imapx_free_capability (is->priv->cinfo);
-               is->priv->cinfo = NULL;
-       }
-
        is->priv->is_cyrus = FALSE;
        is->priv->state = IMAPX_DISCONNECTED;
 
@@ -3820,10 +3885,16 @@ camel_imapx_server_connect_sync (CamelIMAPXServer *is,
        if (!imapx_reconnect (is, cancellable, error))
                return FALSE;
 
+       g_mutex_lock (&is->priv->stream_lock);
+
        if (CAMEL_IMAPX_LACK_CAPABILITY (is->priv->cinfo, NAMESPACE)) {
+               g_mutex_unlock (&is->priv->stream_lock);
+
                /* This also creates a needed faux NAMESPACE */
                if (!camel_imapx_server_list_sync (is, "INBOX", 0, cancellable, error))
                        return FALSE;
+       } else {
+               g_mutex_unlock (&is->priv->stream_lock);
        }
 
        return TRUE;
@@ -4125,10 +4196,14 @@ camel_imapx_server_copy_message_sync (CamelIMAPXServer *is,
 
        /* If we're moving messages, prefer "UID MOVE" if supported. */
        if (delete_originals) {
+               g_mutex_lock (&is->priv->stream_lock);
+
                if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, MOVE)) {
                        delete_originals = FALSE;
                        use_move_command = TRUE;
                }
+
+               g_mutex_unlock (&is->priv->stream_lock);
        }
 
        source_infos = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, camel_message_info_unref);
@@ -5758,11 +5833,17 @@ camel_imapx_server_update_quota_info_sync (CamelIMAPXServer *is,
        g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
        g_return_val_if_fail (CAMEL_IS_IMAPX_MAILBOX (mailbox), FALSE);
 
+       g_mutex_lock (&is->priv->stream_lock);
+
        if (CAMEL_IMAPX_LACK_CAPABILITY (is->priv->cinfo, QUOTA)) {
+               g_mutex_unlock (&is->priv->stream_lock);
+
                g_set_error_literal (
                        error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                        _("IMAP server does not support quotas"));
                return FALSE;
+       } else {
+               g_mutex_unlock (&is->priv->stream_lock);
        }
 
        success = camel_imapx_server_ensure_selected_sync (is, mailbox, cancellable, error);
@@ -6033,9 +6114,14 @@ camel_imapx_server_can_use_idle (CamelIMAPXServer *is)
 {
        gboolean use_idle = FALSE;
 
+       g_mutex_lock (&is->priv->stream_lock);
+
        /* No need for IDLE if the server supports NOTIFY. */
-       if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NOTIFY))
+       if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, NOTIFY)) {
+               g_mutex_unlock (&is->priv->stream_lock);
+
                return FALSE;
+       }
 
        if (CAMEL_IMAPX_HAVE_CAPABILITY (is->priv->cinfo, IDLE)) {
                CamelIMAPXSettings *settings;
@@ -6045,6 +6131,8 @@ camel_imapx_server_can_use_idle (CamelIMAPXServer *is)
                g_object_unref (settings);
        }
 
+       g_mutex_unlock (&is->priv->stream_lock);
+
        return use_idle;
 }
 
@@ -6295,6 +6383,7 @@ camel_imapx_server_register_untagged_handler (CamelIMAPXServer *is,
        return previous;
 }
 
+/* This function is not thread-safe. */
 const struct _capability_info *
 camel_imapx_server_get_capability_info (CamelIMAPXServer *is)
 {
@@ -6303,6 +6392,36 @@ camel_imapx_server_get_capability_info (CamelIMAPXServer *is)
        return is->priv->cinfo;
 }
 
+gboolean
+camel_imapx_server_have_capability (CamelIMAPXServer *is,
+                                   guint32 capability)
+{
+       gboolean have;
+
+       g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+       g_mutex_lock (&is->priv->stream_lock);
+       have = is->priv->cinfo != NULL && (is->priv->cinfo->capa & capability) != 0;
+       g_mutex_unlock (&is->priv->stream_lock);
+
+       return have;
+}
+
+gboolean
+camel_imapx_server_lack_capability (CamelIMAPXServer *is,
+                                   guint32 capability)
+{
+       gboolean lack;
+
+       g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
+
+       g_mutex_lock (&is->priv->stream_lock);
+       lack = is->priv->cinfo != NULL && (is->priv->cinfo->capa & capability) == 0;
+       g_mutex_unlock (&is->priv->stream_lock);
+
+       return lack;
+}
+
 gchar
 camel_imapx_server_get_tagprefix (CamelIMAPXServer *is)
 {
diff --git a/camel/providers/imapx/camel-imapx-server.h b/camel/providers/imapx/camel-imapx-server.h
index a51c39d..16d5090 100644
--- a/camel/providers/imapx/camel-imapx-server.h
+++ b/camel/providers/imapx/camel-imapx-server.h
@@ -135,6 +135,12 @@ CamelIMAPXMailbox *
 const struct _capability_info *
                camel_imapx_server_get_capability_info
                                                (CamelIMAPXServer *is);
+gboolean       camel_imapx_server_have_capability
+                                               (CamelIMAPXServer *is,
+                                                guint32 capability);
+gboolean       camel_imapx_server_lack_capability
+                                               (CamelIMAPXServer *is,
+                                                guint32 capability);
 gchar          camel_imapx_server_get_tagprefix
                                                (CamelIMAPXServer *is);
 void           camel_imapx_server_set_tagprefix
diff --git a/camel/providers/imapx/camel-imapx-store.c b/camel/providers/imapx/camel-imapx-store.c
index 5ea55d6..cb89e18 100644
--- a/camel/providers/imapx/camel-imapx-store.c
+++ b/camel/providers/imapx/camel-imapx-store.c
@@ -3447,7 +3447,7 @@ camel_imapx_store_handle_list_response (CamelIMAPXStore *imapx_store,
 
        /* Fabricate a CamelIMAPXNamespaceResponse if the server lacks the
         * NAMESPACE capability and this is the first LIST / LSUB response. */
-       if (CAMEL_IMAPX_LACK_CAPABILITY (camel_imapx_server_get_capability_info (imapx_server), NAMESPACE)) {
+       if (camel_imapx_server_lack_capability (imapx_server, IMAPX_CAPABILITY_NAMESPACE)) {
                g_mutex_lock (&imapx_store->priv->namespaces_lock);
                if (imapx_store->priv->namespaces == NULL) {
                        imapx_store->priv->namespaces = camel_imapx_namespace_response_faux_new (response);
@@ -3511,7 +3511,7 @@ camel_imapx_store_handle_lsub_response (CamelIMAPXStore *imapx_store,
 
        /* Fabricate a CamelIMAPXNamespaceResponse if the server lacks the
         * NAMESPACE capability and this is the first LIST / LSUB response. */
-       if (CAMEL_IMAPX_LACK_CAPABILITY (camel_imapx_server_get_capability_info (imapx_server), NAMESPACE)) {
+       if (camel_imapx_server_lack_capability (imapx_server, IMAPX_CAPABILITY_NAMESPACE)) {
                g_mutex_lock (&imapx_store->priv->namespaces_lock);
                if (imapx_store->priv->namespaces == NULL) {
                        imapx_store->priv->namespaces = camel_imapx_namespace_response_faux_new (response);


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