/* secmem.c - memory allocation from a secure heap * Copyright (C) 1998, 1999, 2000, 2001, 2002, * 2003, 2007 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 . */ #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 "ath.h" #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) /* The pool of secure memory. */ static void *pool; /* Size of POOL in bytes. */ static size_t pool_size; /* True, if the memory pool is ready for use. May be checked in an atexit function. */ static volatile int pool_okay; /* True, if the memory pool is mmapped. */ static volatile int pool_is_mmapped; /* FIXME? */ static int disable_secmem; static int show_warning; static int not_locked; static int no_warning; static int suspend_warning; /* Stats. */ static unsigned int cur_alloced, cur_blocks; /* Lock protecting accesses to the memory pool. */ static ath_mutex_t secmem_lock; /* Convenient macros. */ #define SECMEM_LOCK ath_mutex_lock (&secmem_lock) #define SECMEM_UNLOCK ath_mutex_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 *) ((char *) addr - BLOCK_HEAD_SIZE) /* Check whether P points into the pool. */ static int ptr_into_pool_p (const void *p) { /* We need to convert pointers to addresses. This is required by C-99 6.5.8 to avoid undefined behaviour. Using size_t is at least only implementation defined. See also http://lists.gnupg.org/pipermail/gcrypt-devel/2007-February/001102.html */ size_t p_addr = (size_t)p; size_t pool_addr = (size_t)pool; return p_addr >= pool_addr && p_addr < pool_addr+pool_size; } /* Update the stats. */ static void stats_update (size_t add, size_t sub) { if (add) { cur_alloced += add; cur_blocks++; } if (sub) { cur_alloced -= sub; cur_blocks--; } } /* Return the block following MB or NULL, if MB is the last block. */ static memblock_t * mb_get_next (memblock_t *mb) { memblock_t *mb_next; mb_next = (memblock_t *) ((char *) mb + BLOCK_HEAD_SIZE + mb->size); if (! ptr_into_pool_p (mb_next)) mb_next = NULL; return mb_next; } /* Return the block preceeding MB or NULL, if MB is the first block. */ static memblock_t * mb_get_prev (memblock_t *mb) { memblock_t *mb_prev, *mb_next; if (mb == pool) mb_prev = NULL; else { mb_prev = (memblock_t *) pool; while (1) { mb_next = mb_get_next (mb_prev); if (mb_next == mb) break; else mb_prev = mb_next; } } return mb_prev; } /* If the preceeding 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 (memblock_t *mb) { memblock_t *mb_prev, *mb_next; mb_prev = mb_get_prev (mb); mb_next = mb_get_next (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 (memblock_t *block, size_t size) { memblock_t *mb, *mb_split; for (mb = block; ptr_into_pool_p (mb); mb = mb_get_next (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 *) (((char *) mb) + BLOCK_HEAD_SIZE + size); mb_split->size = mb->size - size - BLOCK_HEAD_SIZE; mb_split->flags = 0; mb->size = size; mb_merge (mb_split); } break; } if (! ptr_into_pool_p (mb)) 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 into core and drop privileges. */ static void lock_pool (void *p, size_t n) { #if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK) int err; cap_set_proc (cap_from_text ("cap_ipc_lock+ep")); err = mlock (p, n); if (err && errno) err = errno; cap_set_proc (cap_from_text ("cap_ipc_lock+p")); 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 = mlock (p, n); if (err && errno) err = errno; } #else /* !HAVE_BROKEN_MLOCK */ err = mlock (p, n); if (err && errno) err = errno; #endif /* !HAVE_BROKEN_MLOCK */ if (uid && ! geteuid ()) { /* 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 */ #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 */ #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. */ #else log_info ("Please note that you don't have secure memory on this system\n"); #endif } /* Initialize POOL. */ static void init_pool (size_t n) { size_t pgsize; long int pgsize_val; memblock_t *mb; pool_size = n; if (disable_secmem) log_bug ("secure memory is disabled"); #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; #if HAVE_MMAP pool_size = (pool_size + pgsize - 1) & ~(pgsize - 1); #ifdef MAP_ANONYMOUS pool = 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 = (void *) -1; } else { pool = mmap (0, pool_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); close (fd); } } #endif if (pool == (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 if (!pool_okay) { pool = malloc (pool_size); if (!pool) 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; 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; /* 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; SECMEM_UNLOCK; return flags; } /* See _gcry_secmem_init. This function is expected to be called with the secmem lock held. */ static void secmem_init (size_t n) { if (!n) { #ifdef USE_CAPABILITIES /* drop all capabilities */ cap_set_proc (cap_from_text ("all-eip")); #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 (n); lock_pool (pool, 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; secmem_init (n); SECMEM_UNLOCK; } static void * _gcry_secmem_malloc_internal (size_t size) { memblock_t *mb; if (!pool_okay) { /* Try to initialize the pool if the user forgot about it. */ secmem_init (STANDARD_POOL_SIZE); if (!pool_okay) { log_info (_("operation is not possible without " "initialized secure memory\n")); return NULL; } } if (not_locked && fips_mode ()) { log_info (_("secure memory pool is not locked while in FIPS mode\n")); 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 ((memblock_t *) pool, size); if (mb) stats_update (size, 0); return mb ? &mb->aligned.c : NULL; } void * _gcry_secmem_malloc (size_t size) { void *p; SECMEM_LOCK; p = _gcry_secmem_malloc_internal (size); SECMEM_UNLOCK; return p; } static void _gcry_secmem_free_internal (void *a) { memblock_t *mb; int size; if (!a) return; 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 ((memblock_t *) ((char *) mb + BLOCK_HEAD_SIZE), (byte), size); MB_WIPE_OUT (0xff); MB_WIPE_OUT (0xaa); MB_WIPE_OUT (0x55); MB_WIPE_OUT (0x00); stats_update (0, size); mb->flags &= ~MB_FLAG_ACTIVE; /* Update stats. */ mb_merge (mb); } /* Wipe out and release memory. */ void _gcry_secmem_free (void *a) { SECMEM_LOCK; _gcry_secmem_free_internal (a); SECMEM_UNLOCK; } /* Realloc memory. */ void * _gcry_secmem_realloc (void *p, size_t newsize) { memblock_t *mb; size_t size; void *a; SECMEM_LOCK; mb = (memblock_t *) ((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); if (a) { memcpy (a, p, size); memset ((char *) a + size, 0, newsize - size); _gcry_secmem_free_internal (p); } } SECMEM_UNLOCK; return a; } /* Return true if P points into the secure memory area. */ int _gcry_private_is_secure (const void *p) { return pool_okay && ptr_into_pool_p (p); } /**************** * 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 () { if (!pool_okay) return; wipememory2 (pool, 0xff, pool_size); wipememory2 (pool, 0xaa, pool_size); wipememory2 (pool, 0x55, pool_size); wipememory2 (pool, 0x00, pool_size); #if HAVE_MMAP if (pool_is_mmapped) munmap (pool, pool_size); #endif pool = NULL; pool_okay = 0; pool_size = 0; not_locked = 0; } void _gcry_secmem_dump_stats () { #if 1 SECMEM_LOCK; if (pool_okay) log_info ("secmem usage: %u/%lu bytes in %u blocks\n", cur_alloced, (unsigned long)pool_size, cur_blocks); SECMEM_UNLOCK; #else memblock_t *mb; int i; SECMEM_LOCK; for (i = 0, mb = (memblock_t *) pool; ptr_into_pool_p (mb); mb = mb_get_next (mb), i++) log_info ("SECMEM: [%s] block: %i; size: %i\n", (mb->flags & MB_FLAG_ACTIVE) ? "used" : "free", i, mb->size); SECMEM_UNLOCK; #endif }