strscpy is meant to be a safer alternative to strscpy, which always terminates the destination string and returns an error code if truncation happens. To enable porting kernel code using it, import the definition. Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> --- include/linux/string.h | 3 ++ lib/string.c | 71 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/include/linux/string.h b/include/linux/string.h index 75c8cf818b39..32ce56939699 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -46,6 +46,9 @@ extern char * strncpy(char *,const char *, __kernel_size_t); #ifndef __HAVE_ARCH_STRLCPY size_t strlcpy(char *, const char *, size_t); #endif +#ifndef __HAVE_ARCH_STRSCPY +ssize_t strscpy(char *, const char *, size_t); +#endif #ifndef __HAVE_ARCH_STRCAT extern char * strcat(char *, const char *); #endif diff --git a/lib/string.c b/lib/string.c index 166ef190d6aa..bf0f0455ab3f 100644 --- a/lib/string.c +++ b/lib/string.c @@ -22,6 +22,7 @@ #include <linux/types.h> #include <string.h> #include <linux/ctype.h> +#include <asm/word-at-a-time.h> #include <malloc.h> #ifndef __HAVE_ARCH_STRCASECMP @@ -87,6 +88,76 @@ char * strcpy(char * dest,const char *src) #endif EXPORT_SYMBOL(strcpy); +#ifndef __HAVE_ARCH_STRSCPY +ssize_t strscpy(char *dest, const char *src, size_t count) +{ + const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; + size_t max = count; + long res = 0; + + if (count == 0 || WARN_ON_ONCE(count > INT_MAX)) + return -E2BIG; + +#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + /* + * If src is unaligned, don't cross a page boundary, + * since we don't know if the next page is mapped. + */ + if ((long)src & (sizeof(long) - 1)) { + size_t limit = PAGE_SIZE - ((long)src & (PAGE_SIZE - 1)); + if (limit < max) + max = limit; + } +#else + /* If src or dest is unaligned, don't do word-at-a-time. */ + if (((long) dest | (long) src) & (sizeof(long) - 1)) + max = 0; +#endif + + /* + * read_word_at_a_time() below may read uninitialized bytes after the + * trailing zero and use them in comparisons. Disable this optimization + * under KMSAN to prevent false positive reports. + */ + if (IS_ENABLED(CONFIG_KMSAN)) + max = 0; + + while (max >= sizeof(unsigned long)) { + unsigned long c, data; + + c = read_word_at_a_time(src+res); + if (has_zero(c, &data, &constants)) { + data = prep_zero_mask(c, data, &constants); + data = create_zero_mask(data); + *(unsigned long *)(dest+res) = c & zero_bytemask(data); + return res + find_zero(data); + } + *(unsigned long *)(dest+res) = c; + res += sizeof(unsigned long); + count -= sizeof(unsigned long); + max -= sizeof(unsigned long); + } + + while (count) { + char c; + + c = src[res]; + dest[res] = c; + if (!c) + return res; + res++; + count--; + } + + /* Hit buffer length without finding a NUL; force NUL-termination. */ + if (res) + dest[res-1] = '\0'; + + return -E2BIG; +} +EXPORT_SYMBOL(strscpy); +#endif + /** * stpcpy - Copy a %NUL terminated string, but return pointer to %NUL * @dest: Where to copy the string to -- 2.39.2