简体   繁体   中英

Memory confusion for strncpy in C

This week one problem was discussed by my colleague regarding memory:

Sample code 1:

int main()
{
    #define Str "This is String."
    char dest[1];
    char buff[10];

    strncpy(dest, Str, sizeof(Str));
    printf("Dest: %s\n", dest);
    printf("Buff: %s\n", buff);
}

Output:

Dest: This is String.
Buff: his is String.

Sample Code 2:

int main()
{
    #define Str "This is String."
    char dest[1];
    //char buff[10];

    strncpy(dest, Str, sizeof(Str));
    printf("Dest: %s\n", dest);
    //printf("Buff: %s\n", buff);
}

Output:

Dest: This is String.
*** stack smashing detected ***: ./test terminated
Aborted (core dumped)

I am not understanding why i am getting that output in case 1? as buff is not even used in strncpy, and if i comment variable buff it will give stack smashing detected but with output for dest. Also for buff why i am getting Output as "his as string."

This is an interesting problem that we all wish to understand at some point or the other. The problem that occurs here is known as “Buffer Overflow” . The side effects of this problem can vary from system to system (also referred as undefined behavior ). Just to explain you what might be happening in your case lets assume that the memory layout of the variables in your program is as below

在此输入图像描述

Note above representation is just for understanding and doesn't show actual representation for any architecture. After the strncpy command is executed the contents of this memory region are as below

在此输入图像描述

Now when you print buff you can see that the start address of buf now has 'h' in it. The printf starts printing this until it finds a null character which is past the buff memory region. Hence you get 'his is String' when you print buf. However note that program 1 doesn't generate a stack smashing error because of stack guard (which is system/implementation) dependent. So if you execute this code on a system that doesn't include this the Program 1 will also crash (You can test this by increasing Str to a long string).

In case of Program 2 the strncpy just goes past the stack guard over writing the return address from main and hence you get a crash.

Hope this helps.

PS All above description is for understanding and doesn't show any actual system representation.

The C Standard specifies strncpy this way:

7.24.2.4 The strncpy function

Synopsis

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

These semantics are widely misunderstood: strncpy is not a safe version of strcpy , the destination array is NOT null terminated if the source string is longer than the n argument.

In your example, this n argument is larger than the size of the destination array: the behavior is undefined because characters are written beyond the end of the destination array.

You can observe this is the first example as the buff array is positioned by the compiler just after the end of the dest array in automatic storage (aka on the stack ) and is overwritten by strncpy . The compiler could use a different method so the observed behavior is by no means guaranteed.

My advice is to NEVER USE THIS FUNCTION . An opinion shared by other C experts such as Bruce Dawson: Stop using strncpy already!

You should favor a less error-prone function such as this one:

// Utility function: copy with truncation, return source string length
// truncation occurred if return value >= size argument
size_t bstrcpy(char *dest, size_t size, const char *src) {
    size_t i;
    /* copy the portion that fits */
    for (i = 0; i + 1 < size && src[i] != '\0'; i++) {
        dest[i] = src[i];
    }
    /* null terminate destination unless size == 0 */
    if (i < size) {
        dest[i] = '\0';
    }
    /* compute necessary length to allow truncation detection */
    while (src[i] != '\0') {
        i++;
    }
    return i;
}

You would use it this way in your example:

int main(void) {
    #define Str "This is String."
    char dest[12];

    // the size of the destination array is passed
    // after the pointer, just as for `snprintf`
    bstrcpy(dest, sizeof dest, Str);
    printf("Dest: %s\n", dest);
    return 0;
}

Output:

This is a S

The location of the variables on your stack is :-

0. dest
1. buff
12. canary
16. Return address

When buff is present, it protects the canary and return address from damage.

This is undefined behavior (writing more data into dest than fits). The canary has a special random value within it, that is set up when the function starts, and is tested before executing the return instruction. This adds some form of protection to buffer overruns.

Examples of undefined nature, is that the program may have crashed with "illegal instruction @ xxxxxx" due to not having a canary. The program may have behaved normally, if the return address was separate from the variable location.

The stack will typically grow in a negative direction on most current CPUs. Also the location of dest vs buff is compiler dependent. It may have switched them round, or if (for example) you took away the second printf, the compiler may have removed the storage for dest , as it may have decided it was not correctly used.

strncpy(dest, Str, sizeof(Str));

Your dest is only one byte, so here you are writing in memory which you are not supposed to and this invokes undefined behavior. In other words, anything can happen depending on how compiler implement these things.

The most probable reason for buf getting written is that the compiler places dest after buf . So when you are writing past the boundary of dest you are writing to buf . When you comment out buf it leads to crash.

But as I said before, you may get completely different behavior if a different compiler or even different version of same compiler is used.

Summary: Never do anything that invokes undefined behavior. In strncpy you are supposed to use sizeof(dest) , not sizeof(src) and allocate sufficient memory for destination so that data from source is not lost.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM