From cfecea6ead5f15880fc1fb31fc655f8be5cf7424 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Jun 2021 10:57:38 -0700 Subject: lib/string: Move helper functions out of string.c 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: Andrew Morton Cc: Nick Desaulniers Cc: Andy Lavr Cc: Nathan Chancellor Cc: Alexey Dobriyan Cc: Stephen Rothwell Cc: Bartosz Golaszewski Acked-by: Andy Shevchenko Signed-off-by: Kees Cook --- lib/string_helpers.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) (limited to 'lib/string_helpers.c') 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); -- cgit From c430f60036af44079170ff71a461b9d7cf5ee431 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 14 Apr 2021 15:45:39 -0700 Subject: fortify: Move remaining fortify helpers into fortify-string.h When commit a28a6e860c6c ("string.h: move fortified functions definitions in a dedicated header.") moved the fortify-specific code, some helpers were left behind. Move the remaining fortify-specific helpers into fortify-string.h so they're together where they're used. This requires that any FORTIFY helper function prototypes be conditionally built to avoid "no prototype" warnings. Additionally removes unused helpers. Cc: Andrew Morton Cc: Daniel Axtens Cc: Vincenzo Frascino Cc: Andrey Konovalov Cc: Dan Williams Acked-by: Francis Laniel Reviewed-by: Nick Desaulniers Signed-off-by: Kees Cook --- lib/string_helpers.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/string_helpers.c') diff --git a/lib/string_helpers.c b/lib/string_helpers.c index bde13612c25d..faa9d8e4e2c5 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -883,9 +883,11 @@ char *strreplace(char *s, char old, char new) } EXPORT_SYMBOL(strreplace); +#ifdef CONFIG_FORTIFY_SOURCE void fortify_panic(const char *name) { pr_emerg("detected buffer overflow in %s\n", name); BUG(); } EXPORT_SYMBOL(fortify_panic); +#endif /* CONFIG_FORTIFY_SOURCE */ -- cgit From 5c4e0a21fae877a7ef89be6dcc6263ec672372b8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 2 Nov 2021 07:24:20 -0700 Subject: string: uninline memcpy_and_pad When building m68k:allmodconfig, recent versions of gcc generate the following error if the length of UTS_RELEASE is less than 8 bytes. In function 'memcpy_and_pad', inlined from 'nvmet_execute_disc_identify' at drivers/nvme/target/discovery.c:268:2: arch/m68k/include/asm/string.h:72:25: error: '__builtin_memcpy' reading 8 bytes from a region of size 7 Discussions around the problem suggest that this only happens if an architecture does not provide strlen(), if -ffreestanding is provided as compiler option, and if CONFIG_FORTIFY_SOURCE=n. All of this is the case for m68k. The exact reasons are unknown, but seem to be related to the ability of the compiler to evaluate the return value of strlen() and the resulting execution flow in memcpy_and_pad(). It would be possible to work around the problem by using sizeof(UTS_RELEASE) instead of strlen(UTS_RELEASE), but that would only postpone the problem until the function is called in a similar way. Uninline memcpy_and_pad() instead to solve the problem for good. Suggested-by: Linus Torvalds Reviewed-by: Geert Uytterhoeven Acked-by: Andy Shevchenko Signed-off-by: Guenter Roeck Signed-off-by: Linus Torvalds --- lib/string_helpers.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'lib/string_helpers.c') diff --git a/lib/string_helpers.c b/lib/string_helpers.c index faa9d8e4e2c5..d5d008f5b1d9 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -883,6 +883,26 @@ char *strreplace(char *s, char old, char new) } EXPORT_SYMBOL(strreplace); +/** + * memcpy_and_pad - Copy one buffer to another with padding + * @dest: Where to copy to + * @dest_len: The destination buffer size + * @src: Where to copy from + * @count: The number of bytes to copy + * @pad: Character to use for padding if space is left in destination. + */ +void memcpy_and_pad(void *dest, size_t dest_len, const void *src, size_t count, + int pad) +{ + if (dest_len > count) { + memcpy(dest, src, count); + memset(dest + count, pad, dest_len - count); + } else { + memcpy(dest, src, dest_len); + } +} +EXPORT_SYMBOL(memcpy_and_pad); + #ifdef CONFIG_FORTIFY_SOURCE void fortify_panic(const char *name) { -- cgit From 418e0a3551bbef5b221705b0e5b8412cdc0afd39 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 5 Nov 2021 14:42:24 +0200 Subject: lib/string_helpers: Introduce kasprintf_strarray() We have a few users already that basically want to have array of sequential strings to be allocated and filled. Provide a helper for them (basically adjusted version from gpio-mockup.c). Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij --- lib/string_helpers.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'lib/string_helpers.c') diff --git a/lib/string_helpers.c b/lib/string_helpers.c index d5d008f5b1d9..9758997c465e 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -674,6 +674,39 @@ char *kstrdup_quotable_file(struct file *file, gfp_t gfp) } EXPORT_SYMBOL_GPL(kstrdup_quotable_file); +/** + * kasprintf_strarray - allocate and fill array of sequential strings + * @gfp: flags for the slab allocator + * @prefix: prefix to be used + * @n: amount of lines to be allocated and filled + * + * Allocates and fills @n strings using pattern "%s-%zu", where prefix + * is provided by caller. The caller is responsible to free them with + * kfree_strarray() after use. + * + * Returns array of strings or NULL when memory can't be allocated. + */ +char **kasprintf_strarray(gfp_t gfp, const char *prefix, size_t n) +{ + char **names; + size_t i; + + names = kcalloc(n + 1, sizeof(char *), gfp); + if (!names) + return NULL; + + for (i = 0; i < n; i++) { + names[i] = kasprintf(gfp, "%s-%zu", prefix, i); + if (!names[i]) { + kfree_strarray(names, i); + return NULL; + } + } + + return names; +} +EXPORT_SYMBOL_GPL(kasprintf_strarray); + /** * kfree_strarray - free a number of dynamically allocated strings contained * in an array and the array itself -- cgit From acdb89b6c87a2d7b5c48a82756e6f5c6f599f60a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 5 Nov 2021 14:42:25 +0200 Subject: lib/string_helpers: Introduce managed variant of kasprintf_strarray() Some of the users want to have easy way to allocate array of strings that will be automatically cleaned when associated device is gone. Introduce managed variant of kasprintf_strarray() for such use cases. Signed-off-by: Andy Shevchenko --- lib/string_helpers.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'lib/string_helpers.c') diff --git a/lib/string_helpers.c b/lib/string_helpers.c index 9758997c465e..90f9f1b7afec 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -730,6 +731,36 @@ void kfree_strarray(char **array, size_t n) } EXPORT_SYMBOL_GPL(kfree_strarray); +struct strarray { + char **array; + size_t n; +}; + +static void devm_kfree_strarray(struct device *dev, void *res) +{ + struct strarray *array = res; + + kfree_strarray(array->array, array->n); +} + +char **devm_kasprintf_strarray(struct device *dev, const char *prefix, size_t n) +{ + struct strarray *ptr; + + ptr = devres_alloc(devm_kfree_strarray, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + ptr->array = kasprintf_strarray(GFP_KERNEL, prefix, n); + if (!ptr->array) { + devres_free(ptr); + return ERR_PTR(-ENOMEM); + } + + return ptr->array; +} +EXPORT_SYMBOL_GPL(devm_kasprintf_strarray); + /** * strscpy_pad() - Copy a C-string into a sized buffer * @dest: Where to copy the string to -- cgit