-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lib: add generic strnlen_user() function
This adds a new generic optimized strnlen_user() function that uses the <asm/word-at-a-time.h> infrastructure to portably do efficient string handling. In many ways, strnlen is much simpler than strncpy, and in particular we can always pre-align the words we load from memory. That means that all the worries about alignment etc are a non-issue, so this one can easily be used on any architecture. You obviously do have to do the appropriate word-at-a-time.h macros. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Paul Reioux <reioux@gmail.com>
- Loading branch information
Showing
3 changed files
with
142 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
#include <linux/kernel.h> | ||
#include <linux/export.h> | ||
#include <linux/uaccess.h> | ||
|
||
#include <asm/word-at-a-time.h> | ||
|
||
/* Set bits in the first 'n' bytes when loaded from memory */ | ||
#ifdef __LITTLE_ENDIAN | ||
# define aligned_byte_mask(n) ((1ul << 8*(n))-1) | ||
#else | ||
# define aligned_byte_mask(n) (~0xfful << 8*(7-(n))) | ||
#endif | ||
|
||
/* | ||
* Do a strnlen, return length of string *with* final '\0'. | ||
* 'count' is the user-supplied count, while 'max' is the | ||
* address space maximum. | ||
* | ||
* Return 0 for exceptions (which includes hitting the address | ||
* space maximum), or 'count+1' if hitting the user-supplied | ||
* maximum count. | ||
* | ||
* NOTE! We can sometimes overshoot the user-supplied maximum | ||
* if it fits in a aligned 'long'. The caller needs to check | ||
* the return value against "> max". | ||
*/ | ||
static inline long do_strnlen_user(const char __user *src, unsigned long count, unsigned long max) | ||
{ | ||
const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; | ||
long align, res = 0; | ||
unsigned long c; | ||
|
||
/* | ||
* Truncate 'max' to the user-specified limit, so that | ||
* we only have one limit we need to check in the loop | ||
*/ | ||
if (max > count) | ||
max = count; | ||
|
||
/* | ||
* Do everything aligned. But that means that we | ||
* need to also expand the maximum.. | ||
*/ | ||
align = (sizeof(long) - 1) & (unsigned long)src; | ||
src -= align; | ||
max += align; | ||
|
||
if (unlikely(__get_user(c,(unsigned long __user *)src))) | ||
return 0; | ||
c |= aligned_byte_mask(align); | ||
|
||
for (;;) { | ||
unsigned long data; | ||
if (has_zero(c, &data, &constants)) { | ||
data = prep_zero_mask(c, data, &constants); | ||
data = create_zero_mask(data); | ||
return res + find_zero(data) + 1 - align; | ||
} | ||
res += sizeof(unsigned long); | ||
if (unlikely(max < sizeof(unsigned long))) | ||
break; | ||
max -= sizeof(unsigned long); | ||
if (unlikely(__get_user(c,(unsigned long __user *)(src+res)))) | ||
return 0; | ||
} | ||
res -= align; | ||
|
||
/* | ||
* Uhhuh. We hit 'max'. But was that the user-specified maximum | ||
* too? If so, return the marker for "too long". | ||
*/ | ||
if (res >= count) | ||
return count+1; | ||
|
||
/* | ||
* Nope: we hit the address space limit, and we still had more | ||
* characters the caller would have wanted. That's 0. | ||
*/ | ||
return 0; | ||
} | ||
|
||
/** | ||
* strnlen_user: - Get the size of a user string INCLUDING final NUL. | ||
* @str: The string to measure. | ||
* @count: Maximum count (including NUL character) | ||
* | ||
* Context: User context only. This function may sleep. | ||
* | ||
* Get the size of a NUL-terminated string in user space. | ||
* | ||
* Returns the size of the string INCLUDING the terminating NUL. | ||
* If the string is too long, returns 'count+1'. | ||
* On exception (or invalid count), returns 0. | ||
*/ | ||
long strnlen_user(const char __user *str, long count) | ||
{ | ||
unsigned long max_addr, src_addr; | ||
|
||
if (unlikely(count <= 0)) | ||
return 0; | ||
|
||
max_addr = user_addr_max(); | ||
src_addr = (unsigned long)str; | ||
if (likely(src_addr < max_addr)) { | ||
unsigned long max = max_addr - src_addr; | ||
return do_strnlen_user(str, count, max); | ||
} | ||
return 0; | ||
} | ||
EXPORT_SYMBOL(strnlen_user); | ||
|
||
/** | ||
* strlen_user: - Get the size of a user string INCLUDING final NUL. | ||
* @str: The string to measure. | ||
* | ||
* Context: User context only. This function may sleep. | ||
* | ||
* Get the size of a NUL-terminated string in user space. | ||
* | ||
* Returns the size of the string INCLUDING the terminating NUL. | ||
* On exception, returns 0. | ||
* | ||
* If there is a limit on the length of a valid string, you may wish to | ||
* consider using strnlen_user() instead. | ||
*/ | ||
long strlen_user(const char __user *str) | ||
{ | ||
unsigned long max_addr, src_addr; | ||
|
||
max_addr = user_addr_max(); | ||
src_addr = (unsigned long)str; | ||
if (likely(src_addr < max_addr)) { | ||
unsigned long max = max_addr - src_addr; | ||
return do_strnlen_user(str, ~0ul, max); | ||
} | ||
return 0; | ||
} | ||
EXPORT_SYMBOL(strlen_user); |