From 41a9c9c9b7dbc2c1915db5701720d3d366069cb4 Mon Sep 17 00:00:00 2001 From: George Burgess IV Date: Tue, 7 Apr 2020 17:07:21 -0700 Subject: [PATCH] WIP: fortify: swap to _chk-ish intrinsics First pass at converting the FORTIFY magics in the kernel to use _chk intrinsics, rather than having all size checks inline. Notes are included for which `_chk` functions clang/gcc (since 5.1) recognize. If a compiler recognizes `__foo_chk`, the compiler is capable of optimizing it to `foo` if the `__foo_chk` variant would just call `foo()` anyway. For clang in particular, these transformations are a win. For GCC, it's _potentially_ better, but less certain than for clang. --- include/linux/string.h | 55 +++++++++++++++++++++++++++++------------- lib/string.c | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/include/linux/string.h b/include/linux/string.h index 6dfbb2efa81578..5f50c27ae7de3b 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -272,31 +272,45 @@ void __read_overflow3(void) __compiletime_error("detected read beyond size of ob void __write_overflow(void) __compiletime_error("detected write beyond size of object passed as 1st parameter"); #if !defined(__NO_FORTIFY) && defined(__OPTIMIZE__) && defined(CONFIG_FORTIFY_SOURCE) + +/* + * NOTE: Proof of __builtin___strncpy_chk => strncpy: + * https://godbolt.org/z/nA4wVC + */ __FORTIFY_INLINE char *strncpy(char *p, const char *q, __kernel_size_t size) { size_t p_size = __builtin_object_size(p, 0); if (__builtin_constant_p(size) && p_size < size) __write_overflow(); - if (p_size < size) - fortify_panic(__func__); - return __builtin_strncpy(p, q, size); + return __builtin___strncpy_chk(p, q, size, p_size); } +/* NOTE: Proof of __strcat_chk => strcat: https://godbolt.org/z/Hquxv9 */ +extern char *__strcat_chk(char *p, const char *q, size_t p_size); __FORTIFY_INLINE char *strcat(char *p, const char *q) { size_t p_size = __builtin_object_size(p, 0); - if (p_size == (size_t)-1) - return __builtin_strcat(p, q); - if (strlcat(p, q, p_size) >= p_size) - fortify_panic(__func__); - return p; + return __strcat_chk(p, q, p_size); } +/* + * NOTE: Clang recognizes __strlen_chk, but GCC doesn't. Doesn't seem likely + * that we'll get it backported into previous GCC releases, either. + * https://godbolt.org/z/Sapi3H . + * + * Presented is the 'best' middle ground I think we can reach for both + * compilers WRT optimizability. + */ +#ifdef __clang__ +extern __kernel_size_t __strlen_chk(const char *p, size_t p_size); +#endif __FORTIFY_INLINE __kernel_size_t strlen(const char *p) { - __kernel_size_t ret; size_t p_size = __builtin_object_size(p, 0); - +#ifdef __clang__ + return __strlen_chk(p, p_size); +#else + __kernel_size_t ret; /* Work around gcc excess stack consumption issue */ if (p_size == (size_t)-1 || (__builtin_constant_p(p[p_size - 1]) && p[p_size - 1] == '\0')) @@ -305,6 +319,7 @@ __FORTIFY_INLINE __kernel_size_t strlen(const char *p) if (p_size <= ret) fortify_panic(__func__); return ret; +#endif } extern __kernel_size_t __real_strnlen(const char *, __kernel_size_t) __RENAME(strnlen); @@ -317,6 +332,14 @@ __FORTIFY_INLINE __kernel_size_t strnlen(const char *p, __kernel_size_t maxlen) return ret; } +/* + * NOTE: Clang recognizes __strlcpy_chk, but GCC doesn't. Doesn't seem likely + * that we'll get it backported into previous GCC releases, either. + * https://godbolt.org/z/RsTLtV . + * + * I'm not sure how deeply we care about strlcpy optimizations. Maybe not enough + * to have compiler-specific code for this handrolled strlcpy? + */ /* defined after fortified strlen to reuse it */ extern size_t __real_strlcpy(char *, const char *, size_t) __RENAME(strlcpy); __FORTIFY_INLINE size_t strlcpy(char *p, const char *q, size_t size) @@ -361,9 +384,7 @@ __FORTIFY_INLINE void *memset(void *p, int c, __kernel_size_t size) size_t p_size = __builtin_object_size(p, 0); if (__builtin_constant_p(size) && p_size < size) __write_overflow(); - if (p_size < size) - fortify_panic(__func__); - return __builtin_memset(p, c, size); + return __builtin___memset_chk(p, c, size, p_size); } __FORTIFY_INLINE void *memcpy(void *p, const void *q, __kernel_size_t size) @@ -376,9 +397,9 @@ __FORTIFY_INLINE void *memcpy(void *p, const void *q, __kernel_size_t size) if (q_size < size) __read_overflow2(); } - if (p_size < size || q_size < size) + if (q_size < size) fortify_panic(__func__); - return __builtin_memcpy(p, q, size); + return __builtin___memcpy_chk(p, q, size, p_size); } __FORTIFY_INLINE void *memmove(void *p, const void *q, __kernel_size_t size) @@ -391,9 +412,9 @@ __FORTIFY_INLINE void *memmove(void *p, const void *q, __kernel_size_t size) if (q_size < size) __read_overflow2(); } - if (p_size < size || q_size < size) + if (q_size < size) fortify_panic(__func__); - return __builtin_memmove(p, q, size); + return __builtin___memmove_chk(p, q, size, p_size); } extern void *__real_memscan(void *, int, __kernel_size_t) __RENAME(memscan); diff --git a/lib/string.c b/lib/string.c index 6012c385fb314d..d2709dfd9e0d1a 100644 --- a/lib/string.c +++ b/lib/string.c @@ -126,6 +126,14 @@ char *strncpy(char *dest, const char *src, size_t count) EXPORT_SYMBOL(strncpy); #endif +char *__strncpy_chk(char *dest, const char *src, size_t count, size_t dest_size) +{ + if (unlikely(dest_size < count)) + fortify_panic(__func__); + return __builtin_strncpy(dest, src, count); +} +EXPORT_SYMBOL(__strncpy_chk); + #ifndef __HAVE_ARCH_STRLCPY /** * strlcpy - Copy a C-string into a sized buffer @@ -292,6 +300,14 @@ char *strcat(char *dest, const char *src) EXPORT_SYMBOL(strcat); #endif +char *__strcat_chk(char *dest, const char *src, size_t p_size) +{ + if (unlikely(strlcat(dest, src, p_size) >= p_size)) + fortify_panic(__func__); + return dest; +} +EXPORT_SYMBOL(__strcat_chk); + #ifndef __HAVE_ARCH_STRNCAT /** * strncat - Append a length-limited, C-string to another @@ -548,6 +564,17 @@ size_t strlen(const char *s) EXPORT_SYMBOL(strlen); #endif +#ifdef __clang__ +size_t __strlen_chk(const char *p, size_t p_size) +{ + __kernel_size_t ret = __real_strnlen(p, p_size); + if (unlikely(p_size <= ret)) + fortify_panic(__func__); + return ret; +} +EXPORT_SYMBOL(__strlen_chk); +#endif + #ifndef __HAVE_ARCH_STRNLEN /** * strnlen - Find the length of a length-limited string @@ -781,6 +808,14 @@ void *memset(void *s, int c, size_t count) EXPORT_SYMBOL(memset); #endif +void *__memset_chk(void *s, int c, size_t count, size_t s_size) +{ + if (unlikely(s_size < count)) + fortify_panic(__func__); + return __builtin_memset(s, c, count); +} +EXPORT_SYMBOL(__memset_chk); + #ifndef __HAVE_ARCH_MEMSET16 /** * memset16() - Fill a memory area with a uint16_t @@ -869,6 +904,14 @@ void *memcpy(void *dest, const void *src, size_t count) EXPORT_SYMBOL(memcpy); #endif +void *__memcpy_chk(void *dest, const void *src, size_t count, size_t dest_size) +{ + if (unlikely(dest_size) < count) + fortify_panic(__func__); + return __builtin_memcpy(dest, src, count); +} +EXPORT_SYMBOL(__memcpy_chk); + #ifndef __HAVE_ARCH_MEMMOVE /** * memmove - Copy one area of memory to another @@ -901,6 +944,14 @@ void *memmove(void *dest, const void *src, size_t count) EXPORT_SYMBOL(memmove); #endif +void *__memmove_chk(void *dest, const void *src, size_t count, size_t dest_size) +{ + if (unlikely(dest_size < count)) + fortify_panic(__func__); + return __builtin_memmove(dest, src, count); +} +EXPORT_SYMBOL(__memmove_chk); + #ifndef __HAVE_ARCH_MEMCMP /** * memcmp - Compare two areas of memory