/* memory.c - memory allocation * Copyright (C) 1998, 1999, 2001, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG 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. * * GnuPG 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 #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 #ifndef __riscos__ #define store_len(p,n,m) do { add_entry(p,n,m, \ info, __FUNCTION__); } while(0) #else #define store_len(p,n,m) do { add_entry(p,n,m, \ info, __func__ ); } while(0) #endif #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 ) { #ifndef __riscos__ check_mem(e->user_p-4-EXTRA_ALIGN, info); #else check_mem((const byte *) e->user_p-4-EXTRA_ALIGN, info); #endif } } } #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"); } #if defined(M_GUARD) && defined(__riscos__) abort(); #endif 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); }