diff --git a/src/secmem.c b/src/secmem.c index 928e03fc..4fa267b6 100644 --- a/src/secmem.c +++ b/src/secmem.c @@ -1,789 +1,878 @@ /* secmem.c - memory allocation from a secure heap * Copyright (C) 1998, 1999, 2000, 2001, 2002, * 2003, 2007 Free Software Foundation, Inc. * Copyright (C) 2013, 2016 g10 Code GmbH * * This file is part of Libgcrypt. * * Libgcrypt is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser general Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Libgcrypt 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #if defined(HAVE_MLOCK) || defined(HAVE_MMAP) #include #include #include #ifdef USE_CAPABILITIES #include #endif #endif #include "g10lib.h" #include "secmem.h" #if defined (MAP_ANON) && ! defined (MAP_ANONYMOUS) #define MAP_ANONYMOUS MAP_ANON #endif #define MINIMUM_POOL_SIZE 16384 #define STANDARD_POOL_SIZE 32768 #define DEFAULT_PAGE_SIZE 4096 typedef struct memblock { unsigned size; /* Size of the memory available to the user. */ int flags; /* See below. */ PROPERLY_ALIGNED_TYPE aligned; } memblock_t; /* This flag specifies that the memory block is in use. */ #define MB_FLAG_ACTIVE (1 << 0) /* An object describing a memory pool. */ typedef struct pooldesc_s { + /* A link to the next pool. This is used to connect the overflow + * pools. */ + struct pooldesc_s *next; + /* A memory buffer used as allocation pool. */ void *mem; /* The allocated size of MEM. */ size_t size; /* Flag indicating that this memory pool is ready for use. May be * checked in an atexit function. */ volatile int okay; /* Flag indicating whether MEM is mmapped. */ volatile int is_mmapped; + /* The number of allocated bytes and the number of used blocks in + * this pool. */ + unsigned int cur_alloced, cur_blocks; } pooldesc_t; -/* The pool of secure memory. */ +/* The pool of secure memory. This is the head of a linked list with + * the first element being the standard mlock-ed pool and the + * following elements being the overflow pools. */ static pooldesc_t mainpool; /* A couple of flags whith some beeing set early. */ static int disable_secmem; static int show_warning; static int not_locked; static int no_warning; static int suspend_warning; static int no_mlock; static int no_priv_drop; -/* Stats. */ -static unsigned int cur_alloced, cur_blocks; - /* Lock protecting accesses to the memory pools. */ GPGRT_LOCK_DEFINE (secmem_lock); /* Convenient macros. */ #define SECMEM_LOCK gpgrt_lock_lock (&secmem_lock) #define SECMEM_UNLOCK gpgrt_lock_unlock (&secmem_lock) /* The size of the memblock structure; this does not include the memory that is available to the user. */ #define BLOCK_HEAD_SIZE \ offsetof (memblock_t, aligned) /* Convert an address into the according memory block structure. */ #define ADDR_TO_BLOCK(addr) \ (memblock_t *) (void *) ((char *) addr - BLOCK_HEAD_SIZE) /* Check whether P points into POOL. */ -static int +static inline int ptr_into_pool_p (pooldesc_t *pool, const void *p) { /* We need to convert pointers to addresses. This is required by C-99 6.5.8 to avoid undefined behaviour. See also http://lists.gnupg.org/pipermail/gcrypt-devel/2007-February/001102.html */ uintptr_t p_addr = (uintptr_t)p; uintptr_t pool_addr = (uintptr_t)pool->mem; return p_addr >= pool_addr && p_addr < pool_addr + pool->size; } /* Update the stats. */ static void -stats_update (size_t add, size_t sub) +stats_update (pooldesc_t *pool, size_t add, size_t sub) { if (add) { - cur_alloced += add; - cur_blocks++; + pool->cur_alloced += add; + pool->cur_blocks++; } if (sub) { - cur_alloced -= sub; - cur_blocks--; + pool->cur_alloced -= sub; + pool->cur_blocks--; } } /* Return the block following MB or NULL, if MB is the last block. */ static memblock_t * mb_get_next (pooldesc_t *pool, memblock_t *mb) { memblock_t *mb_next; mb_next = (memblock_t *) (void *) ((char *) mb + BLOCK_HEAD_SIZE + mb->size); if (! ptr_into_pool_p (pool, mb_next)) mb_next = NULL; return mb_next; } /* Return the block preceding MB or NULL, if MB is the first block. */ static memblock_t * mb_get_prev (pooldesc_t *pool, memblock_t *mb) { memblock_t *mb_prev, *mb_next; if (mb == pool->mem) mb_prev = NULL; else { mb_prev = (memblock_t *) pool->mem; while (1) { mb_next = mb_get_next (pool, mb_prev); if (mb_next == mb) break; else mb_prev = mb_next; } } return mb_prev; } /* If the preceding block of MB and/or the following block of MB exist and are not active, merge them to form a bigger block. */ static void mb_merge (pooldesc_t *pool, memblock_t *mb) { memblock_t *mb_prev, *mb_next; mb_prev = mb_get_prev (pool, mb); mb_next = mb_get_next (pool, mb); if (mb_prev && (! (mb_prev->flags & MB_FLAG_ACTIVE))) { mb_prev->size += BLOCK_HEAD_SIZE + mb->size; mb = mb_prev; } if (mb_next && (! (mb_next->flags & MB_FLAG_ACTIVE))) mb->size += BLOCK_HEAD_SIZE + mb_next->size; } /* Return a new block, which can hold SIZE bytes. */ static memblock_t * mb_get_new (pooldesc_t *pool, memblock_t *block, size_t size) { memblock_t *mb, *mb_split; for (mb = block; ptr_into_pool_p (pool, mb); mb = mb_get_next (pool, mb)) if (! (mb->flags & MB_FLAG_ACTIVE) && mb->size >= size) { /* Found a free block. */ mb->flags |= MB_FLAG_ACTIVE; if (mb->size - size > BLOCK_HEAD_SIZE) { /* Split block. */ mb_split = (memblock_t *) (void *) (((char *) mb) + BLOCK_HEAD_SIZE + size); mb_split->size = mb->size - size - BLOCK_HEAD_SIZE; mb_split->flags = 0; mb->size = size; mb_merge (pool, mb_split); } break; } if (! ptr_into_pool_p (pool, mb)) { gpg_err_set_errno (ENOMEM); mb = NULL; } return mb; } /* Print a warning message. */ static void print_warn (void) { if (!no_warning) log_info (_("Warning: using insecure memory!\n")); } /* Lock the memory pages of pool P of size N into core and drop * privileges. */ static void lock_pool_pages (void *p, size_t n) { #if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK) int err; { cap_t cap; if (!no_priv_drop) { cap = cap_from_text ("cap_ipc_lock+ep"); cap_set_proc (cap); cap_free (cap); } err = no_mlock? 0 : mlock (p, n); if (err && errno) err = errno; if (!no_priv_drop) { cap = cap_from_text ("cap_ipc_lock+p"); cap_set_proc (cap); cap_free(cap); } } if (err) { if (errno != EPERM #ifdef EAGAIN /* OpenBSD returns this */ && errno != EAGAIN #endif #ifdef ENOSYS /* Some SCOs return this (function not implemented) */ && errno != ENOSYS #endif #ifdef ENOMEM /* Linux might return this. */ && errno != ENOMEM #endif ) log_error ("can't lock memory: %s\n", strerror (err)); show_warning = 1; not_locked = 1; } #elif defined(HAVE_MLOCK) uid_t uid; int err; uid = getuid (); #ifdef HAVE_BROKEN_MLOCK /* Under HP/UX mlock segfaults if called by non-root. Note, we have noch checked whether mlock does really work under AIX where we also detected a broken nlock. Note further, that using plock () is not a good idea under AIX. */ if (uid) { errno = EPERM; err = errno; } else { err = no_mlock? 0 : mlock (p, n); if (err && errno) err = errno; } #else /* !HAVE_BROKEN_MLOCK */ err = no_mlock? 0 : mlock (p, n); if (err && errno) err = errno; #endif /* !HAVE_BROKEN_MLOCK */ /* Test whether we are running setuid(0). */ if (uid && ! geteuid ()) { /* Yes, we are. */ if (!no_priv_drop) { /* Check that we really dropped the privs. * Note: setuid(0) should always fail */ if (setuid (uid) || getuid () != geteuid () || !setuid (0)) log_fatal ("failed to reset uid: %s\n", strerror (errno)); } } if (err) { if (errno != EPERM #ifdef EAGAIN /* OpenBSD returns this. */ && errno != EAGAIN #endif #ifdef ENOSYS /* Some SCOs return this (function not implemented). */ && errno != ENOSYS #endif #ifdef ENOMEM /* Linux might return this. */ && errno != ENOMEM #endif ) log_error ("can't lock memory: %s\n", strerror (err)); show_warning = 1; not_locked = 1; } #elif defined ( __QNX__ ) /* QNX does not page at all, so the whole secure memory stuff does * not make much sense. However it is still of use because it * wipes out the memory on a free(). * Therefore it is sufficient to suppress the warning. */ (void)p; (void)n; #elif defined (HAVE_DOSISH_SYSTEM) || defined (__CYGWIN__) /* It does not make sense to print such a warning, given the fact that * this whole Windows !@#$% and their user base are inherently insecure. */ (void)p; (void)n; #elif defined (__riscos__) /* No virtual memory on RISC OS, so no pages are swapped to disc, * besides we don't have mmap, so we don't use it! ;-) * But don't complain, as explained above. */ (void)p; (void)n; #else (void)p; (void)n; if (!no_mlock) log_info ("Please note that you don't have secure memory on this system\n"); #endif } /* Initialize POOL. */ static void init_pool (pooldesc_t *pool, size_t n) { memblock_t *mb; pool->size = n; if (disable_secmem) log_bug ("secure memory is disabled"); #if HAVE_MMAP { size_t pgsize; long int pgsize_val; # if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) pgsize_val = sysconf (_SC_PAGESIZE); # elif defined(HAVE_GETPAGESIZE) pgsize_val = getpagesize (); # else pgsize_val = -1; # endif pgsize = (pgsize_val != -1 && pgsize_val > 0)? pgsize_val:DEFAULT_PAGE_SIZE; pool->size = (pool->size + pgsize - 1) & ~(pgsize - 1); # ifdef MAP_ANONYMOUS pool->mem = mmap (0, pool->size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); # else /* map /dev/zero instead */ { int fd; fd = open ("/dev/zero", O_RDWR); if (fd == -1) { log_error ("can't open /dev/zero: %s\n", strerror (errno)); pool->mem = (void *) -1; } else { pool->mem = mmap (0, pool->size, (PROT_READ | PROT_WRITE), MAP_PRIVATE, fd, 0); close (fd); } } # endif if (pool->mem == (void *) -1) log_info ("can't mmap pool of %u bytes: %s - using malloc\n", (unsigned) pool->size, strerror (errno)); else { pool->is_mmapped = 1; pool->okay = 1; } } #endif /*HAVE_MMAP*/ if (!pool->okay) { pool->mem = malloc (pool->size); if (!pool->mem) log_fatal ("can't allocate memory pool of %u bytes\n", (unsigned) pool->size); else pool->okay = 1; } /* Initialize first memory block. */ mb = (memblock_t *) pool->mem; mb->size = pool->size; mb->flags = 0; } void _gcry_secmem_set_flags (unsigned flags) { int was_susp; SECMEM_LOCK; was_susp = suspend_warning; no_warning = flags & GCRY_SECMEM_FLAG_NO_WARNING; suspend_warning = flags & GCRY_SECMEM_FLAG_SUSPEND_WARNING; no_mlock = flags & GCRY_SECMEM_FLAG_NO_MLOCK; no_priv_drop = flags & GCRY_SECMEM_FLAG_NO_PRIV_DROP; /* and now issue the warning if it is not longer suspended */ if (was_susp && !suspend_warning && show_warning) { show_warning = 0; print_warn (); } SECMEM_UNLOCK; } unsigned int _gcry_secmem_get_flags (void) { unsigned flags; SECMEM_LOCK; flags = no_warning ? GCRY_SECMEM_FLAG_NO_WARNING : 0; flags |= suspend_warning ? GCRY_SECMEM_FLAG_SUSPEND_WARNING : 0; flags |= not_locked ? GCRY_SECMEM_FLAG_NOT_LOCKED : 0; flags |= no_mlock ? GCRY_SECMEM_FLAG_NO_MLOCK : 0; flags |= no_priv_drop ? GCRY_SECMEM_FLAG_NO_PRIV_DROP : 0; SECMEM_UNLOCK; return flags; } /* This function initializes the main memory pool MAINPOOL. Itis * expected to be called with the secmem lock held. */ static void _gcry_secmem_init_internal (size_t n) { pooldesc_t *pool; pool = &mainpool; if (!n) { #ifdef USE_CAPABILITIES /* drop all capabilities */ if (!no_priv_drop) { cap_t cap; cap = cap_from_text ("all-eip"); cap_set_proc (cap); cap_free (cap); } #elif !defined(HAVE_DOSISH_SYSTEM) uid_t uid; disable_secmem = 1; uid = getuid (); if (uid != geteuid ()) { if (setuid (uid) || getuid () != geteuid () || !setuid (0)) log_fatal ("failed to drop setuid\n"); } #endif } else { if (n < MINIMUM_POOL_SIZE) n = MINIMUM_POOL_SIZE; if (! pool->okay) { init_pool (pool, n); lock_pool_pages (pool->mem, n); } else log_error ("Oops, secure memory pool already initialized\n"); } } /* Initialize the secure memory system. If running with the necessary privileges, the secure memory pool will be locked into the core in order to prevent page-outs of the data. Furthermore allocated secure memory will be wiped out when released. */ void _gcry_secmem_init (size_t n) { SECMEM_LOCK; _gcry_secmem_init_internal (n); SECMEM_UNLOCK; } gcry_err_code_t _gcry_secmem_module_init () { /* Not anymore needed. */ return 0; } static void * -_gcry_secmem_malloc_internal (size_t size) +_gcry_secmem_malloc_internal (size_t size, int xhint) { pooldesc_t *pool; memblock_t *mb; pool = &mainpool; if (!pool->okay) { /* Try to initialize the pool if the user forgot about it. */ _gcry_secmem_init_internal (STANDARD_POOL_SIZE); if (!pool->okay) { log_info (_("operation is not possible without " "initialized secure memory\n")); gpg_err_set_errno (ENOMEM); return NULL; } } if (not_locked && fips_mode ()) { log_info (_("secure memory pool is not locked while in FIPS mode\n")); gpg_err_set_errno (ENOMEM); return NULL; } if (show_warning && !suspend_warning) { show_warning = 0; print_warn (); } /* Blocks are always a multiple of 32. */ size = ((size + 31) / 32) * 32; mb = mb_get_new (pool, (memblock_t *) pool->mem, size); if (mb) - stats_update (size, 0); + { + stats_update (pool, size, 0); + return &mb->aligned.c; + } + + /* If we are called from xmalloc style function resort to the + * overflow pools to return memory. We don't do this in FIPS mode, + * though. */ + if (xhint && !fips_mode ()) + { + for (pool = pool->next; pool; pool = pool->next) + { + mb = mb_get_new (pool, (memblock_t *) pool->mem, size); + if (mb) + { + stats_update (pool, size, 0); + return &mb->aligned.c; + } + } + /* Allocate a new overflow pool. We put a new pool right after + * the mainpool so that the next allocation will happen in that + * pool and not in one of the older pools. When this new pool + * gets full we will try to find space in the older pools. */ + pool = calloc (1, sizeof *pool); + if (!pool) + return NULL; /* Not enough memory for a new pool descriptor. */ + pool->size = STANDARD_POOL_SIZE; + pool->mem = malloc (pool->size); + if (!pool->mem) + return NULL; /* Not enough memory available for a new pool. */ + /* Initialize first memory block. */ + mb = (memblock_t *) pool->mem; + mb->size = pool->size; + mb->flags = 0; + + pool->okay = 1; + + /* Take care: in _gcry_private_is_secure we do not lock and thus + * we assume that the second assignment below is atomic. */ + pool->next = mainpool.next; + mainpool.next = pool; + + /* After the first time we allocated an overflow pool, print a + * warning. */ + if (!pool->next) + print_warn (); + + /* Allocate. */ + mb = mb_get_new (pool, (memblock_t *) pool->mem, size); + if (mb) + { + stats_update (pool, size, 0); + return &mb->aligned.c; + } + } - return mb ? &mb->aligned.c : NULL; + return NULL; } /* Allocate a block from the secmem of SIZE. With XHINT set assume * that the caller is a xmalloc style function. */ void * _gcry_secmem_malloc (size_t size, int xhint) { void *p; SECMEM_LOCK; - p = _gcry_secmem_malloc_internal (size); + p = _gcry_secmem_malloc_internal (size, xhint); SECMEM_UNLOCK; return p; } -static void +static int _gcry_secmem_free_internal (void *a) { pooldesc_t *pool; memblock_t *mb; int size; - pool = &mainpool; + for (pool = &mainpool; pool; pool = pool->next) + if (pool->okay && ptr_into_pool_p (pool, a)) + break; + if (!pool) + return 0; /* A does not belong to use. */ mb = ADDR_TO_BLOCK (a); size = mb->size; /* This does not make much sense: probably this memory is held in the * cache. We do it anyway: */ #define MB_WIPE_OUT(byte) \ wipememory2 (((char *) mb + BLOCK_HEAD_SIZE), (byte), size); MB_WIPE_OUT (0xff); MB_WIPE_OUT (0xaa); MB_WIPE_OUT (0x55); MB_WIPE_OUT (0x00); /* Update stats. */ - stats_update (0, size); + stats_update (pool, 0, size); mb->flags &= ~MB_FLAG_ACTIVE; - mb_merge (pool, mb); + + return 1; /* Freed. */ } -/* Wipe out and release memory. */ -void + +/* Wipe out and release memory. Returns true if this function + * actually released A. */ +int _gcry_secmem_free (void *a) { + int mine; + if (!a) - return; + return 1; /* Tell caller that we handled it. */ SECMEM_LOCK; - _gcry_secmem_free_internal (a); + mine = _gcry_secmem_free_internal (a); SECMEM_UNLOCK; + return mine; } static void * -_gcry_secmem_realloc_internal (void *p, size_t newsize) +_gcry_secmem_realloc_internal (void *p, size_t newsize, int xhint) { memblock_t *mb; size_t size; void *a; mb = (memblock_t *) (void *) ((char *) p - ((size_t) &((memblock_t *) 0)->aligned.c)); size = mb->size; if (newsize < size) { /* It is easier to not shrink the memory. */ a = p; } else { - a = _gcry_secmem_malloc_internal (newsize); + a = _gcry_secmem_malloc_internal (newsize, xhint); if (a) { memcpy (a, p, size); memset ((char *) a + size, 0, newsize - size); _gcry_secmem_free_internal (p); } } return a; } /* Realloc memory. With XHINT set assume that the caller is a xmalloc * style function. */ void * _gcry_secmem_realloc (void *p, size_t newsize, int xhint) { void *a; SECMEM_LOCK; - a = _gcry_secmem_realloc_internal (p, newsize); + a = _gcry_secmem_realloc_internal (p, newsize, xhint); SECMEM_UNLOCK; return a; } -/* Return true if P points into the secure memory area. */ +/* Return true if P points into the secure memory areas. */ int _gcry_private_is_secure (const void *p) { pooldesc_t *pool; - pool = &mainpool; - return pool->okay && ptr_into_pool_p (pool, p); + /* We do no lock here because once a pool is allocatred it will not + * be removed anymore (except for gcry_secmem_term). Further, + * adding a new pool to the list should be atomic. */ + for (pool = &mainpool; pool; pool = pool->next) + if (pool->okay && ptr_into_pool_p (pool, p)) + return 1; + + return 0; } /**************** * Warning: This code might be called by an interrupt handler * and frankly, there should really be such a handler, * to make sure that the memory is wiped out. * We hope that the OS wipes out mlocked memory after * receiving a SIGKILL - it really should do so, otherwise * there is no chance to get the secure memory cleaned. */ void _gcry_secmem_term () { - pooldesc_t *pool; + pooldesc_t *pool, *next; - pool = &mainpool; - if (!pool->okay) - return; - - wipememory2 (pool->mem, 0xff, pool->size); - wipememory2 (pool->mem, 0xaa, pool->size); - wipememory2 (pool->mem, 0x55, pool->size); - wipememory2 (pool->mem, 0x00, pool->size); + for (pool = &mainpool; pool; pool = next) + { + next = pool->next; + if (!pool->okay) + continue; + + wipememory2 (pool->mem, 0xff, pool->size); + wipememory2 (pool->mem, 0xaa, pool->size); + wipememory2 (pool->mem, 0x55, pool->size); + wipememory2 (pool->mem, 0x00, pool->size); + if (0) + ; #if HAVE_MMAP - if (pool->is_mmapped) - munmap (pool->mem, pool->size); + else if (pool->is_mmapped) + munmap (pool->mem, pool->size); #endif - pool->mem = NULL; - pool->okay = 0; - pool->size = 0; + else + free (pool->mem); + pool->mem = NULL; + pool->okay = 0; + pool->size = 0; + if (pool != &mainpool) + free (pool); + } + mainpool.next = NULL; not_locked = 0; } /* Print stats of the secmem allocator. With EXTENDED passwed as true * a detiled listing is returned (used for testing). */ void _gcry_secmem_dump_stats (int extended) { pooldesc_t *pool; memblock_t *mb; - int i; + int i, poolno; SECMEM_LOCK; - pool = &mainpool; - if (!extended) + for (pool = &mainpool, poolno = 0; pool; pool = pool->next, poolno++) { - if (pool->okay) - log_info ("secmem usage: %u/%lu bytes in %u blocks\n", - cur_alloced, (unsigned long)pool->size, cur_blocks); + if (!extended) + { + if (pool->okay) + log_info ("%-13s %u/%lu bytes in %u blocks\n", + pool == &mainpool? "secmem usage:":"", + pool->cur_alloced, (unsigned long)pool->size, + pool->cur_blocks); + } + else + { + for (i = 0, mb = (memblock_t *) pool->mem; + ptr_into_pool_p (pool, mb); + mb = mb_get_next (pool, mb), i++) + log_info ("SECMEM: pool %d %s block %i size %i\n", + poolno, + (mb->flags & MB_FLAG_ACTIVE) ? "used" : "free", + i, + mb->size); + } } - else - { - for (i = 0, mb = (memblock_t *) pool->mem; - ptr_into_pool_p (pool, mb); - mb = mb_get_next (pool, mb), i++) - log_info ("SECMEM: pool %p %s block %i size %i\n", - pool, - (mb->flags & MB_FLAG_ACTIVE) ? "used" : "free", - i, - mb->size); - } - SECMEM_UNLOCK; } diff --git a/src/secmem.h b/src/secmem.h index c69fe88d..29dd64fc 100644 --- a/src/secmem.h +++ b/src/secmem.h @@ -1,41 +1,41 @@ /* secmem.h - internal definitions for secmem * Copyright (C) 2000, 2001, 2002, 2003 Free Software Foundation, Inc. * * This file is part of Libgcrypt. * * Libgcrypt is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser general Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Libgcrypt 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #ifndef G10_SECMEM_H #define G10_SECMEM_H 1 void _gcry_secmem_init (size_t npool); void _gcry_secmem_term (void); void *_gcry_secmem_malloc (size_t size, int xhint) _GCRY_GCC_ATTR_MALLOC; void *_gcry_secmem_realloc (void *a, size_t newsize, int xhint); -void _gcry_secmem_free (void *a); +int _gcry_secmem_free (void *a); void _gcry_secmem_dump_stats (int extended); void _gcry_secmem_set_flags (unsigned flags); unsigned _gcry_secmem_get_flags(void); int _gcry_private_is_secure (const void *p); /* Flags for _gcry_secmem_{set,get}_flags. */ #define GCRY_SECMEM_FLAG_NO_WARNING (1 << 0) #define GCRY_SECMEM_FLAG_SUSPEND_WARNING (1 << 1) #define GCRY_SECMEM_FLAG_NOT_LOCKED (1 << 2) #define GCRY_SECMEM_FLAG_NO_MLOCK (1 << 3) #define GCRY_SECMEM_FLAG_NO_PRIV_DROP (1 << 4) #endif /* G10_SECMEM_H */ diff --git a/src/stdmem.c b/src/stdmem.c index cf937ffb..cbda8d89 100644 --- a/src/stdmem.c +++ b/src/stdmem.c @@ -1,244 +1,242 @@ /* stdmem.c - private memory allocator * Copyright (C) 1998, 2000, 2002, 2005, 2008 Free Software Foundation, Inc. * * This file is part of Libgcrypt. * * Libgcrypt is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser general Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Libgcrypt 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . */ /* * Description of the layered memory management in Libgcrypt: * * [User] * | * | * \ / * global.c: [MM entrance points] -----> [user callbacks] * | | * | | * \ / \ / * * stdmem.c: [non-secure handlers] [secure handlers] * * | | * | | * \ / \ / * * stdmem.c: [ memory guard ] * * | | * | | * \ / \ / * * libc: [ MM functions ] secmem.c: [ secure MM functions] */ #include #include #include #include #include #include #include "g10lib.h" #include "stdmem.h" #include "secmem.h" #define MAGIC_NOR_BYTE 0x55 #define MAGIC_SEC_BYTE 0xcc #define MAGIC_END_BYTE 0xaa #if SIZEOF_UNSIGNED_LONG == 8 #define EXTRA_ALIGN 4 #else #define EXTRA_ALIGN 0 #endif static int use_m_guard = 0; /**************** * Warning: Never use this function after any of the functions * here have been used. */ void _gcry_private_enable_m_guard (void) { use_m_guard = 1; } /* * Allocate memory of size n. * Return NULL if we are out of memory. */ void * _gcry_private_malloc (size_t n) { if (!n) { gpg_err_set_errno (EINVAL); return NULL; /* Allocating 0 bytes is undefined - we better return an error to detect such coding errors. */ } if (use_m_guard) { char *p; if ( !(p = malloc (n + EXTRA_ALIGN+5)) ) return NULL; ((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] = MAGIC_NOR_BYTE; p[4+EXTRA_ALIGN+n] = MAGIC_END_BYTE; return p+EXTRA_ALIGN+4; } else { return malloc( n ); } } /* * Allocate memory of size N from the secure memory pool. Return NULL * if we are out of memory. XHINT tells the allocator that the caller * used an xmalloc style call. */ void * _gcry_private_malloc_secure (size_t n, int xhint) { if (!n) { gpg_err_set_errno (EINVAL); return NULL; /* Allocating 0 bytes is undefined - better return an error to detect such coding errors. */ } if (use_m_guard) { char *p; if (!(p = _gcry_secmem_malloc (n + EXTRA_ALIGN + 5, xhint))) return NULL; ((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] = MAGIC_SEC_BYTE; p[4+EXTRA_ALIGN+n] = MAGIC_END_BYTE; return p+EXTRA_ALIGN+4; } else { return _gcry_secmem_malloc (n, xhint); } } /* * Realloc and clear the old space. XHINT tells the allocator that * the caller used an xmalloc style call. Returns NULL if there is * not enough memory. */ void * _gcry_private_realloc (void *a, size_t n, int xhint) { if (use_m_guard) { unsigned char *p = a; char *b; size_t len; if (!a) return _gcry_private_malloc(n); _gcry_private_check_heap(p); len = p[-4]; len |= p[-3] << 8; len |= p[-2] << 16; if( len >= n ) /* We don't shrink for now. */ return a; if (p[-1] == MAGIC_SEC_BYTE) b = _gcry_private_malloc_secure (n, xhint); else b = _gcry_private_malloc(n); if (!b) return NULL; memcpy (b, a, len); memset (b+len, 0, n-len); _gcry_private_free (p); return b; } else if ( _gcry_private_is_secure(a) ) { return _gcry_secmem_realloc (a, n, xhint); } else { return realloc( a, n ); } } void _gcry_private_check_heap (const void *a) { if (use_m_guard) { const byte *p = a; size_t len; if (!p) return; if ( !(p[-1] == MAGIC_NOR_BYTE || p[-1] == MAGIC_SEC_BYTE) ) _gcry_log_fatal ("memory at %p corrupted (underflow=%02x)\n", p, p[-1]); len = p[-4]; len |= p[-3] << 8; len |= p[-2] << 16; if ( p[len] != MAGIC_END_BYTE ) _gcry_log_fatal ("memory at %p corrupted (overflow=%02x)\n", p, p[-1]); } } /* * Free a memory block allocated by this or the secmem module */ void _gcry_private_free (void *a) { unsigned char *p = a; if (!p) return; if (use_m_guard ) { _gcry_private_check_heap(p); - if ( _gcry_private_is_secure(a) ) - _gcry_secmem_free(p-EXTRA_ALIGN-4); - else + if (! _gcry_secmem_free (p - EXTRA_ALIGN - 4)) { - free(p-EXTRA_ALIGN-4); + free (p - EXTRA_ALIGN - 4); } } - else if ( _gcry_private_is_secure(a) ) - _gcry_secmem_free(p); - else - free(p); + else if (!_gcry_secmem_free (p)) + { + free(p); + } }