/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gtksecurememory.c - API for allocating memory that is non-pageable

   Copyright  2007 Stefan Walter
   Copyright  2020 GNOME Foundation

   SPDX-License-Identifier: LGPL-2.0-or-later

   The Gnome Keyring Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Keyring Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   see <http://www.gnu.org/licenses/>.

   Author: Stef Walter <stef@memberwebs.com>
*/

/*
 * IMPORTANT: This is pure vanila standard C, no glib. We need this
 * because certain consumers of this protocol need to be built
 * without linking in any special libraries. ie: the PKCS#11 module.
 */

#include "config.h"

#include "gtksecurememoryprivate.h"

#include <sys/types.h>

#if defined(HAVE_MLOCK)
#include <sys/mman.h>
#endif

#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <assert.h>

#ifdef WITH_VALGRIND
#include <valgrind/valgrind.h>
#include <valgrind/memcheck.h>
#endif

#define DEBUG_SECURE_MEMORY 0

#if DEBUG_SECURE_MEMORY
#define DEBUG_ALLOC(msg, n) 	fprintf(stderr, "%s %lu bytes\n", msg, n);
#else
#define DEBUG_ALLOC(msg, n)
#endif

#define DEFAULT_BLOCK_SIZE 16384

#define DO_LOCK() \
	GTK_SECURE_GLOBALS.lock ();

#define DO_UNLOCK() \
	GTK_SECURE_GLOBALS.unlock ();

typedef struct {
    void       (* lock)        (void);
    void       (* unlock)      (void);
    void *     (* fallback_alloc) (void *pointer,
                                   size_t length);
    void       (* fallback_free) (void *pointer);
    void *        pool_data;
    const char *  pool_version;
} GtkSecureGlob;

#include <glib.h>

#ifdef G_OS_WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <dpapi.h> /* for CryptProtectMemory() */
#endif

#define GTK_SECURE_POOL_VER_STR             "1.0"

static int show_warning = 1;
static int gtk_secure_warnings = 1;
static GMutex memory_mutex;

static void
gtk_memory_lock (void)
{
  g_mutex_lock (&memory_mutex);
}

static void
gtk_memory_unlock (void)
{
  g_mutex_unlock (&memory_mutex);
}

static GtkSecureGlob GTK_SECURE_GLOBALS = {
  .lock = gtk_memory_lock,
  .unlock = gtk_memory_unlock,
  .fallback_alloc = g_realloc,
  .fallback_free = g_free,
  .pool_data = NULL,
  .pool_version = GTK_SECURE_POOL_VER_STR,
};

/*
 * We allocate all memory in units of sizeof(void*). This
 * is our definition of 'word'.
 */
typedef void* word_t;

/* The amount of extra words we can allocate */
#define WASTE   4

/*
 * Track allocated memory or a free block. This structure is not stored
 * in the secure memory area. It is allocated from a pool of other
 * memory. See meta_pool_xxx ().
 */
typedef struct _Cell {
	word_t *words;          /* Pointer to secure memory */
	size_t n_words;         /* Amount of secure memory in words */
	size_t requested;       /* Amount actually requested by app, in bytes, 0 if unused */
	const char *tag;        /* Tag which describes the allocation */
	struct _Cell *next;     /* Next in memory ring */
	struct _Cell *prev;     /* Previous in memory ring */
} Cell;

/*
 * A block of secure memory. This structure is the header in that block.
 */
typedef struct _Block {
	word_t *words;              /* Actual memory hangs off here */
	size_t n_words;             /* Number of words in block */
	size_t n_used;              /* Number of used allocations */
	struct _Cell* used_cells;   /* Ring of used allocations */
	struct _Cell* unused_cells; /* Ring of unused allocations */
	struct _Block *next;        /* Next block in list */
} Block;

/* -----------------------------------------------------------------------------
 * UNUSED STACK
 */

static inline void
unused_push (void **stack, void *ptr)
{
	g_assert (ptr);
	g_assert (stack);
	*((void**)ptr) = *stack;
	*stack = ptr;
}

static inline void*
unused_pop (void **stack)
{
	void *ptr;
	g_assert (stack);
	ptr = *stack;
	*stack = *(void**)ptr;
	return ptr;

}

static inline void*
unused_peek (void **stack)
{
	g_assert (stack);
	return *stack;
}

/* -----------------------------------------------------------------------------
 * POOL META DATA ALLOCATION
 *
 * A pool for memory meta data. We allocate fixed size blocks. There are actually
 * two different structures stored in this pool: Cell and Block. Cell is allocated
 * way more often, and is bigger so we just allocate that size for both.
 */

/* Pool allocates this data type */
typedef union _Item {
	Cell cell;
	Block block;
} Item;

typedef struct _Pool {
	struct _Pool *next;    /* Next pool in list */
	size_t length;         /* Length in bytes of the pool */
	size_t used;           /* Number of cells used in pool */
	void *unused;          /* Unused stack of unused stuff */
	size_t n_items;        /* Total number of items in pool */
	Item items[1];         /* Actual items hang off here */
} Pool;

static int
check_pool_version (void)
{
        if (GTK_SECURE_GLOBALS.pool_version == NULL ||
	    strcmp (GTK_SECURE_GLOBALS.pool_version, GTK_SECURE_POOL_VER_STR) != 0) {
                return 0;
        }

        return 1;
}

static void *
pool_alloc (void)
{
        if (!check_pool_version ()) {
		if (show_warning && gtk_secure_warnings)
			fprintf (stderr, "the secure memory pool version does not match the code '%s' != '%s'\n",
			         GTK_SECURE_GLOBALS.pool_version ? GTK_SECURE_GLOBALS.pool_version : "(null)",
			         GTK_SECURE_POOL_VER_STR);
		show_warning = 0;
		return NULL;
	}

#ifdef HAVE_MMAP
	/* A pool with an available item */
        Pool *pool = NULL;

	for (pool = GTK_SECURE_GLOBALS.pool_data; pool != NULL; pool = pool->next) {
		if (unused_peek (&pool->unused))
			break;
	}

        void *item = NULL;

	/* Create a new pool */
	if (pool == NULL) {
		size_t len = getpagesize () * 2;
		void *pages = mmap (0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
		if (pages == MAP_FAILED)
			return NULL;

		/* Fill in the block header, and inlude in block list */
		pool = pages;
		pool->next = GTK_SECURE_GLOBALS.pool_data;
		GTK_SECURE_GLOBALS.pool_data = pool;
		pool->length = len;
		pool->used = 0;
		pool->unused = NULL;

		/* Fill block with unused items */
		pool->n_items = (len - sizeof (Pool)) / sizeof (Item);
		for (size_t i = 0; i < pool->n_items; ++i)
			unused_push (&pool->unused, pool->items + i);

#ifdef WITH_VALGRIND
		VALGRIND_CREATE_MEMPOOL(pool, 0, 0);
#endif
	}

	++pool->used;
	g_assert (unused_peek (&pool->unused));
	item = unused_pop (&pool->unused);

#ifdef WITH_VALGRIND
	VALGRIND_MEMPOOL_ALLOC (pool, item, sizeof (Item));
#endif

	return memset (item, 0, sizeof (Item));
#else /* HAVE_MMAP */
        return NULL;
#endif
}

static void
pool_free (void* item)
{
#ifdef HAVE_MMAP
	Pool *pool, **at;
	char *ptr, *beg, *end;

	ptr = item;

	/* Find which block this one belongs to */
	for (at = (Pool **)&GTK_SECURE_GLOBALS.pool_data, pool = *at;
	     pool != NULL; at = &pool->next, pool = *at) {
		beg = (char*)pool->items;
		end = (char*)pool + pool->length - sizeof (Item);
		if (ptr >= beg && ptr <= end) {
			g_assert ((ptr - beg) % sizeof (Item) == 0);
			break;
		}
	}

	/* Otherwise invalid meta */
	g_assert (at);
	g_assert (pool);
	g_assert (pool->used > 0);

	/* No more meta cells used in this block, remove from list, destroy */
	if (pool->used == 1) {
		*at = pool->next;

#ifdef WITH_VALGRIND
		VALGRIND_DESTROY_MEMPOOL (pool);
#endif

		munmap (pool, pool->length);
		return;
	}

#ifdef WITH_VALGRIND
	VALGRIND_MEMPOOL_FREE (pool, item);
	VALGRIND_MAKE_MEM_UNDEFINED (item, sizeof (Item));
#endif

	--pool->used;
	memset (item, 0xCD, sizeof (Item));
	unused_push (&pool->unused, item);
#endif /* HAVE_MMAP */
}

#ifndef G_DISABLE_ASSERT

static int
pool_valid (void* item)
{
	Pool *pool;
	char *ptr, *beg, *end;

	ptr = item;

	/* Find which block this one belongs to */
	for (pool = GTK_SECURE_GLOBALS.pool_data; pool; pool = pool->next) {
		beg = (char*)pool->items;
		end = (char*)pool + pool->length - sizeof (Item);
		if (ptr >= beg && ptr <= end)
			return (pool->used && (ptr - beg) % sizeof (Item) == 0);
	}

	return 0;
}

#endif /* G_DISABLE_g_assert */

/* -----------------------------------------------------------------------------
 * SEC ALLOCATION
 *
 * Each memory cell begins and ends with a pointer to its metadata. These are also
 * used as guards or red zones. Since they're treated as redzones by valgrind we
 * have to jump through a few hoops before reading and/or writing them.
 */

static inline size_t
sec_size_to_words (size_t length)
{
	return (length % sizeof (void*) ? 1 : 0) + (length / sizeof (void*));
}

static inline void
sec_write_guards (Cell *cell)
{
#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_UNDEFINED (cell->words, sizeof (word_t));
	VALGRIND_MAKE_MEM_UNDEFINED (cell->words + cell->n_words - 1, sizeof (word_t));
#endif

	((void**)cell->words)[0] = (void*)cell;
	((void**)cell->words)[cell->n_words - 1] = (void*)cell;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_NOACCESS (cell->words, sizeof (word_t));
	VALGRIND_MAKE_MEM_NOACCESS (cell->words + cell->n_words - 1, sizeof (word_t));
#endif
}

static inline void
sec_check_guards (Cell *cell)
{
#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (cell->words, sizeof (word_t));
	VALGRIND_MAKE_MEM_DEFINED (cell->words + cell->n_words - 1, sizeof (word_t));
#endif

	g_assert(((void**)cell->words)[0] == (void*)cell);
	g_assert(((void**)cell->words)[cell->n_words - 1] == (void*)cell);

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_NOACCESS (cell->words, sizeof (word_t));
	VALGRIND_MAKE_MEM_NOACCESS (cell->words + cell->n_words - 1, sizeof (word_t));
#endif
}

static void
sec_insert_cell_ring (Cell **ring, Cell *cell)
{
	g_assert (ring);
	g_assert (cell);
	g_assert (cell != *ring);
	g_assert (cell->next == NULL);
	g_assert (cell->prev == NULL);

	/* Insert back into the mix of available memory */
	if (*ring) {
		cell->next = (*ring)->next;
		cell->prev = *ring;
		cell->next->prev = cell;
		cell->prev->next = cell;
	} else {
		cell->next = cell;
		cell->prev = cell;
	}

	*ring = cell;
	g_assert (cell->next->prev == cell);
	g_assert (cell->prev->next == cell);
}

static void
sec_remove_cell_ring (Cell **ring, Cell *cell)
{
	g_assert (ring);
	g_assert (*ring);
	g_assert (cell->next);
	g_assert (cell->prev);

	g_assert (cell->next->prev == cell);
	g_assert (cell->prev->next == cell);

	if (cell == *ring) {
		/* The last meta? */
		if (cell->next == cell) {
			g_assert (cell->prev == cell);
			*ring = NULL;

		/* Just pointing to this meta */
		} else {
			g_assert (cell->prev != cell);
			*ring = cell->next;
		}
	}

	cell->next->prev = cell->prev;
	cell->prev->next = cell->next;
	cell->next = cell->prev = NULL;

	g_assert (*ring != cell);
}

static inline void*
sec_cell_to_memory (Cell *cell)
{
	return cell->words + 1;
}

static inline int
sec_is_valid_word (Block *block, word_t *word)
{
	return (word >= block->words && word < block->words + block->n_words);
}

static inline void
sec_clear_undefined (void *memory,
                     size_t from,
                     size_t to)
{
	char *ptr = memory;
	g_assert (from <= to);
#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_UNDEFINED (ptr + from, to - from);
#endif
	memset (ptr + from, 0, to - from);
#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_UNDEFINED (ptr + from, to - from);
#endif
}
static inline void
sec_clear_noaccess (void *memory, size_t from, size_t to)
{
	char *ptr = memory;
	g_assert (from <= to);
#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_UNDEFINED (ptr + from, to - from);
#endif
	memset (ptr + from, 0, to - from);
#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_NOACCESS (ptr + from, to - from);
#endif
}

static Cell*
sec_neighbor_before (Block *block, Cell *cell)
{
	word_t *word;

	g_assert (cell);
	g_assert (block);

	word = cell->words - 1;
	if (!sec_is_valid_word (block, word))
		return NULL;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (word, sizeof (word_t));
#endif

	cell = *word;
	sec_check_guards (cell);

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_NOACCESS (word, sizeof (word_t));
#endif

	return cell;
}

static Cell*
sec_neighbor_after (Block *block, Cell *cell)
{
	word_t *word;

	g_assert (cell);
	g_assert (block);

	word = cell->words + cell->n_words;
	if (!sec_is_valid_word (block, word))
		return NULL;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (word, sizeof (word_t));
#endif

	cell = *word;
	sec_check_guards (cell);

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_NOACCESS (word, sizeof (word_t));
#endif

	return cell;
}

static void*
sec_alloc (Block *block,
           const char *tag,
           size_t length)
{
	Cell *cell, *other;
	size_t n_words;
	void *memory;

	g_assert (block);
	g_assert (length);
	g_assert (tag);

	if (!block->unused_cells)
		return NULL;

	/*
	 * Each memory allocation is aligned to a pointer size, and
	 * then, sandwidched between two pointers to its meta data.
	 * These pointers also act as guards.
	 *
	 * We allocate memory in units of sizeof (void*)
	 */

	n_words = sec_size_to_words (length) + 2;

	/* Look for a cell of at least our required size */
	cell = block->unused_cells;
	while (cell->n_words < n_words) {
		cell = cell->next;
		if (cell == block->unused_cells) {
			cell = NULL;
			break;
		}
	}

	if (!cell)
		return NULL;

	g_assert (cell->tag == NULL);
	g_assert (cell->requested == 0);
	g_assert (cell->prev);
	g_assert (cell->words);
	sec_check_guards (cell);

	/* Steal from the cell if it's too long */
	if (cell->n_words > n_words + WASTE) {
		other = pool_alloc ();
		if (!other)
			return NULL;
		other->n_words = n_words;
		other->words = cell->words;
		cell->n_words -= n_words;
		cell->words += n_words;

		sec_write_guards (other);
		sec_write_guards (cell);

		cell = other;
	}

	if (cell->next)
		sec_remove_cell_ring (&block->unused_cells, cell);

	++block->n_used;
	cell->tag = tag;
	cell->requested = length;
	sec_insert_cell_ring (&block->used_cells, cell);
	memory = sec_cell_to_memory (cell);

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_UNDEFINED (memory, length);
#endif

	return memset (memory, 0, length);
}

static void*
sec_free (Block *block, void *memory)
{
	Cell *cell, *other;
	word_t *word;

	g_assert (block);
	g_assert (memory);

	word = memory;
	--word;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (word, sizeof (word_t));
#endif

	/* Lookup the meta for this memory block (using guard pointer) */
	g_assert (sec_is_valid_word (block, word));
	g_assert (pool_valid (*word));
	cell = *word;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (cell->words, cell->n_words * sizeof (word_t));
#endif

	sec_check_guards (cell);
	sec_clear_noaccess (memory, 0, cell->requested);

	sec_check_guards (cell);
	g_assert (cell->requested > 0);
	g_assert (cell->tag != NULL);

	/* Remove from the used cell ring */
	sec_remove_cell_ring (&block->used_cells, cell);

	/* Find previous unallocated neighbor, and merge if possible */
	other = sec_neighbor_before (block, cell);
	if (other && other->requested == 0) {
		g_assert (other->tag == NULL);
		g_assert (other->next && other->prev);
		other->n_words += cell->n_words;
		sec_write_guards (other);
		pool_free (cell);
		cell = other;
	}

	/* Find next unallocated neighbor, and merge if possible */
	other = sec_neighbor_after (block, cell);
	if (other && other->requested == 0) {
		g_assert (other->tag == NULL);
		g_assert (other->next && other->prev);
		other->n_words += cell->n_words;
		other->words = cell->words;
		if (cell->next)
			sec_remove_cell_ring (&block->unused_cells, cell);
		sec_write_guards (other);
		pool_free (cell);
		cell = other;
	}

	/* Add to the unused list if not already there */
	if (!cell->next)
		sec_insert_cell_ring (&block->unused_cells, cell);

	cell->tag = NULL;
	cell->requested = 0;
	--block->n_used;
	return NULL;
}

static void
memcpy_with_vbits (void *dest,
                   void *src,
                   size_t length)
{
#ifdef WITH_VALGRIND
	int vbits_setup = 0;
	void *vbits = NULL;

	if (RUNNING_ON_VALGRIND) {
		vbits = malloc (length);
		if (vbits != NULL)
			vbits_setup = VALGRIND_GET_VBITS (src, vbits, length);
		VALGRIND_MAKE_MEM_DEFINED (src, length);
	}
#endif

	memcpy (dest, src, length);

#ifdef WITH_VALGRIND
	if (vbits_setup == 1) {
		VALGRIND_SET_VBITS (dest, vbits, length);
		VALGRIND_SET_VBITS (src, vbits, length);
	}
	free (vbits);
#endif
}

static void*
sec_realloc (Block *block,
             const char *tag,
             void *memory,
             size_t length)
{
	Cell *cell, *other;
	word_t *word;
	size_t n_words;
	size_t valid;
	void *alloc;

	/* Standard realloc behavior, should have been handled elsewhere */
	g_assert (memory != NULL);
	g_assert (length > 0);
	g_assert (tag != NULL);

	/* Dig out where the meta should be */
	word = memory;
	--word;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (word, sizeof (word_t));
#endif

	g_assert (sec_is_valid_word (block, word));
	g_assert (pool_valid (*word));
	cell = *word;

	/* Validate that it's actually for real */
	sec_check_guards (cell);
	g_assert (cell->requested > 0);
	g_assert (cell->tag != NULL);

	/* The amount of valid data */
	valid = cell->requested;

	/* How many words we actually want */
	n_words = sec_size_to_words (length) + 2;

	/* Less memory is required than is in the cell */
	if (n_words <= cell->n_words) {

		/* TODO: No shrinking behavior yet */
		cell->requested = length;
		alloc = sec_cell_to_memory (cell);

		/*
		 * Even though we may be reusing the same cell, that doesn't
		 * mean that the allocation is shrinking. It could have shrunk
		 * and is now expanding back some.
		 */
		if (length < valid)
			sec_clear_undefined (alloc, length, valid);

		return alloc;
	}

	/* Need braaaaaiiiiiinsss... */
	while (cell->n_words < n_words) {

		/* See if we have a neighbor who can give us some memory */
		other = sec_neighbor_after (block, cell);
		if (!other || other->requested != 0)
			break;

		/* Eat the whole neighbor if not too big */
		if (n_words - cell->n_words + WASTE >= other->n_words) {
			cell->n_words += other->n_words;
			sec_write_guards (cell);
			sec_remove_cell_ring (&block->unused_cells, other);
			pool_free (other);

		/* Steal from the neighbor */
		} else {
			other->words += n_words - cell->n_words;
			other->n_words -= n_words - cell->n_words;
			sec_write_guards (other);
			cell->n_words = n_words;
			sec_write_guards (cell);
		}
	}

	if (cell->n_words >= n_words) {
		cell->requested = length;
		cell->tag = tag;
		alloc = sec_cell_to_memory (cell);
		sec_clear_undefined (alloc, valid, length);
		return alloc;
	}

	/* That didn't work, try alloc/free */
	alloc = sec_alloc (block, tag, length);
	if (alloc) {
		memcpy_with_vbits (alloc, memory, valid);
		sec_free (block, memory);
	}

	return alloc;
}


static size_t
sec_allocated (Block *block, void *memory)
{
	Cell *cell;
	word_t *word;

	g_assert (block);
	g_assert (memory);

	word = memory;
	--word;

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (word, sizeof (word_t));
#endif

	/* Lookup the meta for this memory block (using guard pointer) */
	g_assert (sec_is_valid_word (block, word));
	g_assert (pool_valid (*word));
	cell = *word;

	sec_check_guards (cell);
	g_assert (cell->requested > 0);
	g_assert (cell->tag != NULL);

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_NOACCESS (word, sizeof (word_t));
#endif

	return cell->requested;
}

static void
sec_validate (Block *block)
{
	Cell *cell;
	word_t *word, *last;

#ifdef WITH_VALGRIND
	if (RUNNING_ON_VALGRIND)
		return;
#endif

	word = block->words;
	last = word + block->n_words;

	for (;;) {
		g_assert (word < last);

		g_assert (sec_is_valid_word (block, word));
		g_assert (pool_valid (*word));
		cell = *word;

		/* Validate that it's actually for real */
		sec_check_guards (cell);

		/* Is it an allocated block? */
		if (cell->requested > 0) {
			g_assert (cell->tag != NULL);
			g_assert (cell->next != NULL);
			g_assert (cell->prev != NULL);
			g_assert (cell->next->prev == cell);
			g_assert (cell->prev->next == cell);
			g_assert (cell->requested <= (cell->n_words - 2) * sizeof (word_t));

		/* An unused block */
		} else {
			g_assert (cell->tag == NULL);
			g_assert (cell->next != NULL);
			g_assert (cell->prev != NULL);
			g_assert (cell->next->prev == cell);
			g_assert (cell->prev->next == cell);
		}

		word += cell->n_words;
		if (word == last)
			break;
	}
}

/* -----------------------------------------------------------------------------
 * LOCKED MEMORY
 */

static void*
sec_acquire_pages (size_t *sz,
                   const char *during_tag)
{
	g_assert (sz);
	g_assert (*sz);
	g_assert (during_tag);

#if defined(HAVE_MLOCK) && defined(HAVE_MMAP)
	/* Make sure sz is a multiple of the page size */
	unsigned long pgsize = getpagesize ();

	*sz = (*sz + pgsize -1) & ~(pgsize - 1);

	void *pages = mmap (0, *sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
	if (pages == MAP_FAILED) {
		if (show_warning && gtk_secure_warnings)
			fprintf (stderr, "couldn't map %lu bytes of memory (%s): %s\n",
			         (unsigned long)*sz, during_tag, strerror (errno));
		show_warning = 0;
		return NULL;
	}

	if (mlock (pages, *sz) < 0) {
		if (show_warning && gtk_secure_warnings && errno != EPERM) {
			fprintf (stderr, "couldn't lock %lu bytes of memory (%s): %s\n",
			         (unsigned long)*sz, during_tag, strerror (errno));
			show_warning = 0;
		}
		munmap (pages, *sz);
		return NULL;
	}

	DEBUG_ALLOC ("gtk-secure-memory: new block ", *sz);

#if defined(HAVE_MADVISE) && defined(MADV_DONTDUMP)
	if (madvise (pages, *sz, MADV_DONTDUMP) < 0) {
		if (show_warning && gtk_secure_warnings) {
			/*
			 * Not fatal - this was added in Linux 3.4 and older
			 * kernels will legitimately fail this at runtime
			 */
			fprintf (stderr, "couldn't MADV_DONTDUMP %lu bytes of memory (%s): %s\n",
				 (unsigned long)*sz, during_tag, strerror (errno));
		}
	}
#endif

	show_warning = 1;
	return pages;

#elif defined G_OS_WIN32
	/* Make sure sz is a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE in wincrypt.h */
	*sz = (*sz + CRYPTPROTECTMEMORY_BLOCK_SIZE - 1) & ~(CRYPTPROTECTMEMORY_BLOCK_SIZE - 1);

	void *data = (void *) LocalAlloc (LPTR, *sz);

	if (data == NULL) {
		if (show_warning && gtk_secure_warnings)
			fprintf (stderr, "couldn't allocate %lu bytes of memory (%s): %#010lX\n",
			         (unsigned long)*sz, during_tag, GetLastError ());
		show_warning = 0;
		return NULL;
	}

	if (!CryptProtectMemory (data, *sz, CRYPTPROTECTMEMORY_SAME_PROCESS)) {
		if (show_warning && gtk_secure_warnings)
			fprintf (stderr, "couldn't encrypt %lu bytes of memory (%s): %#010lX\n",
			         (unsigned long)*sz, during_tag, GetLastError ());
		show_warning = 0;
		return NULL;
	}

	DEBUG_ALLOC ("gtk-secure-memory: new block ", *sz);

	show_warning = 1;
	return data;
	
#else
	if (show_warning && gtk_secure_warnings)
		fprintf (stderr, "your system does not support private memory");
	show_warning = 0;
	return NULL;
#endif

}

static void
sec_release_pages (void *pages, size_t sz)
{
	g_assert (pages);

#if defined(HAVE_MLOCK)
	g_assert (sz % getpagesize () == 0);

	if (munlock (pages, sz) < 0 && gtk_secure_warnings)
		fprintf (stderr, "couldn't unlock private memory: %s\n", strerror (errno));

	if (munmap (pages, sz) < 0 && gtk_secure_warnings)
		fprintf (stderr, "couldn't unmap private anonymous memory: %s\n", strerror (errno));

	DEBUG_ALLOC ("gtk-secure-memory: freed block ", sz);

#elif defined G_OS_WIN32
	g_assert (sz % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0);

	if (!CryptUnprotectMemory (pages, sz, CRYPTPROTECTMEMORY_SAME_PROCESS))
		fprintf (stderr, "couldn't decrypt private memory: %#010lX\n", GetLastError ());

	if (LocalFree (pages) != NULL)
		fprintf (stderr, "couldn't free private anonymous memory: %#010lX\n", GetLastError ());

	DEBUG_ALLOC ("gtk-secure-memory: freed block ", sz);
#else
	g_assert (FALSE);
#endif
}

/* -----------------------------------------------------------------------------
 * MANAGE DIFFERENT BLOCKS
 */

static Block *all_blocks = NULL;

static Block*
sec_block_create (size_t size,
                  const char *during_tag)
{
	Block *block;
	Cell *cell;

	g_assert (during_tag);

	/* We can force all all memory to be malloced */
	if (getenv ("SECMEM_FORCE_FALLBACK"))
		return NULL;

	block = pool_alloc ();
	if (!block)
		return NULL;

	cell = pool_alloc ();
	if (!cell) {
		pool_free (block);
		return NULL;
	}

	/* The size above is a minimum, we're free to go bigger */
	if (size < DEFAULT_BLOCK_SIZE)
		size = DEFAULT_BLOCK_SIZE;

	block->words = sec_acquire_pages (&size, during_tag);
	block->n_words = size / sizeof (word_t);
	if (!block->words) {
		pool_free (block);
		pool_free (cell);
		return NULL;
	}

#ifdef WITH_VALGRIND
	VALGRIND_MAKE_MEM_DEFINED (block->words, size);
#endif

	/* The first cell to allocate from */
	cell->words = block->words;
	cell->n_words = block->n_words;
	cell->requested = 0;
	sec_write_guards (cell);
	sec_insert_cell_ring (&block->unused_cells, cell);

	block->next = all_blocks;
	all_blocks = block;

	return block;
}

static void
sec_block_destroy (Block *block)
{
	Block *bl, **at;
	Cell *cell;

	g_assert (block);
	g_assert (block->words);
	g_assert (block->n_used == 0);

	/* Remove from the list */
	for (at = &all_blocks, bl = *at; bl; at = &bl->next, bl = *at) {
		if (bl == block) {
			*at = block->next;
			break;
		}
	}

	/* Must have been found */
	g_assert (bl == block);
	g_assert (block->used_cells == NULL);

	/* Release all the meta data cells */
	while (block->unused_cells) {
		cell = block->unused_cells;
		sec_remove_cell_ring (&block->unused_cells, cell);
		pool_free (cell);
	}

	/* Release all pages of secure memory */
	sec_release_pages (block->words, block->n_words * sizeof (word_t));

	pool_free (block);
}

/* ------------------------------------------------------------------------
 * PUBLIC FUNCTIONALITY
 */

void*
gtk_secure_alloc_full (const char *tag,
                       size_t length,
                       int flags)
{
	Block *block;
	void *memory = NULL;

	if (tag == NULL)
		tag = "?";

	if (length > 0xFFFFFFFF / 2) {
		if (gtk_secure_warnings)
			fprintf (stderr, "tried to allocate an insane amount of memory: %lu\n",
			         (unsigned long)length);
		return NULL;
	}

	/* Can't allocate zero bytes */
	if (length == 0)
		return NULL;

	DO_LOCK ();

	for (block = all_blocks; block; block = block->next) {
		memory = sec_alloc (block, tag, length);
		if (memory)
			break;
	}

	/* None of the current blocks have space, allocate new */
	if (!memory) {
		block = sec_block_create (length, tag);
		if (block)
			memory = sec_alloc (block, tag, length);
	}

#ifdef WITH_VALGRIND
	if (memory != NULL)
		VALGRIND_MALLOCLIKE_BLOCK (memory, length, sizeof (void*), 1);
#endif

	DO_UNLOCK ();

	if (!memory && (flags & GTK_SECURE_USE_FALLBACK) && GTK_SECURE_GLOBALS.fallback_alloc != NULL) {
		memory = GTK_SECURE_GLOBALS.fallback_alloc (NULL, length);
		if (memory) /* Our returned memory is always zeroed */
			memset (memory, 0, length);
	}

	if (!memory)
		errno = ENOMEM;

	return memory;
}

void*
gtk_secure_realloc_full (const char *tag,
                         void *memory,
                         size_t length,
                         int flags)
{
	Block *block = NULL;
	size_t previous = 0;
	int donew = 0;
	void *alloc = NULL;

	if (tag == NULL)
		tag = "?";

	if (length > 0xFFFFFFFF / 2) {
		if (gtk_secure_warnings)
			fprintf (stderr, "tried to allocate an excessive amount of memory: %lu\n",
			         (unsigned long)length);
		return NULL;
	}

	if (memory == NULL)
		return gtk_secure_alloc_full (tag, length, flags);
	if (!length) {
		gtk_secure_free_full (memory, flags);
		return NULL;
	}

	DO_LOCK ();

	/* Find out where it belongs to */
	for (block = all_blocks; block; block = block->next) {
		if (sec_is_valid_word (block, memory)) {
			previous = sec_allocated (block, memory);

#ifdef WITH_VALGRIND
			/* Let valgrind think we are unallocating so that it'll validate */
			VALGRIND_FREELIKE_BLOCK (memory, sizeof (word_t));
#endif

			alloc = sec_realloc (block, tag, memory, length);

#ifdef WITH_VALGRIND
			/* Now tell valgrind about either the new block or old one */
			VALGRIND_MALLOCLIKE_BLOCK (alloc ? alloc : memory,
			                           alloc ? length : previous,
			                           sizeof (word_t), 1);
#endif
			break;
		}
	}

	/* If it didn't work we may need to allocate a new block */
	if (block && !alloc)
		donew = 1;

	if (block && block->n_used == 0)
		sec_block_destroy (block);

	DO_UNLOCK ();

	if (!block) {
		if ((flags & GTK_SECURE_USE_FALLBACK) && GTK_SECURE_GLOBALS.fallback_alloc) {
			/*
			 * In this case we can't zero the returned memory,
			 * because we don't know what the block size was.
			 */
			return GTK_SECURE_GLOBALS.fallback_alloc (memory, length);
		} else {
			if (gtk_secure_warnings)
				fprintf (stderr, "memory does not belong to secure memory pool: 0x%08" G_GUINTPTR_FORMAT "x\n",
				         (guintptr) memory);
			g_assert (0 && "memory does does not belong to secure memory pool");
			return NULL;
		}
	}

	if (donew) {
		alloc = gtk_secure_alloc_full (tag, length, flags);
		if (alloc) {
			memcpy_with_vbits (alloc, memory, previous);
			gtk_secure_free_full (memory, flags);
		}
	}

	if (!alloc)
		errno = ENOMEM;

	return alloc;
}

void
gtk_secure_free (void *memory)
{
	gtk_secure_free_full (memory, GTK_SECURE_USE_FALLBACK);
}

void
gtk_secure_free_full (void *memory, int flags)
{
	Block *block = NULL;

	if (memory == NULL)
		return;

	DO_LOCK ();

	/* Find out where it belongs to */
	for (block = all_blocks; block; block = block->next) {
		if (sec_is_valid_word (block, memory))
			break;
	}

#ifdef WITH_VALGRIND
	/* We like valgrind's warnings, so give it a first whack at checking for errors */
	if (block != NULL || !(flags & GTK_SECURE_USE_FALLBACK))
		VALGRIND_FREELIKE_BLOCK (memory, sizeof (word_t));
#endif

	if (block != NULL) {
		sec_free (block, memory);
		if (block->n_used == 0)
			sec_block_destroy (block);
	}

	DO_UNLOCK ();

	if (!block) {
		if ((flags & GTK_SECURE_USE_FALLBACK) && GTK_SECURE_GLOBALS.fallback_free) {
			GTK_SECURE_GLOBALS.fallback_free (memory);
		} else {
			if (gtk_secure_warnings)
				fprintf (stderr, "memory does not belong to secure memory pool: 0x%08" G_GUINTPTR_FORMAT "x\n",
				         (guintptr) memory);
			g_assert (0 && "memory does does not belong to secure memory pool");
		}
	}
}

int
gtk_secure_check (const void *memory)
{
	Block *block = NULL;

	DO_LOCK ();

	/* Find out where it belongs to */
	for (block = all_blocks; block; block = block->next) {
		if (sec_is_valid_word (block, (word_t*)memory))
			break;
	}

	DO_UNLOCK ();

	return block == NULL ? 0 : 1;
}

void
gtk_secure_validate (void)
{
	Block *block = NULL;

	DO_LOCK ();

	for (block = all_blocks; block; block = block->next)
		sec_validate (block);

	DO_UNLOCK ();
}


static gtk_secure_rec *
records_for_ring (Cell *cell_ring,
                  gtk_secure_rec *records,
                  unsigned int *count,
                  unsigned int *total)
{
	gtk_secure_rec *new_rec;
	unsigned int allocated = *count;
	Cell *cell;

	cell = cell_ring;
	do {
		if (*count >= allocated) {
			new_rec = realloc (records, sizeof (gtk_secure_rec) * (allocated + 32));
			if (new_rec == NULL) {
				*count = 0;
				free (records);
				return NULL;
			} else {
				records = new_rec;
				allocated += 32;
			}
		}

		if (cell != NULL) {
			records[*count].request_length = cell->requested;
			records[*count].block_length = cell->n_words * sizeof (word_t);
			records[*count].tag = cell->tag;
			(*count)++;
			(*total) += cell->n_words;
			cell = cell->next;
		}
	} while (cell != NULL && cell != cell_ring);

	return records;
}

gtk_secure_rec *
gtk_secure_records (unsigned int *count)
{
	gtk_secure_rec *records = NULL;
	Block *block = NULL;
	unsigned int total;

	*count = 0;

	DO_LOCK ();

	for (block = all_blocks; block != NULL; block = block->next) {
		total = 0;

		records = records_for_ring (block->unused_cells, records, count, &total);
		if (records == NULL)
			break;
		records = records_for_ring (block->used_cells, records, count, &total);
		if (records == NULL)
			break;

		/* Make sure this actualy accounts for all memory */
		g_assert (total == block->n_words);
	}

	DO_UNLOCK ();

	return records;
}

char*
gtk_secure_strdup_full (const char *tag,
                        const char *str,
                        int options)
{
	size_t len;
	char *res;

	if (!str)
		return NULL;

	len = strlen (str) + 1;
	res = (char *)gtk_secure_alloc_full (tag, len, options);
	strcpy (res, str);
	return res;
}

char *
gtk_secure_strndup_full (const char *tag,
                         const char *str,
                         size_t length,
                         int options)
{
	size_t len;
	char *res;
	const char *end;

	if (!str)
		return NULL;

	end = memchr (str, '\0', length);
	if (end != NULL)
		length = (end - str);
	len = length + 1;
	res = (char *)gtk_secure_alloc_full (tag, len, options);
	memcpy (res, str, len);
	return res;
}

void
gtk_secure_clear (void *p, size_t length)
{
	volatile char *vp;

	if (p == NULL)
		return;

        vp = (volatile char*)p;
        while (length) {
	        *vp = 0xAA;
	        vp++;
	        length--;
	}
}

void
gtk_secure_strclear (char *str)
{
	if (!str)
		return;
	gtk_secure_clear ((unsigned char*)str, strlen (str));
}

void
gtk_secure_strfree (char *str)
{
	/*
	 * If we're using unpageable 'secure' memory, then the free call
	 * should zero out the memory, but because on certain platforms
	 * we may be using normal memory, zero it out here just in case.
	 */

	gtk_secure_strclear (str);
	gtk_secure_free_full (str, GTK_SECURE_USE_FALLBACK);
}
