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);
}