strcpy strncpy() strlcpy() and strscpy()

The kernel has a number of strcpy() functions that copy a string from one pointer to another. This blog is a short guide.

strcpy() is dangerous because it has no bounds checking and can lead to a buffer overflow.

strncpy() will not overflow the destination buffer. But the problem is that if it has to truncate the string, then it will not add a NUL terminator at the end. The other thing to note about strncpy() is that it always writes n characters. If the src string doesn’t have n characters then it will fill the remaining parts of the string with zeroes. This feature is sometimes used in the kernel to prevent information leaks. In the kernel, when you allocate memory with kmalloc() then the old information is still there so you have to clear it out before you copy it to the user. strncpy() will copy over the whole buffer. (This is called zero padding).

I’m going to note here that although the problem with strncpy() is that it doesn’t put a NUL terminator on the end of the buffer, there are some places which want this behavior. There are buffers where we read to the first NUL character or up to 16 characters. I am not a huge fan of this behavior but it does exist. One piece of social advice that I can give is that you should not get into arguments with people who write code like this. These are not people motivated by reason.

strlcpy() will not overflow the destination buffer and it always adds a NUL terminator. However it doesn’t pad the string with zeros. But the main thing wrong with strlcpy() is that it does a strlen() on the src string. So if the src string is not NULL terminated then you can end up reading beyond the end of the buffer. If you get really unlucky then the memory beyond the end of the buffer might not be mapped and the kernel would Oops.

strscpy() and strscpy_pad() these functions don’t overflow, and they add a NUL terminator and the don’t do a strlen(src). The strcpy() function does not zero pad the string and the strscpy_pad() function does.

Obviously in new code, you should always use strscpy() and strscpy_pad(). Or you can use seq_buf_printf() or various other options. But don’t use strcpy(), strncpy() and strlcpy(). Eventually we want to get rid of these functions.

If you are updating code from the old strcpy() functions to the new functions then we want you to do some analysis. If you are converting from strcpy() then it’s simple to use strscpy() but you must analyze if the original code was buggy. Converting from strlcpy() to strscpy() is easy as well, but you must say if the src string is NUL terminated or not. Although I say that it’s easy to make this transition, actually keep your eye out for alternative potentially better ways like using snprintf() instead.

Converting from strncpy() is more tricky. You have to analyze if the resulting string could fit or if the NUL terminator was potentially left off. The other thing to decide is whether to use strscpy() or strscpy_pad(). Is the original destination buffer zeroed already? Does it get copied to user space? This must all be explained in the commit message.

So to recap here are the following questions you must ask when converting a an old strcpy(), strncpy() or strlcpy() function.

1) The destination buffer: How big is it? What is in the destination buffer before we start? Is the destination buffer zeroed already?
2) The source buffer: Where does it come from? Is it a string literal? Does it come from the user? Is it NUL terminated? How long is it?
3) The length: What length is it? Hopefully, it’s the length of the destination buffer but for strncpy() it might be “len – 1”. Is it longer than source buffer?

4) The return value: Very few callers check the return value, but if they do then the values are different.
5) The final string: Does the surrounding code assume that it is NUL terminated? Does the following code manually add a NUL terminator? Do we copy the code to the user?

So a typical commit messages might say something like these:

“Checkpatch complains that strscpy() is better than strcpy(). In this case, we are copying a string literal to a buffer which is 64 characters long. So strcpy() works fine, but let’s change it anyway to make checkpatch happy. (Don’t add a Fixes tag because it’s not a bug).”

“Checkpatch complains that strcpy() is better than strncpy(). This code manually adds a NUL terminator so it’s fine, but using strscpy() is cleaner. In this case, we are using strncpy() to copy data to &foo.str. We copy the &foo.str data to the user. The &foo.str already contains data from the user because of the copy_from_user() at the top of the function the so it’s not an information leak if we use strscpy(). But let’s be more elegant than that and strscpy_pad() to zero out the rest of the buffer.”

“Checkpatch complains that strscpy() is better than strcpy(). In this case we are copying a 16 byte string into a 12 byte buffer so we need to make the destination buffer larger as well. (Add a Fixes tag, CC stable)”.

“We have been trying to get rid of strlcpy() functions. The problem with strlcpy() is that it does a strlen() on the source buffer which can cause a problem if the src buffer is not NUL terminated. In this case the src buffer is a string literal so that’s not an issue. When the src buffer is a string literal then strscpy() is a one for one replacement with strlcpy() so this change will not effect runtime.”

“Checkpatch complains that strscpy() is better than strlcpy(). The problem with strlcpy() is that it does a strlen() on the source buffer. That is a problem in this case because the src buffer comes from the user and it may not be NUL terminated. If the strlen() reads beyond the end of the buffer there is a chance that it will try read unmapped memory and cause a kernel Oops. (Add a Fixes tag, CC stable).

One response to “strcpy strncpy() strlcpy() and strscpy()”

Leave a comment

Design a site like this with WordPress.com
Get started