Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
strncpy(3) strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7). It was added with one use case in mind: copying a source string into a destination character sequence in a fixe...
#7: Post edited
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate uses for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation (also be careful), the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, there was bcopy() instead of memcpy(3). That's why all the byte functions were provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate uses for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V, in `<memory.h>`, alongside the other mem*() functions. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation (also be careful), the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
#6: Post edited
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate uses for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate uses for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation (also be careful), the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
#5: Post edited
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate uses for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
#4: Post edited
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
The Linux kernel uses this function internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`.- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function (strtcpy()) internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`. They don't have an strtcat() equivalent.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
#3: Post edited
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
Plan9 provides this function under the name strecpy(2), although it has an important implementation bug.- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides a similar function under the name strecpy(2), although it reports truncation by returning `end` instead of `NULL`, and has an important bug in a related function: seprint(2).
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
#2: Post edited
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strcpy(3) for something it wasn't designed for. This is negligence.- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides this function under the name strecpy(2), although it has an important implementation bug.
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
- ### strncpy(3)
- strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
- It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
- The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
- > A string is a contiguous sequence of characters terminated by and including the first null character.
- Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
- The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
- ---
- ### strn*() functions, and `[[gnu::nonstring]]`
- In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
- See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
- ---
- ### The Linux kernel: strtomem_pad()
- Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
- This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
- The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
- ```sh
- $ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
- include/linux/string.h:1
- lib/tests/string_kunit.c:1
- drivers/soc/qcom/cmd-db.c:1
- drivers/gpu/drm/drm_connector.c:2
- drivers/auxdisplay/panel.c:3
- arch/x86/coco/tdx/tdx.c:1
- fs/nilfs2/ioctl.c:2
- fs/ext4/file.c:1
- fs/ext4/super.c:1
- ```
- ---
- ### SEI CERT STR32-C
- SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
- <https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
- Here's the code it recommends using:
- ```c
- size_t func(const char *source) {
- char c_str[STR_SIZE];
- size_t ret = 0;
- if (source) {
- strncpy(c_str, source, sizeof(c_str) - 1);
- c_str[sizeof(c_str) - 1] = '\0';
- ret = strlen(c_str);
- } else {
- /* Handle null pointer */
- }
- return ret;
- }
- ```
- Instead I would recommend this:
- ```c
- strtcpy(buf, source, countof(buf));
- ```
- Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
- ```c
- ssize_t
- strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
- {
- bool trunc;
- size_t dlen, slen;
- if (dsize == 0)
- abort();
- slen = strnlen(src, dsize);
- trunc = (slen == dsize);
- dlen = slen - trunc;
- stpcpy(mempcpy(dst, src, dlen), "");
- if (trunc) {
- errno = E2BIG;
- return -1;
- }
- return slen;
- }
- ```
- SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strncpy(3) for something it wasn't designed for. This is negligence.
- ---
- ### memccpy(3)
- memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
- Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
- Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
- ```c
- if (fmt[0] != '}') {
- char *end;
- end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
- if (end == NULL) {
- /*
- * Format too long or no '}', so
- * ignore "\D{" altogether.
- * The loop will do i++, but nothing
- * was written to ps, so do i-- here.
- * Rewind fmt for similar reason.
- */
- i--;
- fmt--;
- break;
- }
- *--end = '\0'; /* Ignore the copy of '}'. */
- fmt += end - tfmt;
- }
- ```
- This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
- ---
- ### POSIX and memccpy(3)
- POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
- Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
- > The memccpy() function does not check for the overflow of the receiving memory area.
- This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
- This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
- ---
- ### ISO C23, n2349, memccpy(3)
- #### N2349 - Toward more efficient string copying and concatenation
- <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
- The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
- This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
- There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
- However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
- The proposal seems to focus on efficiency... Except it doesn't, either.
- memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
- #### POSIX stpcpy(3)
- n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
- #### stpecpy(), Plan9 strecpy(2)
- Reading n2349 further, one finds an example of copying with truncation:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- dsize -= (p - d - 1);
- memccpy (p - 1, s2, '\0', dsize);
- ```
- This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
- In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
- At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
- ```c
- char *p = memccpy (d, s1, '\0', dsize);
- if (p) {
- --p;
- p = memccpy (p, "/", '\0', dsize - (p - d));
- if (p) {
- --p;
- p = memccpy (p, s2, '\0', dsize - (p - d));
- }
- }
- if (!p)
- d[dsize - 1] = '\0';
- ```
- I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
- Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
- ```c
- char *p = d;
- char *e = d + dsize;
- p = stpecpy(p, e, s1);
- p = stpecpy(p, e, "/");
- p = stpecpy(p, e, s2);
- if (p == NULL)
- goto trunc; // The string was truncated
- ```
- Here's how I implemented stpecpy():
- ```c
- char *
- stpecpy(char *dst, const char *end, const char *restrict src)
- {
- ssize_t dlen;
- if (dst == NULL)
- return NULL;
- dlen = strtcpy(dst, src, end - dst);
- if (dlen == -1)
- return NULL;
- return dst + dlen;
- }
- ```
- Plan9 provides this function under the name strecpy(2), although it has an important implementation bug.
- #### strtcpy(), strtcat(), Linux's strscpy(9)
- If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
- ```c
- ssize_t
- strtcat(char *restrict dst, const char *restrict src, size_t dsize)
- {
- char *p, *end;
- end = dst + dsize;
- p = stpecpy(strnul(dst), end, src);
- if (p == NULL)
- return -1;
- return p - dst;
- }
- ```
- They allow the code above to be rewritten as
- ```c
- if (strtcpy(d, s1, dsize) == -1)
- goto trunc;
- if (strtcat(d, "/", dsize) == -1)
- goto trunc;
- if (strtcat(d, s2, dsize) == -1)
- goto trunc;
- ```
- The Linux kernel uses this function internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`.
- ---
- ### POSIX/OpenBSD strlcpy(3)/strlcat(3)
- POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
- These have also been used to copy strings with truncation.
- They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
- Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
- Compare:
- ```c
- if (strtcpy(d, s, countof(d)) == -1)
- goto trunc;
- ```
- vs
- ```c
- if (strlcpy(d, s, countof(d)) >= countof(d))
- goto trunc;
- ```
- The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
- ---
- So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
- If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
- Misusing other functions instead is negligence, and will increase the chances of having important bugs.
#1: Initial revision
### strncpy(3)
strncpy(3) was originally invented in Seventh Edition Unix (a.k.a., V7).
It was added with one use case in mind: copying a source string into a destination character sequence in a fixed-size buffer (not a string), and padding the unused bytes with `'\0'`. This was useful back then in members of the utmp(5) structure, and modern shadow-utils still need this function for dealing with utmp(5). I've heard it is also useful for dealing with some tar(1) structures, although I haven't seen that code myself. In general, any structures with fixed-size members that need padding and which don't have a terminating null-byte (to avoid wasting one byte) need this function.
The strncpy(3) is a very niche function for dealing with null-padded fixed-size buffers, and it should have remained like that. Its problem is not the semantics of the function, but its name. Today, the concept of a string is very clear. It was defined in C89 in 4.1.1:
> A string is a contiguous sequence of characters terminated by and including the first null character.
Back in the times of V7, the concept of a string was less specific, and what we now call byte arrays, they called byte strings. In fact, memcmp(3) was then called bcmp(3). That's why all the byte functions are provided in <string.h>.
The strncpy(3) function would have been better called strtomem_pad(), which better reflects what it does. Maybe that would have reduced misuses of this function.
---
### strn*() functions, and `[[gnu::nonstring]]`
In general, the names of strn*() functions are unfortunate, because they are better suited for handling nonstrings. By nonstrings, I mean character sequences that don't fit in the standard description of string; GNU C has the attribute `[[gnu::nonstring]]` to refer to these things. I use the 'n' in strn*() as a mnemonic for **n**on**str**ing, and indeed, this partially solves the naming issue for me. Still, strtomem_pad() would be a more appropriate name.
See <https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonstring>.
---
### The Linux kernel: strtomem_pad()
Recently, it appeared in the (fake) news that Linux had completely removed uses of strncpy(3), and banned it in new code. For example, <https://www.phoronix.com/news/Linux-7.2-Drops-strncpy>.
This is not really true. They renamed it to strtomem_pad(), which has the same semantics of strncpy(3) with a different name, and still use it. This reflects that there are still legitimate users for which strncpy(3) is still good and necessary.
The number of uses is small, because it's a niche function, but they exist. Here are the uses in the Linux source at the moment (some time after 7.2-rc2):
```sh
$ grep -rc 'strtomem_pad(.\+)' | grep -v :0$
include/linux/string.h:1
lib/tests/string_kunit.c:1
drivers/soc/qcom/cmd-db.c:1
drivers/gpu/drm/drm_connector.c:2
drivers/auxdisplay/panel.c:3
arch/x86/coco/tdx/tdx.c:1
fs/nilfs2/ioctl.c:2
fs/ext4/file.c:1
fs/ext4/super.c:1
```
---
### SEI CERT STR32-C
SEI CERT --which supposedly is a "secure" coding guideline-- recommends using strncpy(3) for copying a string with truncation. This is insane.
<https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/characters-and-strings-str/str32-c/#compliant-solution-truncation>
Here's the code it recommends using:
```c
size_t func(const char *source) {
char c_str[STR_SIZE];
size_t ret = 0;
if (source) {
strncpy(c_str, source, sizeof(c_str) - 1);
c_str[sizeof(c_str) - 1] = '\0';
ret = strlen(c_str);
} else {
/* Handle null pointer */
}
return ret;
}
```
Instead I would recommend this:
```c
strtcpy(buf, source, countof(buf));
```
Which requires adding an strtcpy() function, but this is relatively easy, and only needs to be done once in a project.
```c
ssize_t
strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
{
bool trunc;
size_t dlen, slen;
if (dsize == 0)
abort();
slen = strnlen(src, dsize);
trunc = (slen == dsize);
dlen = slen - trunc;
stpcpy(mempcpy(dst, src, dlen), "");
if (trunc) {
errno = E2BIG;
return -1;
}
return slen;
}
```
SEI CERT is one of many examples of bad teaching around strncpy(3). They suggest misusing strcpy(3) for something it wasn't designed for. This is negligence.
---
### memccpy(3)
memccpy(3) was invented in System V. The System V sources are not public (AFAIK), so it's not known what this function was used for. It was added to the BSDs and glibc for compatibility with System V, but nobody really knew what this function is good for. It doesn't seem ergonomic for any basic functionality.
Ignoring tests, you can find 0 calls to memccpy(3) in NetBSD, and 3 calls in FreeBSD (two of which are in two repeated implementations of strncat(3), and the other one is really unique, in `bin/sh/parser.c`).
Those two unique calls to memccpy(3) in FreeBSD are terrible. They show how error-prone this function is.
```c
if (fmt[0] != '}') {
char *end;
end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
if (end == NULL) {
/*
* Format too long or no '}', so
* ignore "\D{" altogether.
* The loop will do i++, but nothing
* was written to ps, so do i-- here.
* Rewind fmt for similar reason.
*/
i--;
fmt--;
break;
}
*--end = '\0'; /* Ignore the copy of '}'. */
fmt += end - tfmt;
}
```
This seems like a legitimate use case of memccpy(3): we want to copy the leading part of a string until a delimiter character is found. And even this legitimate use of memccpy(3) is fully of opportunities for off-by-one bugs, and shows how terrible this function is, even for the main use case. A better design would have not copied the delimiter, allowing the user to decide whether to copy it or not (instead of forcing it to go back and remove it, which is more complex).
---
### POSIX and memccpy(3)
POSIX standardized memccpy(3) just because it was in System V. POSIX derives from Issue 1 of the SVID, which is the System V Interface Definition. It's not surprising that it's there.
Interestingly, POSIX mentions that memccpy(3) does not check for overflow.
> The memccpy() function does not check for the overflow of the receiving memory area.
This is because the 4th parameter to memccpy(3) is not the size of the destination buffer, but the size of the source buffer. It is assumed that the destination buffer is large enough.
This hints that the original (System V) authors of the function didn't consider copying strings as a use case for this function.
---
### ISO C23, n2349, memccpy(3)
#### N2349 - Toward more efficient string copying and concatenation
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm>
The C Committee, for C23, considered adding new functions for copying strings. They didn't really know what use case they intended to cover, and thus didn't really take an informed decision regarding the design of the function. This reminds us of the fiasco of Annex K.
This time, at least, they decided to restrict the search to existing functions, instead of inventing more Annex-K-like functions. This reduced the risk, but didn't remove it.
There was an inherent desire to add a function for copying strings with truncation, due to the frustration of the historic misuses of strncpy(3) and strncat(3).
However, the paper that discussed this (n2349) --surprisingly-- didn't mention safety in the title.
The proposal seems to focus on efficiency... Except it doesn't, either.
memccpy(3) has been historically not used (see above, 0 uses in NetBSD, and 2 or 3 uses in FreeBSD), which has caused that implementations have very little interest in optimizing it, and thus is possibly one of the slowest <string.h> functions.
#### POSIX stpcpy(3)
n2349 first suggests that `strcat(strcpy(d, s1), s2)` could be written as `memccpy(memccpy(d, s1, '\0', SIZE_MAX) - 1, s2, '\0', SIZE_MAX)` to be more efficient. This is insanely dangerous. There's a risk of off-by-one bugs, and isn't even efficient. A much more efficient and safe alternative is to use POSIX's stpcpy(3): `stpcpy(stpcpy(d, s1), s2)`. Because stpcpy(3) has a hard-coded delimiter, doesn't need to check the source size limit, and can't return NULL, it can be optimized much more than memccpy(3) ever could. And its simplicity makes it much safer.
#### stpecpy(), Plan9 strecpy(2)
Reading n2349 further, one finds an example of copying with truncation:
```c
char *p = memccpy (d, s1, '\0', dsize);
dsize -= (p - d - 1);
memccpy (p - 1, s2, '\0', dsize);
```
This code is more prone to bugs than the case above, and more than strncpy(3). Anyone suggesting to use this to improve safety compared to strncpy(3), please explain to me how they think this is safer in any way.
In fact, that code is completely bogus, because if the string is truncated, `p` will be `NULL`, and it invokes UB in line 2. See how it was predictably prone to bugs? :)
At the bottom of the n2349 paper, there's a more correct example of how memccpy(3) could be used to copy strings. This shows how terrible this function is --at least for copying strings--:
```c
char *p = memccpy (d, s1, '\0', dsize);
if (p) {
--p;
p = memccpy (p, "/", '\0', dsize - (p - d));
if (p) {
--p;
p = memccpy (p, s2, '\0', dsize - (p - d));
}
}
if (!p)
d[dsize - 1] = '\0';
```
I don't think I need to explain what can go wrong in such unreadable, brittle, and complex code.
Using a more suitable function --similar to POSIX's stpcpy(3)--, this could be written much more safely:
```c
char *p = d;
char *e = d + dsize;
p = stpecpy(p, e, s1);
p = stpecpy(p, e, "/");
p = stpecpy(p, e, s2);
if (p == NULL)
goto trunc; // The string was truncated
```
Here's how I implemented stpecpy():
```c
char *
stpecpy(char *dst, const char *end, const char *restrict src)
{
ssize_t dlen;
if (dst == NULL)
return NULL;
dlen = strtcpy(dst, src, end - dst);
if (dlen == -1)
return NULL;
return dst + dlen;
}
```
Plan9 provides this function under the name strecpy(2), although it has an important implementation bug.
#### strtcpy(), strtcat(), Linux's strscpy(9)
If one prefers the simplicity of strcpy(3)/strcat(3) compared to stpcpy(3), one can also write a suitable pair of functions. See strtcpy() above. Here's an implementation of strtcat():
```c
ssize_t
strtcat(char *restrict dst, const char *restrict src, size_t dsize)
{
char *p, *end;
end = dst + dsize;
p = stpecpy(strnul(dst), end, src);
if (p == NULL)
return -1;
return p - dst;
}
```
They allow the code above to be rewritten as
```c
if (strtcpy(d, s1, dsize) == -1)
goto trunc;
if (strtcat(d, "/", dsize) == -1)
goto trunc;
if (strtcat(d, s2, dsize) == -1)
goto trunc;
```
The Linux kernel uses this function internally under the name strscpy(9), with the minor difference that instead of returning `-1` and setting `errno=E2BIG`, it returns `-E2BIG`.
---
### POSIX/OpenBSD strlcpy(3)/strlcat(3)
POSIX.1-2024 standardized the OpenBSD functions strlcpy(3) and strlcat(3).
These have also been used to copy strings with truncation.
They are vulnerable to denial-of-service (DoS) attacks, if an attacker controls the length of the source string. This is because the functions must read the entire source string, even if they already know it will be truncated. strtcpy()/strtcat() and stpecpy() (see above) don't have this problem.
Other than this DoS problem, they are also more difficult to use than strtcpy()/strtcat()/stpecpy(), because instead of a simple error code (`-1`/`NULL`), they return the size of the hypothetical string they tried to create (ignoring truncation), which must be compared to the size of the buffer.
Compare:
```c
if (strtcpy(d, s, countof(d)) == -1)
goto trunc;
```
vs
```c
if (strlcpy(d, s, countof(d)) >= countof(d))
goto trunc;
```
The second example could be accidentally written with `>` instead of `>=`, which would result in an off-by-one bug.
---
So, please use the right tools for the job. For copying strings without truncation (be careful), the right tools are strcpy(3)/strcat(3) (C89), and stpcpy(3) (POSIX). For copying strings with truncation, the right tools are strtcpy()/strtcat() and stpecpy().
If those tools are not available in your system, you should write them. They won't cost you more than a few lines of code.
Misusing other functions instead is negligence, and will increase the chances of having important bugs.
