[gtk/ebassi/secure-buffer: 3/7] Import egg-secure-memory allocator from gcr




commit 4cca1c8db54dcbdea0e257aeceb84c7189c0b8c6
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Tue Sep 8 17:40:32 2020 +0100

    Import egg-secure-memory allocator from gcr
    
    We can re-use the code inside gcr, as we know that it's working, tested,
    and license compatible.

 gtk/gtksecurememory.c        | 1433 ++++++++++++++++++++++++++++++++++++++++++
 gtk/gtksecurememoryprivate.h |   95 +++
 gtk/meson.build              |    1 +
 3 files changed, 1529 insertions(+)
---
diff --git a/gtk/gtksecurememory.c b/gtk/gtksecurememory.c
new file mode 100644
index 0000000000..fe226e5e95
--- /dev/null
+++ b/gtk/gtksecurememory.c
@@ -0,0 +1,1433 @@
+/* -*- 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>
+#include <unistd.h>
+#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>
+
+#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);
+
+       show_warning = 1;
+       return pages;
+
+#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);
+       g_assert (sz % getpagesize () == 0);
+
+#if defined(HAVE_MLOCK)
+       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);
+
+#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%08lx\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%08lx\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);
+}
diff --git a/gtk/gtksecurememoryprivate.h b/gtk/gtksecurememoryprivate.h
new file mode 100644
index 0000000000..0209d4fc11
--- /dev/null
+++ b/gtk/gtksecurememoryprivate.h
@@ -0,0 +1,95 @@
+/* gtksecurememoryprivate.h - Allocator for non-pageable memory
+
+   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>
+*/
+
+#pragma once
+
+#include <stdlib.h>
+
+/*
+ * Main functionality
+ *
+ * Allocations return NULL on failure.
+ */
+
+#define GTK_SECURE_USE_FALLBACK     0x0001
+
+void *          gtk_secure_alloc_full   (const char *tag,
+                                         size_t length,
+                                         int options);
+
+void *          gtk_secure_realloc_full (const char *tag,
+                                         void *p,
+                                         size_t length,
+                                         int options);
+
+void            gtk_secure_free         (void *p);
+
+void            gtk_secure_free_full    (void *p,
+                                         int fallback);
+
+void            gtk_secure_clear        (void *p,
+                                         size_t length);
+
+int             gtk_secure_check        (const void *p);
+
+void            gtk_secure_validate     (void);
+
+char *          gtk_secure_strdup_full  (const char *tag,
+                                         const char *str,
+                                         int options);
+
+char *          gtk_secure_strndup_full (const char *tag,
+                                         const char *str,
+                                         size_t length,
+                                         int options);
+
+void            gtk_secure_strclear     (char *str);
+
+void            gtk_secure_strfree      (char *str);
+
+/* Simple wrappers */
+
+static inline void *gtk_secure_alloc (size_t length) {
+  return gtk_secure_alloc_full ("gtk", length, GTK_SECURE_USE_FALLBACK);
+}
+
+static inline void *gtk_secure_realloc (void *p, size_t length) {
+  return gtk_secure_realloc_full ("gtk", p, length, GTK_SECURE_USE_FALLBACK);
+}
+
+static inline void *gtk_secure_strdup (const char *str) {
+  return gtk_secure_strdup_full ("gtk", str, GTK_SECURE_USE_FALLBACK);
+}
+
+static inline void *gtk_secure_strndup (const char *str, size_t length) {
+  return gtk_secure_strndup_full ("gtk", str, length, GTK_SECURE_USE_FALLBACK);
+}
+
+typedef struct {
+  const char *tag;
+  size_t request_length;
+  size_t block_length;
+} gtk_secure_rec;
+
+gtk_secure_rec *   gtk_secure_records    (unsigned int *count);
diff --git a/gtk/meson.build b/gtk/meson.build
index 13bdd71d61..c644ac9696 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -134,6 +134,7 @@ gtk_private_sources = files([
   'gtkscaler.c',
   'gtksearchengine.c',
   'gtksearchenginemodel.c',
+  'gtksecurememory.c',
   'gtksizerequestcache.c',
   'gtksortkeys.c',
   'gtkstyleanimation.c',



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]