X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/6d95688d6a272297a6a47f2fd2695cc8e5b8b730..ee3c2fea18d0c940c2256c6bf041f546c703c375:/src/src/store.c diff --git a/src/src/store.c b/src/src/store.c index a06e1c19a..9d155821b 100644 --- a/src/src/store.c +++ b/src/src/store.c @@ -3,7 +3,7 @@ *************************************************/ /* 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 @@ -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. +- 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 - 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. + + 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; -#define NPOOLS 6 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 @@ -140,54 +153,69 @@ 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_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", +[POOL_MESSAGE] = US"untainted", [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_untainted_free(void *, const char *, int linenumber); -static void internal_tainted_free(storeblock *, const char *, int linenumber); +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; +} /******************************************************************************/ -/* Slower version check, for use when platform intermixes malloc and mmap area -addresses. */ +/* 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; -int pool; -for (pool = 0; pool < nelem(chainbase); pool++) +for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++) if ((b = current_block[pool])) { - char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK; - if (CS p >= bc && CS p <= bc + b->length) goto hit; + uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; + if (US p >= bc && US p < bc + b->length) return TRUE; } -for (pool = 0; pool < nelem(chainbase); pool++) +for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++) for (b = chainbase[pool]; b; b = b->next) { - char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK; - if (CS p >= bc && CS p <= bc + b->length) goto hit; + uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; + if (US p >= bc && US p < bc + b->length) return TRUE; } return FALSE; - -hit: -return pool >= POOL_TAINT_BASE; } @@ -199,6 +227,7 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n", } + /************************************************* * Get a block from the current pool * *************************************************/ @@ -209,7 +238,8 @@ block, getting a new one if necessary. The address is saved in 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 @@ -248,10 +278,7 @@ if (size > yield_length[pool]) { /* Give up on this block, because it's too small */ nblocks[pool]--; - if (pool < POOL_TAINT_BASE) - internal_untainted_free(newblock, func, linenumber); - else - internal_tainted_free(newblock, func, linenumber); + internal_store_free(newblock, func, linenumber); newblock = NULL; } @@ -267,9 +294,7 @@ if (size > yield_length[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; @@ -294,10 +319,7 @@ store_last_get[pool] = next_yield[pool]; /* 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); @@ -388,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. */ -#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); @@ -509,19 +528,13 @@ while ((b = bb)) nbytes[pool] -= siz; pool_malloc -= siz; nblocks[pool]--; - if (pool < POOL_TAINT_BASE) - internal_untainted_free(b, func, linenumber); - else - internal_tainted_free(b, func, linenumber); + internal_store_free(b, func, linenumber); } /* 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, @@ -531,19 +544,19 @@ DEBUG(D_memory) 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; -if (pool >= POOL_TAINT_BASE) +if (store_pool >= POOL_TAINT_BASE) 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); -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; } @@ -605,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. */ -#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); @@ -679,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. */ -#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); @@ -730,7 +737,7 @@ int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; BOOL release_ok = !tainted && store_last_get[pool] == block; uschar * newtext; -#ifndef MACRO_PREDEF +#if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY) if (is_tainted(block) != tainted) die_tainted(US"store_newblock", CUS func, linenumber); #endif @@ -744,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 = US yield + size) > tainted_top) tainted_top = top; - -return store_alloc_tail(yield, size, func, line, US"Mmap"); -} - /************************************************* * Malloc store * *************************************************/ @@ -804,13 +762,13 @@ function is called via the macro store_malloc(). 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 * -internal_store_malloc(int size, const char *func, int linenumber) +internal_store_malloc(int size, const char *func, int line) { void * yield; @@ -818,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: " - "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 * @@ -847,12 +821,9 @@ Returns: nothing */ static void -internal_untainted_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 */ @@ -863,21 +834,7 @@ void store_free_3(void * block, const char * func, int linenumber) { n_nonpool_blocks--; -internal_untainted_free(block, func, linenumber); -} - -/******************************************************************************/ -static void -internal_tainted_free(storeblock * block, const char * func, int linenumber) -{ -#ifdef COMPILE_UTILITY -func = func; -linenumber = linenumber; -#else -DEBUG(D_memory) - debug_printf("---Unmap %6p %-20s %4d\n", block, func, linenumber); -#endif -munmap((void *)block, block->length + ALIGNED_SIZEOF_STOREBLOCK); +internal_store_free(block, func, linenumber); } /******************************************************************************/ @@ -898,4 +855,29 @@ DEBUG(D_memory) #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 */