Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

strscpy: write destination buffer only once

The point behind strscpy() was to once and for all avoid all the
problems with 'strncpy()' and later broken "fixed" versions like
strlcpy() that just made things worse.

So strscpy not only guarantees NUL-termination (unlike strncpy), it also
doesn't do unnecessary padding at the destination. But at the same time
also avoids byte-at-a-time reads and writes by _allowing_ some extra NUL
writes - within the size, of course - so that the whole copy can be done
with word operations.

It is also stable in the face of a mutable source string: it explicitly
does not read the source buffer multiple times (so an implementation
using "strnlen()+memcpy()" would be wrong), and does not read the source
buffer past the size (like the mis-design that is strlcpy does).

Finally, the return value is designed to be simple and unambiguous: if
the string cannot be copied fully, it returns an actual negative error,
making error handling clearer and simpler (and the caller already knows
the size of the buffer). Otherwise it returns the string length of the
result.

However, there was one final stability issue that can be important to
callers: the stability of the destination buffer.

In particular, the same way we shouldn't read the source buffer more
than once, we should avoid doing multiple writes to the destination
buffer: first writing a potentially non-terminated string, and then
terminating it with NUL at the end does not result in a stable result
buffer.

Yes, it gives the right result in the end, but if the rule for the
destination buffer was that it is _always_ NUL-terminated even when
accessed concurrently with updates, the final byte of the buffer needs
to always _stay_ as a NUL byte.

[ Note that "final byte is NUL" here is literally about the final byte
in the destination array, not the terminating NUL at the end of the
string itself. There is no attempt to try to make concurrent reads and
writes give any kind of consistent string length or contents, but we
do want to guarantee that there is always at least that final
terminating NUL character at the end of the destination array if it
existed before ]

This is relevant in the kernel for the tsk->comm[] array, for example.
Even without locking (for either readers or writers), we want to know
that while the buffer contents may be garbled, it is always a valid C
string and always has a NUL character at 'comm[TASK_COMM_LEN-1]' (and
never has any "out of thin air" data).

So avoid any "copy possibly non-terminated string, and terminate later"
behavior, and write the destination buffer only once.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

+17 -6
+17 -6
lib/string.c
··· 104 104 EXPORT_SYMBOL(strncpy); 105 105 #endif 106 106 107 + #ifdef __BIG_ENDIAN 108 + # define ALLBUTLAST_BYTE_MASK (~255ul) 109 + #else 110 + # define ALLBUTLAST_BYTE_MASK (~0ul >> 8) 111 + #endif 112 + 107 113 ssize_t sized_strscpy(char *dest, const char *src, size_t count) 108 114 { 109 115 const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; ··· 153 147 *(unsigned long *)(dest+res) = c & zero_bytemask(data); 154 148 return res + find_zero(data); 155 149 } 150 + count -= sizeof(unsigned long); 151 + if (unlikely(!count)) { 152 + c &= ALLBUTLAST_BYTE_MASK; 153 + *(unsigned long *)(dest+res) = c; 154 + return -E2BIG; 155 + } 156 156 *(unsigned long *)(dest+res) = c; 157 157 res += sizeof(unsigned long); 158 - count -= sizeof(unsigned long); 159 158 max -= sizeof(unsigned long); 160 159 } 161 160 162 - while (count) { 161 + while (count > 1) { 163 162 char c; 164 163 165 164 c = src[res]; ··· 175 164 count--; 176 165 } 177 166 178 - /* Hit buffer length without finding a NUL; force NUL-termination. */ 179 - if (res) 180 - dest[res-1] = '\0'; 167 + /* Force NUL-termination. */ 168 + dest[res] = '\0'; 181 169 182 - return -E2BIG; 170 + /* Return E2BIG if the source didn't stop */ 171 + return src[res] ? -E2BIG : res; 183 172 } 184 173 EXPORT_SYMBOL(sized_strscpy); 185 174