How to properly uninitialize OpenSSL

2019-01-07 18:44发布

In my OpenSSL client I have the problem that the very moment I chose to link libeay32 and ssleay32 statically instead of dynamically I got tons of memory leak errors from Visual Leak Detector. I copied the commands from the OP in this thread, but I still had 6 left. Then I added sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); as advised by 4LegsDrivenCat in the same thread and only 4 more were left, all of which are apparently related to loading a trusted certificate which I use to compare to the server's certificate.

I use Visual Studio 2013 Express, OpenSSL 1.0.1L (both 32 and 64 Bit), VLD 2.4RC2 and my PC is Windows 7 64 Bit.

The callstack below is 64 Bit from VLD in safe mode. In 32 Bit VLD crashed in safe mode (while it works in fast mode but doesn't yield a decent callstack). I removed the parts of the callstack that referred to my own functions as well as the hex-data.

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.

edit: I pinned the leaks down to the call to SSL_CTX_load_verify_locations. Does anyone know what I need to de-allocate when using this function? Just commenting this function causes the leaks to disappear, so it's not because of the parameters I pass to it.

2条回答
SAY GOODBYE
2楼-- · 2019-01-07 19:12

d:\cfiles\projects\winssl\openssl-1.0.1l\crypto\err\err.c

Latching on to this, it looks like some error state (or strings) needs to be free'd.


How to properly uninitialize OpenSSL

The code for startup and shutdown is shown below (including FIPS). If you do things like load DH parameters, then you will need to clean them up, too.

Dr. Henson was very helpful with his suggestion of calling ERR_remove_state for each thread. See Order of Cleanup to avoid memory leaks? on the OpenSSL mailing list.

Startup

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

Shutdown

  • 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();

And, for each thread:

  • ERR_remove_state();

You only need CRYPTO_set_id_callback and CRYPTO_set_locking_callback if your program is multi-threaded. See the Openssl threads(3) man page.

I believe you can call SSL_COMP_free_compression_methods in OpenSSL 1.0.2 and above. That addresses some of the complaint below. But its not available in OpenSSL 1.0.1 and below.


SSL_COMP_get_compression_methods()...

Yeah, this causes a leak. Its well known and due to ssl_comp_methods being lazily allocated but never freed. See OpenSSL Issue 2561: Memory leak with SSL built-in compressions.

Some of the OpenSSL devs don't feel its worth their time to fix it. See Small memory leak on multithreaded server on the OpenSSL mailing list.

The following is one of the dev's position on it:

A fixed amount of memory that is not deallocated and is independent of the number of operations performed, is NOT a memory leak. Libraries to allocate memory for the lifetime of the process during one time initialization or first use of a function. This is normal.

Tracking this down is a waste of time IMHO.

And here's another thread about that particular leak: Preferred way to free ssl_comp_methods?. And here's that same dev's response:

Why is anyone obsessed about freeing memory that is assigned to static pointers at most once. There's no "memory leak" associated with such allocations because the amount of extra memory used is fixed.

What he failed to realize is Java and .Net will load/unload the library many times during a program's lifecycle, so that small amount of "who cares" can grow unbounded.

When he was told about the alternate use case, here was his reply. I guess, he is suggesting that Oracle and Java re-architect their languages. Or don't use OpenSSL in them.

Unloading of shared libraries is generally unsafe.

Here was the response of one of the folks who maintain a Java VM:

As the maintainer of an "alternative" JavaVM I can confirm that we absolutely had to support library unloading because one customer was using it heavily - and that was quite a few years ago. Early Sun VMs didn't support library unloading, but then those VMs also did not garbage-collect obsolete classes either.


Here a section on fixing the ssl_comp_methods leak.

In all cases, you will need to add the patch described below.

For a program to do it manually, just add a function named free_compressions (or similar) and call it on shutdown like all the other methods listed above. The function needs to be exported.

To do it automatically under Linux, its takes a little trickery. You have to use a GCC extension: __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 

To do it automatically under Windows, you have to do it in DllMain. But you have to be careful about what you do in DllMain, and how you do it. So maybe something like:

/* 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 

-----

Why is this thread being downvoted? The thread I linked is a lot less detailed and it got 10 upvotes (plus one from me). Did you guys become a lot more strict in the last few years?

Looking at the close reason (which you can't do at the moment), the close vote was cast with the reason:

Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself.

Normally that applies. But in your case it does not; and its not readily apparent to those not familiar with the issue. In fact, you could write a simple program that just initializes and then unitializes the library and it will probably leak...

As a matter of policy, the site can't make a rule "Always provide relevant code except for some OpenSSL memory leaks" (which is effectively what we need to handle your case).

查看更多
我想做一个坏孩纸
3楼-- · 2019-01-07 19:25

Small addition to jww post, if your OpenSSL version is built with zlib library then you should add COMP_zlib_cleanup(); to shutdown section. Because its DSO module is not freed by default. So, full shutdown code should be:

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();
查看更多
登录 后发表回答