On Sun, Aug 22, 2021 at 12:51 AM Kees Cook <keescook@xxxxxxxxxxxx> wrote: > > The core functions of string.c are those that may be implemented by > per-architecture functions, or overloaded by FORTIFY_SOURCE. As a > result, it needs to be built with __NO_FORTIFY. Without this, macros Sorry, is there more info on what exactly __NO_FORTIFY is (and why we "need" it)? > will collide with function declarations. This was accidentally working > due to -ffreestanding (on some architectures). Make this deterministic > by explicitly setting __NO_FORTIFY and move all the helper functions > into string_helpers.c so that they gain the fortification coverage they > had been missing. > > Acked-by: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx> > Signed-off-by: Kees Cook <keescook@xxxxxxxxxxxx> > --- > arch/arm/boot/compressed/string.c | 1 + > arch/s390/lib/string.c | 3 + > arch/x86/boot/compressed/misc.h | 2 + > arch/x86/boot/compressed/pgtable_64.c | 2 + > arch/x86/lib/string_32.c | 1 + > lib/string.c | 210 +------------------------- > lib/string_helpers.c | 193 +++++++++++++++++++++++ > 7 files changed, 208 insertions(+), 204 deletions(-) > > diff --git a/arch/arm/boot/compressed/string.c b/arch/arm/boot/compressed/string.c > index 8c0fa276d994..fcc678fce045 100644 > --- a/arch/arm/boot/compressed/string.c > +++ b/arch/arm/boot/compressed/string.c > @@ -5,6 +5,7 @@ > * Small subset of simple string routines > */ > > +#define __NO_FORTIFY > #include <linux/string.h> > > /* > diff --git a/arch/s390/lib/string.c b/arch/s390/lib/string.c > index cfcdf76d6a95..392fb9f4f4db 100644 > --- a/arch/s390/lib/string.c > +++ b/arch/s390/lib/string.c > @@ -8,6 +8,9 @@ > */ > > #define IN_ARCH_STRING_C 1 > +#ifndef __NO_FORTIFY > +# define __NO_FORTIFY > +#endif > > #include <linux/types.h> > #include <linux/string.h> > diff --git a/arch/x86/boot/compressed/misc.h b/arch/x86/boot/compressed/misc.h > index 31139256859f..49bde196da9b 100644 > --- a/arch/x86/boot/compressed/misc.h > +++ b/arch/x86/boot/compressed/misc.h > @@ -14,6 +14,8 @@ > #undef CONFIG_KASAN > #undef CONFIG_KASAN_GENERIC > > +#define __NO_FORTIFY > + > /* cpu_feature_enabled() cannot be used this early */ > #define USE_EARLY_PGTABLE_L5 > > diff --git a/arch/x86/boot/compressed/pgtable_64.c b/arch/x86/boot/compressed/pgtable_64.c > index 2a78746f5a4c..a1733319a22a 100644 > --- a/arch/x86/boot/compressed/pgtable_64.c > +++ b/arch/x86/boot/compressed/pgtable_64.c > @@ -1,3 +1,5 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include "misc.h" ^ should we just `#define __NO_FORTIFY` here in pgtable_64.c rather than set that for a whole header? > #include <linux/efi.h> > #include <asm/e820/types.h> > #include <asm/processor.h> > diff --git a/arch/x86/lib/string_32.c b/arch/x86/lib/string_32.c > index d15fdae9656e..53b3f202267c 100644 > --- a/arch/x86/lib/string_32.c > +++ b/arch/x86/lib/string_32.c > @@ -11,6 +11,7 @@ > * strings. > */ > > +#define __NO_FORTIFY > #include <linux/string.h> > #include <linux/export.h> > > diff --git a/lib/string.c b/lib/string.c > index 77bd0b1d3296..1e6259f263b8 100644 > --- a/lib/string.c > +++ b/lib/string.c > @@ -6,20 +6,15 @@ > */ > > /* > - * stupid library routines.. The optimized versions should generally be found > - * as inline code in <asm-xx/string.h> > + * This file should be used only for "library" routines that may have > + * alternative implementations on specific architectures (generally > + * found in <asm-xx/string.h>), or get overloaded by FORTIFY_SOURCE. > + * (Specifically, this file is built with __NO_FORTIFY.) > * > - * These are buggy as well.. > - * > - * * Fri Jun 25 1999, Ingo Oeser <ioe@xxxxxxxxxxxxxxxxxxxxxxxxx> > - * - Added strsep() which will replace strtok() soon (because strsep() is > - * reentrant and should be faster). Use only strsep() in new code, please. > - * > - * * Sat Feb 09 2002, Jason Thomas <jason@xxxxxxxxxxxx>, > - * Matthew Hawkins <matt@xxxxxxxxxxxxxxxxx> > - * - Kissed strtok() goodbye > + * Other helper functions should live in string_helpers.c. > */ > > +#define __NO_FORTIFY > #include <linux/types.h> > #include <linux/string.h> > #include <linux/ctype.h> > @@ -237,40 +232,6 @@ ssize_t strscpy(char *dest, const char *src, size_t count) > EXPORT_SYMBOL(strscpy); > #endif > > -/** > - * strscpy_pad() - Copy a C-string into a sized buffer > - * @dest: Where to copy the string to > - * @src: Where to copy the string from > - * @count: Size of destination buffer > - * > - * Copy the string, or as much of it as fits, into the dest buffer. The > - * behavior is undefined if the string buffers overlap. The destination > - * buffer is always %NUL terminated, unless it's zero-sized. > - * > - * If the source string is shorter than the destination buffer, zeros > - * the tail of the destination buffer. > - * > - * For full explanation of why you may want to consider using the > - * 'strscpy' functions please see the function docstring for strscpy(). > - * > - * Returns: > - * * The number of characters copied (not including the trailing %NUL) > - * * -E2BIG if count is 0 or @src was truncated. > - */ > -ssize_t strscpy_pad(char *dest, const char *src, size_t count) > -{ > - ssize_t written; > - > - written = strscpy(dest, src, count); > - if (written < 0 || written == count - 1) > - return written; > - > - memset(dest + written + 1, 0, count - written - 1); > - > - return written; > -} > -EXPORT_SYMBOL(strscpy_pad); > - > /** > * stpcpy - copy a string from src to dest returning a pointer to the new end > * of dest, including src's %NUL-terminator. May overrun dest. > @@ -513,46 +474,6 @@ char *strnchr(const char *s, size_t count, int c) > EXPORT_SYMBOL(strnchr); > #endif > > -/** > - * skip_spaces - Removes leading whitespace from @str. > - * @str: The string to be stripped. > - * > - * Returns a pointer to the first non-whitespace character in @str. > - */ > -char *skip_spaces(const char *str) > -{ > - while (isspace(*str)) > - ++str; > - return (char *)str; > -} > -EXPORT_SYMBOL(skip_spaces); > - > -/** > - * strim - Removes leading and trailing whitespace from @s. > - * @s: The string to be stripped. > - * > - * Note that the first trailing whitespace is replaced with a %NUL-terminator > - * in the given string @s. Returns a pointer to the first non-whitespace > - * character in @s. > - */ > -char *strim(char *s) > -{ > - size_t size; > - char *end; > - > - size = strlen(s); > - if (!size) > - return s; > - > - end = s + size - 1; > - while (end >= s && isspace(*end)) > - end--; > - *(end + 1) = '\0'; > - > - return skip_spaces(s); > -} > -EXPORT_SYMBOL(strim); > - > #ifndef __HAVE_ARCH_STRLEN > /** > * strlen - Find the length of a string > @@ -687,101 +608,6 @@ char *strsep(char **s, const char *ct) > EXPORT_SYMBOL(strsep); > #endif > > -/** > - * sysfs_streq - return true if strings are equal, modulo trailing newline > - * @s1: one string > - * @s2: another string > - * > - * This routine returns true iff two strings are equal, treating both > - * NUL and newline-then-NUL as equivalent string terminations. It's > - * geared for use with sysfs input strings, which generally terminate > - * with newlines but are compared against values without newlines. > - */ > -bool sysfs_streq(const char *s1, const char *s2) > -{ > - while (*s1 && *s1 == *s2) { > - s1++; > - s2++; > - } > - > - if (*s1 == *s2) > - return true; > - if (!*s1 && *s2 == '\n' && !s2[1]) > - return true; > - if (*s1 == '\n' && !s1[1] && !*s2) > - return true; > - return false; > -} > -EXPORT_SYMBOL(sysfs_streq); > - > -/** > - * match_string - matches given string in an array > - * @array: array of strings > - * @n: number of strings in the array or -1 for NULL terminated arrays > - * @string: string to match with > - * > - * This routine will look for a string in an array of strings up to the > - * n-th element in the array or until the first NULL element. > - * > - * Historically the value of -1 for @n, was used to search in arrays that > - * are NULL terminated. However, the function does not make a distinction > - * when finishing the search: either @n elements have been compared OR > - * the first NULL element was found. > - * > - * Return: > - * index of a @string in the @array if matches, or %-EINVAL otherwise. > - */ > -int match_string(const char * const *array, size_t n, const char *string) > -{ > - int index; > - const char *item; > - > - for (index = 0; index < n; index++) { > - item = array[index]; > - if (!item) > - break; > - if (!strcmp(item, string)) > - return index; > - } > - > - return -EINVAL; > -} > -EXPORT_SYMBOL(match_string); > - > -/** > - * __sysfs_match_string - matches given string in an array > - * @array: array of strings > - * @n: number of strings in the array or -1 for NULL terminated arrays > - * @str: string to match with > - * > - * Returns index of @str in the @array or -EINVAL, just like match_string(). > - * Uses sysfs_streq instead of strcmp for matching. > - * > - * This routine will look for a string in an array of strings up to the > - * n-th element in the array or until the first NULL element. > - * > - * Historically the value of -1 for @n, was used to search in arrays that > - * are NULL terminated. However, the function does not make a distinction > - * when finishing the search: either @n elements have been compared OR > - * the first NULL element was found. > - */ > -int __sysfs_match_string(const char * const *array, size_t n, const char *str) > -{ > - const char *item; > - int index; > - > - for (index = 0; index < n; index++) { > - item = array[index]; > - if (!item) > - break; > - if (sysfs_streq(item, str)) > - return index; > - }? > - > - return -EINVAL; > -} > -EXPORT_SYMBOL(__sysfs_match_string); > - are memset16, memset32, and memset64 worth moving as well? Also, memscan(), check_bytes(), memchr_inv()? > #ifndef __HAVE_ARCH_MEMSET > /** > * memset - Fill a region of memory with the given value > @@ -1125,27 +951,3 @@ void *memchr_inv(const void *start, int c, size_t bytes) > return check_bytes8(start, value, bytes % 8); > } > EXPORT_SYMBOL(memchr_inv); > - > -/** > - * strreplace - Replace all occurrences of character in string. > - * @s: The string to operate on. > - * @old: The character being replaced. > - * @new: The character @old is replaced with. > - * > - * Returns pointer to the nul byte at the end of @s. > - */ > -char *strreplace(char *s, char old, char new) > -{ > - for (; *s; ++s) > - if (*s == old) > - *s = new; > - return s; > -} > -EXPORT_SYMBOL(strreplace); > - > -void fortify_panic(const char *name) > -{ > - pr_emerg("detected buffer overflow in %s\n", name); > - BUG(); > -} > -EXPORT_SYMBOL(fortify_panic); > diff --git a/lib/string_helpers.c b/lib/string_helpers.c > index 5a35c7e16e96..e9433caab217 100644 > --- a/lib/string_helpers.c > +++ b/lib/string_helpers.c > @@ -692,3 +692,196 @@ void kfree_strarray(char **array, size_t n) > kfree(array); > } > EXPORT_SYMBOL_GPL(kfree_strarray); > + > +/** > + * strscpy_pad() - Copy a C-string into a sized buffer > + * @dest: Where to copy the string to > + * @src: Where to copy the string from > + * @count: Size of destination buffer > + * > + * Copy the string, or as much of it as fits, into the dest buffer. The > + * behavior is undefined if the string buffers overlap. The destination > + * buffer is always %NUL terminated, unless it's zero-sized. > + * > + * If the source string is shorter than the destination buffer, zeros > + * the tail of the destination buffer. > + * > + * For full explanation of why you may want to consider using the > + * 'strscpy' functions please see the function docstring for strscpy(). > + * > + * Returns: > + * * The number of characters copied (not including the trailing %NUL) > + * * -E2BIG if count is 0 or @src was truncated. > + */ > +ssize_t strscpy_pad(char *dest, const char *src, size_t count) > +{ > + ssize_t written; > + > + written = strscpy(dest, src, count); > + if (written < 0 || written == count - 1) > + return written; > + > + memset(dest + written + 1, 0, count - written - 1); > + > + return written; > +} > +EXPORT_SYMBOL(strscpy_pad); > + > +/** > + * skip_spaces - Removes leading whitespace from @str. > + * @str: The string to be stripped. > + * > + * Returns a pointer to the first non-whitespace character in @str. > + */ > +char *skip_spaces(const char *str) > +{ > + while (isspace(*str)) > + ++str; > + return (char *)str; > +} > +EXPORT_SYMBOL(skip_spaces); > + > +/** > + * strim - Removes leading and trailing whitespace from @s. > + * @s: The string to be stripped. > + * > + * Note that the first trailing whitespace is replaced with a %NUL-terminator > + * in the given string @s. Returns a pointer to the first non-whitespace > + * character in @s. > + */ > +char *strim(char *s) > +{ > + size_t size; > + char *end; > + > + size = strlen(s); > + if (!size) > + return s; > + > + end = s + size - 1; > + while (end >= s && isspace(*end)) > + end--; > + *(end + 1) = '\0'; > + > + return skip_spaces(s); > +} > +EXPORT_SYMBOL(strim); > + > +/** > + * sysfs_streq - return true if strings are equal, modulo trailing newline > + * @s1: one string > + * @s2: another string > + * > + * This routine returns true iff two strings are equal, treating both > + * NUL and newline-then-NUL as equivalent string terminations. It's > + * geared for use with sysfs input strings, which generally terminate > + * with newlines but are compared against values without newlines. > + */ > +bool sysfs_streq(const char *s1, const char *s2) > +{ > + while (*s1 && *s1 == *s2) { > + s1++; > + s2++; > + } > + > + if (*s1 == *s2) > + return true; > + if (!*s1 && *s2 == '\n' && !s2[1]) > + return true; > + if (*s1 == '\n' && !s1[1] && !*s2) > + return true; > + return false; > +} > +EXPORT_SYMBOL(sysfs_streq); > + > +/** > + * match_string - matches given string in an array > + * @array: array of strings > + * @n: number of strings in the array or -1 for NULL terminated arrays > + * @string: string to match with > + * > + * This routine will look for a string in an array of strings up to the > + * n-th element in the array or until the first NULL element. > + * > + * Historically the value of -1 for @n, was used to search in arrays that > + * are NULL terminated. However, the function does not make a distinction > + * when finishing the search: either @n elements have been compared OR > + * the first NULL element was found. > + * > + * Return: > + * index of a @string in the @array if matches, or %-EINVAL otherwise. > + */ > +int match_string(const char * const *array, size_t n, const char *string) > +{ > + int index; > + const char *item; > + > + for (index = 0; index < n; index++) { > + item = array[index]; > + if (!item) > + break; > + if (!strcmp(item, string)) > + return index; > + } > + > + return -EINVAL; > +} > +EXPORT_SYMBOL(match_string); > + > +/** > + * __sysfs_match_string - matches given string in an array > + * @array: array of strings > + * @n: number of strings in the array or -1 for NULL terminated arrays > + * @str: string to match with > + * > + * Returns index of @str in the @array or -EINVAL, just like match_string(). > + * Uses sysfs_streq instead of strcmp for matching. > + * > + * This routine will look for a string in an array of strings up to the > + * n-th element in the array or until the first NULL element. > + * > + * Historically the value of -1 for @n, was used to search in arrays that > + * are NULL terminated. However, the function does not make a distinction > + * when finishing the search: either @n elements have been compared OR > + * the first NULL element was found. > + */ > +int __sysfs_match_string(const char * const *array, size_t n, const char *str) > +{ > + const char *item; > + int index; > + > + for (index = 0; index < n; index++) { > + item = array[index]; > + if (!item) > + break; > + if (sysfs_streq(item, str)) > + return index; > + } > + > + return -EINVAL; > +} > +EXPORT_SYMBOL(__sysfs_match_string); > + > +/** > + * strreplace - Replace all occurrences of character in string. > + * @s: The string to operate on. > + * @old: The character being replaced. > + * @new: The character @old is replaced with. > + * > + * Returns pointer to the nul byte at the end of @s. > + */ > +char *strreplace(char *s, char old, char new) > +{ > + for (; *s; ++s) > + if (*s == old) > + *s = new; > + return s; > +} > +EXPORT_SYMBOL(strreplace); > + > +void fortify_panic(const char *name) > +{ > + pr_emerg("detected buffer overflow in %s\n", name); > + BUG(); > +} > +EXPORT_SYMBOL(fortify_panic); > -- -- Thanks, ~Nick Desaulniers