diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-26 14:06:38 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-26 14:33:53 -0400 |
commit | a08c5356a3aaf638c41897ae4169de18db89595e (patch) | |
tree | fe0d1cb48a26cc000c199d484a139799d559a178 /lib/strnlen_user.c | |
parent | 36126f8f2ed8168eb13aa0662b9b9585cba100a9 (diff) |
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>
Diffstat (limited to 'lib/strnlen_user.c')
-rw-r--r-- | lib/strnlen_user.c | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/lib/strnlen_user.c b/lib/strnlen_user.c new file mode 100644 index 000000000000..90900ecfeb54 --- /dev/null +++ b/lib/strnlen_user.c | |||
@@ -0,0 +1,138 @@ | |||
1 | #include <linux/kernel.h> | ||
2 | #include <linux/export.h> | ||
3 | #include <linux/uaccess.h> | ||
4 | |||
5 | #include <asm/word-at-a-time.h> | ||
6 | |||
7 | /* Set bits in the first 'n' bytes when loaded from memory */ | ||
8 | #ifdef __LITTLE_ENDIAN | ||
9 | # define aligned_byte_mask(n) ((1ul << 8*(n))-1) | ||
10 | #else | ||
11 | # define aligned_byte_mask(n) (~0xfful << 8*(7-(n))) | ||
12 | #endif | ||
13 | |||
14 | /* | ||
15 | * Do a strnlen, return length of string *with* final '\0'. | ||
16 | * 'count' is the user-supplied count, while 'max' is the | ||
17 | * address space maximum. | ||
18 | * | ||
19 | * Return 0 for exceptions (which includes hitting the address | ||
20 | * space maximum), or 'count+1' if hitting the user-supplied | ||
21 | * maximum count. | ||
22 | * | ||
23 | * NOTE! We can sometimes overshoot the user-supplied maximum | ||
24 | * if it fits in a aligned 'long'. The caller needs to check | ||
25 | * the return value against "> max". | ||
26 | */ | ||
27 | static inline long do_strnlen_user(const char __user *src, unsigned long count, unsigned long max) | ||
28 | { | ||
29 | const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; | ||
30 | long align, res = 0; | ||
31 | unsigned long c; | ||
32 | |||
33 | /* | ||
34 | * Truncate 'max' to the user-specified limit, so that | ||
35 | * we only have one limit we need to check in the loop | ||
36 | */ | ||
37 | if (max > count) | ||
38 | max = count; | ||
39 | |||
40 | /* | ||
41 | * Do everything aligned. But that means that we | ||
42 | * need to also expand the maximum.. | ||
43 | */ | ||
44 | align = (sizeof(long) - 1) & (unsigned long)src; | ||
45 | src -= align; | ||
46 | max += align; | ||
47 | |||
48 | if (unlikely(__get_user(c,(unsigned long __user *)src))) | ||
49 | return 0; | ||
50 | c |= aligned_byte_mask(align); | ||
51 | |||
52 | for (;;) { | ||
53 | unsigned long data; | ||
54 | if (has_zero(c, &data, &constants)) { | ||
55 | data = prep_zero_mask(c, data, &constants); | ||
56 | data = create_zero_mask(data); | ||
57 | return res + find_zero(data) + 1 - align; | ||
58 | } | ||
59 | res += sizeof(unsigned long); | ||
60 | if (unlikely(max < sizeof(unsigned long))) | ||
61 | break; | ||
62 | max -= sizeof(unsigned long); | ||
63 | if (unlikely(__get_user(c,(unsigned long __user *)(src+res)))) | ||
64 | return 0; | ||
65 | } | ||
66 | res -= align; | ||
67 | |||
68 | /* | ||
69 | * Uhhuh. We hit 'max'. But was that the user-specified maximum | ||
70 | * too? If so, return the marker for "too long". | ||
71 | */ | ||
72 | if (res >= count) | ||
73 | return count+1; | ||
74 | |||
75 | /* | ||
76 | * Nope: we hit the address space limit, and we still had more | ||
77 | * characters the caller would have wanted. That's 0. | ||
78 | */ | ||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * strnlen_user: - Get the size of a user string INCLUDING final NUL. | ||
84 | * @str: The string to measure. | ||
85 | * @count: Maximum count (including NUL character) | ||
86 | * | ||
87 | * Context: User context only. This function may sleep. | ||
88 | * | ||
89 | * Get the size of a NUL-terminated string in user space. | ||
90 | * | ||
91 | * Returns the size of the string INCLUDING the terminating NUL. | ||
92 | * If the string is too long, returns 'count+1'. | ||
93 | * On exception (or invalid count), returns 0. | ||
94 | */ | ||
95 | long strnlen_user(const char __user *str, long count) | ||
96 | { | ||
97 | unsigned long max_addr, src_addr; | ||
98 | |||
99 | if (unlikely(count <= 0)) | ||
100 | return 0; | ||
101 | |||
102 | max_addr = user_addr_max(); | ||
103 | src_addr = (unsigned long)str; | ||
104 | if (likely(src_addr < max_addr)) { | ||
105 | unsigned long max = max_addr - src_addr; | ||
106 | return do_strnlen_user(str, count, max); | ||
107 | } | ||
108 | return 0; | ||
109 | } | ||
110 | EXPORT_SYMBOL(strnlen_user); | ||
111 | |||
112 | /** | ||
113 | * strlen_user: - Get the size of a user string INCLUDING final NUL. | ||
114 | * @str: The string to measure. | ||
115 | * | ||
116 | * Context: User context only. This function may sleep. | ||
117 | * | ||
118 | * Get the size of a NUL-terminated string in user space. | ||
119 | * | ||
120 | * Returns the size of the string INCLUDING the terminating NUL. | ||
121 | * On exception, returns 0. | ||
122 | * | ||
123 | * If there is a limit on the length of a valid string, you may wish to | ||
124 | * consider using strnlen_user() instead. | ||
125 | */ | ||
126 | long strlen_user(const char __user *str) | ||
127 | { | ||
128 | unsigned long max_addr, src_addr; | ||
129 | |||
130 | max_addr = user_addr_max(); | ||
131 | src_addr = (unsigned long)str; | ||
132 | if (likely(src_addr < max_addr)) { | ||
133 | unsigned long max = max_addr - src_addr; | ||
134 | return do_strnlen_user(str, ~0ul, max); | ||
135 | } | ||
136 | return 0; | ||
137 | } | ||
138 | EXPORT_SYMBOL(strlen_user); | ||