如何正确地取消初始化OpenSSL

在我的OpenSSL客户端中,我遇到了这样的问题,即当我select静态链接libeay32和ssleay32而不是dynamic时,我从Visual Leak Detector中得到了大量的内存泄漏错误。 我在这个线程中复制了来自OP的命令,但是我还剩下6个。 然后我添加了sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); 就像4LegsDrivenCat在同一个线程中所build议的一样,剩下的只剩下4个了,所有这些显然都与加载一个我用来比较服务器证书的可信证书有关。

我使用Visual Studio 2013 Express,OpenSSL 1.0.1L(32和64位),VLD 2.4RC2和我的电脑是Windows 7 64位。

下面的callstack是安全模式下VLD的64位。 在32位VLD崩溃在安全模式(虽然它在快速模式下工作,但不会产生一个像样的调用堆栈)。 我删除了引用我自己的函数以及hex数据的callstack部分。

 Visual Leak Detector Version 2.4RC2 installed. WARNING: Visual Leak Detector detected memory leaks! ---------- Block 5671 at 0x000000000097E9B0: 180 bytes ---------- Leak Hash: 0xA14DA3AA, Count: 1, Total 180 bytes Call Stack (TID 7088): 0x000000007746FAC0 (File and line number not available): ntdll.dll!RtlAllocateHeap f:\dd\vctools\crt\crtw32\heap\malloc.c (58): MyLib.dll!_heap_alloc_base f:\dd\vctools\crt\crtw32\misc\dbgheap.c (431): MyLib.dll!_heap_alloc_dbg_impl + 0xA bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (239): MyLib.dll!_nh_malloc_dbg_impl + 0x22 bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (302): MyLib.dll!_nh_malloc_dbg + 0x2A bytes f:\dd\vctools\crt\crtw32\misc\dbgmalloc.c (56): MyLib.dll!malloc + 0x21 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\mem.c (312): MyLib.dll!CRYPTO_malloc + 0xF bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\lhash\lhash.c (121): MyLib.dll!lh_new + 0x16 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (450): MyLib.dll!int_thread_get + 0x13 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (509): MyLib.dll!int_thread_set_item + 0xF bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (1031): MyLib.dll!ERR_get_state d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (730): MyLib.dll!ERR_put_error + 0x5 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_lib.c (703): MyLib.dll!PEM_read_bio + 0x20 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_info.c (280): MyLib.dll!PEM_X509_INFO_read_bio + 0x10 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (278): MyLib.dll!X509_load_cert_crl_file d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (123): MyLib.dll!by_file_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_lu.c (120): MyLib.dll!X509_LOOKUP_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_d2.c (92): MyLib.dll!X509_STORE_load_locations + 0x1D bytes ---------- Block 5670 at 0x000000001AC815C0: 164 bytes ---------- Leak Hash: 0x38C8916E, Count: 1, Total 164 bytes Call Stack (TID 7088): 0x000000007746FAC0 (File and line number not available): ntdll.dll!RtlAllocateHeap f:\dd\vctools\crt\crtw32\heap\malloc.c (58): MyLib.dll!_heap_alloc_base f:\dd\vctools\crt\crtw32\misc\dbgheap.c (431): MyLib.dll!_heap_alloc_dbg_impl + 0xA bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (239): MyLib.dll!_nh_malloc_dbg_impl + 0x22 bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (302): MyLib.dll!_nh_malloc_dbg + 0x2A bytes f:\dd\vctools\crt\crtw32\misc\dbgmalloc.c (56): MyLib.dll!malloc + 0x21 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\mem.c (312): MyLib.dll!CRYPTO_malloc + 0xF bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\lhash\lhash.c (119): MyLib.dll!lh_new + 0x13 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (450): MyLib.dll!int_thread_get + 0x13 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (509): MyLib.dll!int_thread_set_item + 0xF bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (1031): MyLib.dll!ERR_get_state d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (730): MyLib.dll!ERR_put_error + 0x5 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_lib.c (703): MyLib.dll!PEM_read_bio + 0x20 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_info.c (280): MyLib.dll!PEM_X509_INFO_read_bio + 0x10 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (278): MyLib.dll!X509_load_cert_crl_file d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (123): MyLib.dll!by_file_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_lu.c (120): MyLib.dll!X509_LOOKUP_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_d2.c (92): MyLib.dll!X509_STORE_load_locations + 0x1D bytes ---------- Block 5669 at 0x000000001ADABE80: 588 bytes ---------- Leak Hash: 0xC3E47B0F, Count: 1, Total 588 bytes Call Stack (TID 7088): 0x000000007746FAC0 (File and line number not available): ntdll.dll!RtlAllocateHeap f:\dd\vctools\crt\crtw32\heap\malloc.c (58): MyLib.dll!_heap_alloc_base f:\dd\vctools\crt\crtw32\misc\dbgheap.c (431): MyLib.dll!_heap_alloc_dbg_impl + 0xA bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (239): MyLib.dll!_nh_malloc_dbg_impl + 0x22 bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (302): MyLib.dll!_nh_malloc_dbg + 0x2A bytes f:\dd\vctools\crt\crtw32\misc\dbgmalloc.c (56): MyLib.dll!malloc + 0x21 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\mem.c (312): MyLib.dll!CRYPTO_malloc + 0xF bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (1019): MyLib.dll!ERR_get_state + 0x17 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (730): MyLib.dll!ERR_put_error + 0x5 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_lib.c (703): MyLib.dll!PEM_read_bio + 0x20 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_info.c (280): MyLib.dll!PEM_X509_INFO_read_bio + 0x10 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (278): MyLib.dll!X509_load_cert_crl_file d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (123): MyLib.dll!by_file_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_lu.c (120): MyLib.dll!X509_LOOKUP_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_d2.c (92): MyLib.dll!X509_STORE_load_locations + 0x1D bytes ---------- Block 5672 at 0x000000001ADC4180: 76 bytes ---------- Leak Hash: 0x02B2EA5E, Count: 1, Total 76 bytes Call Stack (TID 7088): 0x000000007746FAC0 (File and line number not available): ntdll.dll!RtlAllocateHeap f:\dd\vctools\crt\crtw32\heap\malloc.c (58): MyLib.dll!_heap_alloc_base f:\dd\vctools\crt\crtw32\misc\dbgheap.c (431): MyLib.dll!_heap_alloc_dbg_impl + 0xA bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (239): MyLib.dll!_nh_malloc_dbg_impl + 0x22 bytes f:\dd\vctools\crt\crtw32\misc\dbgheap.c (302): MyLib.dll!_nh_malloc_dbg + 0x2A bytes f:\dd\vctools\crt\crtw32\misc\dbgmalloc.c (56): MyLib.dll!malloc + 0x21 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\mem.c (312): MyLib.dll!CRYPTO_malloc + 0xF bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\lhash\lhash.c (193): MyLib.dll!lh_insert + 0x15 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (515): MyLib.dll!int_thread_set_item d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (1031): MyLib.dll!ERR_get_state d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c (730): MyLib.dll!ERR_put_error + 0x5 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_lib.c (703): MyLib.dll!PEM_read_bio + 0x20 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\pem\pem_info.c (280): MyLib.dll!PEM_X509_INFO_read_bio + 0x10 bytes d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (278): MyLib.dll!X509_load_cert_crl_file d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\by_file.c (123): MyLib.dll!by_file_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_lu.c (120): MyLib.dll!X509_LOOKUP_ctrl d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\x509\x509_d2.c (92): MyLib.dll!X509_STORE_load_locations + 0x1D bytes Visual Leak Detector detected 4 memory leaks (1008 bytes). Largest number used: 529114 bytes. Total allocations: 1070421 bytes. Visual Leak Detector is now exiting. 

编辑:我把泄漏locking到对SSL_CTX_load_verify_locations的调用。 有谁知道我需要什么时候使用这个function取消分配? 只是评论这个函数导致泄漏消失,所以这不是因为我传递给它的参数。

d:\ cfiles \项目\ winssl \的OpenSSL 1.0.1l \加密\ ERR \ err.c

锁定在这里,它看起来像一些错误状态(或字符串)需要free'd。


如何正确地取消初始化OpenSSL

启动和关闭代码如下所示(包括FIPS)。 如果你做加载DH参数的东西,那么你也需要清理它们。

Henson博士对于他为每个线程调用ERR_remove_state建议非常有帮助。 请参阅清理顺序以避免内存泄漏? 在OpenSSL邮件列表上。

启动

  • SSL_library_init();
  • SSL_load_error_strings();
  • FIPS_mode_set(1);
  • CRYPTO_set_id_callback(<fn>);
  • CRYPTO_set_locking_callback(<fn>);

关掉

  • FIPS_mode_set(0);
  • CRYPTO_set_locking_callback(NULL);
  • CRYPTO_set_id_callback(NULL);
  • ENGINE_cleanup();
  • CONF_modules_unload();
  • ERR_free_strings();
  • EVP_cleanup();
  • CRYPTO_cleanup_all_ex_data();

而且,对于每个线程:

  • ERR_remove_state();

如果你的程序是多线程的,你只需要CRYPTO_set_id_callbackCRYPTO_set_locking_callback 请参阅Openssl 线程(3)手册页 。

我相信你可以在OpenSSL 1.0.2及更高SSL_COMP_free_compression_methods中调用SSL_COMP_free_compression_methods 这解决了下面的一些投诉。 但是它在OpenSSL 1.0.1及以下版本中不可用。


SSL_COMP_get_compression_methods()…

是的,这会导致泄漏。 它是众所周知的,由于ssl_comp_methods懒惰地分配,但从来没有释放。 请参阅OpenSSL问题2561:使用SSL内置压缩的内存泄漏 。

一些OpenSSL开发人员觉得不值得他们的时间来解决这个问题。 请参阅OpenSSL邮件列表上的多线程服务器上的小内存泄漏 。

以下是开发者的立场之一:

固定数量的内存不被释放,独立于执行操作的数量,不是内存泄漏。 在一次初始化或首次使用函数期间,库为进程的整个生命周期分配内存。 这个是正常的。

跟踪下来是浪费时间恕我直言。

这里是关于特定泄漏的另一个线程: 首选的方法来释放ssl_comp_methods? 这就是同样的开发者的回应:

为什么有人痴迷于释放分配给静态指针最多一次的内存。 没有与这种分配相关的“内存泄漏”,因为所使用的额外内存量是固定的。

他没有意识到的是Java和.Net会在一个程序的生命周期中多次加载/卸载这个库,这样少量的“谁在乎”就会变得无限。

当他被告知备用案例时,这是他的回答。 我想,他建议甲骨文和Java重新设计他们的语言。 或者不要在他们中使用OpenSSL。

卸载共享库通常是不安全的。

这是维护Java VM的人之一的回应:

作为一个“另类”JavaVM的维护者,我可以确认,我们绝对不得不支持库卸载,因为一个客户正在大量使用它 – 而那是几年前。 早期的Sun虚拟机不支持库卸载,但那些虚拟机也没有垃圾收集过时的类。


这里是关于修复ssl_comp_methods泄漏的一节。

在任何情况下,您都需要添加下面描述的修补程序。

对于手动执行的程序,只需添加一个名为free_compressions (或类似的函数)的函数,并像上面列出的所有其他方法那样在关闭时调用它。 该功能需要导出。

要在Linux下自动完成,需要一点小小的诡计。 你必须使用GCC扩展: __attribute__ ((destructor))

 /* Add to end of <openssl dir>/ssl/ssl_ciph.c */ #if !defined(OPENSSL_NO_COMP) && defined(__GNU_C__) void free_compressions(void) __attribute__ ((destructor)); void free_compressions(void) { if (ssl_comp_methods != NULL) { sk_SSL_COMP_free(ssl_comp_methods); ssl_comp_methods = NULL; } } #endif 

要在Windows下自动完成,你必须在DllMain 但是你必须小心你在DllMain什么 ,以及你如何做。 所以可能是这样的:

 /* Add to end of <openssl dir>/ssl/ssl.h*/ #if !defined(OPENSSL_NO_COMP) && defined(WIN32) __declspec(dllexport) void free_compressions(void); #endof /* Add to end of <openssl dir>/ssl/ssl_ciph.c */ #if !defined(OPENSSL_NO_COMP) && defined(WIN32) void free_compressions(void) { if (ssl_comp_methods != NULL) { sk_SSL_COMP_free(ssl_comp_methods); ssl_comp_methods = NULL; } } #endif 

—–

为什么这个线程downvoted? 我链接的线程是少得多的细节,它得到10 upvotes(加一个从我)。 你们在过去几年里变得更严格了吗?

看看这个关键的原因( 你现在不能这样做 ),最后的投票结果如下:

寻求调试帮助的问题(“为什么这个代码不工作?”)必须包含所需的行为,特定的问题或错误,以及在问题本身中重现问题所需的最短代码。

通常这适用。 但在你的情况下,它不; 对那些不熟悉这个问题的人来说,这一点并不明显。 事实上,你可以写一个简单的程序,只是初始化,然后单元化库,它可能会泄漏…

作为一个政策的问题,该网站不能规定“除了一些OpenSSL内存泄漏之外,总是提供相关的代码” (这实际上是我们需要处理您的情况)。

除了jww文章之外,如果你的OpenSSL版本是用zlib库构建的,那么你应该添加COMP_zlib_cleanup(); 关机部分。 因为它的DSO模块在默认情况下没有被释放。 所以,完整的关机代码应该是:

 FIPS_mode_set(0); CRYPTO_set_locking_callback(nullptr); CRYPTO_set_id_callback(nullptr); ERR_remove_state(0); SSL_COMP_free_compression_methods(); ENGINE_cleanup(); CONF_modules_free(); CONF_modules_unload(1); COMP_zlib_cleanup(); ERR_free_strings(); EVP_cleanup(); CRYPTO_cleanup_all_ex_data();