696 lines
15 KiB
C
696 lines
15 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <stddef.h>
|
|
|
|
#if defined(HAVE_MLOCK) || defined(HAVE_MMAP)
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#ifdef USE_CAPABILITIES
|
|
#include <sys/capability.h>
|
|
#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 preceding 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 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 (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))
|
|
{
|
|
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 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. */
|
|
(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;
|
|
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"));
|
|
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 ((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
|
|
}
|