From 28e77cc1c0686621a4d416f599cee5ab369daa0a Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 16 Jun 2021 14:42:23 -0700 Subject: [PATCH] fortify: Detect struct member overflows in memset() at compile-time 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 --- include/linux/fortify-string.h | 54 ++++++++++++++++--- .../write_overflow_field-memset.c | 5 ++ 2 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 lib/test_fortify/write_overflow_field-memset.c diff --git a/include/linux/fortify-string.h b/include/linux/fortify-string.h index 098d8a322a7a..53123712bb3b 100644 --- a/include/linux/fortify-string.h +++ b/include/linux/fortify-string.h @@ -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 index 000000000000..2331da26909e --- /dev/null +++ b/lib/test_fortify/write_overflow_field-memset.c @@ -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"