我们在多线程c++应用程序中使用openssl库。只有在启用SSL证书验证的情况下,应用程序才会在3天内(7 GB实例)内因为内存泄漏而耗尽所有内存。
请在这里找到我的申请流程:
在应用程序启动时,我们创建了150个线程,用于同步30k用户数据,并为每个线程保留一个SSL_CTX_new对象。相同的对象将被重用,直到进程被终止。对象SSL_CTX_new只在线程初始化时创建一次,它将被用于所有后续的ssl连接。
我们在处理用户数据时执行以下操作:
线程通过ssl创建到第三方服务器的新套接字连接,一旦从第三方服务器获取所需的数据,ssl连接就被终止。类似地,所有线程一次从队列中获取一个用户,并从服务器获取数据。
我们必须为所有30k用户进行上述连接、获取数据和断开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 = '\0';
/* 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;
}发布于 2016-07-19 07:15:36
if ((err = ssl_validate_cert (cert = SSL_get_peer_certificate (sslconnection),host))) {
return NULL;
}您必须使用X509*从SSL_get_peer_certificate中释放返回的X509_free。可能会有更多的漏洞,但这似乎与你对问题的描述一致。
内存泄漏也是OpenSSL的TLS客户端示例立即释放X509*的原因。它的引用计数,因此可以安全地减少计数并使用X509*,直到会话被销毁( SSL*)。
X509* cert = SSL_get_peer_certificate(ssl);
if(cert) { X509_free(cert); } /* Free immediately */
if(NULL == cert) handleFailure();
...您还可能泄漏了从名称may返回的一些名称。我不使用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时,您还应该很好地测试应用程序。我好像记得它导致了记忆错误。也见缓冲器,CVE-2010-5298,和Adam的超频SSL。
wiki TLS客户端上提供的示例程序也可以帮助您进行名称匹配。据我所知,代码很容易受到Marlinspike嵌入的空技巧的攻击。有关更多细节,请参见他在更多在实践中击败SSL的技巧的黑帽演讲。
这只是一个观察..。既然您使用的是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)>;https://stackoverflow.com/questions/38450767
复制相似问题