SECURITY: Refuse negative and large store allocations
[exim.git] / src / src / store.c
index 8faefd492cc7d60dd081ccdb5e278c60fcde4b19..2a32e9b5c40caa50cf7743ce8328ab29d918ff84 100644 (file)
@@ -201,7 +201,6 @@ for (int i = 0; i < NPOOLS; i++)
   yield_length[i] = -1;
   store_block_order[i] = 12; /* log2(allocation_size) ie. 4kB */
   }
-store_block_order[POOL_MAIN] = 13;
 }
 
 /******************************************************************************/
@@ -269,6 +268,17 @@ store_get_3(int size, BOOL tainted, const char *func, int linenumber)
 {
 int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
 
+/* Ensure we've been asked to allocate memory.
+A negative size is a sign of a security problem.
+A zero size might be also suspect, but our internal usage deliberately
+does this to return a current watermark value for a later release of
+allocated store. */
+
+if (size < 0 || size >= INT_MAX/2)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+            "bad memory allocation requested (%d bytes) at %s %d",
+            size, func, linenumber);
+
 /* Round up the size to a multiple of the alignment. Although this looks a
 messy statement, because "alignment" is a constant expression, the compiler can
 do a reasonable job of optimizing, especially if the value of "alignment" is a
@@ -283,7 +293,9 @@ these functions are mostly called for small amounts of store. */
 
 if (size > yield_length[pool])
   {
-  int length = MAX(STORE_BLOCK_SIZE(store_block_order[pool]), size);
+  int length = MAX(
+         STORE_BLOCK_SIZE(store_block_order[pool]) - ALIGNED_SIZEOF_STOREBLOCK,
+         size);
   int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
   storeblock * newblock;
 
@@ -416,6 +428,11 @@ int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
 int inc = newsize - oldsize;
 int rounded_oldsize = oldsize;
 
+if (oldsize < 0 || newsize < oldsize || newsize >= INT_MAX/2)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+            "bad memory extension requested (%d -> %d bytes) at %s %d",
+            oldsize, newsize, func, linenumber);
+
 /* Check that the block being extended was already of the required taint status;
 refuse to extend if not. */
 
@@ -546,15 +563,10 @@ if (  yield_length[pool] < STOREPOOL_MIN_SIZE
 bb = b->next;
 b->next = NULL;
 
-/* If there will be only one block left in the pool, drop one
-most-recent allocation size increase, ensuring it does not increase
-forever. */
-
-if (!bb && store_block_order[pool] > 12) store_block_order[pool]--;
-
 while ((b = bb))
   {
   int siz = b->length + ALIGNED_SIZEOF_STOREBLOCK;
+
 #ifndef COMPILE_UTILITY
   if (debug_store)
     assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
@@ -565,6 +577,10 @@ while ((b = bb))
   pool_malloc -= siz;
   nblocks[pool]--;
   internal_store_free(b, func, linenumber);
+
+#ifndef RESTRICTED_MEMORY
+  if (store_block_order[pool] > 13) store_block_order[pool]--;
+#endif
   }
 
 /* Cut out the debugging stuff for utilities, but stop picky compilers from
@@ -740,7 +756,7 @@ for (storeblock * b = chainbase[pool]; b; b = b->next)
       memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK);
 #endif  /* COMPILE_UTILITY */
 
-    free(bb);
+    internal_store_free(bb, func, linenumber);
     return;
     }
   }
@@ -784,6 +800,11 @@ if (is_tainted(block) != tainted)
   die_tainted(US"store_newblock", CUS func, linenumber);
 #endif
 
+if (len < 0 || len > newsize)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+            "bad memory extension requested (%d -> %d bytes) at %s %d",
+            len, newsize, func, linenumber);
+
 newtext = store_get(newsize, tainted);
 memcpy(newtext, block, len);
 if (release_ok) store_release_3(block, pool, func, linenumber);
@@ -814,10 +835,12 @@ internal_store_malloc(int size, const char *func, int line)
 {
 void * yield;
 
-#ifndef COMPILE_UTILITY
-DEBUG(D_memory) size += sizeof(int);   /* space to store the size */
-#endif
+if (size < 0 || size >= INT_MAX/2)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+            "bad memory allocation requested (%d bytes) at %s %d",
+            size, func, line);
 
+size += sizeof(int);   /* space to store the size, used under debug */
 if (size < 16) size = 16;
 
 if (!(yield = malloc((size_t)size)))
@@ -825,8 +848,9 @@ if (!(yield = malloc((size_t)size)))
     "called from line %d in %s", size, line, func);
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory) { *(int *)yield = size; yield = US yield + sizeof(int); }
+DEBUG(D_any) *(int *)yield = size;
 #endif
+yield = US yield + sizeof(int);
 
 if ((nonpool_malloc += size) > max_nonpool_malloc)
   max_nonpool_malloc = nonpool_malloc;
@@ -873,9 +897,9 @@ Returns:      nothing
 static void
 internal_store_free(void * block, const char * func, int linenumber)
 {
-uschar * p = block;
+uschar * p = US block - sizeof(int);
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory) { p -= sizeof(int); nonpool_malloc -= *(int *)p; }
+DEBUG(D_any) nonpool_malloc -= *(int *)p;
 DEBUG(D_memory) debug_printf("----Free %6p %5d bytes\t%-20s %4d\n", block, *(int *)p, func, linenumber);
 #endif
 free(p);
@@ -901,7 +925,7 @@ DEBUG(D_memory)
  debug_printf("----Exit npools  max: %3d kB\n", max_pool_malloc/1024);
  for (int i = 0; i < NPOOLS; i++)
   debug_printf("----Exit  pool %d max: %3d kB in %d blocks at order %u\t%s %s\n",
-    i, maxbytes[i]/1024, maxblocks[i], maxorder[i],
+    i, (maxbytes[i]+1023)/1024, maxblocks[i], maxorder[i],
     poolclass[i], pooluse[i]);
  }
 #endif