]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Fix use after free in quic_connection freeing if up ref fails
authorNeil Horman <nhorman@openssl.org>
Sun, 8 Mar 2026 20:49:09 +0000 (16:49 -0400)
committerTomas Mraz <tomas@openssl.org>
Wed, 11 Mar 2026 09:23:20 +0000 (10:23 +0100)
Issue https://github.com/openssl/openssl/issues/3030

Found a use after free case in ossl_quic_accept_connection in the event
that we fail to up_ref the associated quic listener object.

If we fail to take the up ref on the listener object in this function,
we free the SSL object, which calls into
SSL_free->ossl_quic_free->qc_cleanup, which because we have an
associated listener, we free the mutex for, and then get a use-afer-free
when we try to unlock that mutex shortly thereafter.

We really need to fix 3 problems here:

1) The use after free.  Handle this bt ensuring that the listener is
   assigned first.

2) A deadlock, since we already hold the associated mutex, we need to
   defer the free operation until after we unlock the mutex.

3) Don't drop the refcount on the listener object in ossl_quic_cleanup
   (since we failed to up-ref it here).  Handle this by adding a flag to
   indicate up-ref failure in the quic-connection object.

Problem was confirmed by synthetically failing the up ref in local
testing, and this patch was confirmed to fix the issue.

Also, we need  to adjust some of the tests in quicapitest here, as
several tests just assume that SSL_accept_connection will return a
non-null value.

Fixes #30307

Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
MergeDate: Wed Mar 11 09:22:35 2026
(Merged from https://github.com/openssl/openssl/pull/30311)

(cherry picked from commit 0ed06337e38ec70e5beb043d5a1da9a6b6e8c57e)

ssl/quic/quic_impl.c
test/quicapitest.c

index fd4e0ced3f601fcdb70ab53db66ebf2f7cde5349..ea764300093d437f99bef1fad67691b5735d9dcd 100644 (file)
@@ -4736,9 +4736,10 @@ SSL *ossl_quic_accept_connection(SSL *ssl, uint64_t flags)
     int ret;
     QCTX ctx;
     SSL *conn_ssl = NULL;
+    SSL *conn_ssl_tmp = NULL;
     SSL_CONNECTION *conn = NULL;
     QUIC_CHANNEL *new_ch = NULL;
-    QUIC_CONNECTION *qc;
+    QUIC_CONNECTION *qc = NULL;
     int no_block = ((flags & SSL_ACCEPT_CONNECTION_NO_BLOCK) != 0);
 
     if (!expect_quic_listener(ssl, &ctx))
@@ -4793,28 +4794,38 @@ SSL *ossl_quic_accept_connection(SSL *ssl, uint64_t flags)
      * bound to new_ch. If channel constructor fails to create any item here
      * it just fails to create channel.
      */
-    if (!ossl_assert((conn_ssl = ossl_quic_channel_get0_tls(new_ch)) != NULL)
-        || !ossl_assert((conn = SSL_CONNECTION_FROM_SSL(conn_ssl)) != NULL)
-        || !ossl_assert((conn_ssl = SSL_CONNECTION_GET_USER_SSL(conn)) != NULL))
+    if (!ossl_assert((conn_ssl_tmp = ossl_quic_channel_get0_tls(new_ch)) != NULL)
+        || !ossl_assert((conn = SSL_CONNECTION_FROM_SSL(conn_ssl_tmp)) != NULL)
+        || !ossl_assert((conn_ssl_tmp = SSL_CONNECTION_GET_USER_SSL(conn)) != NULL))
         goto out;
 
-    qc = (QUIC_CONNECTION *)conn_ssl;
-    qc->pending = 0;
-    if (!SSL_up_ref(&ctx.ql->obj.ssl)) {
-        /*
-         * You might expect ossl_quic_channel_free() to be called here. Be
-         * assured it happens, The process goes as follows:
-         *    - The SSL_free() here is being handled by ossl_quic_free().
-         *    - The very last step of ossl_quic_free() is call to qc_cleanup()
-         *      where channel gets freed.
-         */
-        SSL_free(conn_ssl);
+    qc = (QUIC_CONNECTION *)conn_ssl_tmp;
+    if (SSL_up_ref(&ctx.ql->obj.ssl)) {
+        qc->listener = ctx.ql;
+        conn_ssl = conn_ssl_tmp;
+        conn_ssl_tmp = NULL;
+        qc->pending = 0;
     }
-    qc->listener = ctx.ql;
 
 out:
 
     qctx_unlock(&ctx);
+    /*
+     * You might expect ossl_quic_channel_free() to be called here. Be
+     * assured it happens, The process goes as follows:
+     *    - The SSL_free() here is being handled by ossl_quic_free().
+     *    - The very last step of ossl_quic_free() is call to qc_cleanup()
+     *      where channel gets freed.
+     * NOTE: We defer this SSL_free until after the call to qctx_unlock above
+     * to avoid the deadlock that would occur when ossl_quic_free attempts to
+     * re-acquire this mutex.  We also do the gymnastics with conn_ssl and
+     * conn_ssl_tmp above so that we only actually do the free on the SSL
+     * object if the up-ref above fails, in such a way that we don't unbalance
+     * the listener refcount (i.e. if the up-ref fails above, we don't set the
+     * listener pointer so that we don't then drop the ref-count erroneously
+     * during the free operation.
+     */
+    SSL_free(conn_ssl_tmp);
     return conn_ssl;
 }
 
index c322aed5100214da550716ed51173652eef02a39..ad2c8ca2a9d136b40ce0e5f71117d0c440b2452c 100644 (file)
@@ -2894,8 +2894,7 @@ static int test_ssl_listen_ex(void)
         goto err;
 
     /* Call SSL_accept() and SSL_connect() until we are connected */
-    if (!TEST_true(create_bare_ssl_connection(serverssl, clientssl,
-            SSL_ERROR_NONE, 0, 0)))
+    if (!TEST_true(create_bare_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE, 0, 0)))
 
         /*
          * Ensure that, now that we have used SSL_listen_ex, SSL_accept_connection
@@ -3012,8 +3011,8 @@ static int test_ssl_set_verify(void)
     serverssl = SSL_accept_connection(qlistener, 0);
 
     /* Call SSL_accept() and SSL_connect() until we are connected */
-    if (!TEST_true(create_bare_ssl_connection(serverssl, clientssl,
-            SSL_ERROR_NONE, 0, 0)))
+    if (!TEST_ptr(serverssl)
+        || !TEST_true(create_bare_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE, 0, 0)))
         goto err;
 
     testresult = 1;
@@ -3225,8 +3224,8 @@ static int test_client_hello_retry(void)
     serverssl = SSL_accept_connection(qlistener, 0);
 
     /* Call SSL_accept() and SSL_connect() until we are connected */
-    if (!TEST_true(create_bare_ssl_connection(serverssl, clientssl,
-            SSL_ERROR_NONE, 0, 0)))
+    if (!TEST_ptr(serverssl)
+        || !TEST_true(create_bare_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE, 0, 0)))
         goto err;
 
     testresult = 1;