Take the strlcpy() function from OpenBSD and incorporate that into your source if you don't have access. Strlcpy() is a well designed substitute for strcpy.
> Now having a function like this in the standard library isn't such a bad thing in itself. It's designed to deal with a specialized data structure ...
> The problem is that the name strncpy() strongly implies that it's a "safer" version of strcpy(). It isn't. ....
> It's because strncpy()'s name implies something that it isn't that it's such a trap for the unwary. It's not a useless function, but I see far more incorrect uses of it than correct uses. This article is my modest attempt to spread the word that strncpy() isn't what you probably think it is.
This argument seems correct to me. "Safer," here, does not mean "If you swap out every use of strcpy for a correct use of strncpy, you'll have fewer bugs." As the author is defining it, "safer" includes the risk that someone will be less rigorous with their code by assuming strncpy will solve all their problems. It won't. It will solve some of their problems, yes, but they still have to analyze their problem almost as rigorously as if they were using strcpy.
If a code reviewer cares deeply about strcpy and less deeply about strncpy (and anecdotally this is a thing code reviewers do), then in practice, the use of strncpy is not safer.
Any use of strncpy that breaks would also have broken if strcpy had been used instead, and there exist some situations where strncpy would not break while strcpy would; for instance, if we just inspect a prefix of the resulting string.
That said, it's certainly the case that most breaking uses of strcpy, if naively replaced with strncpy, still break.
Yes, I agree that this statement is true. But my claim is that this is not what the article means by "safer", and the article's definition of "safer" is more useful.
That is, people who are saying "The article is wrong because it is possible to use strncpy correctly in cases where strcpy was not being used correctly" are not disagreeing with the article so much as talking past it.
Why rigorously? A blind
dest[n-1] = '\0';
always worked for me, the only assumption is that n > 0. (in larger projects and/or teams you won't stop there of course, but that's a different topic)strncpy will /terminate/ at the end of the string if it's shorter than the allocated buffer.
If, somehow, the source string is invalid it will then terminate at the specified cutoff point.
However one should argue that if it has overflowed bounds, that if the source isn't a valid string, you've already violated the contract of a secure environment and thus something has gone wrong and should be handled at a level higher than string copy preferences.
Use of strncpy, however, assumes that the source string /is/ valid, but might overflow the target buffer. In /that/ case truncation may be desired and if not the program flow reacting accordingly is probably desired. (This is C, you don't /throw/ errors, you write explicit paths.)
Since treating the destination buffer as a string is very tempting to do given that it came from a function named "str*cpy()", you've basically added another "Gotcha" to strcpy() without gaining any safety. You're basically trading one form of undefined behavior that's explicit in strcpy for another form of undefined behavior when a developer mishandles the return value of strncpy.
This would be like claiming that malloc is unsafe because it doesn't return a null terminated string.
Well, no, because this behavior is very close to the more commonly desired behavior and often indistinguishable therefrom. That makes it more of a "gotcha".
Agreed.
> arrays were zero-indexed to save one or two instructions in a compiler
Actually, I find zero-indexing more consistent whenever you need to do math with the indexes.
And thinking that strncpy will enforce a null at the end actually seems nonsensical to me. Now. I agree that it may not have at some point in my life. But this is again arguing that it is not safer than strcpy. It most certainly is. Just not completely safe. Which again, is no surprise. Why not point out that it does no checking that you passed it pointers you are allowed to write into?
I mean, I get that mistakes can be made. I even agree it is not safe. However, to claim it is not safer is akin to the people that claim Java is not safer than C because example.
> And thinking that strncpy will enforce a null at the end actually seems nonsensical to me.
That's bizarre, seeing as there are similar functions that do exactly that. I certainly understand having internalized that strncpy doesn't happen to, but I don't see how you can call it nonsensical.
It is nonsensical because I know it doesn't. I grant that that is a learned thing.
I do question how folks thought that it could do this. If you pass it n bytes with no null, how should it pick where to start putting the nulls? Last byte is somewhat intuitive, but then the behaviour will lead to string corruption pretty quickly.
You seem to be missing a distinction I would draw between "counterfactual" and "nonsensical".
The assumption that a function prefaced with "str" makes a well-formed str seems reasonable (if, it happens, misguided). When given too long a string, the function will truncate - there's not really anything else it could do. The question is only where it truncates.
> the behaviour will lead to string corruption pretty quickly.
It will lead immediately to string truncation - whether that's acceptable depends on context, and can be checked for. Is there something you're worried about that I'm missing?
My sibling comment of treating this like knives is ultimately my view. Strncpy is a very sharp tool. This makes it safer than strcpy. It is by no means safe, though. Just like sharper knives are safer, but still only safe with training and proper user.
So, that a function prefixed with str has some special treatment for strings makes sense. Otherwise just use memcpy. :)
The question is natural for how it truncates. It so happens the answer is by stopping. You imply it should do so by altering the string. I can see arguments either way.
For the rest, Truncation is a form of corruption. So I'm not sure where you are taking that line. Was it just my rhetoric? (I'm mainly typing on my phone, so using loose and likely mistyped phrases.)
> The question is natural for how it truncates. It so happens the answer is by stopping. You imply it should do so by altering the string. I can see arguments either way.
So can I, and I think that's the distinction I draw. Something is nonsensical if I really can't see any arguments for it. It's counterfactual if things could reasonably have gone that way but didn't.
> For the rest, Truncation is a form of corruption. So I'm not sure where you are taking that line. Was it just my rhetoric?
Truncation is, indeed, a form of corruption. It is perhaps acceptable in more circumstances than other forms of corruption. It wasn't exactly that I thought, from your rhetoric, that you were referring to something else. But on the off chance you were, I definitely wanted to know about it.
And yes, I would absolutely argue that malloc is unsafe. Certainly it's "less safe" in the language of this thread than calloc, and certainly it's completely type-unsafe. But more than that, if you want a string, there should be a function that allocates and returns an empty but well-formed string in one action, and malloc should be the lower-level unsafe API that this function uses. The myriad alternatives to C strings that I mentioned in my comment all support this - bstring has bfromcstr(""), GLib has g_string_new(""), Rust has String::new(), Ruby has "", and so forth.
Think of it as the argument that sharper knives are safer than duller ones. Certainly not true for untrained users. But widely true for trained ones.
It's a primitive and gives you choices. You can either:
A) call it with sizeof(target) - 1 to explicitly tell it to NOT over-write the guard byte at the end, OR
B) you can add an explicit operation to always over-write the byte at the end