/* memory.c - memory allocation
* Modified by No Such Labs. (C) 2015. See README.
*
* This file was originally part of Gnu Privacy Guard (GPG), ver. 1.4.10,
* SHA256(gnupg-1.4.10.tar.gz):
* 0bfd74660a2f6cedcf7d8256db4a63c996ffebbcdc2cf54397bfb72878c5a85a
* (C) 1994-2005 Free Software Foundation, Inc.
*
* 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 3 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 .
*/
/* We use our own memory allocation functions instead of plain malloc(),
* so that we can provide some special enhancements:
* a) functions to provide memory from a secure memory.
* b) by looking at the requested allocation size we
* can reuse memory very quickly (e.g. MPI storage)
* (really needed?)
* c) memory usage reporting if compiled with M_DEBUG
* d) memory checking if compiled with M_GUARD
*/
#include
#include
#include
#include
#include "knobs.h"
#include "types.h"
#include "memory.h"
#include "util.h"
#define MAGIC_NOR_BYTE 0x55
#define MAGIC_SEC_BYTE 0xcc
#define MAGIC_END_BYTE 0xaa
/* This is a very crude alignment check which does not work on all CPUs
* IIRC, I once introduced it for testing on an Alpha. We should better
* replace this guard stuff with one provided by a modern malloc library
*/
#if SIZEOF_UNSIGNED_LONG == 8
#define EXTRA_ALIGN 4
#else
#define EXTRA_ALIGN 0
#endif
#if defined(M_DEBUG) || defined(M_GUARD)
static void membug( const char *fmt, ... );
#endif
#ifdef M_DEBUG
#ifndef M_GUARD
#define M_GUARD 1
#endif
#undef xmalloc
#undef xtrymalloc
#undef xmalloc_clear
#undef xmalloc_secure
#undef xmalloc_secure_clear
#undef xrealloc
#undef xfree
#undef m_check
#undef xstrdup
#undef xtrystrdup
#define FNAME(a) m_debug_ ##a
#define FNAMEX(a) m_debug_ ##a
#define FNAMEXM(a) m_debug_ ##a
#define FNAMEPRT , const char *info
#define FNAMEARG , info
#define store_len(p,n,m) do { add_entry(p,n,m, \
info, __FUNCTION__); } while(0)
#else
#define FNAME(a) m_ ##a
#define FNAMEX(a) x ##a
#define FNAMEXM(a) xm ##a
#define FNAMEPRT
#define FNAMEARG
#define store_len(p,n,m) do { ((byte*)p)[EXTRA_ALIGN+0] = n; \
((byte*)p)[EXTRA_ALIGN+1] = n >> 8 ; \
((byte*)p)[EXTRA_ALIGN+2] = n >> 16 ; \
((byte*)p)[EXTRA_ALIGN+3] = m? MAGIC_SEC_BYTE \
: MAGIC_NOR_BYTE; \
} while(0)
#endif
#ifdef M_GUARD
static long used_memory;
#endif
#ifdef M_DEBUG /* stuff used for memory debuging */
struct info_entry {
struct info_entry *next;
unsigned count; /* call count */
const char *info; /* the reference to the info string */
};
struct memtbl_entry {
const void *user_p; /* for reference: the pointer given to the user */
size_t user_n; /* length requested by the user */
struct memtbl_entry *next; /* to build a list of unused entries */
const struct info_entry *info; /* points into the table with */
/* the info strings */
unsigned inuse:1; /* this entry is in use */
unsigned count:31;
};
#define INFO_BUCKETS 53
#define info_hash(p) ( *(u32*)((p)) % INFO_BUCKETS )
static struct info_entry *info_strings[INFO_BUCKETS]; /* hash table */
static struct memtbl_entry *memtbl; /* the table with the memory info */
static unsigned memtbl_size; /* number of allocated entries */
static unsigned memtbl_len; /* number of used entries */
static struct memtbl_entry *memtbl_unused;/* to keep track of unused entries */
static void dump_table_at_exit(void);
static void dump_table(void);
static void check_allmem( const char *info );
/****************
* Put the new P into the debug table and return a pointer to the table entry.
* mode is true for security. BY is the name of the function which called us.
*/
static void
add_entry( byte *p, unsigned n, int mode, const char *info, const char *by )
{
unsigned index;
struct memtbl_entry *e;
struct info_entry *ie;
if( memtbl_len < memtbl_size )
index = memtbl_len++;
else {
struct memtbl_entry *e;
/* look for a used entry in the table. We take the first one,
* so that freed entries remain as long as possible in the table
* (free appends a new one)
*/
if( (e = memtbl_unused) ) {
index = e - memtbl;
memtbl_unused = e->next;
e->next = NULL;
}
else { /* no free entries in the table: extend the table */
if( !memtbl_size ) { /* first time */
memtbl_size = 100;
if( !(memtbl = calloc( memtbl_size, sizeof *memtbl )) )
membug("memory debug table malloc failed\n");
index = 0;
memtbl_len = 1;
atexit( dump_table_at_exit );
}
else { /* realloc */
unsigned n = memtbl_size / 4; /* enlarge by 25% */
if(!(memtbl = realloc(memtbl, (memtbl_size+n)*sizeof *memtbl)))
membug("memory debug table realloc failed\n");
memset(memtbl+memtbl_size, 0, n*sizeof *memtbl );
memtbl_size += n;
index = memtbl_len++;
}
}
}
e = memtbl+index;
if( e->inuse )
membug("Ooops: entry %u is flagged as in use\n", index);
e->user_p = p + EXTRA_ALIGN + 4;
e->user_n = n;
e->count++;
if( e->next )
membug("Ooops: entry is in free entry list\n");
/* do we already have this info string */
for( ie = info_strings[info_hash(info)]; ie; ie = ie->next )
if( ie->info == info )
break;
if( !ie ) { /* no: make a new entry */
if( !(ie = malloc( sizeof *ie )) )
membug("can't allocate info entry\n");
ie->next = info_strings[info_hash(info)];
info_strings[info_hash(info)] = ie;
ie->info = info;
ie->count = 0;
}
ie->count++;
e->info = ie;
e->inuse = 1;
/* put the index at the start of the memory */
p[EXTRA_ALIGN+0] = index;
p[EXTRA_ALIGN+1] = index >> 8 ;
p[EXTRA_ALIGN+2] = index >> 16 ;
p[EXTRA_ALIGN+3] = mode? MAGIC_SEC_BYTE : MAGIC_NOR_BYTE ;
if( DBG_MEMORY )
log_debug( "%s allocates %u bytes using %s\n", info, e->user_n, by );
}
/****************
* Check that the memory block is correct. The magic byte has already been
* checked. Checks which are done here:
* - see whether the index points into our memory table
* - see whether P is the same as the one stored in the table
* - see whether we have already freed this block.
*/
struct memtbl_entry *
check_mem( const byte *p, const char *info )
{
unsigned n;
struct memtbl_entry *e;
n = p[EXTRA_ALIGN+0];
n |= p[EXTRA_ALIGN+1] << 8;
n |= p[EXTRA_ALIGN+2] << 16;
if( n >= memtbl_len )
membug("memory at %p corrupted: index=%u table_len=%u (%s)\n",
p+EXTRA_ALIGN+4, n, memtbl_len, info );
e = memtbl+n;
if( e->user_p != p+EXTRA_ALIGN+4 )
membug("memory at %p corrupted: reference mismatch (%s)\n",
p+EXTRA_ALIGN+4, info );
if( !e->inuse )
membug("memory at %p corrupted: marked as free (%s)\n",
p+EXTRA_ALIGN+4, info );
if( !(p[EXTRA_ALIGN+3] == MAGIC_NOR_BYTE
|| p[EXTRA_ALIGN+3] == MAGIC_SEC_BYTE) )
membug("memory at %p corrupted: underflow=%02x (%s)\n",
p+EXTRA_ALIGN+4, p[EXTRA_ALIGN+3], info );
if( p[EXTRA_ALIGN+4+e->user_n] != MAGIC_END_BYTE )
membug("memory at %p corrupted: overflow=%02x (%s)\n",
p+EXTRA_ALIGN+4, p[EXTRA_ALIGN+4+e->user_n], info );
return e;
}
/****************
* free the entry and the memory (replaces free)
*/
static void
free_entry( byte *p, const char *info )
{
struct memtbl_entry *e, *e2;
check_allmem("add_entry");
e = check_mem(p, info);
if( DBG_MEMORY )
log_debug( "%s frees %u bytes alloced by %s\n",
info, e->user_n, e->info->info );
if( !e->inuse ) {
if( e->user_p == p + EXTRA_ALIGN+ 4 )
membug("freeing an already freed pointer at %p\n", p+EXTRA_ALIGN+4 );
else
membug("freeing pointer %p which is flagged as freed\n", p+EXTRA_ALIGN+4 );
}
e->inuse = 0;
e->next = NULL;
if( !memtbl_unused )
memtbl_unused = e;
else {
for(e2=memtbl_unused; e2->next; e2 = e2->next )
;
e2->next = e;
}
if( m_is_secure(p+EXTRA_ALIGN+4) )
secmem_free(p);
else {
memset(p,'f', e->user_n+5);
free(p);
}
}
static void
dump_entry(struct memtbl_entry *e )
{
unsigned n = e - memtbl;
fprintf(stderr, "mem %4u%c %5u %p %5u %s (%u)\n",
n, e->inuse?'a':'u', e->count, e->user_p, e->user_n,
e->info->info, e->info->count );
}
static void
dump_table_at_exit( void)
{
if( DBG_MEMSTAT )
dump_table();
}
static void
dump_table( void)
{
unsigned n;
struct memtbl_entry *e;
ulong sum = 0, chunks =0;
for( e = memtbl, n = 0; n < memtbl_len; n++, e++ ) {
if(e->inuse) {
dump_entry(e);
sum += e->user_n;
chunks++;
}
}
fprintf(stderr, " memory used: %8lu bytes in %ld chunks\n",
sum, chunks );
}
static void
check_allmem( const char *info )
{
unsigned n;
struct memtbl_entry *e;
for( e = memtbl, n = 0; n < memtbl_len; n++, e++ ) {
if( e->inuse ) {
check_mem(e->user_p-4-EXTRA_ALIGN, info);
}
}
}
#endif /* M_DEBUG */
#if defined(M_DEBUG) || defined(M_GUARD)
static void
membug( const char *fmt, ... )
{
va_list arg_ptr ;
fprintf(stderr, "\nMemory Error: " ) ;
va_start( arg_ptr, fmt ) ;
vfprintf(stderr,fmt,arg_ptr) ;
va_end(arg_ptr);
fflush(stderr);
#ifdef M_DEBUG
if( DBG_MEMSTAT )
dump_table();
#endif
abort();
}
#endif
void
m_print_stats( const char *prefix )
{
#ifdef M_DEBUG
unsigned n;
struct memtbl_entry *e;
ulong sum = 0, chunks =0;
for( e = memtbl, n = 0; n < memtbl_len; n++, e++ ) {
if(e->inuse) {
sum += e->user_n;
chunks++;
}
}
log_debug( "%s%smemstat: %8lu bytes in %ld chunks used\n",
prefix? prefix:"", prefix? ": ":"", sum, chunks );
#elif defined(M_GUARD)
log_debug( "%s%smemstat: %8ld bytes\n",
prefix? prefix:"", prefix? ": ":"", used_memory );
#endif
}
void
m_dump_table( const char *prefix )
{
#ifdef M_DEBUG
fprintf(stderr,"Memory-Table-Dump: %s\n", prefix);
dump_table();
#endif
m_print_stats( prefix );
}
static void
out_of_core(size_t n, int secure)
{
log_error ("out of %s memory while allocating %u bytes\n",
secure? "secure":"" ,(unsigned)n );
if (secure) {
/*secmem_dump_stats ();*/
log_info ("(this may be caused by too many secret keys used "
"simultaneously or due to excessive large key sizes)\n");
}
exit(2);
}
/****************
* Allocate memory of size n.
* This function gives up if we do not have enough memory
*/
void *
FNAMEXM(alloc)( size_t n FNAMEPRT )
{
char *p;
#ifdef M_GUARD
if(!n)
out_of_core(n,0); /* should never happen */
if( !(p = malloc( n + EXTRA_ALIGN+5 )) )
out_of_core(n,0);
store_len(p,n,0);
used_memory += n;
p[4+EXTRA_ALIGN+n] = MAGIC_END_BYTE;
return p+EXTRA_ALIGN+4;
#else
/* mallocing zero bytes is undefined by ISO-C, so we better make
sure that it won't happen */
if (!n)
n = 1;
if( !(p = malloc( n )) )
out_of_core(n,0);
return p;
#endif
}
/* Allocate memory of size n. This function returns NULL if we do not
have enough memory. */
void *
FNAMEX(trymalloc)(size_t n FNAMEPRT)
{
#ifdef M_GUARD
char *p;
if (!n)
n = 1;
p = malloc (n + EXTRA_ALIGN+5);
if (!p)
return NULL;
store_len(p,n,0);
used_memory += n;
p[4+EXTRA_ALIGN+n] = MAGIC_END_BYTE;
return p+EXTRA_ALIGN+4;
#else
/* Mallocing zero bytes is undefined by ISO-C, so we better make
sure that it won't happen. */
return malloc (n? n: 1);
#endif
}
/****************
* Allocate memory of size n from the secure memory pool.
* This function gives up if we do not have enough memory
*/
void *
FNAMEXM(alloc_secure)( size_t n FNAMEPRT )
{
char *p;
#ifdef M_GUARD
if(!n)
out_of_core(n,1); /* should never happen */
if( !(p = secmem_malloc( n +EXTRA_ALIGN+ 5 )) )
out_of_core(n,1);
store_len(p,n,1);
p[4+EXTRA_ALIGN+n] = MAGIC_END_BYTE;
return p+EXTRA_ALIGN+4;
#else
/* mallocing zero bytes is undefined by ISO-C, so we better make
sure that it won't happen */
if (!n)
n = 1;
if( !(p = secmem_malloc( n )) )
out_of_core(n,1);
return p;
#endif
}
void *
FNAMEXM(alloc_clear)( size_t n FNAMEPRT )
{
void *p;
p = FNAMEXM(alloc)( n FNAMEARG );
memset(p, 0, n );
return p;
}
void *
FNAMEXM(alloc_secure_clear)( size_t n FNAMEPRT)
{
void *p;
p = FNAMEXM(alloc_secure)( n FNAMEARG );
memset(p, 0, n );
return p;
}
/****************
* realloc and clear the old space
*/
void *
FNAMEX(realloc)( void *a, size_t n FNAMEPRT )
{
void *b;
#ifdef M_GUARD
if( a ) {
#error "--enable-m-guard does not currently work"
unsigned char *p = a;
size_t len = m_size(a);
if( len >= n ) /* we don't shrink for now */
return a;
if( p[-1] == MAGIC_SEC_BYTE )
b = FNAME(alloc_secure_clear)(n FNAMEARG);
else
b = FNAME(alloc_clear)(n FNAMEARG);
FNAME(check)(NULL FNAMEARG);
memcpy(b, a, len );
FNAME(free)(p FNAMEARG);
}
else
b = FNAME(alloc)(n FNAMEARG);
#else
if( m_is_secure(a) ) {
if( !(b = secmexrealloc( a, n )) )
out_of_core(n,1);
}
else {
if( !(b = realloc( a, n )) )
out_of_core(n,0);
}
#endif
return b;
}
/****************
* Free a pointer
*/
void
FNAMEX(free)( void *a FNAMEPRT )
{
byte *p = a;
if( !p )
return;
#ifdef M_DEBUG
free_entry(p-EXTRA_ALIGN-4, info);
#elif defined M_GUARD
m_check(p);
if( m_is_secure(a) )
secmem_free(p-EXTRA_ALIGN-4);
else {
used_memory -= m_size(a);
free(p-EXTRA_ALIGN-4);
}
#else
if( m_is_secure(a) )
secmem_free(p);
else
free(p);
#endif
}
void
FNAME(check)( const void *a FNAMEPRT )
{
#ifdef M_GUARD
const byte *p = a;
#ifdef M_DEBUG
if( p )
check_mem(p-EXTRA_ALIGN-4, info);
else
check_allmem(info);
#else
if( !p )
return;
if( !(p[-1] == MAGIC_NOR_BYTE || p[-1] == MAGIC_SEC_BYTE) )
membug("memory at %p corrupted (underflow=%02x)\n", p, p[-1] );
else if( p[m_size(p)] != MAGIC_END_BYTE )
membug("memory at %p corrupted (overflow=%02x)\n", p, p[-1] );
#endif
#endif
}
size_t
m_size( const void *a )
{
#ifndef M_GUARD
log_debug("dummy m_size called\n");
return 0;
#else
const byte *p = a;
size_t n;
#ifdef M_DEBUG
n = check_mem(p-EXTRA_ALIGN-4, "m_size")->user_n;
#else
n = ((byte*)p)[-4];
n |= ((byte*)p)[-3] << 8;
n |= ((byte*)p)[-2] << 16;
#endif
return n;
#endif
}
char *
FNAMEX(strdup)( const char *a FNAMEPRT )
{
size_t n = strlen(a);
char *p = FNAMEXM(alloc)(n+1 FNAMEARG);
strcpy(p, a);
return p;
}
char *
FNAMEX(trystrdup)(const char *a FNAMEPRT)
{
size_t n = strlen (a);
char *p = FNAMEX(trymalloc)(n+1 FNAMEARG);
if (p)
strcpy (p, a);
return p;
}
/* Wrapper around xmalloc_clear to take the usual 2 arguments of a
calloc style function. */
void *
xcalloc (size_t n, size_t m)
{
size_t nbytes;
nbytes = n * m;
if (m && nbytes / m != n)
out_of_core (nbytes, 0);
return xmalloc_clear (nbytes);
}
/* Wrapper around xmalloc_csecure_lear to take the usual 2 arguments
of a calloc style function. */
void *
xcalloc_secure (size_t n, size_t m)
{
size_t nbytes;
nbytes = n * m;
if (m && nbytes / m != n)
out_of_core (nbytes, 1);
return xmalloc_secure_clear (nbytes);
}