I was able to dynamically load OpenSSL, initialize it, unload it, and then reload it back up again using your example along with some of my cleanup code. Since I don't know your specific use case, I don't know if something like this will work for you, but figured I'd send it along just in case. Here's the console output:

[la...@magma Desktop]$ gcc -std=gnu99 -g -ldl ssl-dlopen-crash.c 2>&1 ; /a.out ; valgrind --tool=memcheck --leak-check=yes ./a.out
Opening libssl.so...
Initializing with OpenSSL...
Closing libssl.so...
Opening libssl.so...
Initializing with OpenSSL...
Closing libssl.so...
==21089== Memcheck, a memory error detector
==21089== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==21089== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==21089== Command: ./a.out
==21089==
Opening libssl.so...
Initializing with OpenSSL...
Closing libssl.so...
Opening libssl.so...
Initializing with OpenSSL...
Closing libssl.so...
==21089==
==21089== HEAP SUMMARY:
==21089==     in use at exit: 0 bytes in 0 blocks
==21089== total heap usage: 5,293 allocs, 5,293 frees, 207,568 bytes allocated
==21089==
==21089== All heap blocks were freed -- no leaks are possible
==21089==
==21089== For counts of detected and suppressed errors, rerun with: -v
==21089== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 22 from 9)

And a variant of the cleanup function I took from my actual application:

void ssl_stop(void) {

    if (ssl_locks != NULL) {

        ERR_remove_state_d(0);
        COMP_zlib_cleanup_d();
        CONF_modules_unload_d(1);
        OBJ_cleanup_d();
        OBJ_NAME_cleanup_d(-1);
        BIO_sock_cleanup_d();
        EVP_cleanup_d();
        ENGINE_cleanup_d();
        CRYPTO_cleanup_all_ex_data_d();
        ERR_free_strings_d();
        ASN1_STRING_TABLE_cleanup_d();
        CRYPTO_set_locking_callback_d(NULL);
        RAND_cleanup_d();

// The SSL compression method stack doesn't get freed properly by any of the functions above. I'm looking at you Mr COMP_zlib_cleanup.
        // This was necessary as of 1.0.0a, but may be fixed at some point.
sk_pop_free_d((_STACK *)SSL_COMP_get_compression_methods_d(), bl_free);

        // Destroy and free all of the mutexes.
        for (uint64_t i = 0; i < CRYPTO_num_locks_d(); i++) {
            mutex_destroy(*(ssl_locks + i));
            bl_free(*(ssl_locks + i));
        }

        free(ssl_locks);
        ssl_locks = NULL;
    }

    return;
}


--
Ladar Levison
Lavabit LLC
http://lavabit.com

/*
 * Demo for the SSL memory corruption bug. The problem is if libssl is
 * dlopen()ed, SSL error strings loaded, and the library is dlclose()d then. The
 * hash string table built in memory still uses references to the memory
 * previously occupied by libssl since there is no clean-up. If we map
 * non-accessible memory to that address range, the next
 * SSL_load_error_strings() causes a SIGSEGV.
 *
 * Tested with 0.9.8e, 0.9.8o, and 1.0.0a. See a comment with mmap() below in
 * the code if you want to run this with 1.0.0.
 *
 * Compile:
 *
 * $ gcc -g -lcrypto ssl-dlopen-crash.c 
 *
 * Stack trace on FreeBSD 7.2:
 * #0  0x2813da5a in ERR_unload_strings () from /lib/libcrypto.so.5
 * #1  0x2818393e in lh_retrieve () from /lib/libcrypto.so.5
 * #2  0x2813e38e in ERR_get_implementation () from /lib/libcrypto.so.5
 * #3  0x2813dd77 in ERR_func_error_string () from /lib/libcrypto.so.5
 * #4  0x2844e1c0 in ERR_load_SSL_strings () from /usr/lib/libssl.so
 * #5  0x2844e18c in SSL_load_error_strings () from /usr/lib/libssl.so
 * #6  0x080488f4 in testssl () at ssl-dlopen-freebsd.c:53
 * #7  0x08048a11 in main () at ssl-dlopen-freebsd.c:113
 *
 * Stack trace on OpenSolaris:
 * libcrypto.so.0.9.8`err_cmp+6(fecb70b0, 804775c, 804774c, feeda26e)
 * libcrypto.so.0.9.8`getrn+0x7d(8061228, 804775c, 8047710, 110)
 * libcrypto.so.0.9.8`lh_retrieve+0x1e(8061228, 804775c, fef4b408, 12a)
 * libcrypto.so.0.9.8`int_err_get_item+0x51(804775c, fef69000, 804776c, 
feedd808)
 * libcrypto.so.0.9.8`ERR_func_error_string+0x47(14064000, fef69000, 804778c,
 *                                               fe82cff8)
 * libssl.so.0.9.8`ERR_load_SSL_strings+0x23(80477ac)
 * libssl.so.0.9.8`SSL_load_error_strings+0x1d(fe822330, feaa0a80, 80477b8,
 *                                             8050f03, 8047898, 80477e4)
 * testssl+0x76(8047898, 80477e4, 8050d3d, 1, 80477f0, 80477f8)
 * main+0x43(1, 80477f0, 80477f8, 80477ac)
 * _start+0x7d(1, 8047900, 0, 8047908, 8047921, 8047943)
 *
 *
 * We do not use Linux regularly and we found the only way to reproduce it there
 * which was to use gdb that switches off memory randomization and to run the
 * program with strace to get the right memory address:
 *
 * $ gdb strace
 * (gdb) set args -o output ./a.out
 * (gdb) run
 *
 * Next, check what memory address is used to mmap() libssl at in the output
 * file. Use that exact memory address in the mmap() call below in the code with
 * the MAP_FIXED flag. You may need to use more than 250KB for 1.0.0 but be
 * careful, we needed MAP_FIXED to simulate the situation but a greater number
 * can cause a crash in mmap() itself. So, for example:
 *
 *      mmap((void *)0xf7f93000, 250 * 1024, PROT_NONE,
 *          MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
 *
 * And run the program in gdb again. We can see this on our Linux distro:
 *
 * $ gdb ./a.out
 * (gdb) run
 * Starting program: /afs/ms.mff.cuni.cz/u/p/pechanec/a.out 
 * Opening libssl.so...
 * Initializing with SSL_load_error_strings...
 * Closing libssl.so...
 * Opening libssl.so...
 * Initializing with SSL_load_error_strings...
 * 
 * Program received signal SIGSEGV, Segmentation fault.
 * 0xf7ee99cf in ?? () from /usr/lib/libcrypto.so.0.9.8
 * (gdb) bt
 * #0  0xf7ee99cf in ?? () from /usr/lib/libcrypto.so.0.9.8
 * #1  0xf7ee6fec in ?? () from /usr/lib/libcrypto.so.0.9.8
 * #2  0xf7fd8e20 in ?? ()
 * #3  0xffffc90c in ?? ()
 * #4  0x29063004 in ?? ()
 * #5  0xf7e7cfbd in CRYPTO_lock () from /usr/lib/libcrypto.so.0.9.8
 * #6  0xffffc90c in ?? ()
 * #7  0x14064057 in ?? ()
 * #8  0xf7ee99cb in ?? () from /usr/lib/libcrypto.so.0.9.8
 * #9  0x0804b008 in ?? ()
 * #10 0x00000000 in ?? ()
 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <link.h>
#include <unistd.h>
#include <err.h>
#include <inttypes.h>
#include <pthread.h>

#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <sys/mman.h>

typedef struct {
        char * name;
        void **pointer;
} symbol_t;

pthread_mutex_t **ssl_locks = NULL;

void (*ERR_load_crypto_strings_d)(void) = NULL;
char **SSL_version_str_d = NULL;
void (*RAND_cleanup_d)(void) = NULL;
int (*RAND_bytes_d)(unsigned char *buf, int num) = NULL;
int (*RAND_load_file_d)(const char *filename, long max_bytes) = NULL;
void (*OBJ_cleanup_d)(void) = NULL;
void (*EVP_cleanup_d)(void) = NULL;
void (*SSL_free_d)(SSL *ssl) = NULL;
int (*SSL_accept_d)(SSL *ssl) = NULL;
void (*ENGINE_cleanup_d)(void) = NULL;
int (*SSL_shutdown_d)(SSL *ssl) = NULL;
int (*CRYPTO_num_locks_d)(void) = NULL;
void (*ERR_free_strings_d)(void) = NULL;
void (*SSL_library_init_d)(void) = NULL;
SSL * (*SSL_new_d)(SSL_CTX * ctx) = NULL;
void (*BIO_sock_cleanup_d)(void) = NULL;
const EVP_MD * (*EVP_md5_d)(void) = NULL;
void (*COMP_zlib_cleanup_d)(void) = NULL;
unsigned long (*ERR_get_error_d)(void) = NULL;
void (*OBJ_NAME_cleanup_d)(int type) = NULL;
const EVP_MD * (*EVP_sha512_d)(void) = NULL;
void (*SSL_CTX_free_d)(SSL_CTX *ctx) = NULL;
void (*CONF_modules_unload_d)(int all) = NULL;
void (*SSL_load_error_strings_d)(void) = NULL;
BIO * (*SSL_get_wbio_d)(const SSL * ssl) = NULL;
const char * (*SSLeay_version_d)(int t) = NULL;
void (*ASN1_STRING_TABLE_cleanup_d)(void) = NULL;
void (*CRYPTO_cleanup_all_ex_data_d)(void) = NULL;
SSL_METHOD * (*SSLv23_server_method_d)(void) = NULL;
void (*ERR_remove_state_d)(unsigned long pid) = NULL;
SSL_CTX * (*SSL_CTX_new_d)(SSL_METHOD * method) = NULL;
int (*SSL_read_d)(SSL *ssl, void *buf, int num) = NULL;
int (*SSL_CTX_check_private_key_d)(SSL_CTX *ctx) = NULL;
BIO * (*BIO_new_socket_d)(int sock, int close_flag) = NULL;
void (*SSL_set_bio_d)(SSL *ssl, BIO *rbio, BIO *wbio) = NULL;
int (*SSL_write_d)(SSL *ssl, const void *buf, int num) = NULL;
void (*sk_pop_free_d)(_STACK *st, void (*func)(void *)) = NULL;
int (*EVP_DigestInit_d)(EVP_MD_CTX *ctx, const EVP_MD *type) = NULL;
int (*BIO_vprintf_d)(BIO *bio, const char *format, va_list args) = NULL;
_STACK * (*SSL_COMP_get_compression_methods_d)(void) = NULL;
void (*ERR_error_string_n_d)(unsigned long e, char *buf, size_t len) = NULL;
void (*CRYPTO_set_id_callback_d)(unsigned long (*id_function)(void)) = NULL;
long (*SSL_CTX_ctrl_d)(SSL_CTX *ctx, int cmd, long larg, void *parg) = NULL;
int (*EVP_DigestUpdate_d)(EVP_MD_CTX *ctx, const void *d, unsigned int cnt) = 
NULL;
int (*SSL_CTX_use_certificate_chain_file_d)(SSL_CTX *ctx, const char *file) = 
NULL;
int (*EVP_DigestFinal_d)(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s) = 
NULL;
int (*SSL_CTX_use_PrivateKey_file_d)(SSL_CTX *ctx, const char *file, int type) 
= NULL;
void (*CRYPTO_set_locking_callback_d)(void (*locking_function)(int mode, int n, 
const char *file, int line)) = NULL;

// Call back function used by OpenSSL to enforce thread concurrency.
void ssl_locking_callback(int mode, int n, const char *file, int line) {

        // Do a comparison. This eliminates the compiler warning about unused 
variables.
        //if ((int)file == line) {};

        // Get a lock.
        if (mode & CRYPTO_LOCK) {
                pthread_mutex_lock(*(ssl_locks + n));
        }
        // Release a lock.
        else {
                pthread_mutex_unlock(*(ssl_locks + n));
        }
}

void testssl(void) {


        void *handle1, *handle2;

        symbol_t openssl[] = {
                {
                        .name = "EVP_DigestFinal",
                        .pointer = (void *)&EVP_DigestFinal_d
                },
                {
                        .name = "EVP_DigestUpdate",
                        .pointer = (void *)&EVP_DigestUpdate_d
                },
                {
                        .name = "EVP_DigestInit",
                        .pointer = (void *)&EVP_DigestInit_d
                },
                {
                        .name = "BIO_new_socket",
                        .pointer = (void *)&BIO_new_socket_d
                },
                {
                        .name = "SSL_accept",
                        .pointer = (void *)&SSL_accept_d
                },
                {
                        .name = "SSL_free",
                        .pointer = (void *)&SSL_free_d
                },
                {
                        .name = "SSL_set_bio",
                        .pointer = (void *)&SSL_set_bio_d
                },
                {
                        .name = "SSL_read",
                        .pointer = (void *)&SSL_read_d
                },
                {
                        .name = "SSL_write",
                        .pointer = (void *)&SSL_write_d
                },
                {
                        .name = "SSL_CTX_free",
                        .pointer = (void *)&SSL_CTX_free_d
                },
                {
                        .name = "SSL_shutdown",
                        .pointer = (void *)&SSL_shutdown_d
                },
                {
                        .name = "SSL_load_error_strings",
                        .pointer = (void *)&SSL_load_error_strings_d
                },
                {
                        .name = "SSL_library_init",
                        .pointer = (void *)&SSL_library_init_d
                },
                {
                        .name = "ERR_free_strings",
                        .pointer = (void *)&ERR_free_strings_d
                },
                {
                        .name = "CRYPTO_num_locks",
                        .pointer = (void *)&CRYPTO_num_locks_d
                },
                {
                        .name = "CRYPTO_set_id_callback",
                        .pointer = (void *)&CRYPTO_set_id_callback_d
                },
                {
                        .name = "CRYPTO_set_locking_callback",
                        .pointer = (void *)&CRYPTO_set_locking_callback_d
                },
                {
                        .name = "SSL_CTX_new",
                        .pointer = (void *)&SSL_CTX_new_d
                },
                {
                        .name = "SSL_CTX_check_private_key",
                        .pointer = (void *)&SSL_CTX_check_private_key_d
                },
                {
                        .name = "SSLv23_server_method",
                        .pointer = (void *)&SSLv23_server_method_d
                },
                {
                        .name = "SSL_CTX_ctrl",
                        .pointer = (void *)&SSL_CTX_ctrl_d
                },
                {
                        .name = "SSL_CTX_use_certificate_chain_file",
                        .pointer = (void *)&SSL_CTX_use_certificate_chain_file_d
                },
                {
                        .name = "SSL_CTX_use_PrivateKey_file",
                        .pointer = (void *)&SSL_CTX_use_PrivateKey_file_d
                },
                {
                        .name = "SSL_new",
                        .pointer = (void *)&SSL_new_d
                },
                {
                        .name = "EVP_sha512",
                        .pointer = (void *)&EVP_sha512_d
                },
                {
                        .name = "SSLeay_version",
                        .pointer = (void *)&SSLeay_version_d
                },
                {
                        .name = "ERR_remove_state",
                        .pointer = (void *)&ERR_remove_state_d
                },
                {
                        .name = "EVP_md5",
                        .pointer = (void *)&EVP_md5_d
                },
                {
                        .name = "SSL_version_str",
                        .pointer = (void *)&SSL_version_str_d
                },
                {
                        .name = "BIO_vprintf",
                        .pointer = (void *)&BIO_vprintf_d
                },
                {
                        .name = "SSL_get_wbio",
                        .pointer = (void *)&SSL_get_wbio_d
                },
                {
                        .name = "OBJ_cleanup",
                        .pointer = (void *)&OBJ_cleanup_d
                },
                {
                        .name = "EVP_cleanup",
                        .pointer = (void *)&EVP_cleanup_d
                },
                {
                        .name = "ENGINE_cleanup",
                        .pointer = (void *)&ENGINE_cleanup_d
                },
                {
                        .name = "CRYPTO_cleanup_all_ex_data",
                        .pointer = (void *)&CRYPTO_cleanup_all_ex_data_d
                },
                {
                        .name = "COMP_zlib_cleanup",
                        .pointer = (void *)&COMP_zlib_cleanup_d
                },
                {
                        .name = "CONF_modules_unload",
                        .pointer = (void *)&CONF_modules_unload_d
                },
                {
                        .name = "OBJ_NAME_cleanup",
                        .pointer = (void *)&OBJ_NAME_cleanup_d
                },
                {
                        .name = "BIO_sock_cleanup",
                        .pointer = (void *)&BIO_sock_cleanup_d
                },
                {
                        .name = "ASN1_STRING_TABLE_cleanup",
                        .pointer = (void *)&ASN1_STRING_TABLE_cleanup_d
                },
                {
                        .name = "SSL_COMP_get_compression_methods",
                        .pointer = (void *)&SSL_COMP_get_compression_methods_d
                },
                {
                        .name = "sk_pop_free",
                        .pointer = (void *)&sk_pop_free_d
                },
                {
                        .name = "ERR_error_string_n",
                        .pointer = (void *)&ERR_error_string_n_d
                },
                {
                        .name = "ERR_get_error",
                        .pointer = (void *)&ERR_get_error_d
                },
                {
                        .name = "RAND_load_file",
                        .pointer = (void *)&RAND_load_file_d
                },
                {
                        .name = "RAND_cleanup",
                        .pointer = (void *)&RAND_cleanup_d
                },
                {
                        .name = "RAND_bytes",
                        .pointer = (void *)&RAND_bytes_d
                },
                {
                        .name = "ERR_load_crypto_strings",
                        .pointer = (void *)&ERR_load_crypto_strings_d
                }
        };

        printf("Opening libssl.so...\n");
        if ((handle1 = 
dlopen("/home/ladar/Lavabit/magma.so/sources/openssl/libcrypto.so", RTLD_NOW)) 
== NULL ||
                (handle2 = 
dlopen("/home/ladar/Lavabit/magma.so/sources/openssl/libssl.so", RTLD_NOW)) == 
NULL) {
                printf("dlopen failed: %s\n", dlerror());
                return;
        }


        // Loop through and setup the function pointers.
        for (size_t i = 0; i < (sizeof(openssl) / sizeof(symbol_t)); i++) {
                if ((*(openssl[i].pointer) = dlsym(handle1, openssl[i].name)) 
== NULL && (*(openssl[i].pointer) = dlsym(handle2, openssl[i].name)) == NULL) {
                        printf("Unable to establish a pointer to the function 
%s.", openssl[i].name);
                        printf("dlopen failed: %s\n", dlerror());
                        return;
                }
        }

        printf("Initializing with OpenSSL...\n");

        // Thread locking setup.
        if (!(ssl_locks = malloc(CRYPTO_num_locks_d() * sizeof(pthread_mutex_t 
*)))) {
                printf("Could not allocate %zu bytes for the SSL library 
locks.", CRYPTO_num_locks_d() * sizeof(pthread_mutex_t *));
                return;
        }

        // Get the SSL library setup.
        ERR_load_crypto_strings_d();
        SSL_load_error_strings_d();
        SSL_library_init_d();

        // Initialize all of the mutexes.
        for (uint64_t i = 0; i < CRYPTO_num_locks_d(); i++) {

                if (!(*(ssl_locks + i) = malloc(sizeof(pthread_mutex_t)))) {
                        printf("Could not allocate %zu bytes for SSL lock 
%lu.", sizeof(pthread_mutex_t), i);
                        return;
                }
                else if (pthread_mutex_init(*(ssl_locks + i), NULL)) {
                        printf("Could not initialize SSL mutex %lu.", i);
                        return;
                }
        }

        CRYPTO_set_id_callback_d(&pthread_self);
        CRYPTO_set_locking_callback_d(&ssl_locking_callback);

        ERR_remove_state_d(0);
        COMP_zlib_cleanup_d();
        CONF_modules_unload_d(1);
        OBJ_cleanup_d();
        OBJ_NAME_cleanup_d(-1);
        BIO_sock_cleanup_d();
        EVP_cleanup_d();
        ENGINE_cleanup_d();
        CRYPTO_cleanup_all_ex_data_d();
        ERR_free_strings_d();
        ASN1_STRING_TABLE_cleanup_d();
        CRYPTO_set_locking_callback_d(NULL);
        RAND_cleanup_d();

        // The SSL compression method stack doesn't get freed properly by any 
of the functions above.
        // This was necessary as of 1.0.0-beta3, but may be fixed.
        sk_pop_free_d((_STACK *)SSL_COMP_get_compression_methods_d(), free);

        // Destroy and free all of the mutexes.
        for (uint64_t i = 0; i < CRYPTO_num_locks_d(); i++) {
                pthread_mutex_destroy(*(ssl_locks + i));
                free(*(ssl_locks + i));
        }

        free(ssl_locks);
        ssl_locks = NULL;

        printf("Closing libssl.so...\n");
        dlclose(handle1);
        dlclose(handle2);

        // Loop through and setup the function pointers.
        for (size_t i = 0; i < (sizeof(openssl) / sizeof(symbol_t)); i++) {
                *(openssl[i].pointer) = NULL;
        }

        return;

}

void
old_testssl(void)
{
        void *handle;
        void (*loadstrings)();

        printf("Opening libssl.so...\n");
        if ((handle = 
dlopen("/home/ladar/Lavabit/magma.so/sources/openssl/libssl.so", RTLD_LAZY)) == 
NULL) {
                printf("dlopen failed: %s\n", dlerror());
                return;
        }

        /*
         * Call the function to initialize the SSL error string tables.  The
         * actual error tables are managed in libcrypto so this is where the
         * problem starts.  libcrypto memory ends up with pointers to static
         * memory from libssl.
         */
        loadstrings = (void (*)())dlsym(handle, "SSL_load_error_strings");
        if (loadstrings != NULL) {
                printf("Initializing with SSL_load_error_strings...\n");
                (*loadstrings)();
        } else {
                printf("dlsym failed to find "
                    "SSL_load_error_strings: %s\n", dlerror());
        }

        printf("Closing libssl.so...\n");
        dlclose(handle);
}

int
main(int argc, char *argv[])
{
        /*
         * Since we linked with libcrypto directly, call the libcrypto error
         * string initialization stuff.
         */
        //ERR_load_crypto_strings();

        /*
         * Dynamically load libssl to simulate what happens with Apache mod_ssl
         * (and probably other libraries on the system as well). This function
         * also initializes the SSL error tables and then unloads libssl.
         */
        testssl();
        
        /*
         * Map some anonymous memory that can not be accessed. Usually it will
         * be mapped from the address where libssl was before. With libssl.1.0.0
         * you might need to increase the segment to 350KB since libssl from
         * 1.0.0 is larger than from 0.9.8.
         */
        if (mmap(0, 280 * 1024, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0)
            == MAP_FAILED) {
                err(1, "mmap");
        }

        /*
         * Repeat the above testssl step. This normally causes SEGV because the
         * error tables managed by libcrypto have bad pointers still pointing to
         * the old libssl which now is occupied by the mapped anonymous pages
         * that can not be accessed. The libssl library does not cleanup
         * properly before it closes.
         */
        testssl();
        return(0);
}

Reply via email to