Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752128AbbD3QCL (ORCPT ); Thu, 30 Apr 2015 12:02:11 -0400 Received: from mail-am1on0054.outbound.protection.outlook.com ([157.56.112.54]:27872 "EHLO emea01-am1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1750823AbbD3QBi (ORCPT ); Thu, 30 Apr 2015 12:01:38 -0400 Authentication-Results: spf=fail (sender IP is 12.216.194.146) smtp.mailfrom=ezchip.com; vger.kernel.org; dkim=none (message not signed) header.d=none; From: Chris Metcalf To: Linus Torvalds CC: Chris Metcalf , Al Viro , Fabian Frederick , Randy Dunlap , Rickard Strandqvist , , Peter Zijlstra , "David S. Miller" , Frederic Weisbecker , Andrew Morton , Sam Ravnborg , Stephen Rothwell , "Theodore Ts'o" , "Grant Likely" , Linux Kernel Mailing List , Subject: [PATCH 2/3] string: provide strscpy() and strscpy_truncate() Date: Thu, 30 Apr 2015 12:01:16 -0400 Message-ID: <1430409677-13284-3-git-send-email-cmetcalf@ezchip.com> X-Mailer: git-send-email 2.1.2 In-Reply-To: <1430409677-13284-1-git-send-email-cmetcalf@ezchip.com> References: <1430409677-13284-1-git-send-email-cmetcalf@ezchip.com> X-EOPAttributedMessage: 0 X-Forefront-Antispam-Report: CIP:12.216.194.146;CTRY:US;IPV:NLI;EFV:NLI;SFV:NSPM;SFS:(10009020)(6009001)(339900001)(189002)(199003)(19580395003)(106466001)(6806004)(19580405001)(46102003)(48376002)(50986999)(76176999)(229853001)(85426001)(86362001)(50466002)(42186005)(110136002)(105606002)(36756003)(87936001)(50226001)(62966003)(92566002)(77156002)(2950100001)(47776003)(33646002)(104016003)(5001960100002);DIR:OUT;SFP:1101;SCL:1;SRVR:AM2PR02MB0770;H:ld-1.internal.tilera.com;FPR:;SPF:Fail;MLV:sfv;MX:1;A:1;LANG:en; MIME-Version: 1.0 Content-Type: text/plain X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:AM2PR02MB0770; X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(601004)(5005006)(3002001);SRVR:AM2PR02MB0770;BCL:0;PCL:0;RULEID:;SRVR:AM2PR02MB0770; X-Forefront-PRVS: 056297E276 X-OriginatorOrg: ezchip.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 30 Apr 2015 16:01:34.9346 (UTC) X-MS-Exchange-CrossTenant-Id: 0fc16e0a-3cd3-4092-8b2f-0a42cff122c3 X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=0fc16e0a-3cd3-4092-8b2f-0a42cff122c3;Ip=[12.216.194.146];Helo=[ld-1.internal.tilera.com] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: AM2PR02MB0770 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 6098 Lines: 186 The strscpy() API is intended to be used instead of strlcpy(), and instead of most uses of strncpy(). - The API provides an easy way to check for destination buffer overflow: a -E2BIG error return value. - By default, truncation causes the destination buffer to be the empty string, so users don't blindly assume a truncated string is valid. If you know a truncated string still has valid semantics, you can use the provided strscpy_truncate(), which has the same return value semantics but does not make the destination an empty string on error. - The provided implementation is robust in the face of the source buffer being asynchronously changed during the copy, unlike the current implementation of strlcpy(). - The implementation should be reasonably performant on all platforms since it uses the asm/word-at-a-time.h API rather than simple byte copy. Kernel-to-kernel string copy is not considered to be performance critical in any case. - If the remainder of the destination buffer must be zeroed for some reason, strscpy() + error check + memset() is probably the easiest pattern, but using strncpy() in a pattern where the last byte of the buffer is first set non-NUL, then tested for NUL afterwards, can also be safely used. Signed-off-by: Chris Metcalf --- include/linux/string.h | 6 +++ lib/string.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/include/linux/string.h b/include/linux/string.h index e40099e585c9..7942944f3abb 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -25,6 +25,12 @@ 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_STRSCPY_TRUNCATE +ssize_t strscpy_truncate(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 a5792019193c..84d5b6eceb74 100644 --- a/lib/string.c +++ b/lib/string.c @@ -27,6 +27,9 @@ #include #include +#include +#include + #ifndef __HAVE_ARCH_STRNCASECMP /** * strncasecmp - Case insensitive, length-limited string comparison @@ -146,6 +149,112 @@ size_t strlcpy(char *dest, const char *src, size_t size) EXPORT_SYMBOL(strlcpy); #endif +#ifndef __HAVE_ARCH_STRSCPY_TRUNCATE +/** + * strscpy_truncate - 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 routine returns the number of characters copied (not including + * the trailing NUL) or -E2BIG if the destination buffer wasn't big enough. + * The behavior is undefined if the string buffers overlap. + * + * Note that the implementation is robust to the string changing out + * from underneath it, unlike the current strlcpy() implementation, + * and it is easier to check overflow than with strlcpy()'s API. + * + * strscpy() is preferred over this function unless a truncated string + * provides some valid semantics in the destination buffer. + */ +ssize_t strscpy_truncate(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) + 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 + + while (max >= sizeof(unsigned long)) { + unsigned long c, data; + + c = *(unsigned long *)(src+res); + *(unsigned long *)(dest+res) = c; + if (has_zero(c, &data, &constants)) { + data = prep_zero_mask(c, data, &constants); + data = create_zero_mask(data); + return res + find_zero(data); + } + 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_truncate); +#endif + +#ifndef __HAVE_ARCH_STRSCPY +/** + * strscpy - Copy a C-string into a sized buffer, but only if it fits + * @dest: Where to copy the string to + * @src: Where to copy the string from + * @count: Size of destination buffer + * + * Copy the string into the dest buffer. The routine returns the + * number of characters copied (not including the trailing NUL) or + * -E2BIG if the destination buffer wasn't big enough. The behavior + * is undefined if the string buffers overlap. The destination buffer + * is set to the empty string if the buffer is not big enough. + * + * Preferred over strlcpy() in all cases, and over strncpy() unless + * the latter's zero-fill behavior is desired and truncation of the + * source string is known not to be an issue. + */ +ssize_t strscpy(char *dest, const char *src, size_t count) +{ + ssize_t res = strscpy_truncate(dest, src, count); + if (res < 0 && count != 0) + dest[0] = '\0'; + return res; +} +EXPORT_SYMBOL(strscpy); +#endif + #ifndef __HAVE_ARCH_STRCAT /** * strcat - Append one %NUL-terminated string to another -- 2.1.2 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/