a single message transaction but needed for longer than the use of the main
pool permits. Currently this means only receive-time DKIM information.
+- There is a dedicated pool for configuration data read from the config file(s).
+ Once complete, it is made readonly.
+
. 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,
static int maxbytes[NPOOLS]; /* max number reached */
static int nblocks[NPOOLS]; /* current number of blocks allocated */
static int maxblocks[NPOOLS];
+static unsigned maxorder[NPOOLS];
static int n_nonpool_blocks; /* current number of direct store_malloc() blocks */
static int max_nonpool_blocks;
static int max_pool_malloc; /* max value for pool_malloc */
static const uschar * pooluse[NPOOLS] = {
[POOL_MAIN] = US"main",
[POOL_PERM] = US"perm",
+[POOL_CONFIG] = US"config",
[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_CONFIG] = US"config",
[POOL_TAINT_SEARCH] = US"search",
[POOL_TAINT_MESSAGE] = US"message",
};
static const uschar * poolclass[NPOOLS] = {
[POOL_MAIN] = US"untainted",
[POOL_PERM] = US"untainted",
+[POOL_CONFIG] = US"untainted",
[POOL_SEARCH] = US"untainted",
[POOL_MESSAGE] = US"untainted",
[POOL_TAINT_MAIN] = US"tainted",
[POOL_TAINT_PERM] = US"tainted",
+[POOL_TAINT_CONFIG] = US"tainted",
[POOL_TAINT_SEARCH] = US"tainted",
[POOL_TAINT_MESSAGE] = US"tainted",
};
yield_length[i] = -1;
store_block_order[i] = 12; /* log2(allocation_size) ie. 4kB */
}
-store_block_order[POOL_MAIN] = 13;
}
/******************************************************************************/
+/******************************************************************************/
+void
+store_writeprotect(int pool)
+{
+#if !defined(COMPILE_UTILITY) && !defined(MISSING_POSIX_MEMALIGN)
+for (storeblock * b = chainbase[pool]; b; b = b->next)
+ if (mprotect(b, ALIGNED_SIZEOF_STOREBLOCK + b->length, PROT_READ) != 0)
+ DEBUG(D_any) debug_printf("config block mprotect: (%d) %s\n", errno, strerror(errno));
+#endif
+}
+
+/******************************************************************************/
+
/*************************************************
* Get a block from the current pool *
*************************************************/
{
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
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;
if (++nblocks[pool] > maxblocks[pool])
maxblocks[pool] = nblocks[pool];
- newblock = internal_store_malloc(mlength, func, linenumber);
+#ifndef MISSING_POSIX_MEMALIGN
+ if (pool == POOL_CONFIG)
+ {
+ long pgsize = sysconf(_SC_PAGESIZE);
+ posix_memalign((void **)&newblock, pgsize, (mlength + pgsize - 1) & ~(pgsize - 1));
+ }
+ else
+#endif
+ newblock = internal_store_malloc(mlength, func, linenumber);
newblock->next = NULL;
newblock->length = length;
#ifndef RESTRICTED_MEMORY
- store_block_order[pool]++;
+ if (store_block_order[pool]++ > maxorder[pool])
+ maxorder[pool] = store_block_order[pool];
#endif
if (!chainbase[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. */
pools corresposding to store_pool are reset.
Arguments:
- r place to back up to
+ ptr place to back up to
+ pool pool holding the pointer
func function from which called
linenumber line number in source file
}
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]--;
+if (pool != POOL_CONFIG)
+ b->next = NULL;
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,
nbytes[pool] -= siz;
pool_malloc -= siz;
nblocks[pool]--;
- internal_store_free(b, func, linenumber);
+ if (pool != POOL_CONFIG)
+ 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
memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK);
#endif /* COMPILE_UTILITY */
- free(bb);
+ internal_store_free(bb, func, linenumber);
return;
}
}
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);
{
void * yield;
+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)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
"called from line %d in %s", size, line, func);
+#ifndef COMPILE_UTILITY
+DEBUG(D_any) *(int *)yield = size;
+#endif
+yield = US yield + sizeof(int);
+
if ((nonpool_malloc += size) > max_nonpool_malloc)
max_nonpool_malloc = nonpool_malloc;
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",
+ memset(yield, 0xF0, (size_t)size - sizeof(int));
+DEBUG(D_memory) debug_printf("--Malloc %6p %5d bytes\t%-20s %4d\tpool %5d nonpool %5d\n",
yield, size, func, line, pool_malloc, nonpool_malloc);
#endif /* COMPILE_UTILITY */
static void
internal_store_free(void * block, const char * func, int linenumber)
{
+uschar * p = US block - sizeof(int);
#ifndef COMPILE_UTILITY
-DEBUG(D_memory)
- debug_printf("----Free %6p %-20s %4d\n", block, func, linenumber);
-#endif /* COMPILE_UTILITY */
-free(block);
+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);
}
void
(max_nonpool_malloc+1023)/1024, max_nonpool_blocks);
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\t%s %s\n",
- i, maxbytes[i]/1024, maxblocks[i], poolclass[i], pooluse[i]);
+ debug_printf("----Exit pool %d max: %3d kB in %d blocks at order %u\t%s %s\n",
+ i, (maxbytes[i]+1023)/1024, maxblocks[i], maxorder[i],
+ poolclass[i], pooluse[i]);
}
#endif
}