OSDN Git Service

fortify: Detect struct member overflows in memset() at compile-time
authorKees Cook <keescook@chromium.org>
Wed, 16 Jun 2021 21:42:23 +0000 (14:42 -0700)
committerKees Cook <keescook@chromium.org>
Mon, 14 Feb 2022 00:50:06 +0000 (16:50 -0800)
As done for memcpy(), also update memset() to use the same tightened
compile-time bounds checking under CONFIG_FORTIFY_SOURCE.

Signed-off-by: Kees Cook <keescook@chromium.org>
include/linux/fortify-string.h
lib/test_fortify/write_overflow_field-memset.c [new file with mode: 0644]

index 098d8a3..5312371 100644 (file)
@@ -200,17 +200,56 @@ __FORTIFY_INLINE char *strncat(char *p, const char *q, __kernel_size_t count)
        return p;
 }
 
-__FORTIFY_INLINE void *memset(void *p, int c, __kernel_size_t size)
+__FORTIFY_INLINE void fortify_memset_chk(__kernel_size_t size,
+                                        const size_t p_size,
+                                        const size_t p_size_field)
 {
-       size_t p_size = __builtin_object_size(p, 0);
+       if (__builtin_constant_p(size)) {
+               /*
+                * Length argument is a constant expression, so we
+                * can perform compile-time bounds checking where
+                * buffer sizes are known.
+                */
 
-       if (__builtin_constant_p(size) && p_size < size)
-               __write_overflow();
-       if (p_size < size)
-               fortify_panic(__func__);
-       return __underlying_memset(p, c, size);
+               /* Error when size is larger than enclosing struct. */
+               if (p_size > p_size_field && p_size < size)
+                       __write_overflow();
+
+               /* Warn when write size is larger than dest field. */
+               if (p_size_field < size)
+                       __write_overflow_field(p_size_field, size);
+       }
+       /*
+        * At this point, length argument may not be a constant expression,
+        * so run-time bounds checking can be done where buffer sizes are
+        * known. (This is not an "else" because the above checks may only
+        * be compile-time warnings, and we want to still warn for run-time
+        * overflows.)
+        */
+
+       /*
+        * Always stop accesses beyond the struct that contains the
+        * field, when the buffer's remaining size is known.
+        * (The -1 test is to optimize away checks where the buffer
+        * lengths are unknown.)
+        */
+       if (p_size != (size_t)(-1) && p_size < size)
+               fortify_panic("memset");
 }
 
+#define __fortify_memset_chk(p, c, size, p_size, p_size_field) ({      \
+       size_t __fortify_size = (size_t)(size);                         \
+       fortify_memset_chk(__fortify_size, p_size, p_size_field),       \
+       __underlying_memset(p, c, __fortify_size);                      \
+})
+
+/*
+ * __builtin_object_size() must be captured here to avoid evaluating argument
+ * side-effects further into the macro layers.
+ */
+#define memset(p, c, s) __fortify_memset_chk(p, c, s,                  \
+               __builtin_object_size(p, 0), __builtin_object_size(p, 1))
+
 /*
  * To make sure the compiler can enforce protection against buffer overflows,
  * memcpy(), memmove(), and memset() must not be used beyond individual
@@ -401,7 +440,6 @@ __FORTIFY_INLINE char *strcpy(char *p, const char *q)
 /* Don't use these outside the FORITFY_SOURCE implementation */
 #undef __underlying_memchr
 #undef __underlying_memcmp
-#undef __underlying_memset
 #undef __underlying_strcat
 #undef __underlying_strcpy
 #undef __underlying_strlen
diff --git a/lib/test_fortify/write_overflow_field-memset.c b/lib/test_fortify/write_overflow_field-memset.c
new file mode 100644 (file)
index 0000000..2331da2
--- /dev/null
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define TEST   \
+       memset(instance.buf, 0x42, sizeof(instance.buf) + 1)
+
+#include "test_fortify.h"