Re: [PATCH AUTOSEL 4.14 72/72] string.h: fix incompatibility between FORTIFY_SOURCE and KASAN

From: Daniel Axtens
Date: Mon Jun 08 2020 - 19:46:20 EST


Hi Sasha,

There's nothing inherently wrong with these patches being backported,
but they fix a bug that doesn't cause a crash and only affects debug
kernels compiled with KASAN and FORTIFY_SOURCE. Personally I wouldn't
change a core header file in a stable kernel for that. Perhaps I'm too
risk-averse.

Regards,
Daniel

> From: Daniel Axtens <dja@xxxxxxxxxx>
>
> [ Upstream commit 47227d27e2fcb01a9e8f5958d8997cf47a820afc ]
>
> The memcmp KASAN self-test fails on a kernel with both KASAN and
> FORTIFY_SOURCE.
>
> When FORTIFY_SOURCE is on, a number of functions are replaced with
> fortified versions, which attempt to check the sizes of the operands.
> However, these functions often directly invoke __builtin_foo() once they
> have performed the fortify check. Using __builtins may bypass KASAN
> checks if the compiler decides to inline it's own implementation as
> sequence of instructions, rather than emit a function call that goes out
> to a KASAN-instrumented implementation.
>
> Why is only memcmp affected?
> ============================
>
> Of the string and string-like functions that kasan_test tests, only memcmp
> is replaced by an inline sequence of instructions in my testing on x86
> with gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2).
>
> I believe this is due to compiler heuristics. For example, if I annotate
> kmalloc calls with the alloc_size annotation (and disable some fortify
> compile-time checking!), the compiler will replace every memset except the
> one in kmalloc_uaf_memset with inline instructions. (I have some WIP
> patches to add this annotation.)
>
> Does this affect other functions in string.h?
> =============================================
>
> Yes. Anything that uses __builtin_* rather than __real_* could be
> affected. This looks like:
>
> - strncpy
> - strcat
> - strlen
> - strlcpy maybe, under some circumstances?
> - strncat under some circumstances
> - memset
> - memcpy
> - memmove
> - memcmp (as noted)
> - memchr
> - strcpy
>
> Whether a function call is emitted always depends on the compiler. Most
> bugs should get caught by FORTIFY_SOURCE, but the missed memcmp test shows
> that this is not always the case.
>
> Isn't FORTIFY_SOURCE disabled with KASAN?
> ========================================-
>
> The string headers on all arches supporting KASAN disable fortify with
> kasan, but only when address sanitisation is _also_ disabled. For example
> from x86:
>
> #if defined(CONFIG_KASAN) && !defined(__SANITIZE_ADDRESS__)
> /*
> * For files that are not instrumented (e.g. mm/slub.c) we
> * should use not instrumented version of mem* functions.
> */
> #define memcpy(dst, src, len) __memcpy(dst, src, len)
> #define memmove(dst, src, len) __memmove(dst, src, len)
> #define memset(s, c, n) __memset(s, c, n)
>
> #ifndef __NO_FORTIFY
> #define __NO_FORTIFY /* FORTIFY_SOURCE uses __builtin_memcpy, etc. */
> #endif
>
> #endif
>
> This comes from commit 6974f0c4555e ("include/linux/string.h: add the
> option of fortified string.h functions"), and doesn't work when KASAN is
> enabled and the file is supposed to be sanitised - as with test_kasan.c
>
> I'm pretty sure this is not wrong, but not as expansive it should be:
>
> * we shouldn't use __builtin_memcpy etc in files where we don't have
> instrumentation - it could devolve into a function call to memcpy,
> which will be instrumented. Rather, we should use __memcpy which
> by convention is not instrumented.
>
> * we also shouldn't be using __builtin_memcpy when we have a KASAN
> instrumented file, because it could be replaced with inline asm
> that will not be instrumented.
>
> What is correct behaviour?
> ==========================
>
> Firstly, there is some overlap between fortification and KASAN: both
> provide some level of _runtime_ checking. Only fortify provides
> compile-time checking.
>
> KASAN and fortify can pick up different things at runtime:
>
> - Some fortify functions, notably the string functions, could easily be
> modified to consider sub-object sizes (e.g. members within a struct),
> and I have some WIP patches to do this. KASAN cannot detect these
> because it cannot insert poision between members of a struct.
>
> - KASAN can detect many over-reads/over-writes when the sizes of both
> operands are unknown, which fortify cannot.
>
> So there are a couple of options:
>
> 1) Flip the test: disable fortify in santised files and enable it in
> unsanitised files. This at least stops us missing KASAN checking, but
> we lose the fortify checking.
>
> 2) Make the fortify code always call out to real versions. Do this only
> for KASAN, for fear of losing the inlining opportunities we get from
> __builtin_*.
>
> (We can't use kasan_check_{read,write}: because the fortify functions are
> _extern inline_, you can't include _static_ inline functions without a
> compiler warning. kasan_check_{read,write} are static inline so we can't
> use them even when they would otherwise be suitable.)
>
> Take approach 2 and call out to real versions when KASAN is enabled.
>
> Use __underlying_foo to distinguish from __real_foo: __real_foo always
> refers to the kernel's implementation of foo, __underlying_foo could be
> either the kernel implementation or the __builtin_foo implementation.
>
> This is sometimes enough to make the memcmp test succeed with
> FORTIFY_SOURCE enabled. It is at least enough to get the function call
> into the module. One more fix is needed to make it reliable: see the next
> patch.
>
> Fixes: 6974f0c4555e ("include/linux/string.h: add the option of fortified string.h functions")
> Signed-off-by: Daniel Axtens <dja@xxxxxxxxxx>
> Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
> Tested-by: David Gow <davidgow@xxxxxxxxxx>
> Reviewed-by: Dmitry Vyukov <dvyukov@xxxxxxxxxx>
> Cc: Daniel Micay <danielmicay@xxxxxxxxx>
> Cc: Andrey Ryabinin <aryabinin@xxxxxxxxxxxxx>
> Cc: Alexander Potapenko <glider@xxxxxxxxxx>
> Link: http://lkml.kernel.org/r/20200423154503.5103-3-dja@xxxxxxxxxx
> Signed-off-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
> Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
> ---
> include/linux/string.h | 60 +++++++++++++++++++++++++++++++++---------
> 1 file changed, 48 insertions(+), 12 deletions(-)
>
> diff --git a/include/linux/string.h b/include/linux/string.h
> index 3d43329c20be..315fef3aff4e 100644
> --- a/include/linux/string.h
> +++ b/include/linux/string.h
> @@ -238,6 +238,31 @@ 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)
> +
> +#ifdef CONFIG_KASAN
> +extern void *__underlying_memchr(const void *p, int c, __kernel_size_t size) __RENAME(memchr);
> +extern int __underlying_memcmp(const void *p, const void *q, __kernel_size_t size) __RENAME(memcmp);
> +extern void *__underlying_memcpy(void *p, const void *q, __kernel_size_t size) __RENAME(memcpy);
> +extern void *__underlying_memmove(void *p, const void *q, __kernel_size_t size) __RENAME(memmove);
> +extern void *__underlying_memset(void *p, int c, __kernel_size_t size) __RENAME(memset);
> +extern char *__underlying_strcat(char *p, const char *q) __RENAME(strcat);
> +extern char *__underlying_strcpy(char *p, const char *q) __RENAME(strcpy);
> +extern __kernel_size_t __underlying_strlen(const char *p) __RENAME(strlen);
> +extern char *__underlying_strncat(char *p, const char *q, __kernel_size_t count) __RENAME(strncat);
> +extern char *__underlying_strncpy(char *p, const char *q, __kernel_size_t size) __RENAME(strncpy);
> +#else
> +#define __underlying_memchr __builtin_memchr
> +#define __underlying_memcmp __builtin_memcmp
> +#define __underlying_memcpy __builtin_memcpy
> +#define __underlying_memmove __builtin_memmove
> +#define __underlying_memset __builtin_memset
> +#define __underlying_strcat __builtin_strcat
> +#define __underlying_strcpy __builtin_strcpy
> +#define __underlying_strlen __builtin_strlen
> +#define __underlying_strncat __builtin_strncat
> +#define __underlying_strncpy __builtin_strncpy
> +#endif
> +
> __FORTIFY_INLINE char *strncpy(char *p, const char *q, __kernel_size_t size)
> {
> size_t p_size = __builtin_object_size(p, 0);
> @@ -245,14 +270,14 @@ __FORTIFY_INLINE char *strncpy(char *p, const char *q, __kernel_size_t size)
> __write_overflow();
> if (p_size < size)
> fortify_panic(__func__);
> - return __builtin_strncpy(p, q, size);
> + return __underlying_strncpy(p, q, 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);
> + return __underlying_strcat(p, q);
> if (strlcat(p, q, p_size) >= p_size)
> fortify_panic(__func__);
> return p;
> @@ -266,7 +291,7 @@ __FORTIFY_INLINE __kernel_size_t strlen(const char *p)
> /* 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'))
> - return __builtin_strlen(p);
> + return __underlying_strlen(p);
> ret = strnlen(p, p_size);
> if (p_size <= ret)
> fortify_panic(__func__);
> @@ -299,7 +324,7 @@ __FORTIFY_INLINE size_t strlcpy(char *p, const char *q, size_t size)
> __write_overflow();
> if (len >= p_size)
> fortify_panic(__func__);
> - __builtin_memcpy(p, q, len);
> + __underlying_memcpy(p, q, len);
> p[len] = '\0';
> }
> return ret;
> @@ -312,12 +337,12 @@ __FORTIFY_INLINE char *strncat(char *p, const char *q, __kernel_size_t count)
> size_t p_size = __builtin_object_size(p, 0);
> size_t q_size = __builtin_object_size(q, 0);
> if (p_size == (size_t)-1 && q_size == (size_t)-1)
> - return __builtin_strncat(p, q, count);
> + return __underlying_strncat(p, q, count);
> p_len = strlen(p);
> copy_len = strnlen(q, count);
> if (p_size < p_len + copy_len + 1)
> fortify_panic(__func__);
> - __builtin_memcpy(p + p_len, q, copy_len);
> + __underlying_memcpy(p + p_len, q, copy_len);
> p[p_len + copy_len] = '\0';
> return p;
> }
> @@ -329,7 +354,7 @@ __FORTIFY_INLINE void *memset(void *p, int c, __kernel_size_t size)
> __write_overflow();
> if (p_size < size)
> fortify_panic(__func__);
> - return __builtin_memset(p, c, size);
> + return __underlying_memset(p, c, size);
> }
>
> __FORTIFY_INLINE void *memcpy(void *p, const void *q, __kernel_size_t size)
> @@ -344,7 +369,7 @@ __FORTIFY_INLINE void *memcpy(void *p, const void *q, __kernel_size_t size)
> }
> if (p_size < size || q_size < size)
> fortify_panic(__func__);
> - return __builtin_memcpy(p, q, size);
> + return __underlying_memcpy(p, q, size);
> }
>
> __FORTIFY_INLINE void *memmove(void *p, const void *q, __kernel_size_t size)
> @@ -359,7 +384,7 @@ __FORTIFY_INLINE void *memmove(void *p, const void *q, __kernel_size_t size)
> }
> if (p_size < size || q_size < size)
> fortify_panic(__func__);
> - return __builtin_memmove(p, q, size);
> + return __underlying_memmove(p, q, size);
> }
>
> extern void *__real_memscan(void *, int, __kernel_size_t) __RENAME(memscan);
> @@ -385,7 +410,7 @@ __FORTIFY_INLINE int memcmp(const void *p, const void *q, __kernel_size_t size)
> }
> if (p_size < size || q_size < size)
> fortify_panic(__func__);
> - return __builtin_memcmp(p, q, size);
> + return __underlying_memcmp(p, q, size);
> }
>
> __FORTIFY_INLINE void *memchr(const void *p, int c, __kernel_size_t size)
> @@ -395,7 +420,7 @@ __FORTIFY_INLINE void *memchr(const void *p, int c, __kernel_size_t size)
> __read_overflow();
> if (p_size < size)
> fortify_panic(__func__);
> - return __builtin_memchr(p, c, size);
> + return __underlying_memchr(p, c, size);
> }
>
> void *__real_memchr_inv(const void *s, int c, size_t n) __RENAME(memchr_inv);
> @@ -426,11 +451,22 @@ __FORTIFY_INLINE char *strcpy(char *p, const char *q)
> size_t p_size = __builtin_object_size(p, 0);
> size_t q_size = __builtin_object_size(q, 0);
> if (p_size == (size_t)-1 && q_size == (size_t)-1)
> - return __builtin_strcpy(p, q);
> + return __underlying_strcpy(p, q);
> memcpy(p, q, strlen(q) + 1);
> return p;
> }
>
> +/* Don't use these outside the FORITFY_SOURCE implementation */
> +#undef __underlying_memchr
> +#undef __underlying_memcmp
> +#undef __underlying_memcpy
> +#undef __underlying_memmove
> +#undef __underlying_memset
> +#undef __underlying_strcat
> +#undef __underlying_strcpy
> +#undef __underlying_strlen
> +#undef __underlying_strncat
> +#undef __underlying_strncpy
> #endif
>
> /**
> --
> 2.25.1