On better understanding the strncpy() function behavior - c

In the Linux manpage of strncpy I read:
If the length of src is less than n, strncpy() writes additional
null bytes to dest to ensure that a total of n bytes are written.
In this limit case (no \0 at the end of both strings) where n>4:
char dest[8]="qqqqqqqq";
char src[4] = "abcd";
strncpy(dest, src, 5);
printing dest char by char gives "abcdqqqq".
As src has no \0 in it, no \0 is copied from the src to dest, but if I understand correctly the man page, other 4 characters should be copied in any case, and they should be \0s.
Moreover, if src is "abc" (so it is NUL terminated), dest contains "abc\0\0qqq".
I add the whole code I used to test (yes it is going to the 8-th character to look at it also):
#include <stdio.h>
#include <string.h>
int main()
{
char dest[8]="qqqqqqqq";
char src[4] = "abcd"; // "abc"
strncpy(dest, src, 5);
for (int i=0; i<9; i++)
printf("%2x ", dest[i]);
putchar('\n');
return 0;
}
Is this a faulty implementation or do I miss something?

If the length of src
That's the problem, your src is not a null terminated string so strncpy has no idea how long it is. The manpage also has a sample code showing how strncpy works: it stops at a null byte and if it doesn't find one it simply keeps reading until it reaches n. And in your case it reads the 4 characters of src and gets the fifth from dest because it's next in memory. (You can try printing src[4], nothing will stop you and you'll get the q from dest. Isn't C nice?)

You seem to be using strncpy() with a source argument that is not a string but an array of characters. So why do you expect it to behave sensibly? The manual on Linux clearly says it takes a string as the source argument, and that it copies it "including the terminating null byte" (which presumes such a byte). Note that there is no such thing as a "non-terminated string". An array of characters is just an array of characters, and string functions are not guaranteed to work on it.
However, the specific behavior that you are observing is expected and documented.
The rationale for strncpy() in the POSIX standard contains the following sentence:
If there is no NUL character byte in the first n bytes of the array pointed to by s2, the result is not null-terminated.
... where s2 is what you call src.
The manual for strncpy() on at least Ubuntu and OpenBSD contains similar wordings.

Let’s look at this logically, using quotes from the GNU man page dated 2017-09-15, as included in Debian 10:
If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.
As you correctly state (sort of), n == 5. So what is the “length of src”? As we should all know, C strings are null-terminated. The man page hints at this:
The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0') … The strncpy() function is similar, except that at most n bytes of src are copied.
Your string is not null-terminated, though. So after reading the 4 bytes you defined, strncpy tries to read a fifth byte, and what does it find? The first byte of dest, apparently (actually, this is implementation-dependent, as far as I know). It still has not found a null to terminate the string, so does it keep reading? No, because as stated above:
a total of n bytes are written
and
at most n bytes of src are copied.
So it copies the 5 bytes "abcdq" into dest.
You said:
other 4 characters should be copied in any case, and they should be \0s
This implies a total length of 8 bytes. Where would you get this from? This is the “length” (or at least declared array length) of dest, not src, and in any case would be overridden by the fact that n < 8.
The case where src is null-terminated is straightforward.
So no, the implementation is not faulty.
Actually, you got lucky:
The strings may not overlap
They do here, so, technically, anything could happen. Indeed, a comment on the question says that Valgrind warns about this.
and the destination string dest must be large enough to receive the copy. Beware of buffer overruns!
This carelessness with string lengths should be a warning to take extra care, before you create a buffer overrun.
On another note, while it is possible that a function like strncpy would have a bug, it is extremely unlikely. Standard implementations have been thoroughly tested in a wide variety of environments. Any apparent bug is much more likely to a bug in your own code, or a lack of understanding of your own code, as is the case here.

Related

Can `snprintf()` read out of bounds when a string argument is not null-terminated?

I have the following piece of code, which a colleague claims may contain an out-of-bounds read, which I do not agree with. Could you help settle this argument and explain why?
char *test_filename = malloc(Size + 1);
sprintf(test_filename, "");
if (Size > 0 && Data)
snprintf(test_filename, Size + 1, "%s", Data);
where Data is a non-null-terminated string of type const uint8_t *Data and Size is the size of Data, i.e., number of bytes in Data, of type size_t.
It may read out-of-bounds because the format string is %s, perhaps?
Your colleague is correct. Perhaps unintuitively, snprintf(test_filename, Size + 1, "%s", Data) is guaranteed to read bytes starting at Data until a 0 byte is encountered, in your case typically resulting in an out-of-bounds read.
It will only write Size of these bytes to test_filename and null terminate them, respecting the size limit of the destination; but it will continue to read on. The reason for that is a design choice which enables the caller to determine the needed destination size for dynamic allocation before anything is actually written: snprintf() returns the number of bytes which would be written if the destination had infinite space. This feature is supposed to be used with a destination size of 0 (and potentially a null pointer as the destination). This functionality is useful for arguments which are not strings: With numbers etc. the size of the output is difficult to predict (e.g. locale dependent) and best left to the function at run time.
At the same time the return value indicates whether the output was truncated: If it is greater or equal to the size parameter, not all of the input was used in the output. In your case, what was left out were the bytes starting a Data[Size] and ending with the first 0 byte, or a segmentation fault ;-).
Suggestion for a fix: First of all it is unclear why you would use the printf family to print a string; simply copy it. And then Andrew has a point in his comments that since Datais not null terminated it is not really a string (even if all bytes are printable); so don't start fiddling with strcpy and friends but simply memcpy() the bytes, and null terminate manually.
Oh, and the preceding sprintf(test_filename, ""); does not serve any discernible purpose. If you want to write a null byte to *Data, simply do so; but since you are not using strcat, which would rely on a terminated destination string to extend, it is quite unnecessary.
from the MAN page for snprintf()
The functions snprintf() and vsnprintf() write at most size bytes (including the terminating null byte ('\0')) to str.
Note the at most size bytes
This means that snprintf() will stop transferring bytes after the parameter Size bytes are transferred.
this statement;
sprintf(test_filename, "");
is completely unneeded and has no effect on the operation of the second call to snprintf()
If you want to result in a 'proper' string, suggest:
char *test_filename = calloc( sizeof( char ), Size + 1);
if (Size > 0 && Data)
snprintf(test_filename, Size, "%s", Data);
however, the function: snprintf() keeps reading until a NUL byte is encountered. This can create problems, upto and including a seg fault event.
The function: memcpy() is made for this kind of job. Suggest replacing the call to snprintf() with
memcpy( test_filename, Data, Size );

ANSI C strncpy messing up screen output and other variables' values

Using ANSI C, screen is messing up after the strncpy. Also if I try to print any int variable values become incorrect. However if I move the print line before strncpy everything is fine.
Does anybody know why?
#define TICKET_NAME_LEN 40
struct stock_data
{
char ticket_name[TICKET_NAME_LEN+1];
};
struct stock_data user_input;
char tname[TICKET_NAME_LEN+1] = "testing it";
strncpy(user_input.ticket_name, tname, TICKET_NAME_LEN);
The symptoms you are describing are the classic ones for a copy that is out of control. However, the real source of your problem is almost certainly not in the code you show.
The only possible issue with the code you show is that strncpy() does not guarantee that the output (target) string is null terminated. This won't hurt with the code shown (it doesn't do anything untoward), but other code that expects the string to be null terminated that blithely copies it without ensuring that there's space may go trampling other memory because the string is not null terminated.
If the input (source) string is longer than the space specified (in this case more than TICKET_NAME_LEN bytes long), then user_input.ticket_name will not be null terminated except by accident. If it is shorter, then user_input.ticket_name will be null padded to the length TICKET_NAME_LEN bytes.
If this is the problem, a very simple fix is to add the line:
user_input.ticket_name[TICKET_NAME_LEN] = '\0';
after (or even before, but it is more conventional to do it after) the strncpy().
However, to run into this problem, you'd have to be trying to copy a name of 41 or more characters into the ticket name member of the structure.
It is much more likely that something else is the cause of your trouble.
ISO/IEC 9899:2011 §7.24.2.4 The strncpy function
¶2 The strncpy function copies not more than n characters (characters that follow a null
character are not copied) from the array pointed to by s2 to the array pointed to by
s1.308) If copying takes place between objects that overlap, the behavior is undefined.
¶3 If the array pointed to by s2 is a string that is shorter than n characters, null characters
are appended to the copy in the array pointed to by s1, until n characters in all have been
written.
308) Thus, if there is no null character in the first n characters of the array pointed to by s2, the result will not be null-terminated.

strncpy doesn't always null-terminate

I am using the code below:
char filename[ 255 ];
strncpy( filename, getenv( "HOME" ), 235 );
strncat( filename, "/.config/stationlist.xml", 255 );
Get this message:
(warning) Dangerous usage of strncat - 3rd parameter is the maximum number of characters to append.
(error) Dangerous usage of 'filename' (strncpy doesn't always null-terminate it).
I typically avoid using str*cpy() and str*cat(). You have to contend with boundary conditions, arcane API definitions, and unintended performance consequences.
You can use snprintf() instead. You only have to be contend with the size of the destination buffer. And, it is safer in that it will not overflow, and will always NUL terminate for you.
char filename[255];
const char *home = getenv("HOME");
if (home == 0) home = ".";
int r = snprintf(filename, sizeof(filename), "%s%s", home, "/.config/stationlist.xml");
if (r >= sizeof(filename)) {
/* need a bigger filename buffer... */
} else if (r < 0) {
/* handle error... */
}
You may overflow filename with your strncat call.
Use:
strncat(filename, "/.config/stationlist.xml",
sizeof filename - strlen(filename) - 1);
Also be sure to null terminate your buffer after strncpy call:
strncpy( filename, getenv( "HOME" ), 235 );
filename[235] = '\0';
as strncpy does not null terminate its destination buffer if the length of the source is larger or equal than the maximum number of character to copy.
man strncpy has this to say:
Warning: If there is no null byte among the first n bytes
of src, the string placed in dest will not be null terminated.
If it encounters the 0 byte in the source before it exhausts the maximum length, it will be copied. But if the maximum length is reached before the first 0 in the source, the destination will not be terminated. Best to make sure it is yourself after strncpy() returns...
Both strncpy() and (even more so) strncat() have non-obvious behaviours and you would be best off not using either.
strncpy()
If your target string is, for sake of argument, 255 bytes long, strncpy() will always write to all 255 bytes. If the source string is shorter than 255 bytes, it will zero pad the remainder. If the source string is longer than 255 bytes, it will stop copying after 255 bytes, leaving the target without a null terminator.
strncat()
The size argument for most of the 'sized' functions (strncpy(), memcpy(), memmove(), etc) is the number of bytes in the target string (memory). With strncat(), the size is the amount of space left after the end of the string that's already in the target. Therefore, you can only safely use strncat() when you know both how big the target buffer is (S) and how long the target string currently is (L). The safe parameter to strncat() is then S-L (we'll worry about whether there's an off-by-one some other time). But given that you know L, there is no point in making strncat() skip the L characters; you could have passed target+L as the place to start, and simply copied the data. And you could use memmove() or memcpy(), or you could use strcpy(), or even strncpy(). If you don't know the length of the source string, you've got to be confident that it makes sense to truncate it.
Analysis of code in question
char filename[255];
strncpy(filename, getenv("HOME"), 235);
strncat(filename, "/.config/stationlist.xml", 255);
The first line is unexceptionable unless the size is deemed too small (or you run the program in a context where $HOME is not set), but that's out of scope for this question. The call to strncpy() does not use sizeof(filename) for the size, but rather an arbitrarily small number. It isn't the end of the world, but there's no guarantee that the last 20 bytes of the variable are zero bytes (or even that any of them is a zero byte), in general. Under some circumstances (filename is a global variable, previously unused) the zeros might be guaranteed.
The strncat() call tries to append 24 characters to the end of the string in filename that might already be 232-234 bytes long, or that might be arbitrarily longer than 235 bytes. Either way, that is a guaranteed buffer overflow. The usage of strncat() also falls directly into the trap about its size. You've said that it is OK to add up to 255 characters beyond the end of what's already in filename, which is blatantly wrong (unless the string from getenv("HOME") happens to be empty).
Safer code:
char filename[255];
static const char config_file[] = "/.config/stationlist.xml";
const char *home = getenv("HOME");
size_t len = strlen(home);
if (len > sizeof(filename) - sizeof(config_file))
...error file name will be too long...
else
{
memmove(filename, home, len);
memmove(filename+len, config_file, sizeof(config_file));
}
There will be those who insist that 'memcpy() is safe because the strings cannot overlap', and at one level they're correct, but overlap should be a non-issue and with memmove(), it is a non-issue. So, I use memmove() all the time...but I've not done the timing measurements to see how big of a problem it is, if it is a problem at all. Maybe the other people have done the measurements.
Summary
Don't use strncat().
Use strncpy() cautiously (noting its behaviour on very big buffers!).
Plan to use memmove() or memcpy() instead; if you can do the copy safely, you know the sizes necessary to make this sensible.
1) Your strncpy does not necessarily null-terminate filename. In fact, if getenv("HOME") is longer than 235 characters and getenv("HOME")[234] is not a 0, it won't.
2) Your strncat() may attempt to extend filename beyond 255 characters, because, as it says,
3rd parameter is the maximum number of characters to append.
(not the total allowed length of dst)
strncpy(Copied_to,Copied_from,sizeof_input) outputs garbage values after the character array (not used for string type). To solve it output using a for loop traversing the character array rather than simply using cout<<var;
for(i=0;i<size;i++){cout<<var[i]}
I couldn't find a work around for traversal on a windows system using minGW compiler.
Null termination does not solve the problem. Online compilers works just fine.

strncpy introduces funny character

When I run some code on my machine then it behaves as I expect it to.
When I run it on a colleagues it misbehaves. This is what happens.
I have a string with a value of:
croc_data_0001.idx
when I do a strncpy on the string providing 18 as the length my copied string has a value of:
croc_data_0001.idx♂
If I do the following
myCopiedString[18]='\0';
puts (myCopiedString);
Then the value of the copied string is:
croc_data_0001.idx
What could be causing this problem and why does it get resolved by setting the last char to \0?
According to http://www.cplusplus.com/reference/clibrary/cstring/strncpy/
char * strncpy ( char * destination, const char * source, size_t num );
Copy characters from string
Copies the first num characters of source to destination. If the end
of the source C string (which is signaled by a null-character) is
found before num characters have been copied, destination is padded
with zeros until a total of num characters have been written to it.
No null-character is implicitly appended to the end of destination, so destination will only be null-terminated if the length
of the C string in source is less than num.
Thus, you need to manually terminate your destination with '\0'.
strncpy does not want the size of the string to be copied, but the size of the target buffer.
In your case, the target buffer is 1 too short, disabling strncpy to zero-terminate the string. So everything that is behind the string resp. position 18 and is non-zero will be treated as belonging to the string.
Normally, functions taking a buffer size are called with exactly that, i. e.
char dest[50];
strncpy(dest, "croc_data_0001.idx", sizeof dest);
With this and an additional
dest[sizeof dest - 1] = '\0';
the string will always be 0-terminated.
I think the C standard describes this function in a clearer manner than the links others have posted.
ISO 9899:2011
7.24.2.4 The strncpy function
char *strncpy (char * restrict s1,
const char * restrict s2,
size_t n);
The strncpy function copies not more than n characters (characters that follow a null
character are not copied) from the array pointed to by s2 to the array pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.
If the array pointed to by s2 is a string that is shorter than n characters, null characters
are appended to the copy in the array pointed to by s1, until n characters in all have been
written.
how much space have been alloted to myCopiedString variable? if its more than the length of the source string, then make sure you use bzero to clear out the destination variable.
strncpy does not always add a \0. See http://www.cplusplus.com/reference/clibrary/cstring/strncpy/
So either clear out your destination buffer beforehand, or always add the \0 yourself, or use strcpy.
If the question is: "why does uninitialised memory on my machine have different content than on another machine", well, one can only guess.
Edit changed wording somewhat; see comment.

Why should you use strncpy instead of strcpy?

Edit: I've added the source for the example.
I came across this example:
char source[MAX] = "123456789";
char source1[MAX] = "123456789";
char destination[MAX] = "abcdefg";
char destination1[MAX] = "abcdefg";
char *return_string;
int index = 5;
/* This is how strcpy works */
printf("destination is originally = '%s'\n", destination);
return_string = strcpy(destination, source);
printf("after strcpy, dest becomes '%s'\n\n", destination);
/* This is how strncpy works */
printf( "destination1 is originally = '%s'\n", destination1 );
return_string = strncpy( destination1, source1, index );
printf( "After strncpy, destination1 becomes '%s'\n", destination1 );
Which produced this output:
destination is originally = 'abcdefg'
After strcpy, destination becomes '123456789'
destination1 is originally = 'abcdefg'
After strncpy, destination1 becomes '12345fg'
Which makes me wonder why anyone would want this effect. It looks like it would be confusing. This program makes me think you could basically copy over someone's name (eg. Tom Brokaw) with Tom Bro763.
What are the advantages of using strncpy() over strcpy()?
The strncpy() function was designed with a very particular problem in mind: manipulating strings stored in the manner of original UNIX directory entries. These used a short fixed-sized array (14 bytes), and a nul-terminator was only used if the filename was shorter than the array.
That's what's behind the two oddities of strncpy():
It doesn't put a nul-terminator on the destination if it is completely filled; and
It always completely fills the destination, with nuls if necessary.
For a "safer strcpy()", you are better off using strncat() like so:
if (dest_size > 0)
{
dest[0] = '\0';
strncat(dest, source, dest_size - 1);
}
That will always nul-terminate the result, and won't copy more than necessary.
strncpy combats buffer overflow by requiring you to put a length in it. strcpy depends on a trailing \0, which may not always occur.
Secondly, why you chose to only copy 5 characters on 7 character string is beyond me, but it's producing expected behavior. It's only copying over the first n characters, where n is the third argument.
The n functions are all used as defensive coding against buffer overflows. Please use them in lieu of older functions, such as strcpy.
While I know the intent behind strncpy, it is not really a good function. Avoid both. Raymond Chen explains.
Personally, my conclusion is simply to avoid strncpy and all its friends if you are dealing with null-terminated strings. Despite the "str" in the name, these functions do not produce null-terminated strings. They convert a null-terminated string into a raw character buffer. Using them where a null-terminated string is expected as the second buffer is plain wrong. Not only do you fail to get proper null termination if the source is too long, but if the source is short you get unnecessary null padding.
See also Why is strncpy insecure?
strncpy is NOT safer than strcpy, it just trades one type of bugs with another. In C, when handling C strings, you need to know the size of your buffers, there is no way around it. strncpy was justified for the directory thing mentioned by others, but otherwise, you should never use it:
if you know the length of your string and buffer, why using strncpy ? It is a waste of computing power at best (adding useless 0)
if you don't know the lengths, then you risk silently truncating your strings, which is not much better than a buffer overflow
What you're looking for is the function strlcpy() which does terminate always the string with 0 and initializes the buffer. It also is able to detect overflows. Only problem, it's not (really) portable and is present only on some systems (BSD, Solaris). The problem with this function is that it opens another can of worms as can be seen by the discussions on
http://en.wikipedia.org/wiki/Strlcpy
My personal opinion is that it is vastly more useful than strncpy() and strcpy(). It has better performance and is a good companion to snprintf(). For platforms which do not have it, it is relatively easy to implement.
(for the developement phase of a application I substitute these two function (snprintf() and strlcpy()) with a trapping version which aborts brutally the program on buffer overflows or truncations. This allows to catch quickly the worst offenders. Especially if you work on a codebase from someone else.
EDIT: strlcpy() can be implemented easily:
size_t strlcpy(char *dst, const char *src, size_t dstsize)
{
size_t len = strlen(src);
if(dstsize) {
size_t bl = (len < dstsize-1 ? len : dstsize-1);
((char*)memcpy(dst, src, bl))[bl] = 0;
}
return len;
}
The strncpy() function is the safer one: you have to pass the maximum length the destination buffer can accept. Otherwise it could happen that the source string is not correctly 0 terminated, in which case the strcpy() function could write more characters to destination, corrupting anything which is in the memory after the destination buffer. This is the buffer-overrun problem used in many exploits
Also for POSIX API functions like read() which does not put the terminating 0 in the buffer, but returns the number of bytes read, you will either manually put the 0, or copy it using strncpy().
In your example code, index is actually not an index, but a count - it tells how many characters at most to copy from source to destination. If there is no null byte among the first n bytes of source, the string placed in destination will not be null terminated
strncpy fills the destination up with '\0' for the size of source, eventhough the size of the destination is smaller....
manpage:
If the length of src is less than n, strncpy() pads the remainder of
dest with null bytes.
and not only the remainder...also after this until n characters is
reached. And thus you get an overflow... (see the man page
implementation)
This may be used in many other scenarios, where you need to copy only a portion of your original string to the destination. Using strncpy() you can copy a limited portion of the original string as opposed by strcpy(). I see the code you have put up comes from publib.boulder.ibm.com.
That depends on our requirement.
For windows users
We use strncpy whenever we don't want to copy entire string or we want to copy only n number of characters. But strcpy copies the entire string including terminating null character.
These links will help you more to know about strcpy and strncpy
and where we can use.
about strcpy
about strncpy
the strncpy is a safer version of strcpy as a matter of fact you should never use strcpy because its potential buffer overflow vulnerability which makes you system vulnerable to all sort of attacks

Resources