ssl证书验证上的多线程c++应用程序内存泄漏

Memory leak with multi threaded c++ application on ssl certificate validation

本文关键字:c++ 应用程序 内存 泄漏 多线程 证书 验证 ssl      更新时间:2023-10-16

我们在多线程c++应用程序中使用openssl库。只有在启用SSL证书验证的情况下,由于内存泄漏,应用程序才会在3天内耗尽所有内存(7 GB实例)。

请在这里找到我的申请流程:

在应用程序启动时,我们创建了150个线程,用于同步3万用户数据,并为每个线程保留一个SSL_CTX_new对象。重复使用同一个对象,直到进程终止。对象SSL_CTX_new只在线程初始化时创建一次,它将被重新用于所有后续的SSL连接。

我们在处理用户数据方面做了以下工作:

线程通过ssl创建一个到第三方服务器的新套接字连接,一旦从第三方server获取了所需的数据,ssl连接就会终止。类似地,所有线程每次从队列中提取一个用户,并从服务器获取数据。

我们必须为所有3万用户进行上述连接、获取数据和断开ssl连接。

请找到我们应用程序的伪代码示例:

ThreadTask() 
{
    ssldata* ssl_data;
   1. Creates SSL connection: user_ssl_new_connect(ssl_data)
   2. Fetch users data
   3. Terminate ssl connection: ssl_abort()
}

char* user_ssl_new_connect(ssldata* ssl_data)  {
  SSL_CTX *ssl_context = InitsslonePerThread();
  if (!ssl_context) {
    if (!(ssl_context = SSL_CTX_new (SSLv23_client_method ()) {
      retur NULL;
    } 
  } 
  SSL_CTX_set_options (ssl_context,SSL_OP_NO_COMPRESSION|SSL_MODE_RELEASE_BUFFERS);
  SSL_CTX_set_verify (ssl_context,SSL_VERIFY_PEER,ssl_open_verify);
  SSL_CTX_set_default_verify_paths (ssl_context);
  char * s = "sslpath"
  SSL_CTX_load_verify_locations (ssl_context,s,NIL);
  SetsslconnectionPerThread(ssl_context);
  if (!(ssl_data->sslconnection = (SSL *) SSL_new (ssl_context)))
    return NULL
  bio = BIO_new_socket (ssl_data->sockettcpsi,BIO_NOCLOSE);
  SSL_set_bio (ssl_data->sslconnection,bio,bio);
  SSL_set_connect_state(ssl_data->sslconnection);
  if (SSL_in_init(ssl_data->sslconnection)) SSL_total_renegotiations (ssl_data->sslconnection);
      /* now negotiate SSL */
  if ((retval = SSL_write (ssl_data->sslconnection,"",0)) < 0) {    
    return NULL
  }
      /* validating host names? */
  if ((err = ssl_validate_cert (cert = SSL_get_peer_certificate   (sslconnection),host))) {
      return NULL;
  }
}

// one ssl_context per thread in global variable 
ssl_context* InitsslonePerThread() {
  yULong threadid;
  threadid = (unsigned long) pthread_self();
  if ssl_context is not created for this threadid
     returns new ssl_context.
  else 
    returns previous ssl_context.
}
void SetsslconnectionPerThread(ssl_context*) {
  yULong threadid;
  threadid = (unsigned long) pthread_self();
  #setting ssl_context in global variable 
}

long ssl_abort (ssldata* ssl_data)
{
   if (ssl_data->sslconnection) {        /* close SSL connection */
        SSL_shutdown (ssl_data->sslconnection);
        SSL_free (ssl_data->sslconnection);
    }
    return NIL;
}

static char *ssl_validate_cert (X509 *cert,char *host)
{
    int i,n;
    char *s,*t,*ret;
    void *ext;
    GENERAL_NAME *name;
    char tmp[MAILTMPLEN];
    /* make sure have a certificate */
    if (!cert) ret = "No certificate from server";
    /* and that it has a name */
    else if (!cert->name) ret = "No name in certificate";
    /* locate CN */
    else if (s = strstr (cert->name,"/CN=")) {
        if (t = strchr (s += 4,'/')) *t = '';
        /* host name matches pattern? */
        ret = ssl_compare_hostnames (host,s) ? NIL :
              "Server name does not match certificate";
        if (t) *t = '/';        /* restore smashed delimiter */
        /* if mismatch, see if in extensions */
        if (ret && (ext = X509_get_ext_d2i (cert,NID_subject_alt_name,NIL,NIL)) &&
            (n = sk_GENERAL_NAME_num (ext)))
            /* older versions of OpenSSL use "ia5" instead of dNSName */
            for (i = 0; ret && (i < n); i++)
                if ((name = sk_GENERAL_NAME_value (ext,i)) &&
                    (name->type = GEN_DNS) && (s = name->d.ia5->data) &&
                    ssl_compare_hostnames (host,s)) ret = NIL;
    } else ret = "Unable to locate common name in certificate";
    return ret;
}
if ((err = ssl_validate_cert (cert = SSL_get_peer_certificate (sslconnection),host))) {
    return NULL;
}

必须使用X509_free释放从SSL_get_peer_certificate返回的X509*。可能会有更多的泄漏,但这似乎与您对问题的描述一致。

内存泄漏也是OpenSSL的TLS客户端示例立即释放X509*的原因。它的引用计数,因此可以安全地递减计数并使用X509*,直到会话被破坏(SSL*)。

X509* cert = SSL_get_peer_certificate(ssl);
if(cert) { X509_free(cert); } /* Free immediately */
if(NULL == cert) handleFailure();
...

您还可能泄露了从名称漫游返回的一些名称。我不使用IA5字符串,所以我不确定。我使用UTF8字符串,必须释放它们。


以下是一些无关的评论。。。您可能应该包括SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3:

SSL_CTX_set_options (ssl_context,SSL_OP_NO_COMPRESSION|SSL_MODE_RELEASE_BUFFERS);

您可能还应该在某个位置指定SSL_set_tlsext_host_name,以便在托管环境中使用SNI,其中默认站点证书可能不是目标站点的证书。SNI是TLS的扩展,因此它说明了对SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3的需求。

在使用SSL_MODE_RELEASE_BUFFERS时,您还应该测试好应用程序。我似乎记得它造成了记忆错误。另请参阅问题2167:如果从多个线程使用OpenSSL,并且使用SSL_MODE_RELEASE_BUFFERS、CVE-2010-5298和Adam Langley的Overlocking SSL,则OpenSSL会失败。

wiki TLS客户端上提供的示例程序也可以帮助您进行名称匹配。据我所知,该代码很容易受到Marlinspike嵌入的NULL技巧的攻击。有关更多详细信息,请参阅他在实践中击败SSL的更多技巧上的Blackhat演讲。


这只是一个观察。。。既然你在使用C++,为什么不使用智能指针来管理你的资源?这样的东西在实践中运行得很好,而且它已经修复了泄漏,因为X509_ptr指定了析构函数:

X509_ptr cert(SSL_get_peer_certificate(ssl));
// Use cert, its free'd automatically
char* name = ssl_validate_cert(cert.get(), "www.example.com");

和:

using EC_KEY_ptr = std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)>;
using EC_GROUP_ptr = std::unique_ptr<EC_GROUP, decltype(&::EC_GROUP_free)>;
using EC_POINT_ptr = std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)>;
using DH_ptr = std::unique_ptr<DH, decltype(&::DH_free)>;
using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>;
using DSA_ptr = std::unique_ptr<DSA, decltype(&::DSA_free)>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
using FILE_ptr = std::unique_ptr<FILE, decltype(&::fclose)>;
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>;
using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
using ASN1_INTEGER_ptr = std::unique_ptr<ASN1_INTEGER, decltype(&::ASN1_INTEGER_free)>;
using ASN1_TIME_ptr = std::unique_ptr<ASN1_TIME, decltype(&::ASN1_TIME_free)>;
using X509_EXTENSION_ptr = std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)>;
using X509_NAME_ptr = std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)>;
using X509_NAME_ENTRY_ptr = std::unique_ptr<X509_NAME_ENTRY, decltype(&::X509_NAME_ENTRY_free)>;
using X509_STORE_ptr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
using X509_LOOKUP_ptr = std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
using X509_STORE_CTX_ptr = std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;