Message ID | 20210818060533.3569517-27-keescook@chromium.org (mailing list archive) |
---|---|
State | Not Applicable |
Delegated to: | Johannes Berg |
Headers | show |
Series | Introduce strict memcpy() bounds checking | expand |
On Tue, Aug 17, 2021 at 11:04:56PM -0700, Kees Cook 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 > 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. No objections Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > Cc: Andy Shevchenko <andy@kernel.org> > Cc: Andrew Morton <akpm@linux-foundation.org> > Cc: Nick Desaulniers <ndesaulniers@google.com> > Cc: Andy Lavr <andy.lavr@gmail.com> > Cc: Nathan Chancellor <nathan@kernel.org> > Cc: Alexey Dobriyan <adobriyan@gmail.com> > Cc: Stephen Rothwell <sfr@canb.auug.org.au> > Cc: Bartosz Golaszewski <bgolaszewski@baylibre.com> > Signed-off-by: Kees Cook <keescook@chromium.org> > --- > 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" > #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 4fec38fc6e58..4e111d9dd6d5 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@informatik.tu-chemnitz.de> > - * - 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@topic.com.au>, > - * Matthew Hawkins <matt@mh.dropbear.id.au> > - * - 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> > @@ -254,40 +249,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. > @@ -530,46 +491,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 > @@ -704,101 +625,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); > - > #ifndef __HAVE_ARCH_MEMSET > /** > * memset - Fill a region of memory with the given value > @@ -1221,27 +1047,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 3806a52ce697..bde13612c25d 100644 > --- a/lib/string_helpers.c > +++ b/lib/string_helpers.c > @@ -696,3 +696,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); > -- > 2.30.2 >
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" #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 4fec38fc6e58..4e111d9dd6d5 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@informatik.tu-chemnitz.de> - * - 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@topic.com.au>, - * Matthew Hawkins <matt@mh.dropbear.id.au> - * - 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> @@ -254,40 +249,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. @@ -530,46 +491,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 @@ -704,101 +625,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); - #ifndef __HAVE_ARCH_MEMSET /** * memset - Fill a region of memory with the given value @@ -1221,27 +1047,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 3806a52ce697..bde13612c25d 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -696,3 +696,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);
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 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. Cc: Andy Shevchenko <andy@kernel.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Nick Desaulniers <ndesaulniers@google.com> Cc: Andy Lavr <andy.lavr@gmail.com> Cc: Nathan Chancellor <nathan@kernel.org> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Bartosz Golaszewski <bgolaszewski@baylibre.com> Signed-off-by: Kees Cook <keescook@chromium.org> --- 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(-)