strncpy: not the function you are looking for
strncpy
is a function from the C library that copies no more than
a specified number of bytes from a source string into a destination buffer.
Given how long it has been around and how often it is used, you might
think it is pretty useful, or at least ok to use. You get a hint
of this function's treachery if you read the strncpy(3) man page.
There should be a big, flashing, neon warning sign.
Seeing that the name, strncpy
, is so similar to strcpy
, and how that pair
looks analogous to strncat
/ strcat
, many assume strncpy
provides the
same NUL-termination guarantee that those other functions do. Wrong.
strncpy
does not guarantee NUL-termination.
Rather, it guarantees that the result will not
be NUL-terminated in some cases.
Why do I bother to write this, when the documentation already mentions
its problems? Some of us forget to read documentation. Or read it,
and then forget details. A surprising number of developers are
unaware of strncpy
's problems. Far too many uses of strncpy
are misleading, unnecessary or just plain buggy1. When they're
buggy, it is often because the author didn't realize that an over-long
input makes strncpy
create a result with no trailing NUL byte.
Boom! Instant buffer overrun for anyone who provides that long input.
Another common error is to use strncpy
unnecessarily. For most
unnecessary uses, substituting a use of memcpy
(or even strcpy
)
is preferred, partly because it is more efficient, but mainly for
clarity and readability: always try to minimize your reviewer's
WTF-rate.
When you are reviewing code and find a use of strncpy
,
strncpy (dest, src, len);
you should ask these questions:
- Is it ok to create a result that is not NUL-terminated? Does the surrounding code manually assure NUL-termination in DEST? If neither is true, then you have a potential buffer overrun. However, note that when the answer to the first question is "yes", it may be hard to answer definitively.
- Is it required to write NULs out to the maximum length? If not, you're wasting cycles zero-padding for every source string that is shorter than LEN - 1.
- Is strlen(SRC) always < LEN?
If so, just use
memcpy
(being careful to copy the trailing NUL), assuming you don't need the additional zero-padding.
People tend to ignore strncpy
's feature of writing NUL bytes out to the
end of the destination buffer. Code like this is not uncommon:
char buf[PATH_MAX]; ... strncpy (buf, src, sizeof buf); buf[sizeof buf - 1] = 0;
It is good that this code ensures NUL-termination, but it still has problems:
- writing hundreds of bytes to unnecessarily NUL-pad out to
PATH_MAX
- truncating (and using) a file name that would have been longer
than
PATH_MAX
-1 bytes.
If you already know the length of the source string, you can avoid the wasted-work problem with code like this:
size_t src_len = strlen (src); ... size_t len = MIN (src_len, sizeof buf - 1); memcpy (dest, src, len); dest[len] = 0;
However, if this is the only reason for the strlen
call, then the above
is no solution, and will perform more work for some inputs,
since it traverses SRC twice, once via strlen
, and again via memcpy
.
The memccpy(3) function seems promising, since it can be made to do almost all of what we want:
memccpy (dest, src, 0, len);
but that still fails to NUL-terminate when there is no NUL in the first LEN bytes of SRC.
I would like a function that always NUL-terminates (i.e., that may write up to LEN+1 bytes into DEST) and that has this signature:
char *stzncpy (char *restrict dest, char const *restrict src, size_t len)
Given how the above memccpy
use returns NULL when failing to NUL-terminate
(otherwise, it returns a pointer to the byte after the last one copied),
you could do this:
char * stzncpy (char *restrict dest, char const *restrict src, size_t len) { char *p = memccpy (dest, src, 0, len); if (p) --p; else { p = dest + len; *p = 0; } return p; }
While using memccpy
like that might be more efficient for long strings,
the following is more compact, both in source and compiled with gcc -O2
(text size of 151 with memccpy
vs. 88 without):
char * stzncpy (char *restrict dest, char const *restrict src, size_t len) { char const *src_end = src + len; while (src < src_end && *src) *dest++ = *src++; *dest = 0; return dest; }
strlcpy(3) may seem like a good alternative, but it feels like the API has a built-in off-by-one error: one cannot pass it a length of 0, because the length parameter includes the NUL byte it always writes to the destination buffer. For related discussion, see Michael Kerrisk's LWN article.
Of course, one can argue that this stzncpy
is even worse,
since it may write one byte more than the LEN parameter,
and it's not even standardized.
The PATH_MAX
example above uses a fixed-size destination buffer.
Many other uses operate on a variable sized destination, and look like this:
strncpy (buf, src, n);
Some people follow the advice from the strncpy
man page, and add code
like this when they want to ensure that the destination can be used as
a NUL-terminated string:
if (n > 0) buf[n - 1] = 0;
That code is careful to skip the NUL-termination when N is 0.
Without that guard, we would make the error of assigning buf[-1] = 0
,
but using strncpy
like this leaves us with a buffer that is
not NUL-terminated in the pathological N == 0 case.
Hence, any code that requires the resulting "buf" to
be NUL-terminated can be run only when N > 0.
Another special case that must be handled.
If you find yourself tempted to use strncpy
, go ahead and use
it only if you can add a comment certifying that:
- using
memcpy
would not be enough, strncpy
's NUL-padding feature is actually desired, and- the destination buffer need not be NUL-terminated.
If you cannot certify all of that, then don't use strncpy
.
However, if condition 3 is the only problematic one, and IF
it is ok to actually use a truncated result string, then add
the manual NUL-termination, as mentioned above.
For example, if you are forming a file name and you'd have to lop off part of the SRC string to make it fit, then you cannot simply truncate. The truncated name may or may not refer to some existing file, but either way, operating on a truncated name would produce misleading results. Even if you simply print the string, you should give some indication that it has been truncated.
There are so many ways to misuse strncpy
and so few cases in which its
use is justified that it is an easy call simply to prohibit its use.
If you really must, grandfather-in any existing uses, and remove those
as you find time/motivation, but be sure to automate something to prevent
the introduction of any new uses.
Footnotes:
1 Starting a few months ago, I went on a crusade to fix or remove
improper uses of strncpy
. I eliminated all strncpy
uses from coreutils
and a few other packages, and fixed numerous problems in linux, git, qemu,
glusterfs, golang, bison, emacs, gnupg, btrfs-utils and even in gcc and gdb.