arrayscfunctionstrcatstack-smash

Why does referencing this char array cause Stack smashing, using C?


The program takes a pointer to a char array and an int. The char array consists of two numbers, separated by a space.

The use of the function is to read the values of the char array as integers and replace them with the multiplied value of the input:

void read_and_mul(char *arr, int scale) {
    int num_arr[2];                         // saving values in a int[]
    char *ch = strtok(arr, " ");
    num_arr[0] = scale * (atoi(ch));
    ch = strtok(NULL, " ");
    num_arr[1] = scale * (atoi(ch));

    memset(arr, 0, sizeof(arr));      // deleting the previous value of the char[]

    char one[sizeof(int)];
    char two[sizeof(int)];
    sprintf(one, "%d", num_arr[0]);   // saving the altered numbers as chars
    sprintf(two, "%d", num_arr[1]);

    strcat(arr, one);                // writing the multiplied values to the string
    strcat(arr, " ");
    strcat(arr, two);
}

However if I use it like this, it works as intended but causes a stack-smashing:

int main(int argc, char *argv[]) {
    char str[] = "1 2";
    read_and_mul((char *)&str, 10);
    printf("string after call: %s\n", str);
    return 0;
}

The terminal message in CLion is:

*** stack smashing detected ***: terminated
string after call: 10 20

Is this a potential error or an IDE warning and what is causing it?


Solution

  • The function has to build the string "10 20" that contains 6 characters including the terminating null character '\0'.

    But you are trying to store this string in an array that has only 4 characters

    char str[] = "1 2";
    

    due to these statements

    strcat(arr,one);                // writing the multiplied values to the string
    strcat(arr, " ");
    strcat(arr,two);
    

    As a result the function already invokes undefined behavior.

    Another problem is in this call of memset:

    memset(arr,0,sizeof(arr));
    

    The variable arr within the function has the pointer type char *. If sizeof( char * ) is equal to 8 then again there is an attempt to write to memory outside the array.

    And the function should not depend on magic numbers like 2 used in this declaration

    int num_arr[2];
    

    You should always try to write more general functions.

    To resolve the problem you should within the function allocate dynamically a new character array where the result string will be stored and return a pointer to the array from the function.

    Also, pay attention to that it will be more clear and correct to write

    read_and_mul( str, 10 );
    

    instead of

    read_and_mul((char *) &str, 10);
    

    Here is a demonstration program that shows a possible approach to solve the task.

    #include <stdio.h>
    #include <stdlib.h>
    
    char * read_and_mul( const char *s, int scale )
    {
        size_t n = 0;
        size_t length = 0;
    
        const char *tmp = s;
        int value;
    
        for (char *endptr; value = strtol( tmp, &endptr, 10 ), endptr != tmp; tmp = endptr)
        {
            ++n;
            length += snprintf( NULL, 0, "%d", value * scale );
        }
    
        length += n == 0 ? 1 : n;
    
        char *result = calloc( length, sizeof( char ) );
    
        if (result != NULL)
        {
            const char *tmp = s;
            int first = 1;
    
            for (char *pos = result, *endptr; value = strtol( tmp, &endptr, 10 ), endptr != tmp; tmp = endptr)
            {
                if (!first)
                {
                    *pos++ = ' ';
                }
                else
                {
                    first = 0;
                }
    
                pos += sprintf( pos, "%d", value * scale );
            }
        }
    
        return result;
    }
    
    int main( void )
    {
        char s[] = "1 2 3 4 5 6 7 8 9 10";
    
        char *result = read_and_mul( s, 10 );
    
        if (result) printf( "\"%s\"\n", result);
    
        free( result );
    }
    

    The program output is

    "10 20 30 40 50 60 70 80 90 100"
    

    As in general multiplication of two integers can result in overflow then to avoid such a situation you may change these statements

    length += snprintf( NULL, 0, "%d", value * scale );
    pos += sprintf( pos, "%d", value * scale );
    

    to the following

    length += snprintf( NULL, 0, "%lld", ( long long int )value * scale );
    pos += sprintf( pos, "%lld", ( long long int )value * scale );