Image

Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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.

Are memccpy(3) or strncpy(3) bad for copying and catenating strings with truncation?

+0
−0

I heard strncpy(3) is bad, and also heard that C23 added memccpy(3) to replace it.

However, I also heard memccpy(3) is even more terrible than strncpy(3).

Are these functions really bad? How so? Are there any legitimate uses of any of these functions? What should we use instead?

History

1 comment thread

strncpy duplicate (1 comment)

2 answers

+3
−0

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 nonstring, 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):

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

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:

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.

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.

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:

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

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:

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():

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():

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

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:

if (strtcpy(d, s, countof(d)) == -1)
    goto trunc;

vs

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.

History

1 comment thread

The legitimate use case for memccpy(3) (1 comment)
+1
−1

I won't address strncpy here since that's fully covered by Is strcpy dangerous and what should be used instead?

But I can also add that C has no type support for fixed-width strings, so it was therefore nonsense to add fixed-width string handling functions to the ISO C standard. If fixed-width strings were to be covered, then the appropriate data type for that should have been added to the language too, but that wasn't done so it is all misguided all the way back to C89. As noted in the link, the spirit of C was never to store the array size together with the data, for good and bad.


Regarding memccpy, I would agree that the paper N2349 is a bit confusing in several ways, speaking explicitly about strings etc. But that's irrelevant since the actual ISO/IEC 9899:2024 document doesn't say anywhere that the purpose of memccpy is to truncate strings. Other than it residing inside string.h, which is also the case for memcpy and memmove, so putting it in another header would be confusing.

What the C standard does say (7.26.1):

For all functions in this subclause, each character shall be interpreted as if it had the type unsigned char (and therefore every possible object representation is valid and has a different value).

That's nice because then we can rule out misalignment bugs and trap representations. But also signedness of char mishaps as can happen in the broken ctype.h library when an implementation decides to not treat the passed parameter as unsigned char. So it's already safer than a lot of the standard lib and we do get a pointer to where it stopped copying, so we can calculate the size copied. Which really ought to be "minimum viable product" for any function that writes/copies, yet that is impossible in other standard functions that come with a broken API, for example fgets.

The mem... prefix promises that this is a bare bones function which you can't expect to have a ton of safety built-in, because it needs performance, likely to be inlined/replaced by the optimizing compiler. And so memccpy has the same well-known limitations as memcpy: you can't use it for overlapping memory and there is no type safety what-so-ever, since the function is supposed to be used on raw data.

A natural use for the function could for example be to copy stuff from raw memory cells in an embedded system NVM - there may be some use-cases for such in flash memory wear-leveling algorithms for example, or protocol handlers with a fixed sync word in the end. By using a library function rather than hand-crafting it out yourself, there's a bigger potential for compiler optimizations.

Now of course it can also be used for string copying, with the head's up that the function stops after copying the first occurrence of the searched-for character, so it copies the null terminator too, if found. Otherwise it returns a null pointer.

That's the only valid criticism I can come up with for this function - perhaps you wouldn't expect a function called mem... to add null termination etc but rather stop copying before finding a particular character (a sentinel value). So that may be a bit surprising if not reading the function's documentation too carefully. Consider memccpy(buf, from_stdin, '\n', size) - hey why did you copy that crappy line feed for, I don't want it!

If you do use it for strings, I find the usage rather straight-forward:

#include <string.h>
#include <stdio.h>

int main(void)
{
  char s1[] = "hello world";
  char s2[100];
  char* result = memccpy(s2, s1, '\0', sizeof(s2));

  if(result == NULL)
  {
    /* error handling here */
  }
  puts(s2); // already null terminated, we're ready to go
}

The null terminator ends up in the right place no matter if the function stopped before the end of the buffer or not, because it is copied by the function. If we want to know if the whole source string was copied or not, we can do this:

const char* expected_end = s2 + sizeof(s1);
char* result = memccpy(s2, s1, '\0', sizeof(s2));
...
if(result == expected_end)
{ ... }

At every place in the code we use the size of the buffers, never the length of some string, so there isn't really any chance of off-by-one errors because of that.

C does explicitly allow pointers to point one item beyond an array for exactly these kind of scenarios with "end pointers".

Similarly the size copied is easy to obtain:

printf("Size copied: %tu\n", result-s2);

(You do get it as the exotic ptrdiff_t though, rather than size_t. Casting between the two should be safe however.)

If you want the string length rather than the size, you'll naturally need to subtract by 1 to not count the null terminator.

So to me, this is a pretty good function for copying strings, with a rather straight-forward API.

As for comparing memccpy with functions that don't take a buffer size as parameter, that's comparing apples and oranges. If you don't use the buffer size, then you can't use the function for stuff like input sanitation, which would be a possible use-case for memccpy. Unlike all of the str... functions which as indicated by the prefix are to be used for strings, not for input sanitation.


Regarding optimizations:
Yes, memccpy won't be the fastest possible on a target relying on branch prediction. That would be memcpy. If we are talking micro-optimizations, it is however possible to create two different implementations of memccpy inside the standard lib: one for the scenario where the character to look for is zero, and one for any other scenario. That might save a few ticks on an ISA where check vs zero is faster than check vs a value.

However, compilers already do optimizations to enable such checks against zero when they are iterating over a known size. So rather than implementing an up-counting loop that checks against the value size_t n, it can start iterating on that value and implement a down-counting loop that checks against zero.

History

5 comment threads

Usage for copying without truncation (3 comments)
Input sanitation (2 comments)
UB: Pointer arithmetic overflow (1 comment)
fgets(3) (2 comments)
mempcpy(3), minimum viable product (1 comment)

Sign up to answer this question »