CVE-2021-22901 (C, published 2021-06-11T16:15Z)
Description: value: curl 7.75.0 through 7.76.1 suffers from a use-after-free vulnerability resulting in already freed memory being used when a TLS 1.3 session ticket arrives over a connection. A malicious server can use this in rare unfortunate circumstances to potentially reach remote code execution in the client. When libcurl at run-time sets up support for TLS 1.3 session tickets on a connection using OpenSSL, it stores pointers to the transfer in-memory object for later retrieval when a session ticket arrives. If the connection is used by multiple transfers (like with a reused HTTP/1.1 connection or multiplexed HTTP/2 connection) that first transfer object might be freed before the new session is established on that connection and then the function will access a memory buffer that might be freed. When using that memory, libcurl might even call a function pointer in the object, making it possible for a remote code execution if the server could somehow manage to get crafted memory content into the correct place in memory.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-416 \u2014 Use After Free
The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
Repository: https://github.com/curl/curl
Files changed:
lib/multi.c
lib/vtls/gskit.c
lib/vtls/gtls.c
lib/vtls/mbedtls.c
lib/vtls/mesalink.c
lib/vtls/nss.c
lib/vtls/openssl.c
lib/vtls/rustls.c
lib/vtls/schannel.c
lib/vtls/sectransp.c
lib/vtls/vtls.c
lib/vtls/vtls.h
lib/vtls/wolfssl.c
Commit message: openssl: associate/detach the transfer from connection

CVE-2021-22901

Bug: https://curl.se/docs/CVE-2021-22901.html


Vulnerable code:
  if(conn)
  NULL                            /* sha256sum */
  gtls_sha256sum                 /* sha256sum */
  mbedtls_sha256sum                 /* sha256sum */
  NULL                           /* sha256sum */
  nss_sha256sum                 /* sha256sum */
  /* Check if there's a cached ID we can/should use here! */
  if(SSL_SET_OPTION(primary.sessionid)) {
    void *ssl_sessionid = NULL;
    int data_idx = ossl_get_ssl_data_index();
    int connectdata_idx = ossl_get_ssl_conn_index();
    int sockindex_idx = ossl_get_ssl_sockindex_index();
    int proxy_idx = ossl_get_proxy_index();

    if(data_idx >= 0 && connectdata_idx >= 0 && sockindex_idx >= 0 &&
       proxy_idx >= 0) {
      /* Store the data needed for the "new session" callback.
       * The sockindex is stored as a pointer to an array element. */
      SSL_set_ex_data(backend->handle, data_idx, data);
      SSL_set_ex_data(backend->handle, connectdata_idx, conn);
      SSL_set_ex_data(backend->handle, sockindex_idx, conn->sock + sockindex);
#ifndef CURL_DISABLE_PROXY
      SSL_set_ex_data(backend->handle, proxy_idx, SSL_IS_PROXY() ? (void *) 1:
                      NULL);
#else
      SSL_set_ex_data(backend->handle, proxy_idx, NULL);
#endif

    }
    Curl_ssl_sessionid_lock(data);
    if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
                              &ssl_sessionid, NULL, sockindex)) {
      /* we got a session id, use it! */
      if(!SSL_set_session(backend->handle, ssl_sessionid)) {
        Curl_ssl_sessionid_unlock(data);
        failf(data, "SSL: SSL_set_session failed: %s",
              ossl_strerror(ERR_get_error(), error_buffer,
                            sizeof(error_buffer)));
        return CURLE_SSL_CONNECT_ERROR;
      }
      /* Informational message */
      infof(data, "SSL re-using session ID\n");
    Curl_ssl_sessionid_unlock(data);
  ossl_sha256sum            /* sha256sum */
  NULL                      /* sha256sum */
  NULL                             /* sha256sum */
                int *algIds)
  schannel_sha256sum                 /* sha256sum */
  NULL                               /* sha256sum */
  wolfssl_sha256sum                /* sha256sum */

Fixed code:
  if(conn) {
    Curl_ssl_detach_conn(data, conn);
  }
  Curl_ssl_associate_conn(data, conn);
  NULL,                           /* sha256sum */
  NULL,                           /* associate_connection */
  NULL                            /* disassociate_connection */
  gtls_sha256sum,                /* sha256sum */
  NULL,                          /* associate_connection */
  NULL                           /* disassociate_connection */
  mbedtls_sha256sum,                /* sha256sum */
  NULL,                             /* associate_connection */
  NULL                              /* disassociate_connection */
  NULL,                          /* sha256sum */
  NULL,                          /* associate_connection */
  NULL                           /* disassociate_connection */
  nss_sha256sum,                /* sha256sum */
  NULL,                         /* associate_connection */
  NULL                          /* disassociate_connection */
static void ossl_associate_connection(struct Curl_easy *data,
                                      struct connectdata *conn,
                                      int sockindex);

  void *ssl_sessionid = NULL;
  ossl_associate_connection(data, conn, sockindex);
  Curl_ssl_sessionid_lock(data);
  if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
                            &ssl_sessionid, NULL, sockindex)) {
    /* we got a session id, use it! */
    if(!SSL_set_session(backend->handle, ssl_sessionid)) {
      Curl_ssl_sessionid_unlock(data);
      failf(data, "SSL: SSL_set_session failed: %s",
            ossl_strerror(ERR_get_error(), error_buffer,
                          sizeof(error_buffer)));
      return CURLE_SSL_CONNECT_ERROR;
    /* Informational message */
    infof(data, "SSL re-using session ID\n");
  Curl_ssl_sessionid_unlock(data);
static void ossl_associate_connection(struct Curl_easy *data,
                                      struct connectdata *conn,
                                      int sockindex)
{
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  struct ssl_backend_data *backend = connssl->backend;

  /* If we don't have SSL context, do nothing. */
  if(!backend->handle)
    return;

  if(SSL_SET_OPTION(primary.sessionid)) {
    int data_idx = ossl_get_ssl_data_index();
    int connectdata_idx = ossl_get_ssl_conn_index();
    int sockindex_idx = ossl_get_ssl_sockindex_index();
    int proxy_idx = ossl_get_proxy_index();

    if(data_idx >= 0 && connectdata_idx >= 0 && sockindex_idx >= 0 &&
       proxy_idx >= 0) {
      /* Store the data needed for the "new session" callback.
       * The sockindex is stored as a pointer to an array element. */
      SSL_set_ex_data(backend->handle, data_idx, data);
      SSL_set_ex_data(backend->handle, connectdata_idx, conn);
      SSL_set_ex_data(backend->handle, sockindex_idx, conn->sock + sockindex);
#ifndef CURL_DISABLE_PROXY
      SSL_set_ex_data(backend->handle, proxy_idx, SSL_IS_PROXY() ? (void *) 1:
                      NULL);
#else
      SSL_set_ex_data(backend->handle, proxy_idx, NULL);
#endif
    }
  }
}

/*
 * Starting with TLS 1.3, the ossl_new_session_cb callback gets called after
 * the handshake. If the transfer that sets up the callback gets killed before
 * this callback arrives, we must make sure to properly clear the data to
 * avoid UAF problems. A future optimization could be to instead store another
 * transfer that might still be using the same connection.
 */

static void ossl_disassociate_connection(struct Curl_easy *data,
                                         int sockindex)
{
  struct connectdata *conn = data->conn;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  struct ssl_backend_data *backend = connssl->backend;

  /* If we don't have SSL context, do nothing. */
  if(!backend->handle)
    return;

  if(SSL_SET_OPTION(primary.sessionid)) {
    bool isproxy = FALSE;
    bool incache;
    void *old_ssl_sessionid = NULL;
    int data_idx = ossl_get_ssl_data_index();
    int connectdata_idx = ossl_get_ssl_conn_index();
    int sockindex_idx = ossl_get_ssl_sockindex_index();
    int proxy_idx = ossl_get_proxy_index();

    if(data_idx >= 0 && connectdata_idx >= 0 && sockindex_idx >= 0 &&
       proxy_idx >= 0) {
      /* Invalidate the session cache entry, if any */
      isproxy = SSL_get_ex_data(backend->handle, proxy_idx) ? TRUE : FALSE;

      /* Disable references to data in "new session" callback to avoid
       * accessing a stale pointer. */
      SSL_set_ex_data(backend->handle, data_idx, NULL);
      SSL_set_ex_data(backend->handle, connectdata_idx, NULL);
      SSL_set_ex_data(backend->handle, sockindex_idx, NULL);
      SSL_set_ex_data(backend->handle, proxy_idx, NULL);
    }

    Curl_ssl_sessionid_lock(data);
    incache = !(Curl_ssl_getsessionid(data, conn, isproxy,
                                      &old_ssl_sessionid, NULL, sockindex));
    if(incache)
      Curl_ssl_delsessionid(data, old_ssl_sessionid);
    Curl_ssl_sessionid_unlock(data);
  }
}

  ossl_sha256sum,           /* sha256sum */
  NULL,                     /* sha256sum */
  ossl_associate_connection, /* associate_connection */
  ossl_disassociate_connection /* disassociate_connection */
  NULL,                            /* sha256sum */
  NULL,                            /* associate_connection */
  NULL                             /* disassociate_connection */
                ALG_ID *algIds)
  schannel_sha256sum,                /* sha256sum */
  NULL,                              /* associate_connection */
  NULL                               /* disassociate_connection */
  NULL,                               /* associate_connection */
  NULL                                /* disassociate_connection */
void Curl_ssl_associate_conn(struct Curl_easy *data,
                             struct connectdata *conn)
{
  if(Curl_ssl->associate_connection) {
    Curl_ssl->associate_connection(data, conn, FIRSTSOCKET);
    if(conn->sock[SECONDARYSOCKET] && conn->bits.sock_accepted)
      Curl_ssl->associate_connection(data, conn, SECONDARYSOCKET);
  }
}

void Curl_ssl_detach_conn(struct Curl_easy *data,
                          struct connectdata *conn)
{
  if(Curl_ssl->disassociate_connection) {
    Curl_ssl->disassociate_connection(data, FIRSTSOCKET);
    if(conn->sock[SECONDARYSOCKET] && conn->bits.sock_accepted)
      Curl_ssl->disassociate_connection(data, SECONDARYSOCKET);
  }
}
  NULL,                              /* sha256sum */
  NULL,                              /* associate_connection */
  NULL                               /* disassociate_connection */

  void (*associate_connection)(struct Curl_easy *data,
                               struct connectdata *conn,
                               int sockindex);
  void (*disassociate_connection)(struct Curl_easy *data, int sockindex);
void Curl_ssl_associate_conn(struct Curl_easy *data,
                             struct connectdata *conn);
void Curl_ssl_detach_conn(struct Curl_easy *data,
                          struct connectdata *conn);

#define Curl_ssl_associate_conn(a,b) Curl_nop_stmt
#define Curl_ssl_detach_conn(a,b) Curl_nop_stmt
  wolfssl_sha256sum,               /* sha256sum */
  NULL,                            /* associate_connection */
  NULL                             /* disassociate_connection */