ssl证书验证上的多线程c++应用程序内存泄漏
Memory leak with multi threaded c++ application on ssl certificate validation
我们在多线程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)>;
- 试图在visual studio上用C++创建一个桌面应用程序
- FFmpeg:制作一个应用程序比直接使用ffmepg更好吗
- 在C应用程序中运行C++(带有STL)函数
- 使用VerQueryValue检索应用程序的文件描述
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 使用调试/崩溃报告将应用程序部署到客户端
- C++控制台应用程序阻止退出
- 码头化的C++应用程序是否向后兼容早期的内核版本
- 将应用程序从32位移植到64位时出现问题
- 如何改变c++应用程序的视觉效果
- WM_CTLCOLORSTATIC从未在WIN32应用程序中触发
- PC中的程序和PHONE中的本机描述应用程序之间的数据连接
- 应用程序崩溃并显示"symbol _ZdlPvm, version Qt_5 not defined in file libQt5Core.so.5 with link time reference"
- 示例外壳应用程序显示的 V8 "segmentation fault (core dumped)"错误
- phytec phyBOARD iMX-6在从闪存而不是SD卡运行qt5 opengles应用程序时表现不佳(FPS减半
- 为什么导入Mixed native/CLR lib.dll的本机C++应用程序没有在Mixed lib.dll中的外部变
- 如何从Windows应用程序输出到标准?
- C++应用程序 MySQL odbc 数据库连接错误:在引发"otl_tmpl_exception<>"实例后终止调用
- 如何在 64 位 vb.net Windows 应用程序中引用 32 位 dll
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?