blob: 7a458340f772b556c42abbca1e64846d9280a1f6 [file] [log] [blame]
/*
* stunnel TLS offloading and load-balancing proxy
* Copyright (C) 1998-2015 Michal Trojnara <Michal.Trojnara@mirt.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses>.
*
* Linking stunnel statically or dynamically with other modules is making
* a combined work based on stunnel. Thus, the terms and conditions of
* the GNU General Public License cover the whole combination.
*
* In addition, as a special exception, the copyright holder of stunnel
* gives you permission to combine stunnel with free software programs or
* libraries that are released under the GNU LGPL and with code included
* in the standard release of OpenSSL under the OpenSSL License (or
* modified versions of such code, with unchanged license). You may copy
* and distribute such a system following the terms of the GNU GPL for
* stunnel and the licenses of the other code concerned.
*
* Note that people who make modified versions of stunnel are not obligated
* to grant this special exception for their modified versions; it is their
* choice whether to do so. The GNU General Public License gives permission
* to release a modified version without this exception; this exception
* also makes it possible to release a modified version which carries
* forward this exception.
*/
#include "common.h"
#include "prototypes.h"
struct alloc_list_struct {
ALLOC_LIST *prev, *next;
TLS_DATA *tls;
size_t size;
const char *alloc_file, *free_file;
int alloc_line, free_line;
unsigned valid_canary, magic;
/* at least on IA64 allocations need to be aligned */
#ifdef __GNUC__
} __attribute__((aligned(16)));
#else
uint64_t :0; /* align the structure */
};
#endif
#ifdef USE_WIN32
NOEXPORT LPTSTR str_vtprintf(LPCTSTR, va_list);
#endif /* USE_WIN32 */
NOEXPORT ALLOC_LIST *get_alloc_list_ptr(void *, const char *, int);
NOEXPORT void str_leak_debug(ALLOC_LIST *, int);
TLS_DATA *ui_tls;
static uint8_t canary[10]; /* 80-bit canary value */
static volatile unsigned canary_initialized=0xabadbabe;
/**************************************** string manipulation functions */
#ifndef va_copy
#ifdef __va_copy
#define va_copy(dst, src) __va_copy((dst), (src))
#else /* __va_copy */
#define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list))
#endif /* __va_copy */
#endif /* va_copy */
char *str_dup_debug(const char *str, const char *file, int line) {
char *retval;
retval=str_alloc_debug(strlen(str)+1, file, line);
strcpy(retval, str);
return retval;
}
char *str_printf(const char *format, ...) {
char *txt;
va_list arglist;
va_start(arglist, format);
txt=str_vprintf(format, arglist);
va_end(arglist);
return txt;
}
char *str_vprintf(const char *format, va_list start_ap) {
int n;
size_t size=32;
char *p;
va_list ap;
p=str_alloc(size);
for(;;) {
va_copy(ap, start_ap);
n=vsnprintf(p, size, format, ap);
if(n>-1 && n<(int)size)
return p;
if(n>-1) /* glibc 2.1 */
size=(size_t)n+1; /* precisely what is needed */
else /* glibc 2.0, WIN32, etc. */
size*=2; /* twice the old size */
p=str_realloc(p, size);
}
}
#ifdef USE_WIN32
LPTSTR str_tprintf(LPCTSTR format, ...) {
LPTSTR txt;
va_list arglist;
va_start(arglist, format);
txt=str_vtprintf(format, arglist);
va_end(arglist);
return txt;
}
NOEXPORT LPTSTR str_vtprintf(LPCTSTR format, va_list start_ap) {
int n;
size_t size=32;
LPTSTR p;
va_list ap;
p=str_alloc(size*sizeof(TCHAR));
for(;;) {
va_copy(ap, start_ap);
n=_vsntprintf(p, size, format, ap);
if(n>-1 && n<(int)size)
return p;
size*=2;
p=str_realloc(p, size*sizeof(TCHAR));
}
}
#endif
/**************************************** memory allocation wrappers */
void str_init(TLS_DATA *tls_data) {
tls_data->alloc_head=NULL;
tls_data->alloc_bytes=tls_data->alloc_blocks=0;
}
void str_cleanup(TLS_DATA *tls_data) {
/* free all attached allocations */
while(tls_data->alloc_head) /* str_free macro requires an lvalue */
str_free_expression(tls_data->alloc_head+1);
}
void str_canary_init() {
if(canary_initialized!=0xabadbabe)
return; /* prevent double initialization on config reload */
RAND_bytes(canary, sizeof canary);
/* an error would reduce the effectiveness of canaries */
/* this is nothing critical, so the return value is ignored here */
canary_initialized=0xc0ded; /* after RAND_bytes */
}
void str_stats() {
TLS_DATA *tls_data;
ALLOC_LIST *alloc_list;
int i=0;
if(!tls_initialized)
fatal("str not initialized");
tls_data=tls_get();
if(!tls_data || (!tls_data->alloc_blocks && !tls_data->alloc_bytes))
return; /* skip if no data is allocated */
s_log(LOG_DEBUG, "str_stats: %lu block(s), "
"%lu data byte(s), %lu control byte(s)",
(unsigned long)tls_data->alloc_blocks,
(unsigned long)tls_data->alloc_bytes,
(unsigned long)(tls_data->alloc_blocks*
(sizeof(ALLOC_LIST)+sizeof canary)));
for(alloc_list=tls_data->alloc_head; alloc_list; alloc_list=alloc_list->next) {
if(++i>10) /* limit the number of results */
break;
s_log(LOG_DEBUG, "str_stats: %lu byte(s) at %s:%d",
(unsigned long)alloc_list->size,
alloc_list->alloc_file, alloc_list->alloc_line);
}
}
void *str_alloc_debug(size_t size, const char *file, int line) {
TLS_DATA *tls_data;
ALLOC_LIST *alloc_list;
if(!tls_initialized)
fatal_debug("str not initialized", file, line);
tls_data=tls_get();
if(!tls_data) {
tls_data=tls_alloc(NULL, NULL, "alloc");
s_log(LOG_ERR, "INTERNAL ERROR: Uninitialized TLS at %s, line %d",
file, line);
}
alloc_list=(ALLOC_LIST *)str_alloc_detached_debug(size, file, line)-1;
alloc_list->prev=NULL;
alloc_list->next=tls_data->alloc_head;
alloc_list->tls=tls_data;
if(tls_data->alloc_head)
tls_data->alloc_head->prev=alloc_list;
tls_data->alloc_head=alloc_list;
tls_data->alloc_bytes+=size;
tls_data->alloc_blocks++;
return alloc_list+1;
}
void *str_alloc_detached_debug(size_t size, const char *file, int line) {
ALLOC_LIST *alloc_list;
#if 0
printf("allocating %lu bytes at %s:%d\n", (unsigned long)size, file, line);
#endif
alloc_list=calloc(1, sizeof(ALLOC_LIST)+size+sizeof canary);
if(!alloc_list)
fatal_debug("Out of memory", file, line);
alloc_list->prev=NULL; /* for debugging */
alloc_list->next=NULL; /* for debugging */
alloc_list->tls=NULL;
alloc_list->size=size;
alloc_list->alloc_file=file;
alloc_list->alloc_line=line;
alloc_list->free_file="none";
alloc_list->free_line=0;
alloc_list->valid_canary=canary_initialized; /* before memcpy */
memcpy((uint8_t *)(alloc_list+1)+size, canary, sizeof canary);
alloc_list->magic=0xa110c8ed;
str_leak_debug(alloc_list, 1);
return alloc_list+1;
}
void *str_realloc_debug(void *ptr, size_t size, const char *file, int line) {
ALLOC_LIST *prev_alloc_list, *alloc_list;
if(!ptr)
return str_alloc_debug(size, file, line);
prev_alloc_list=get_alloc_list_ptr(ptr, file, line);
str_leak_debug(prev_alloc_list, -1);
if(prev_alloc_list->size>size) /* shrinking the allocation */
memset((uint8_t *)ptr+size, 0, prev_alloc_list->size-size); /* paranoia */
alloc_list=realloc(prev_alloc_list,
sizeof(ALLOC_LIST)+size+sizeof canary);
if(!alloc_list)
fatal_debug("Out of memory", file, line);
ptr=alloc_list+1;
if(size>alloc_list->size) /* growing the allocation */
memset((uint8_t *)ptr+alloc_list->size, 0, size-alloc_list->size);
if(alloc_list->tls) { /* not detached */
/* refresh possibly invalidated linked list pointers */
if(alloc_list->tls->alloc_head==prev_alloc_list)
alloc_list->tls->alloc_head=alloc_list;
if(alloc_list->next)
alloc_list->next->prev=alloc_list;
if(alloc_list->prev)
alloc_list->prev->next=alloc_list;
/* update statistics while the old size is still available */
alloc_list->tls->alloc_bytes+=size-alloc_list->size;
}
alloc_list->size=size;
alloc_list->alloc_file=file;
alloc_list->alloc_line=line;
alloc_list->free_file="none";
alloc_list->free_line=0;
alloc_list->valid_canary=canary_initialized; /* before memcpy */
memcpy((uint8_t *)ptr+size, canary, sizeof canary);
str_leak_debug(alloc_list, 1);
return ptr;
}
/* detach from thread automatic deallocation list */
/* it has no effect if the allocation is already detached */
void str_detach_debug(void *ptr, const char *file, int line) {
ALLOC_LIST *alloc_list;
if(!ptr) /* do not attempt to free null pointers */
return;
alloc_list=get_alloc_list_ptr(ptr, file, line);
if(alloc_list->tls) { /* not detached */
/* remove from linked list */
if(alloc_list->tls->alloc_head==alloc_list)
alloc_list->tls->alloc_head=alloc_list->next;
if(alloc_list->next)
alloc_list->next->prev=alloc_list->prev;
if(alloc_list->prev)
alloc_list->prev->next=alloc_list->next;
/* update statistics */
alloc_list->tls->alloc_bytes-=alloc_list->size;
alloc_list->tls->alloc_blocks--;
/* clear pointers */
alloc_list->next=NULL;
alloc_list->prev=NULL;
alloc_list->tls=NULL;
}
}
void str_free_debug(void *ptr, const char *file, int line) {
ALLOC_LIST *alloc_list;
if(!ptr) /* do not attempt to free null pointers */
return;
alloc_list=(ALLOC_LIST *)ptr-1;
if(alloc_list->magic==0xdefec8ed) {
/* this may (unlikely) log garbage instead of file names */
s_log(LOG_CRIT,
"Double free attempt: ptr=%p alloc=%s:%d free#1=%s:%d free#2=%s:%d",
ptr,
alloc_list->alloc_file, alloc_list->alloc_line,
alloc_list->free_file, alloc_list->free_line,
file, line);
return;
}
str_detach_debug(ptr, file, line);
str_leak_debug(alloc_list, -1);
alloc_list->free_file=file;
alloc_list->free_line=line;
alloc_list->magic=0xdefec8ed; /* to detect double free attempts */
memset(ptr, 0, alloc_list->size); /* paranoia */
free(alloc_list);
}
NOEXPORT ALLOC_LIST *get_alloc_list_ptr(void *ptr, const char *file, int line) {
ALLOC_LIST *alloc_list;
if(!tls_initialized)
fatal_debug("str not initialized", file, line);
alloc_list=(ALLOC_LIST *)ptr-1;
if(alloc_list->magic!=0xa110c8ed) /* not allocated by str_alloc() */
fatal_debug("Bad magic", file, line); /* LOL */
if(alloc_list->tls /* not detached */ && alloc_list->tls!=tls_get())
fatal_debug("Memory allocated in a different thread", file, line);
if(alloc_list->valid_canary!=0xabadbabe &&
safe_memcmp((uint8_t *)ptr+alloc_list->size, canary, sizeof canary))
fatal_debug("Dead canary", file, line); /* LOL */
return alloc_list;
}
/* #define STR_LEAK_DEBUG */
/* the implementation is slow, but it's not going to be used in production */
NOEXPORT void str_leak_debug(ALLOC_LIST *alloc_list, int change) {
#ifndef STR_LEAK_DEBUG
(void)alloc_list; /* skip warning about unused parameter */
(void)change; /* skip warning about unused parameter */
#else
#define ALLOC_TABLE_SIZE 1000
#define MAX_ALLOCS 200
static struct {
const char *alloc_file;
int alloc_line;
int num;
} alloc_table[ALLOC_TABLE_SIZE];
static size_t alloc_num=0;
size_t i;
enter_critical_section(CRIT_LEAK);
for(i=0; i<alloc_num; ++i)
if(alloc_table[i].alloc_file==alloc_list->alloc_file &&
alloc_table[i].alloc_line==alloc_list->alloc_line)
break;
if(i==alloc_num) {
if(alloc_num==ALLOC_TABLE_SIZE) {
leave_critical_section(CRIT_LEAK);
return;
}
alloc_table[i].alloc_file=alloc_list->alloc_file;
alloc_table[i].alloc_line=alloc_list->alloc_line;
alloc_table[i].num=0;
++alloc_num;
}
alloc_table[i].num+=change;
leave_critical_section(CRIT_LEAK);
if(alloc_table[i].num>MAX_ALLOCS &&
strcmp(alloc_table[i].alloc_file, "lhash.c"))
fprintf(stderr, "%d allocations detected at %s:%d\n",
alloc_table[i].num,
alloc_table[i].alloc_file, alloc_table[i].alloc_line);
#endif
}
/**************************************** memcmp() replacement */
/* a version of memcmp() with execution time not dependent on data values */
/* it does *not* allow to test wheter s1 is greater or lesser than s2 */
int safe_memcmp(const void *s1, const void *s2, size_t n) {
uint8_t *p1=(uint8_t *)s1, *p2=(uint8_t *)s2;
int r=0;
while(n--)
r|=*p1++^*p2++;
return r;
}
/* end of str.c */