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;
+}