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:

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:

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 = 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:

  1. using memcpy would not be enough,
  2. strncpy's NUL-padding feature is actually desired, and
  3. 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.


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.

Date: 2012-08-12 22:58:44 CEST

Author: Jim Meyering