From: Timo Sirainen Date: Mon, 12 Dec 2016 02:53:02 +0000 (+0200) Subject: lib: Add MALLOC_MULTIPLY() and MALLOC_ADD() X-Git-Tag: 2.3.0.rc1~2413 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b716136fc47efd434d60be5db262b4013e375fa9;p=thirdparty%2Fdovecot%2Fcore.git lib: Add MALLOC_MULTIPLY() and MALLOC_ADD() These can be used for calculating memory allocation sizes. If there's an overflow, the macro panics. --- diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 451a5141ca..5a7d19d4f8 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -241,6 +241,7 @@ headers = \ macros.h \ md4.h \ md5.h \ + malloc-overflow.h \ mempool.h \ mkdir-parents.h \ mmap-util.h \ @@ -346,6 +347,7 @@ test_lib_SOURCES = \ 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 \ diff --git a/src/lib/lib.h b/src/lib/lib.h index fa386892ae..672ce446ce 100644 --- a/src/lib/lib.h +++ b/src/lib/lib.h @@ -26,6 +26,7 @@ #include "macros.h" #include "failures.h" +#include "malloc-overflow.h" #include "data-stack.h" #include "mempool.h" #include "imem.h" diff --git a/src/lib/malloc-overflow.h b/src/lib/malloc-overflow.h new file mode 100644 index 0000000000..830677a7c5 --- /dev/null +++ b/src/lib/malloc-overflow.h @@ -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 diff --git a/src/lib/test-lib.inc b/src/lib/test-lib.inc index 6968bc9117..10d6a8364a 100644 --- a/src/lib/test-lib.inc +++ b/src/lib/test-lib.inc @@ -39,6 +39,8 @@ TEST(test_json_parser) TEST(test_json_tree) TEST(test_llist) TEST(test_log_throttle) +TEST(test_malloc_overflow) +FATAL(fatal_malloc_overflow) TEST(test_mempool_alloconly) FATAL(fatal_mempool) TEST(test_net) diff --git a/src/lib/test-malloc-overflow.c b/src/lib/test-malloc-overflow.c new file mode 100644 index 0000000000..8a2cc82359 --- /dev/null +++ b/src/lib/test-malloc-overflow.c @@ -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; +}