char dest[8] = {0};
strncpy(dest, src, sizeof(dest) - 1);
or char dest[8];
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest)-1] = '\0';
On the same note I always wondered what "snprintf" does. That is - will it ALWAYS zero terminate (given non-NULL buffer, of size > 0)?The only issue with that idiom is that it is only defined to pass a '\0'-terminated string for src. Although it will only write the specified number of characters, it will read from src until the end of the string, and invoke undefined behavior if src does not point to a well-formed string:
https://taas.trust-in-soft.com/tsnippet/t/e0538551
A mnemonic is that snprintf needs to return the length of the string that would have been output if there had been enough room (not counting the terminating '\0'), so it needs to compute the length of the string pointed by src.
A close variant, below, avoids this issue but instead has the problem that the printf star argument is typed as an int, which is narrower than size_t on a typical 64-bit compilation platform.
snprintf(dest, "%.*s", (int)(sizeof dest)-1, src);If you are using >2 GB buffers and anticipate >2 GB strings, it probably makes sense to track lengths explicitly and use memcpy() etc instead of string routines anyway.
However, while it may also be possible, in some code, for a malicious user to control the buffer size, the int precision argument, as used in this construct, derives from the buffer size, and not the input string.
If the user can control the buffer size, then yes, we get the very undesireable buffer overflow via overflow from positive to negative[0]:
A negative precision is taken as if the precision were
omitted.
[0]: snprintf(3) man page from linuxstrlcpy() has the same semantics as snprintf(buf, sizeof buf, "%s", src) but without the failure mode. Because glibc is so stubborn, and rather than trying to wrap snprintf() to abort on OOM, I invariably include this simple implementation in a common header,
static size_t
aux_strlcpy(char *dst, const char *src, size_t lim)
{
size_t len, n;
len = strlen(src);
if (lim > 0) {
n = MIN(lim - 1, len);
memcpy(dst, src, n);
dst[n] = '\0';
}
return len;
}
which is substantially simpler than the canonical version from OpenBSD.A related problem with snprintf is the mixture of signed and unsigned types for communicating object size. Mixing signed and unsigned types is, IME, error prone, so if I have code that uses snprintf more than a few times I usually wrap it in a function that separates the status from the [logical] size return values. People complain that strlcpy is problematic because it similarly communicates status (i.e. truncation) and object size through the same channel. But 1) it's not as nearly as error prone as mixing signed and unsigned types and 2) idiomatic use of strlcpy is easy to code and mentally parse and I've never felt the urge to wrap strlcpy in a helper routine. If truncation is always a failure than I'll simply use a two-line routine that returns an error code. But more often than not I rely on silent truncation. IMO, C code shouldn't be doing complex string operations using native C strings[2], and where it does make sense to use C strings it's usually things like configuration values where garbage in is garbage out; if an overlong property name is truncated it's no different than if it was misspelled. (People seriously overestimate the utility of supporting dynamic object sizes everywhere, and underestimate the inherent complexity it causes, which is significant even in low-level languages like C++ or Rust that make it safer and more convenient.)
[1] Linux's overcommit doesn't save you from, e.g., process resource limits.
[2] In fact, IMO complex parsing and composition shouldn't even be done with any kind of generic string API. If you get to the point where you're parsing and composing highly structured text, you should be using proper techniques with specialized data types. Writing ad hoc string munging code is error prone and a maintenance nightmare in any language, including scripting languages. But if I must than not only do I avoid C, I avoid any low-level, statically typed language. Scripting languages were literally invented for writing ad hoc string munging code.
I wonder whether glibc's dprintf() is async-signal-safe....
I think it's because drepper hates us [1].
[1] http://www.sourceware.org/ml/libc-alpha/2000-08/msg00061.htm...
This is spot on. There are very few cases when silently truncating a copy of a string is an OK thing to do.
This is C we're talking about. Always be checking return values. (And errno! And errno! And errno!)
That said there are some functions where the possible return values can't indicate an error. In those cases errno should be set to zero before the function call and then a change during will alert you to an error.
Why it's like this I have no idea but it's pretty annoying.
The bit about errno possibly being non-zero but not indicating error unless the function does? I guess because the called function can itself call functions / libs which set errno and properly handle those (or just not care). Without the "errno only has meaning if the function returns error" guard, every single function needs to always reset errno to zero before returning, which is annoying and will almost certainly not happen in the majority of cases.
When the function has no way to communicate error, there is no other option but to use errno. But it's good that it's the exception, I imagine.
Can it makes things worse? Yes. But C is broken.
I still keep the NUL terminator around for compatibility reasons. Linux system calls don't need the NUL, but other C code might depend on it.
Any progress since?
Amusingly, a request for strlcpy was the very 2nd comment on that LWN article.
Unfortunately, glibc still hasn't added it — Roland rejected it again in 2015.[1] Also unfortunately, RH hired Drepper back in 2017. Because they hate their other employees, I guess. Fortunately, no one is eager to give him back the reins of glibc.
[0]: https://lwn.net/Articles/488847/
[1]: https://sourceware.org/ml/libc-alpha/2015-11/msg00086.html
In my implementation, the safeclib, I made it safe just recently.
strncpy(buf, str, buflen - 1);
if (buflen > 0)
buf[buflen - 1] = '\0'; strncpy(buf, str, n);
if (n > 0)
buf[n - 1]= '\0';
Which still checks `n` after calling `strncpy`, like you mentioned, but doesn't have the underflow problem.I was pretty proud of the "string for already allocated buffer" part of this lib tbh.
Anyway, i'll never take C for a four-hour coding challenge, but for bigger, non-web project, C is really awesome, thank to BLAS and to the fact that most languages implement mostly painless C ffi.
The only thing I wish the standard library would have is an `asprintf` which would return an allocated buffer. I've written my own, but the easy version utilizing regular `sprintf` requires calling `sprintf` twice (Once to get the length, and once to actually put the string in the allocated buffer). A regular supported version would be much more efficient.
https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/...
if (n > 0) {
strncpy(buf, str, n - 1);
buf[n - 1] = '\0';
}
Of course, the error isn't super huge considering if you're using a zero-length buffer as a null-terminated string you're going to have other problems. But they still do check for 0, so IMO it's worth correcting.edit: As asveikau notes below, this is not the case
Given this snippet, incorrect. Stack allocated buffers aren't zeroed out as in the above declaration.
strlcpy has a better API, though sadly it's not standard on Linux.