openssl/crypto/mem_dbg.c

504 lines
13 KiB
C
Raw Permalink Normal View History

/*
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "internal/cryptlib.h"
#include "internal/thread_once.h"
#include <openssl/crypto.h>
#include <openssl/buffer.h>
#include "internal/bio.h"
#include <openssl/lhash.h>
#ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
# include <execinfo.h>
#endif
/*
* The state changes to CRYPTO_MEM_CHECK_ON | CRYPTO_MEM_CHECK_ENABLE when
* the application asks for it (usually after library initialisation for
* which no book-keeping is desired). State CRYPTO_MEM_CHECK_ON exists only
* temporarily when the library thinks that certain allocations should not be
* checked (e.g. the data structures used for memory checking). It is not
* suitable as an initial state: the library will unexpectedly enable memory
* checking when it executes one of those sections that want to disable
* checking temporarily. State CRYPTO_MEM_CHECK_ENABLE without ..._ON makes
* no sense whatsoever.
*/
#ifndef OPENSSL_NO_CRYPTO_MDEBUG
static int mh_mode = CRYPTO_MEM_CHECK_OFF;
#endif
#ifndef OPENSSL_NO_CRYPTO_MDEBUG
static unsigned long order = 0; /* number of memory requests */
2008-05-26 11:24:29 +00:00
static CRYPTO_ONCE memdbg_init = CRYPTO_ONCE_STATIC_INIT;
CRYPTO_RWLOCK *memdbg_lock;
static CRYPTO_RWLOCK *long_memdbg_lock;
/* memory-block description */
struct mem_st {
void *addr;
int num;
const char *file;
int line;
CRYPTO_THREAD_ID threadid;
unsigned long order;
time_t time;
#ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
void *array[30];
size_t array_siz;
#endif
};
/*
* hash-table of memory requests (address as * key); access requires
* long_memdbg_lock lock
*/
static LHASH_OF(MEM) *mh = NULL;
/* num_disable > 0 iff mh_mode == CRYPTO_MEM_CHECK_ON (w/o ..._ENABLE) */
static unsigned int num_disable = 0;
/*
* Valid iff num_disable > 0. long_memdbg_lock is locked exactly in this
* case (by the thread named in disabling_thread).
*/
static CRYPTO_THREAD_ID disabling_threadid;
DEFINE_RUN_ONCE_STATIC(do_memdbg_init)
{
Revert the crypto "global lock" implementation Conceptually, this is a squashed version of: Revert "Address feedback" This reverts commit 75551e07bd2339dfea06ef1d31d69929e13a4495. and Revert "Add CRYPTO_thread_glock_new" This reverts commit ed6b2c7938ec6f07b15745d4183afc276e74c6dd. But there were some intervening commits that made neither revert apply cleanly, so instead do it all as one shot. The crypto global locks were an attempt to cope with the awkward POSIX semantics for pthread_atfork(); its documentation (the "RATIONALE" section) indicates that the expected usage is to have the prefork handler lock all "global" locks, and the parent and child handlers release those locks, to ensure that forking happens with a consistent (lock) state. However, the set of functions available in the child process is limited to async-signal-safe functions, and pthread_mutex_unlock() is not on the list of async-signal-safe functions! The only synchronization primitives that are async-signal-safe are the semaphore primitives, which are not really appropriate for general-purpose usage. However, the state consistency problem that the global locks were attempting to solve is not actually a serious problem, particularly for OpenSSL. That is, we can consider four cases of forking application that might use OpenSSL: (1) Single-threaded, does not call into OpenSSL in the child (e.g., the child calls exec() immediately) For this class of process, no locking is needed at all, since there is only ever a single thread of execution and the only reentrancy is due to signal handlers (which are themselves limited to async-signal-safe operation and should not be doing much work at all). (2) Single-threaded, calls into OpenSSL after fork() The application must ensure that it does not fork() with an unexpected lock held (that is, one that would get unlocked in the parent but accidentally remain locked in the child and cause deadlock). Since OpenSSL does not expose any of its internal locks to the application and the application is single-threaded, the OpenSSL internal locks will be unlocked for the fork(), and the state will be consistent. (OpenSSL will need to reseed its PRNG in the child, but that is an orthogonal issue.) If the application makes use of locks from libcrypto, proper handling for those locks is the responsibility of the application, as for any other locking primitive that is available for application programming. (3) Multi-threaded, does not call into OpenSSL after fork() As for (1), the OpenSSL state is only relevant in the parent, so no particular fork()-related handling is needed. The internal locks are relevant, but there is no interaction with the child to consider. (4) Multi-threaded, calls into OpenSSL after fork() This is the case where the pthread_atfork() hooks to ensure that all global locks are in a known state across fork() would come into play, per the above discussion. However, these "calls into OpenSSL after fork()" are still subject to the restriction to async-signal-safe functions. Since OpenSSL uses all sorts of locking and libc functions that are not on the list of safe functions (e.g., malloc()), this case is not currently usable and is unlikely to ever be usable, independently of the locking situation. So, there is no need to go through contortions to attempt to support this case in the one small area of locking interaction with fork(). In light of the above analysis (thanks @davidben and @achernya), go back to the simpler implementation that does not need to distinguish "library-global" locks or to have complicated atfork handling for locks. Reviewed-by: Kurt Roeckx <kurt@roeckx.be> Reviewed-by: Matthias St. Pierre <Matthias.St.Pierre@ncp-e.com> (Merged from https://github.com/openssl/openssl/pull/5089)
2018-01-16 15:49:54 +00:00
memdbg_lock = CRYPTO_THREAD_lock_new();
long_memdbg_lock = CRYPTO_THREAD_lock_new();
if (memdbg_lock == NULL || long_memdbg_lock == NULL) {
CRYPTO_THREAD_lock_free(memdbg_lock);
memdbg_lock = NULL;
CRYPTO_THREAD_lock_free(long_memdbg_lock);
long_memdbg_lock = NULL;
return 0;
}
return 1;
}
#endif
int CRYPTO_mem_ctrl(int mode)
{
#ifdef OPENSSL_NO_CRYPTO_MDEBUG
return mode - mode;
#else
int ret = mh_mode;
if (!RUN_ONCE(&memdbg_init, do_memdbg_init))
return -1;
CRYPTO_THREAD_write_lock(memdbg_lock);
switch (mode) {
default:
break;
case CRYPTO_MEM_CHECK_ON:
mh_mode = CRYPTO_MEM_CHECK_ON | CRYPTO_MEM_CHECK_ENABLE;
num_disable = 0;
break;
case CRYPTO_MEM_CHECK_OFF:
mh_mode = 0;
num_disable = 0;
break;
/* switch off temporarily (for library-internal use): */
case CRYPTO_MEM_CHECK_DISABLE:
if (mh_mode & CRYPTO_MEM_CHECK_ON) {
CRYPTO_THREAD_ID cur = CRYPTO_THREAD_get_current_id();
/* see if we don't have long_memdbg_lock already */
if (!num_disable
|| !CRYPTO_THREAD_compare_id(disabling_threadid, cur)) {
/*
* Long-time lock long_memdbg_lock must not be claimed
* while we're holding memdbg_lock, or we'll deadlock
* if somebody else holds long_memdbg_lock (and cannot
* release it because we block entry to this function). Give
* them a chance, first, and then claim the locks in
* appropriate order (long-time lock first).
*/
CRYPTO_THREAD_unlock(memdbg_lock);
/*
* Note that after we have waited for long_memdbg_lock and
* memdbg_lock, we'll still be in the right "case" and
* "if" branch because MemCheck_start and MemCheck_stop may
* never be used while there are multiple OpenSSL threads.
*/
CRYPTO_THREAD_write_lock(long_memdbg_lock);
CRYPTO_THREAD_write_lock(memdbg_lock);
mh_mode &= ~CRYPTO_MEM_CHECK_ENABLE;
disabling_threadid = cur;
}
num_disable++;
}
break;
case CRYPTO_MEM_CHECK_ENABLE:
if (mh_mode & CRYPTO_MEM_CHECK_ON) {
if (num_disable) { /* always true, or something is going wrong */
num_disable--;
if (num_disable == 0) {
mh_mode |= CRYPTO_MEM_CHECK_ENABLE;
CRYPTO_THREAD_unlock(long_memdbg_lock);
}
}
}
break;
}
CRYPTO_THREAD_unlock(memdbg_lock);
return ret;
#endif
}
#ifndef OPENSSL_NO_CRYPTO_MDEBUG
static int mem_check_on(void)
{
int ret = 0;
CRYPTO_THREAD_ID cur;
if (mh_mode & CRYPTO_MEM_CHECK_ON) {
if (!RUN_ONCE(&memdbg_init, do_memdbg_init))
return 0;
cur = CRYPTO_THREAD_get_current_id();
CRYPTO_THREAD_read_lock(memdbg_lock);
ret = (mh_mode & CRYPTO_MEM_CHECK_ENABLE)
|| !CRYPTO_THREAD_compare_id(disabling_threadid, cur);
CRYPTO_THREAD_unlock(memdbg_lock);
}
return ret;
}
2008-05-26 11:24:29 +00:00
static int mem_cmp(const MEM *a, const MEM *b)
{
2005-07-05 11:44:45 +00:00
#ifdef _WIN64
const char *ap = (const char *)a->addr, *bp = (const char *)b->addr;
if (ap == bp)
return 0;
else if (ap > bp)
return 1;
else
return -1;
2005-07-05 11:44:45 +00:00
#else
return (const char *)a->addr - (const char *)b->addr;
2005-07-05 11:44:45 +00:00
#endif
}
2008-05-26 11:24:29 +00:00
static unsigned long mem_hash(const MEM *a)
{
size_t ret;
ret = (size_t)a->addr;
ret = ret * 17851 + (ret >> 14) * 7 + (ret >> 4) * 251;
return ret;
}
#if !OPENSSL_API_3
int CRYPTO_mem_debug_push(const char *info, const char *file, int line)
{
return 0;
}
int CRYPTO_mem_debug_pop(void)
{
return 0;
}
#endif
static unsigned long break_order_num = 0;
void CRYPTO_mem_debug_malloc(void *addr, size_t num, int before_p,
const char *file, int line)
{
MEM *m, *mm;
switch (before_p & 127) {
case 0:
break;
case 1:
if (addr == NULL)
break;
if (mem_check_on()) {
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_DISABLE);
if (!RUN_ONCE(&memdbg_init, do_memdbg_init)
|| (m = OPENSSL_malloc(sizeof(*m))) == NULL) {
OPENSSL_free(addr);
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ENABLE);
return;
}
if (mh == NULL) {
if ((mh = lh_MEM_new(mem_hash, mem_cmp)) == NULL) {
OPENSSL_free(addr);
OPENSSL_free(m);
addr = NULL;
goto err;
}
}
m->addr = addr;
m->file = file;
m->line = line;
m->num = num;
m->threadid = CRYPTO_THREAD_get_current_id();
if (order == break_order_num) {
/* BREAK HERE */
m->order = order;
}
m->order = order++;
# ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
m->array_siz = backtrace(m->array, OSSL_NELEM(m->array));
# endif
m->time = time(NULL);
if ((mm = lh_MEM_insert(mh, m)) != NULL)
OPENSSL_free(mm);
err:
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ENABLE);
}
break;
}
return;
}
void CRYPTO_mem_debug_free(void *addr, int before_p,
const char *file, int line)
{
MEM m, *mp;
switch (before_p) {
case 0:
if (addr == NULL)
break;
if (mem_check_on() && (mh != NULL)) {
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_DISABLE);
m.addr = addr;
mp = lh_MEM_delete(mh, &m);
OPENSSL_free(mp);
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ENABLE);
}
break;
case 1:
break;
}
}
void CRYPTO_mem_debug_realloc(void *addr1, void *addr2, size_t num,
int before_p, const char *file, int line)
{
MEM m, *mp;
switch (before_p) {
case 0:
break;
case 1:
if (addr2 == NULL)
break;
if (addr1 == NULL) {
CRYPTO_mem_debug_malloc(addr2, num, 128 | before_p, file, line);
break;
}
if (mem_check_on()) {
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_DISABLE);
m.addr = addr1;
mp = lh_MEM_delete(mh, &m);
if (mp != NULL) {
mp->addr = addr2;
mp->num = num;
#ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
mp->array_siz = backtrace(mp->array, OSSL_NELEM(mp->array));
#endif
(void)lh_MEM_insert(mh, mp);
}
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ENABLE);
}
break;
}
return;
}
typedef struct mem_leak_st {
int (*print_cb) (const char *str, size_t len, void *u);
void *print_cb_arg;
int chunks;
long bytes;
} MEM_LEAK;
static void print_leak(const MEM *m, MEM_LEAK *l)
{
char buf[1024];
char *bufp = buf, *hex;
size_t len = sizeof(buf);
int n;
struct tm *lcl = NULL;
lcl = localtime(&m->time);
n = BIO_snprintf(bufp, len, "[%02d:%02d:%02d] ",
lcl->tm_hour, lcl->tm_min, lcl->tm_sec);
if (n <= 0) {
bufp[0] = '\0';
return;
}
bufp += n;
len -= n;
n = BIO_snprintf(bufp, len, "%5lu file=%s, line=%d, ",
m->order, m->file, m->line);
if (n <= 0)
return;
bufp += n;
len -= n;
hex = OPENSSL_buf2hexstr((const unsigned char *)&m->threadid,
sizeof(m->threadid));
n = BIO_snprintf(bufp, len, "thread=%s, number=%d, address=%p\n", hex,
m->num, m->addr);
OPENSSL_free(hex);
if (n <= 0)
return;
bufp += n;
len -= n;
l->print_cb(buf, (size_t)(bufp - buf), l->print_cb_arg);
l->chunks++;
l->bytes += m->num;
#ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
{
size_t i;
char **strings = backtrace_symbols(m->array, m->array_siz);
for (i = 0; i < m->array_siz; i++)
fprintf(stderr, "##> %s\n", strings[i]);
free(strings);
}
#endif
}
IMPLEMENT_LHASH_DOALL_ARG_CONST(MEM, MEM_LEAK);
int CRYPTO_mem_leaks_cb(int (*cb) (const char *str, size_t len, void *u),
void *u)
{
MEM_LEAK ml;
/* Ensure all resources are released */
OPENSSL_cleanup();
if (!RUN_ONCE(&memdbg_init, do_memdbg_init))
return -1;
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_DISABLE);
ml.print_cb = cb;
ml.print_cb_arg = u;
ml.bytes = 0;
ml.chunks = 0;
if (mh != NULL)
lh_MEM_doall_MEM_LEAK(mh, print_leak, &ml);
if (ml.chunks != 0) {
char buf[256];
BIO_snprintf(buf, sizeof(buf), "%ld bytes leaked in %d chunks\n",
ml.bytes, ml.chunks);
cb(buf, strlen(buf), u);
} else {
/*
* Make sure that, if we found no leaks, memory-leak debugging itself
* does not introduce memory leaks (which might irritate external
* debugging tools). (When someone enables leak checking, but does not
* call this function, we declare it to be their fault.)
*/
int old_mh_mode;
CRYPTO_THREAD_write_lock(memdbg_lock);
/*
* avoid deadlock when lh_free() uses CRYPTO_mem_debug_free(), which uses
* mem_check_on
*/
old_mh_mode = mh_mode;
mh_mode = CRYPTO_MEM_CHECK_OFF;
lh_MEM_free(mh);
mh = NULL;
mh_mode = old_mh_mode;
CRYPTO_THREAD_unlock(memdbg_lock);
}
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF);
/* Clean up locks etc */
CRYPTO_THREAD_lock_free(memdbg_lock);
CRYPTO_THREAD_lock_free(long_memdbg_lock);
memdbg_lock = NULL;
long_memdbg_lock = NULL;
return ml.chunks == 0 ? 1 : 0;
}
static int print_bio(const char *str, size_t len, void *b)
{
return BIO_write((BIO *)b, str, len);
}
int CRYPTO_mem_leaks(BIO *b)
{
/*
* OPENSSL_cleanup() will free the ex_data locks so we can't have any
* ex_data hanging around
*/
bio_free_ex_data(b);
return CRYPTO_mem_leaks_cb(print_bio, b);
}
# ifndef OPENSSL_NO_STDIO
int CRYPTO_mem_leaks_fp(FILE *fp)
{
BIO *b;
int ret;
/*
* Need to turn off memory checking when allocated BIOs ... especially as
* we're creating them at a time when we're trying to check we've not
* left anything un-free()'d!!
*/
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_DISABLE);
b = BIO_new(BIO_s_file());
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ENABLE);
if (b == NULL)
return -1;
BIO_set_fp(b, fp, BIO_NOCLOSE);
ret = CRYPTO_mem_leaks_cb(print_bio, b);
BIO_free(b);
return ret;
}
# endif
#endif