X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/6d5f5cafb4c507abe36434bf7695573284eb8761..36eb5d3d77426d8cbf4243ea752f8d8cd1d5c682:/src/src/store.c diff --git a/src/src/store.c b/src/src/store.c index 41ca43d65..aceb0e5d6 100644 --- a/src/src/store.c +++ b/src/src/store.c @@ -41,8 +41,32 @@ The following different types of store are recognized: 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 three 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. + + Internally we currently use malloc for nontainted pools, and mmap for tainted + pools. The disparity is for speed of testing the taintedness of pointers; + because Linux appears to use distinct non-overlapping address allocations for + mmap vs. everything else, which means only two pointer-compares suffice for the + test. Other OS' cannot use that optimisation, and a more lengthy test against + the limits of tainted-pool allcations has to be done. + + 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). */ @@ -135,6 +159,7 @@ static int max_pool_malloc; /* max value for pool_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", @@ -151,14 +176,48 @@ static const uschar * poolclass[NPOOLS] = { [POOL_TAINT_PERM] = US"tainted", [POOL_TAINT_SEARCH] = 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_untainted_free(void *, const char *, int linenumber); +static void internal_tainted_free(storeblock *, const char *, int linenumber); /******************************************************************************/ +/* 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 = 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 (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) { @@ -166,6 +225,13 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n", msg, func, line); } +static void +use_slow_taint_check(void) +{ +DEBUG(D_any) debug_printf("switching to slow-mode taint checking\n"); +f.taint_check_slow = TRUE; +} + /************************************************* * Get a block from the current pool * @@ -177,7 +243,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 @@ -217,15 +284,9 @@ if (size > yield_length[pool]) /* Give up on this block, because it's too small */ nblocks[pool]--; if (pool < POOL_TAINT_BASE) - internal_store_free(newblock, func, linenumber); + internal_untainted_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_tainted_free(newblock, func, linenumber); newblock = NULL; } @@ -484,15 +545,9 @@ while ((b = bb)) pool_malloc -= siz; nblocks[pool]--; if (pool < POOL_TAINT_BASE) - internal_store_free(b, func, linenumber); + internal_untainted_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_tainted_free(b, func, linenumber); } /* Cut out the debugging stuff for utilities, but stop picky compilers from @@ -710,8 +765,10 @@ 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 (is_tainted(block) != tainted) die_tainted(US"store_newblock", CUS func, linenumber); +#endif newtext = store_get(newsize, tainted); memcpy(newtext, block, len); @@ -766,7 +823,7 @@ if (!(yield = mmap(NULL, (size_t)size, "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; +if ((top = US yield + size) > tainted_top) tainted_top = top; return store_alloc_tail(yield, size, func, line, US"Mmap"); } @@ -798,6 +855,14 @@ 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); +/* If malloc ever returns apparently tainted memory, which glibc +malloc will as it uses mmap for larger requests, we must switch to +the slower checking for tainting (checking an address against all +the tainted pool block spans, rather than just the mmap span) */ + +if (!f.taint_check_slow && is_tainted(yield)) + use_slow_taint_check(); + return store_alloc_tail(yield, size, func, linenumber, US"Malloc"); } @@ -825,7 +890,7 @@ Returns: nothing */ static void -internal_store_free(void *block, const char *func, int linenumber) +internal_untainted_free(void * block, const char * func, int linenumber) { #ifdef COMPILE_UTILITY func = func; @@ -838,10 +903,24 @@ free(block); } 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); +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); } /******************************************************************************/