Mercurial > dovecot > core-2.2
changeset 21319:a3bbf15ea8d7
lib: Add MALLOC_MULTIPLY() and MALLOC_ADD()
These can be used for calculating memory allocation sizes. If there's an
overflow, the macro panics.
author | Timo Sirainen <timo.sirainen@dovecot.fi> |
---|---|
date | Mon, 12 Dec 2016 04:53:02 +0200 |
parents | 5e70e91e4c78 |
children | 1bdfc555f6a3 |
files | src/lib/Makefile.am src/lib/lib.h src/lib/malloc-overflow.h src/lib/test-lib.c src/lib/test-lib.h src/lib/test-malloc-overflow.c |
diffstat | 6 files changed, 186 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/src/lib/Makefile.am Mon Dec 12 03:55:54 2016 +0200 +++ b/src/lib/Makefile.am Mon Dec 12 04:53:02 2016 +0200 @@ -235,6 +235,7 @@ macros.h \ md4.h \ md5.h \ + malloc-overflow.h \ mempool.h \ mkdir-parents.h \ mmap-util.h \ @@ -336,6 +337,7 @@ test-json-tree.c \ test-llist.c \ test-log-throttle.c \ + test-malloc-overflow.c \ test-mempool-alloconly.c \ test-pkcs5.c \ test-net.c \
--- a/src/lib/lib.h Mon Dec 12 03:55:54 2016 +0200 +++ b/src/lib/lib.h Mon Dec 12 04:53:02 2016 +0200 @@ -26,6 +26,7 @@ #include "macros.h" #include "failures.h" +#include "malloc-overflow.h" #include "data-stack.h" #include "mempool.h" #include "imem.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/malloc-overflow.h Mon Dec 12 04:53:02 2016 +0200 @@ -0,0 +1,42 @@ +#ifndef MALLOC_OVERFLOW_H +#define MALLOC_OVERFLOW_H + +/* MALLOC_*() can be used to calculate memory allocation sizes. If there's an + overflow, it'll cleanly panic instead of causing a potential buffer + overflow. + + Note that *_malloc(size+1) doesn't need to use MALLOC_ADD(size, 1). It wraps + to size==0 and the *_malloc() calls already panic if size==0. */ +static inline size_t +malloc_multiply_check(size_t a, size_t b, size_t sizeof_a, size_t sizeof_b, + const char *fname, unsigned int linenum) +{ + /* the first sizeof-checks are intended to optimize away this entire + if-check for types that are small enough to never wrap size_t. */ + if ((sizeof_a * 2 > sizeof(size_t) || sizeof_b * 2 > sizeof(size_t)) && + b != 0 && (a > SIZE_MAX / b)) { + i_panic("file %s: line %d: memory allocation overflow: " + "%" PRIuSIZE_T" * %" PRIuSIZE_T, fname, linenum, a, b); + } + return a * b; +} +#define MALLOC_MULTIPLY(a, b) \ + malloc_multiply_check(a, b, sizeof(a), sizeof(b), __FILE__, __LINE__) + +static inline size_t +malloc_add_check(size_t a, size_t b, size_t sizeof_a, size_t sizeof_b, + const char *fname, unsigned int linenum) +{ + /* the first sizeof-checks are intended to optimize away this entire + if-check for types that are small enough to never wrap size_t. */ + if ((sizeof_a >= sizeof(size_t) || sizeof_b >= sizeof(size_t)) && + SIZE_MAX - a < b) { + i_panic("file %s: line %d: memory allocation overflow: " + "%" PRIuSIZE_T" + %" PRIuSIZE_T, fname, linenum, a, b); + } + return a + b; +} +#define MALLOC_ADD(a, b) \ + malloc_add_check(a, b, sizeof(a), sizeof(b), __FILE__, __LINE__) + +#endif
--- a/src/lib/test-lib.c Mon Dec 12 03:55:54 2016 +0200 +++ b/src/lib/test-lib.c Mon Dec 12 04:53:02 2016 +0200 @@ -37,6 +37,7 @@ test_json_tree, test_llist, test_log_throttle, + test_malloc_overflow, test_mempool_alloconly, test_net, test_numpack, @@ -66,6 +67,7 @@ static enum fatal_test_state (*fatal_functions[])(unsigned int) = { fatal_array, fatal_data_stack, + fatal_malloc_overflow, fatal_mempool, fatal_printf_format_fix, NULL
--- a/src/lib/test-lib.h Mon Dec 12 03:55:54 2016 +0200 +++ b/src/lib/test-lib.h Mon Dec 12 04:53:02 2016 +0200 @@ -38,6 +38,8 @@ void test_json_tree(void); void test_llist(void); void test_log_throttle(void); +void test_malloc_overflow(void); +enum fatal_test_state fatal_malloc_overflow(unsigned int); void test_mempool_alloconly(void); enum fatal_test_state fatal_mempool(unsigned int); void test_pkcs5_pbkdf2(void);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/test-malloc-overflow.c Mon Dec 12 04:53:02 2016 +0200 @@ -0,0 +1,137 @@ +/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" + +static void test_malloc_overflow_multiply(void) +{ + const struct { + size_t a, b; + } tests[] = { + { 0, SIZE_MAX }, + { 1, SIZE_MAX }, + { SIZE_MAX/2, 2 }, + }; + test_begin("MALLOC_MULTIPLY()"); + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + test_assert_idx(MALLOC_MULTIPLY(tests[i].a, tests[i].b) == tests[i].a * tests[i].b, i); + test_assert_idx(MALLOC_MULTIPLY(tests[i].b, tests[i].a) == tests[i].b * tests[i].a, i); + } + test_end(); +} + +static void test_malloc_overflow_add(void) +{ + const struct { + size_t a, b; + } tests[] = { + { 0, SIZE_MAX }, + { 1, SIZE_MAX-1 }, + { SIZE_MAX/2+1, SIZE_MAX/2 }, + }; + unsigned short n = 2; + + test_begin("MALLOC_ADD()"); + /* check that no compiler warning is given */ + test_assert(MALLOC_ADD(2, n) == 2+n); + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + test_assert_idx(MALLOC_ADD(tests[i].a, tests[i].b) == tests[i].a + tests[i].b, i); + test_assert_idx(MALLOC_ADD(tests[i].b, tests[i].a) == tests[i].b + tests[i].a, i); + } + test_end(); +} + +void test_malloc_overflow(void) +{ + test_malloc_overflow_multiply(); + test_malloc_overflow_add(); +} + +static enum fatal_test_state fatal_malloc_overflow_multiply(unsigned int *stage) +{ + const struct { + size_t a, b; + } mul_tests[] = { + { SIZE_MAX/2+1, 2 }, + }; + unsigned int i; + + switch (*stage) { + case 0: + test_begin("MALLOC_MULTIPLY() overflows"); + i_error("%"PRIuSIZE_T, MALLOC_MULTIPLY((size_t)SIZE_MAX/2, (uint8_t)3)); + break; + case 1: + i_error("%"PRIuSIZE_T, MALLOC_MULTIPLY((uint8_t)3, (size_t)SIZE_MAX/2)); + break; + } + *stage -= 2; + + if (*stage >= N_ELEMENTS(mul_tests)*2) { + *stage -= N_ELEMENTS(mul_tests)*2; + if (*stage == 0) + test_end(); + return FATAL_TEST_FINISHED; + } + i = *stage / 2; + + if (*stage % 2 == 0) + i_error("%"PRIuSIZE_T, MALLOC_MULTIPLY(mul_tests[i].a, mul_tests[i].b)); + else + i_error("%"PRIuSIZE_T, MALLOC_MULTIPLY(mul_tests[i].b, mul_tests[i].a)); + return FATAL_TEST_FAILURE; +} + +static enum fatal_test_state fatal_malloc_overflow_add(unsigned int *stage) +{ + const struct { + size_t a, b; + } add_tests[] = { + { SIZE_MAX, 1 }, + { SIZE_MAX/2+1, SIZE_MAX/2+1 }, + }; + unsigned int i; + + switch (*stage) { + case 0: + test_begin("MALLOC_ADD() overflows"); + i_error("%"PRIuSIZE_T, MALLOC_ADD((size_t)SIZE_MAX, (uint8_t)1)); + break; + case 1: + i_error("%"PRIuSIZE_T, MALLOC_ADD((uint8_t)1, (size_t)SIZE_MAX)); + break; + } + *stage -= 2; + + if (*stage >= N_ELEMENTS(add_tests)*2) { + *stage -= N_ELEMENTS(add_tests)*2; + if (*stage == 0) + test_end(); + return FATAL_TEST_FINISHED; + } + i = *stage / 2; + + if (*stage % 2 == 0) + i_error("%"PRIuSIZE_T, MALLOC_ADD(add_tests[i].a, add_tests[i].b)); + else + i_error("%"PRIuSIZE_T, MALLOC_ADD(add_tests[i].b, add_tests[i].a)); + return FATAL_TEST_FAILURE; +} + +enum fatal_test_state fatal_malloc_overflow(unsigned int stage) +{ + enum fatal_test_state state; + + state = fatal_malloc_overflow_multiply(&stage); + if (state != FATAL_TEST_FINISHED) + return state; + return fatal_malloc_overflow_add(&stage); + + /* individual checks */ + switch (stage) { + case 0: + if (stage == 0) + test_begin("MALLOC_MULTIPLY() overflows"); + break; + } + return FATAL_TEST_FINISHED; +}