Memory: add MEM_malloc_arrayN() function to protect against overflow.
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Sun, 14 Jan 2018 20:53:32 +0000 (21:53 +0100)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 17 Jan 2018 18:59:47 +0000 (19:59 +0100)
Differential Revision: https://developer.blender.org/D3002

intern/guardedalloc/CMakeLists.txt
intern/guardedalloc/MEM_guardedalloc.h
intern/guardedalloc/intern/mallocn.c
intern/guardedalloc/intern/mallocn_guarded_impl.c
intern/guardedalloc/intern/mallocn_inline.h [new file with mode: 0644]
intern/guardedalloc/intern/mallocn_intern.h
intern/guardedalloc/intern/mallocn_lockfree_impl.c
tests/gtests/guardedalloc/CMakeLists.txt
tests/gtests/guardedalloc/guardedalloc_overflow_test.cc [new file with mode: 0644]

index 1d041ba..10ed428 100644 (file)
@@ -38,6 +38,7 @@ set(SRC
        ./intern/mallocn_lockfree_impl.c
 
        MEM_guardedalloc.h
+       ./intern/mallocn_inline.h
        ./intern/mallocn_intern.h
 
        # only so the header is known by cmake
index f6048a0..a579212 100644 (file)
@@ -113,12 +113,26 @@ extern "C" {
         * pointer to it is stored ! */
        extern void *(*MEM_callocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
 
+       /**
+        * Allocate a block of memory of size (len * size), with tag name
+        * str, aborting in case of integer overflows to prevent vulnerabilities.
+        * The memory is cleared. The name must be static, because only a
+        * pointer to it is stored ! */
+       extern void *(*MEM_calloc_arrayN)(size_t len, size_t size, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1,2) ATTR_NONNULL(3);
+
        /**
         * Allocate a block of memory of size len, with tag name str. The
         * name must be a static, because only a pointer to it is stored !
         * */
        extern void *(*MEM_mallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
 
+       /**
+        * Allocate a block of memory of size (len * size), with tag name str,
+        * aborting in case of integer overflow to prevent vulnerabilities. The
+        * name must be a static, because only a pointer to it is stored !
+        * */
+       extern void *(*MEM_malloc_arrayN)(size_t len, size_t size, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1,2) ATTR_NONNULL(3);
+
        /**
         * Allocate an aligned block of memory of size len, with tag name str. The
         * name must be a static, because only a pointer to it is stored !
index 1fd85a0..b973037 100644 (file)
@@ -43,7 +43,9 @@ void *(*MEM_dupallocN)(const void *vmemh) = MEM_lockfree_dupallocN;
 void *(*MEM_reallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_reallocN_id;
 void *(*MEM_recallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_recallocN_id;
 void *(*MEM_callocN)(size_t len, const char *str) = MEM_lockfree_callocN;
+void *(*MEM_calloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_calloc_arrayN;
 void *(*MEM_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN;
+void *(*MEM_malloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_malloc_arrayN;
 void *(*MEM_mallocN_aligned)(size_t len, size_t alignment, const char *str) = MEM_lockfree_mallocN_aligned;
 void *(*MEM_mapallocN)(size_t len, const char *str) = MEM_lockfree_mapallocN;
 void (*MEM_printmemlist_pydict)(void) = MEM_lockfree_printmemlist_pydict;
@@ -107,7 +109,9 @@ void MEM_use_guarded_allocator(void)
        MEM_reallocN_id = MEM_guarded_reallocN_id;
        MEM_recallocN_id = MEM_guarded_recallocN_id;
        MEM_callocN = MEM_guarded_callocN;
+       MEM_calloc_arrayN = MEM_guarded_calloc_arrayN;
        MEM_mallocN = MEM_guarded_mallocN;
+       MEM_malloc_arrayN = MEM_guarded_malloc_arrayN;
        MEM_mallocN_aligned = MEM_guarded_mallocN_aligned;
        MEM_mapallocN = MEM_guarded_mapallocN;
        MEM_printmemlist_pydict = MEM_guarded_printmemlist_pydict;
index 76b7e07..d012cce 100644 (file)
@@ -542,6 +542,21 @@ void *MEM_guarded_mallocN(size_t len, const char *str)
        return NULL;
 }
 
+void *MEM_guarded_malloc_arrayN(size_t len, size_t size, const char *str)
+{
+       size_t total_size;
+       if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
+               print_error("Malloc array aborted due to integer overflow: "
+                           "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
+                           SIZET_ARG(len), SIZET_ARG(size), str,
+                           (unsigned int) mem_in_use);
+               abort();
+               return NULL;
+       }
+
+       return MEM_guarded_mallocN(total_size, str);
+}
+
 void *MEM_guarded_mallocN_aligned(size_t len, size_t alignment, const char *str)
 {
        MemHead *memh;
@@ -612,6 +627,21 @@ void *MEM_guarded_callocN(size_t len, const char *str)
        return NULL;
 }
 
+void *MEM_guarded_calloc_arrayN(size_t len, size_t size, const char *str)
+{
+       size_t total_size;
+       if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
+               print_error("Calloc array aborted due to integer overflow: "
+                           "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
+                           SIZET_ARG(len), SIZET_ARG(size), str,
+                           (unsigned int) mem_in_use);
+               abort();
+               return NULL;
+       }
+
+       return MEM_guarded_callocN(total_size, str);
+}
+
 /* note; mmap returns zero'd memory */
 void *MEM_guarded_mapallocN(size_t len, const char *str)
 {
diff --git a/intern/guardedalloc/intern/mallocn_inline.h b/intern/guardedalloc/intern/mallocn_inline.h
new file mode 100644 (file)
index 0000000..7f0fa2b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Adapted from jemalloc, to protect against buffer overflow vulnerabilities.
+ *
+ * Copyright (C) 2002-2017 Jason Evans <jasone@canonware.com>.
+ * All rights reserved.
+ * Copyright (C) 2007-2012 Mozilla Foundation.  All rights reserved.
+ * Copyright (C) 2009-2017 Facebook, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice(s),
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice(s),
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** \file guardedalloc/intern/mallocn_inline.h
+ *  \ingroup MEM
+ */
+
+#ifndef __MALLOCN_INLINE_H__
+#define __MALLOCN_INLINE_H__
+
+MEM_INLINE bool MEM_size_safe_multiply(size_t a, size_t b, size_t *result)
+{
+       /* A size_t with its high-half bits all set to 1. */
+       const size_t high_bits = SIZE_MAX << (sizeof(size_t) * 8 / 2);
+       *result = a * b;
+
+       if (UNLIKELY(*result == 0)) {
+               return (a == 0 || b == 0);
+       }
+
+       /*
+        * We got a non-zero size, but we don't know if we overflowed to get
+        * there.  To avoid having to do a divide, we'll be clever and note that
+        * if both A and B can be represented in N/2 bits, then their product
+        * can be represented in N bits (without the possibility of overflow).
+        */
+       return ((high_bits & (a | b)) == 0 || (*result / b == a));
+}
+
+#endif  /* __MALLOCN_INLINE_H__ */
+
index 9a5848c..4b2282a 100644 (file)
@@ -115,6 +115,8 @@ size_t malloc_usable_size(void *ptr);
 /* Real pointer returned by the malloc or aligned_alloc. */
 #define MEMHEAD_REAL_PTR(memh) ((char *)memh - MEMHEAD_ALIGN_PADDING(memh->alignment))
 
+#include "mallocn_inline.h"
+
 void *aligned_malloc(size_t size, size_t alignment);
 void aligned_free(void *ptr);
 
@@ -125,7 +127,9 @@ void *MEM_lockfree_dupallocN(const void *vmemh) ATTR_MALLOC ATTR_WARN_UNUSED_RES
 void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *UNUSED(str))  ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2);
 void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *UNUSED(str))  ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2);
 void *MEM_lockfree_callocN(size_t len, const char *UNUSED(str))  ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
+void *MEM_lockfree_calloc_arrayN(size_t len, size_t size, const char *UNUSED(str))  ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1,2) ATTR_NONNULL(3);
 void *MEM_lockfree_mallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
+void *MEM_lockfree_malloc_arrayN(size_t len, size_t size, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1,2) ATTR_NONNULL(3);
 void *MEM_lockfree_mallocN_aligned(size_t len, size_t alignment, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
 void *MEM_lockfree_mapallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
 void MEM_lockfree_printmemlist_pydict(void);
@@ -152,7 +156,9 @@ void *MEM_guarded_dupallocN(const void *vmemh) ATTR_MALLOC ATTR_WARN_UNUSED_RESU
 void *MEM_guarded_reallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2);
 void *MEM_guarded_recallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2);
 void *MEM_guarded_callocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
+void *MEM_guarded_calloc_arrayN(size_t len, size_t size, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1,2) ATTR_NONNULL(3);
 void *MEM_guarded_mallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
+void *MEM_guarded_malloc_arrayN(size_t len, size_t size, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1,2) ATTR_NONNULL(3);
 void *MEM_guarded_mallocN_aligned(size_t len, size_t alignment, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
 void *MEM_guarded_mapallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
 void MEM_guarded_printmemlist_pydict(void);
index 66573b9..2899a37 100644 (file)
@@ -293,6 +293,21 @@ void *MEM_lockfree_callocN(size_t len, const char *str)
        return NULL;
 }
 
+void *MEM_lockfree_calloc_arrayN(size_t len, size_t size, const char *str)
+{
+       size_t total_size;
+       if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
+               print_error("Calloc array aborted due to integer overflow: "
+                           "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
+                           SIZET_ARG(len), SIZET_ARG(size), str,
+                           (unsigned int) mem_in_use);
+               abort();
+               return NULL;
+       }
+
+       return MEM_lockfree_callocN(total_size, str);
+}
+
 void *MEM_lockfree_mallocN(size_t len, const char *str)
 {
        MemHead *memh;
@@ -318,6 +333,21 @@ void *MEM_lockfree_mallocN(size_t len, const char *str)
        return NULL;
 }
 
+void *MEM_lockfree_malloc_arrayN(size_t len, size_t size, const char *str)
+{
+       size_t total_size;
+       if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
+               print_error("Malloc array aborted due to integer overflow: "
+                           "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
+                           SIZET_ARG(len), SIZET_ARG(size), str,
+                           (unsigned int) mem_in_use);
+               abort();
+               return NULL;
+       }
+
+       return MEM_lockfree_mallocN(total_size, str);
+}
+
 void *MEM_lockfree_mallocN_aligned(size_t len, size_t alignment, const char *str)
 {
        MemHeadAligned *memh;
index f4fb4f8..0063d0e 100644 (file)
@@ -35,3 +35,4 @@ set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${PLATFORM_LIN
 
 
 BLENDER_TEST(guardedalloc_alignment "")
+BLENDER_TEST(guardedalloc_overflow "")
diff --git a/tests/gtests/guardedalloc/guardedalloc_overflow_test.cc b/tests/gtests/guardedalloc/guardedalloc_overflow_test.cc
new file mode 100644 (file)
index 0000000..18cf57b
--- /dev/null
@@ -0,0 +1,61 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+/* We expect to abort on integer overflow, to prevent possible exploits. */
+#ifdef _WIN32
+#define ABORT_PREDICATE ::testing::ExitedWithCode(3)
+#else
+#define ABORT_PREDICATE ::testing::KilledBySignal(SIGABRT)
+#endif
+
+namespace {
+
+void MallocArray(size_t len, size_t size)
+{
+       void *mem = MEM_malloc_arrayN(len, size, "MallocArray");
+       if (mem) {
+               MEM_freeN(mem);
+       }
+}
+
+void CallocArray(size_t len, size_t size)
+{
+       void *mem = MEM_calloc_arrayN(len, size, "CallocArray");
+       if (mem) {
+               MEM_freeN(mem);
+       }
+}
+
+}  // namespace
+
+TEST(guardedalloc, LockfreeIntegerOverflow)
+{
+       MallocArray(1, SIZE_MAX);
+       CallocArray(SIZE_MAX, 1);
+       MallocArray(SIZE_MAX / 2, 2);
+       CallocArray(SIZE_MAX / 1234567, 1234567);
+
+       EXPECT_EXIT(MallocArray(SIZE_MAX, 2), ABORT_PREDICATE, "");
+       EXPECT_EXIT(CallocArray(7, SIZE_MAX), ABORT_PREDICATE, "");
+       EXPECT_EXIT(MallocArray(SIZE_MAX, 12345567), ABORT_PREDICATE, "");
+       EXPECT_EXIT(CallocArray(SIZE_MAX, SIZE_MAX), ABORT_PREDICATE, "");
+}
+
+TEST(guardedalloc, GuardedIntegerOverflow)
+{
+       MEM_use_guarded_allocator();
+
+       MallocArray(1, SIZE_MAX);
+       CallocArray(SIZE_MAX, 1);
+       MallocArray(SIZE_MAX / 2, 2);
+       CallocArray(SIZE_MAX / 1234567, 1234567);
+
+       EXPECT_EXIT(MallocArray(SIZE_MAX, 2), ABORT_PREDICATE, "");
+       EXPECT_EXIT(CallocArray(7, SIZE_MAX), ABORT_PREDICATE, "");
+       EXPECT_EXIT(MallocArray(SIZE_MAX, 12345567), ABORT_PREDICATE, "");
+       EXPECT_EXIT(CallocArray(SIZE_MAX, SIZE_MAX), ABORT_PREDICATE, "");
+}
+