Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

strncpy leading to segmentation fault

I am just messing around with strncpy.

My program looks like this

typedef struct
{
    char from_str[10];
}test;

main ()
{

    test     s1;
    memset(&s1,0,sizeof(test));
    char       src[10]="himansh";
    char       dest[10];

    memset(dest,0,10);
    src[3]='\0';

    printf("src is %s and strlen is %d \n",
            src,strlen(src));

    fflush(stdout);

    strncpy(s1.from_str,src,100);

    printf("s1.from_str is %s , src is %s \n",
            s1.from_str,src);
    return 1;

}

Here before I do strncpy I have added a "\0" character in "src" string, length of "src" string becomes 3 , destination array is of size 10 .But in strncpy I have put number of bytes to be copied as 100.

This means my source string is NULL terminated. Now strncpy like any string function should try to copy only 3 bytes even if the number of bytes I provide is more than 3 (in this case 100). It does that, but I get a segmentation fault too.

My result is shown below

src is him and strlen is 3
s1.from_str is him , src is him
Segmentation fault (core dumped)

Why is this segmentation fault happening over here.

Can any one help me out here.

like image 529
Himanshu Gupta Avatar asked Dec 28 '12 06:12

Himanshu Gupta


3 Answers

I could point you to man pages, websites, etc, but ultimately what matters is the C standard itself. As part of the standard runtime library, the usage and behavior is defined in C99-§7.23.2.4 as:

#include <string.h>
char *strncpy(char * restrict s1,
      const char * restrict s2,
      size_t n);

Description 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.

Returns The strncpy function returns the value of s1.

There is significant implied information here, the most important being: strncpy() will NOT terminate your destination string with a null character if the source string length (not including its null character terminator) meets or exceeds the specified destination buffer length).

Furthermore, though clearly specified in the standard (see above), it continues to confound me how many engineers are NOT aware that strncpy() tail-fills the destination string buffer with null characters until the specified length n is reached when the source string length is less than the destination buffer size. This draws the following inescapable conclusion:

The strncpy() API will ALWAYS write n characters to the address referenced by the destination buffer.

In your case, because the target-buffer is only 10-chars wide, you're writing 90 additional characters past the defined-end of writable memory, and thus walking into the land of undefined behavior.

At this point you have to be asking yourself "So whats the use?" There is an arguably fundamental use-case. It allows you to copy up to n chars to the target buffer with the predictability of knowing you won't overrun past n chars. Period. Ultimately, though, you want a null-terminated string, so the proper usage is this:

char dst[ N ]; 
strncpy(dst, src, N-1);
dst[N-1] = 0;

where N is the hard-length of the dst buffer in chars and is greater-than-or-equal to 1. Note that dst could just-as-well be a dynamic-allocated memory pointer:

char *dst = malloc( N * sizeof(char) ); 
strncpy(dst, src, N-1);
dst[N-1] = 0;

With the above, you will always have a null-terminated string at dst. If the source string length is smaller than the specified target buffer length, strncpy() will tail-fill the rest of the buffer with null characters until a total of source-chars-copied + tail-filled-null-characters equals n, and the final statement is redundant. If the source string length is equal to or greater than the target buffer length, strncpy() will stop copying once N-1 chars are reached, and the final statement sets a null character at the end of the buffer. This results in a "cut-down" prefix string of the original source, but most important, it ensures you will NOT exceed the boundaries of your target buffer with a later string-API call that scans for a terminator.

The usefulness of the above technique is always debatable. I'm a C++ guy, so std::string saves my happy-self from all this insanity. But the reality is this: Sometimes you care if src isn't copied in its entirety to dst; sometimes you don't. The usefulness is very situationally dependent. For presenting string-data in a UI this won't (likely) matter. For copying a string to be used for critical data, a partial-prefix-substring isn't going to be acceptable. When the police issue an arrest warrant to "Joseph Johnson Jr.", there will be some explaining to do when his father ("Joseph Johnson") is hauled into jail because the name-buffer of the warrant-issuance software only held 15 chars.

All of that said, your segmentation fault comes down to this statement:

strncpy(s1.from_str,src, 100); // length parameter is wrong.

Recall the bold statement above: "strncpy() will ALWAYS write n characters to the address referenced by the destination buffer.". This means the above code will always write 100 chars to the target buffer, which in your case is only 10-chars wide, thus undefined behavior and likely ker-boom.

Rectify this by doing the following if the target buffer is a fixed-length character array:

strncpy(s1.from_str,src, sizeof(s1.from_str)/sizeof(s1.from_str[0])-1);
s1.from_str[ sizeof(s1.from_str)/sizeof(s1.from_str[0])-1 ] = 0;

See the prior usage for how to do this for dynamic string of length `N chars.

like image 56
WhozCraig Avatar answered Nov 12 '22 18:11

WhozCraig


From http://www.cplusplus.com/reference/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.

So though the source string length is less than the size of the destination buffer size, yet because of the passing behavior of strncpy, it tries to overlay the rest of the characters beyond the destination buffer size causing Segmentation fault

Instead of copying beyond the 100 characters' the size should be equal to the maximum permissible size in the destination buffer, so, you could have written

strncpy(s1.from_str,src,sizeof(s1.from_str)/sizeof(s1.from_str[0]) - 1); Actual size -1 to accomodate the null terminator 

or better to write a _countof macro

#define _countof(s) (sizeof(s)/sizeof(s[0]))
................
strncpy(s1.from_str,src,_countof(s1.from_str) - 1);
like image 6
Abhijit Avatar answered Nov 12 '22 17:11

Abhijit


See: http://www.manpagez.com/man/3/strncpy/

The stpncpy() and strncpy() functions copy at most n characters from s2 into s1. If s2 is less than n characters long, the remainder of s1 is filled with `\0' characters. Otherwise, s1 is not terminated.

The remainder is filled....

So:

strncpy( s1.from_str, src, 10 );
like image 1
Mario The Spoon Avatar answered Nov 12 '22 16:11

Mario The Spoon