DKIM: verify using separate pool-pair, reset per message
[exim.git] / src / src / store.c
index 41ca43d65f71e22f3f07899e6efd82b3010e9584..9d155821b76146af232c9f4b2c801792a05672a9 100644 (file)
@@ -3,7 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim maintainers 2019 */
+/* Copyright (c) The Exim maintainers 2019 - 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Exim gets and frees all its store through these functions. In the original
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Exim gets and frees all its store through these functions. In the original
@@ -37,12 +37,33 @@ The following different types of store are recognized:
   This means it can be freed when search_tidyup() is called to close down all
   the lookup caching.
 
   This means it can be freed when search_tidyup() is called to close down all
   the lookup caching.
 
+- There is another pool (POOL_MESSAGE) used for medium-lifetime objects; within
+  a single message transaction but needed for longer than the use of the main
+  pool permits.  Currently this means only receive-time DKIM information.
+
 . Orthogonal to the three pool types, there are two classes of memory: untainted
   and tainted.  The latter is used for values derived from untrusted input, and
   the string-expansion mechanism refuses to operate on such values (obviously,
   it can expand an untainted value to return a tainted result).  The classes
 . Orthogonal to the three pool types, there are two classes of memory: untainted
   and tainted.  The latter is used for values derived from untrusted input, and
   the string-expansion mechanism refuses to operate on such values (obviously,
   it can expand an untainted value to return a tainted result).  The classes
-  are implemented by duplicating the three pool types. Pool resets are requested
+  are implemented by duplicating the four pool types.  Pool resets are requested
   against the nontainted sibling and apply to both siblings.
   against the nontainted sibling and apply to both siblings.
+
+  Only memory blocks requested for tainted use are regarded as tainted; anything
+  else (including stack auto variables) is untainted.  Care is needed when coding
+  to not copy untrusted data into untainted memory, as downstream taint-checks
+  would be avoided.
+
+  Intermediate layers (eg. the string functions) can test for taint, and use this
+  for ensurinng that results have proper state.  For example the
+  string_vformat_trc() routing supporting the string_sprintf() interface will
+  recopy a string being built into a tainted allocation if it meets a %s for a
+  tainted argument.  Any intermediate-layer function that (can) return a new
+  allocation should behave this way; returning a tainted result if any tainted
+  content is used.  Intermediate-layer functions (eg. Ustrncpy) that modify
+  existing allocations fail if tainted data is written into an untainted area.
+  Users of functions that modify existing allocations should check if a tainted
+  source and an untainted destination is used, and fail instead (sprintf() being
+  the classic case).
 */
 
 
 */
 
 
@@ -96,18 +117,10 @@ even if the length is zero (which is used for getting a point to reset to). */
 
 int store_pool = POOL_MAIN;
 
 
 int store_pool = POOL_MAIN;
 
-#define NPOOLS 6
 static storeblock *chainbase[NPOOLS];
 static storeblock *current_block[NPOOLS];
 static void *next_yield[NPOOLS];
 static storeblock *chainbase[NPOOLS];
 static storeblock *current_block[NPOOLS];
 static void *next_yield[NPOOLS];
-static int yield_length[NPOOLS] = { -1, -1, -1,  -1, -1, -1 };
-
-/* The limits of the tainted pools.  Tracking these on new allocations enables
-a fast is_tainted implementation. We assume the kernel only allocates mmaps using
-one side or the other of data+heap, not both. */
-
-void * tainted_base = (void *)-1;
-void * tainted_top = (void *)0;
+static int yield_length[NPOOLS];
 
 /* pool_malloc holds the amount of memory used by the store pools; this goes up
 and down as store is reset or released. nonpool_malloc is the total got by
 
 /* pool_malloc holds the amount of memory used by the store pools; this goes up
 and down as store is reset or released. nonpool_malloc is the total got by
@@ -135,29 +148,76 @@ static int max_pool_malloc;       /* max value for pool_malloc */
 static int max_nonpool_malloc; /* max value for nonpool_malloc */
 
 
 static int max_nonpool_malloc; /* max value for nonpool_malloc */
 
 
+#ifndef COMPILE_UTILITY
 static const uschar * pooluse[NPOOLS] = {
 [POOL_MAIN] =          US"main",
 [POOL_PERM] =          US"perm",
 [POOL_SEARCH] =                US"search",
 static const uschar * pooluse[NPOOLS] = {
 [POOL_MAIN] =          US"main",
 [POOL_PERM] =          US"perm",
 [POOL_SEARCH] =                US"search",
+[POOL_MESSAGE] =       US"message",
 [POOL_TAINT_MAIN] =    US"main",
 [POOL_TAINT_PERM] =    US"perm",
 [POOL_TAINT_SEARCH] =  US"search",
 [POOL_TAINT_MAIN] =    US"main",
 [POOL_TAINT_PERM] =    US"perm",
 [POOL_TAINT_SEARCH] =  US"search",
+[POOL_TAINT_SEARCH] =  US"search",
+[POOL_TAINT_MESSAGE] = US"message",
 };
 static const uschar * poolclass[NPOOLS] = {
 [POOL_MAIN] =          US"untainted",
 [POOL_PERM] =          US"untainted",
 [POOL_SEARCH] =                US"untainted",
 };
 static const uschar * poolclass[NPOOLS] = {
 [POOL_MAIN] =          US"untainted",
 [POOL_PERM] =          US"untainted",
 [POOL_SEARCH] =                US"untainted",
+[POOL_MESSAGE] =       US"untainted",
 [POOL_TAINT_MAIN] =    US"tainted",
 [POOL_TAINT_PERM] =    US"tainted",
 [POOL_TAINT_SEARCH] =  US"tainted",
 [POOL_TAINT_MAIN] =    US"tainted",
 [POOL_TAINT_PERM] =    US"tainted",
 [POOL_TAINT_SEARCH] =  US"tainted",
+[POOL_TAINT_MESSAGE] = US"tainted",
 };
 };
+#endif
 
 
 
 
-static void * store_mmap(int, const char *, int);
 static void * internal_store_malloc(int, const char *, int);
 static void   internal_store_free(void *, const char *, int linenumber);
 
 /******************************************************************************/
 static void * internal_store_malloc(int, const char *, int);
 static void   internal_store_free(void *, const char *, int linenumber);
 
 /******************************************************************************/
+/* Initialisation, for things fragile with parameter channges when using
+static initialisers. */
+
+void
+store_init(void)
+{
+for (int i = 0; i < NPOOLS; i++) yield_length[i] = -1;
+}
+
+/******************************************************************************/
+
+/* Test if a pointer refers to tainted memory.
+
+Slower version check, for use when platform intermixes malloc and mmap area
+addresses. Test against the current-block of all tainted pools first, then all
+blocks of all tainted pools.
+
+Return: TRUE iff tainted
+*/
+
+BOOL
+is_tainted_fn(const void * p)
+{
+storeblock * b;
+
+for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
+  if ((b = current_block[pool]))
+    {
+    uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
+    if (US p >= bc && US p < bc + b->length) return TRUE;
+    }
+
+for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
+  for (b = chainbase[pool]; b; b = b->next)
+    {
+    uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
+    if (US p >= bc && US p < bc + b->length) return TRUE;
+    }
+return FALSE;
+}
+
 
 void
 die_tainted(const uschar * msg, const uschar * func, int line)
 
 void
 die_tainted(const uschar * msg, const uschar * func, int line)
@@ -167,6 +227,7 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n",
 }
 
 
 }
 
 
+
 /*************************************************
 *       Get a block from the current pool        *
 *************************************************/
 /*************************************************
 *       Get a block from the current pool        *
 *************************************************/
@@ -177,7 +238,8 @@ block, getting a new one if necessary. The address is saved in
 store_last_was_get.
 
 Arguments:
 store_last_was_get.
 
 Arguments:
-  size        amount wanted
+  size        amount wanted, bytes
+  tainted     class: set to true for untrusted data (eg. from smtp input)
   func        function from which called
   linenumber  line number in source file
 
   func        function from which called
   linenumber  line number in source file
 
@@ -216,16 +278,7 @@ if (size > yield_length[pool])
     {
     /* Give up on this block, because it's too small */
     nblocks[pool]--;
     {
     /* Give up on this block, because it's too small */
     nblocks[pool]--;
-    if (pool < POOL_TAINT_BASE)
-      internal_store_free(newblock, func, linenumber);
-    else
-      {
-#ifndef COMPILE_UTILITY
-      DEBUG(D_memory)
-       debug_printf("---Unmap %6p %-20s %4d\n", newblock, func, linenumber);
-#endif
-      munmap(newblock, newblock->length + ALIGNED_SIZEOF_STOREBLOCK);
-      }
+    internal_store_free(newblock, func, linenumber);
     newblock = NULL;
     }
 
     newblock = NULL;
     }
 
@@ -241,9 +294,7 @@ if (size > yield_length[pool])
     if (++nblocks[pool] > maxblocks[pool])
       maxblocks[pool] = nblocks[pool];
 
     if (++nblocks[pool] > maxblocks[pool])
       maxblocks[pool] = nblocks[pool];
 
-    newblock = tainted
-      ? store_mmap(mlength, func, linenumber)
-      : internal_store_malloc(mlength, func, linenumber);
+    newblock = internal_store_malloc(mlength, func, linenumber);
     newblock->next = NULL;
     newblock->length = length;
 
     newblock->next = NULL;
     newblock->length = length;
 
@@ -268,10 +319,7 @@ store_last_get[pool] = next_yield[pool];
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
 giving warnings. */
 
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
 giving warnings. */
 
-#ifdef COMPILE_UTILITY
-func = func;
-linenumber = linenumber;
-#else
+#ifndef COMPILE_UTILITY
 DEBUG(D_memory)
   debug_printf("---%d Get %6p %5d %-14s %4d\n", pool,
     store_last_get[pool], size, func, linenumber);
 DEBUG(D_memory)
   debug_printf("---%d Get %6p %5d %-14s %4d\n", pool,
     store_last_get[pool], size, func, linenumber);
@@ -362,10 +410,7 @@ if (CS ptr + rounded_oldsize != CS (next_yield[pool]) ||
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
 giving warnings. */
 
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
 giving warnings. */
 
-#ifdef COMPILE_UTILITY
-func = func;
-linenumber = linenumber;
-#else
+#ifndef COMPILE_UTILITY
 DEBUG(D_memory)
   debug_printf("---%d Ext %6p %5d %-14s %4d\n", pool, ptr, newsize,
     func, linenumber);
 DEBUG(D_memory)
   debug_printf("---%d Ext %6p %5d %-14s %4d\n", pool, ptr, newsize,
     func, linenumber);
@@ -483,25 +528,13 @@ while ((b = bb))
   nbytes[pool] -= siz;
   pool_malloc -= siz;
   nblocks[pool]--;
   nbytes[pool] -= siz;
   pool_malloc -= siz;
   nblocks[pool]--;
-  if (pool < POOL_TAINT_BASE)
-    internal_store_free(b, func, linenumber);
-  else
-    {
-#ifndef COMPILE_UTILITY
-    DEBUG(D_memory)
-      debug_printf("---Unmap %6p %-20s %4d\n", b, func, linenumber);
-#endif
-    munmap(b, b->length + ALIGNED_SIZEOF_STOREBLOCK);
-    }
+  internal_store_free(b, func, linenumber);
   }
 
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
 giving warnings. */
 
   }
 
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
 giving warnings. */
 
-#ifdef COMPILE_UTILITY
-func = func;
-linenumber = linenumber;
-#else
+#ifndef COMPILE_UTILITY
 DEBUG(D_memory)
   debug_printf("---%d Rst %6p %5d %-14s %4d %d\n", pool, ptr,
     count + oldmalloc - pool_malloc,
 DEBUG(D_memory)
   debug_printf("---%d Rst %6p %5d %-14s %4d %d\n", pool, ptr,
     count + oldmalloc - pool_malloc,
@@ -511,19 +544,19 @@ DEBUG(D_memory)
 
 
 rmark
 
 
 rmark
-store_reset_3(rmark r, int pool, const char *func, int linenumber)
+store_reset_3(rmark r, const char *func, int linenumber)
 {
 void ** ptr = r;
 
 {
 void ** ptr = r;
 
-if (pool >= POOL_TAINT_BASE)
+if (store_pool >= POOL_TAINT_BASE)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "store_reset called for pool %d: %s %d\n", pool, func, linenumber);
+    "store_reset called for pool %d: %s %d\n", store_pool, func, linenumber);
 if (!r)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "store_reset called with bad mark: %s %d\n", func, linenumber);
 
 if (!r)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "store_reset called with bad mark: %s %d\n", func, linenumber);
 
-internal_store_reset(*ptr, pool + POOL_TAINT_BASE, func, linenumber);
-internal_store_reset(ptr,  pool,                  func, linenumber);
+internal_store_reset(*ptr, store_pool + POOL_TAINT_BASE, func, linenumber);
+internal_store_reset(ptr,  store_pool,            func, linenumber);
 return NULL;
 }
 
 return NULL;
 }
 
@@ -585,10 +618,7 @@ for (int pool = 0; pool < nelem(current_block); pool++)
   /* Cut out the debugging stuff for utilities, but stop picky compilers from
   giving warnings. */
 
   /* Cut out the debugging stuff for utilities, but stop picky compilers from
   giving warnings. */
 
-#ifdef COMPILE_UTILITY
-  func = func;
-  linenumber = linenumber;
-#else
+#ifndef COMPILE_UTILITY
   DEBUG(D_memory)
     debug_printf("---%d Rel %6p %5d %-14s %4d %d\n", pool, ptr, count,
       func, linenumber, pool_malloc);
   DEBUG(D_memory)
     debug_printf("---%d Rel %6p %5d %-14s %4d %d\n", pool, ptr, count,
       func, linenumber, pool_malloc);
@@ -659,10 +689,7 @@ for (storeblock * b = chainbase[pool]; b; b = b->next)
     /* Cut out the debugging stuff for utilities, but stop picky compilers
     from giving warnings. */
 
     /* Cut out the debugging stuff for utilities, but stop picky compilers
     from giving warnings. */
 
-#ifdef COMPILE_UTILITY
-    func = func;
-    linenumber = linenumber;
-#else
+#ifndef COMPILE_UTILITY
     DEBUG(D_memory)
       debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, func,
        linenumber, pool_malloc);
     DEBUG(D_memory)
       debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, func,
        linenumber, pool_malloc);
@@ -710,8 +737,10 @@ int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
 BOOL release_ok = !tainted && store_last_get[pool] == block;
 uschar * newtext;
 
 BOOL release_ok = !tainted && store_last_get[pool] == block;
 uschar * newtext;
 
+#if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
 if (is_tainted(block) != tainted)
   die_tainted(US"store_newblock", CUS func, linenumber);
 if (is_tainted(block) != tainted)
   die_tainted(US"store_newblock", CUS func, linenumber);
+#endif
 
 newtext = store_get(newsize, tainted);
 memcpy(newtext, block, len);
 
 newtext = store_get(newsize, tainted);
 memcpy(newtext, block, len);
@@ -722,55 +751,6 @@ return (void *)newtext;
 
 
 
 
 
 
-/******************************************************************************/
-static void *
-store_alloc_tail(void * yield, int size, const char * func, int line,
-  const uschar * type)
-{
-if ((nonpool_malloc += size) > max_nonpool_malloc)
-  max_nonpool_malloc = nonpool_malloc;
-
-/* Cut out the debugging stuff for utilities, but stop picky compilers from
-giving warnings. */
-
-#ifdef COMPILE_UTILITY
-func = func; line = line; type = type;
-#else
-
-/* If running in test harness, spend time making sure all the new store
-is not filled with zeros so as to catch problems. */
-
-if (f.running_in_test_harness)
-  memset(yield, 0xF0, (size_t)size);
-DEBUG(D_memory) debug_printf("--%6s %6p %5d bytes\t%-14s %4d\tpool %5d  nonpool %5d\n",
-  type, yield, size, func, line, pool_malloc, nonpool_malloc);
-#endif  /* COMPILE_UTILITY */
-
-return yield;
-}
-
-/*************************************************
-*                Mmap store                      *
-*************************************************/
-
-static void *
-store_mmap(int size, const char * func, int line)
-{
-void * yield, * top;
-
-if (size < 16) size = 16;
-
-if (!(yield = mmap(NULL, (size_t)size,
-                 PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to mmap %d bytes of memory: "
-    "called from line %d of %s", size, line, func);
-
-if (yield < tainted_base) tainted_base = yield;
-if ((top = yield + size) > tainted_top) tainted_top = top;
-
-return store_alloc_tail(yield, size, func, line, US"Mmap");
-}
-
 /*************************************************
 *                Malloc store                    *
 *************************************************/
 /*************************************************
 *                Malloc store                    *
 *************************************************/
@@ -782,13 +762,13 @@ function is called via the macro store_malloc().
 Arguments:
   size        amount of store wanted
   func        function from which called
 Arguments:
   size        amount of store wanted
   func        function from which called
-  linenumber  line number in source file
+  line       line number in source file
 
 Returns:      pointer to gotten store (panic on failure)
 */
 
 static void *
 
 Returns:      pointer to gotten store (panic on failure)
 */
 
 static void *
-internal_store_malloc(int size, const char *func, int linenumber)
+internal_store_malloc(int size, const char *func, int line)
 {
 void * yield;
 
 {
 void * yield;
 
@@ -796,9 +776,25 @@ if (size < 16) size = 16;
 
 if (!(yield = malloc((size_t)size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
 
 if (!(yield = malloc((size_t)size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
-    "called from line %d in %s", size, linenumber, func);
+    "called from line %d in %s", size, line, func);
 
 
-return store_alloc_tail(yield, size, func, linenumber, US"Malloc");
+if ((nonpool_malloc += size) > max_nonpool_malloc)
+  max_nonpool_malloc = nonpool_malloc;
+
+/* Cut out the debugging stuff for utilities, but stop picky compilers from
+giving warnings. */
+
+#ifndef COMPILE_UTILITY
+/* If running in test harness, spend time making sure all the new store
+is not filled with zeros so as to catch problems. */
+
+if (f.running_in_test_harness)
+  memset(yield, 0xF0, (size_t)size);
+DEBUG(D_memory) debug_printf("--Malloc %6p %5d bytes\t%-14s %4d\tpool %5d  nonpool %5d\n",
+  yield, size, func, line, pool_malloc, nonpool_malloc);
+#endif  /* COMPILE_UTILITY */
+
+return yield;
 }
 
 void *
 }
 
 void *
@@ -825,12 +821,9 @@ Returns:      nothing
 */
 
 static void
 */
 
 static void
-internal_store_free(void *block, const char *func, int linenumber)
+internal_store_free(void * block, const char * func, int linenumber)
 {
 {
-#ifdef COMPILE_UTILITY
-func = func;
-linenumber = linenumber;
-#else
+#ifndef COMPILE_UTILITY
 DEBUG(D_memory)
   debug_printf("----Free %6p %-20s %4d\n", block, func, linenumber);
 #endif  /* COMPILE_UTILITY */
 DEBUG(D_memory)
   debug_printf("----Free %6p %-20s %4d\n", block, func, linenumber);
 #endif  /* COMPILE_UTILITY */
@@ -838,7 +831,7 @@ free(block);
 }
 
 void
 }
 
 void
-store_free_3(void *block, const char *func, int linenumber)
+store_free_3(void * block, const char * func, int linenumber)
 {
 n_nonpool_blocks--;
 internal_store_free(block, func, linenumber);
 {
 n_nonpool_blocks--;
 internal_store_free(block, func, linenumber);
@@ -862,4 +855,29 @@ DEBUG(D_memory)
 #endif
 }
 
 #endif
 }
 
+
+/******************************************************************************/
+/* Per-message pool management */
+
+static rmark   message_reset_point    = NULL;
+
+void
+message_start(void)
+{
+int oldpool = store_pool;
+store_pool = POOL_MESSAGE;
+if (!message_reset_point) message_reset_point = store_mark();
+store_pool = oldpool;
+}
+
+void message_tidyup(void)
+{
+int oldpool;
+if (!message_reset_point) return;
+oldpool = store_pool;
+store_pool = POOL_MESSAGE;
+message_reset_point = store_reset(message_reset_point);
+store_pool = oldpool;
+}
+
 /* End of store.c */
 /* End of store.c */