Search code examples
cmultidimensional-arraydynamic-memory-allocationaddress-sanitizer

I'm getting a stack overflow and I don't understand why


I have been writing a program that converts a series of strings from the command line into one case based on the case of the first letter. The program itself works and runs fine, but when I use the address sanitizer, it keeps saying I am trying to read outside of my buffer.

Here is my code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define TRUE 1
#define FALSE 0

void print_list(char **data);

int main(int argc, char *argv[]) {
    if(argv[1] == NULL) {
        fprintf(stderr, "Error: No strings were entered\n");
        return EXIT_FAILURE;
    }

    int arg_num = 1;
    char *output[argc - 1];

    while (argv[arg_num] != NULL) {
        int count = 0;
        char *line;
        line = malloc(sizeof(char));
        int size = sizeof(char);
        int word_flag = FALSE;
        int upper_flag = FALSE;
        int lower_flag = FALSE;

        while (argv[arg_num][count] != '\0') {
            size = size + sizeof(char);
            line = realloc(line, size);

            if (argv[arg_num][count] >= 65 && argv[arg_num][count] <= 90) {
                if (word_flag == FALSE && upper_flag == FALSE) {
                    word_flag = TRUE;
                    upper_flag = TRUE;
                    line[count] = argv[arg_num][count];
                }
                else if (word_flag == TRUE && upper_flag == FALSE) {
                    line[count] = (argv[arg_num][count] + 32);
                }
                else {
                    line[count] = argv[arg_num][count];
                }
            }
            else if (argv[arg_num][count] >= 97 && argv[arg_num][count] <= 122) {
                if (word_flag == FALSE && lower_flag == FALSE) {
                    word_flag = TRUE;
                    lower_flag = TRUE;
                    line[count] = argv[arg_num][count];
                }
                else if (word_flag == TRUE && lower_flag == FALSE) {
                    line[count] = (argv[arg_num][count] - 32);
                }
                else {
                    line[count] = argv[arg_num][count];
                }
            }
            else if (argv[arg_num][count] == ' ' || argv[arg_num][count] == '\t') {
                word_flag = FALSE;
                upper_flag = FALSE;
                lower_flag = FALSE;
                line[count] = argv[arg_num][count];
            }
            else {
                line[count] = argv[arg_num][count];
            }

            count++;
        }
        
        line[count] = '\0';

        output[arg_num - 1] = line;

        arg_num++;
    }

    print_list(output);
    return EXIT_SUCCESS;
}

void print_list(char **data) {
    int count = 0;

    while (data[count] != NULL) {
        printf("%s\n", data[count]);
        free(data[count]);
        count++;
    }
}

And here is the output from the sanitizer:

==222362==ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address 0x7ffc487cc7a8 at pc 0x000000401f8f bp 0x7ffc487cc740 sp 0x7ffc487cc738
READ of size 8 at 0x7ffc487cc7a8 thread T0
    #0 0x401f8e in print_list (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401f8e)
    #1 0x401e87 in main (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401e87)
    #2 0x7f0e7364a50f in __libc_start_call_main (/lib64/libc.so.6+0x2750f)
    #3 0x7f0e7364a5c8 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x275c8)
    #4 0x401144 in _start (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401144)

Address 0x7ffc487cc7a8 is located in stack of thread T0
SUMMARY: AddressSanitizer: dynamic-stack-buffer-overflow (/home/evanlewis/Documents/School/CS/306/Homework/Assignment 2/a.out+0x401f8e) in print_list
Shadow bytes around the buggy address:
  0x1000090f18a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f18b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f18c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f18d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f18e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000090f18f0: ca ca ca ca 00[cb]cb cb cb cb cb cb 00 00 00 00
  0x1000090f1900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f1910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f1920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f1930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000090f1940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==222362==ABORTING

According to the sanitizer, the problem is in print_list, the function that prints the strings from the array storing them post-conversion. It insists that I am trying to read outside of my buffer even though I would think the while loop would prevent that from happening. After all the strings have been printed, data[count] should equate to NULL, as there are only as many rows as strings. Is there something I am misunderstanding?


Solution

  • Your print_list method relies on the data array ending with a NULL pointer, but your code in main doesn't put a NULL pointer into that array, and doesn't have enough space in the array for a final null pointer.

    For example, if you pass one argument to the program, argc will be 2 (one for the program name and one for the argument). This line:

    char *output[argc - 1];
    

    will create an array with space for 1 entry.

    When your loop in print_list loops around, it will read past the end of the array as soon as count is > 0

    while (data[count] != NULL) {
    

    One way to fix this would be to ensure your array is large enough, and ensure that the last entry is always null.

    char *output[argc];
    output[argc - 1] = null;