Search code examples
cloopsrecursiongccbuffer-overflow

C Program with No Loop Is Unexpectedly Behaving Like a Loop


I am both confused and excited about this behavior I am getting from my C code. I do not understand how on earth this is happening! Before anything further, let's see the code-

#include <stdio.h>

int main(){

    char string[2];

    printf("Enter your string here: ");
    gets(string);
    printf("%s \n", string);

    return 0;
}

It is very clear that- nothing is special here. Actually, this was an assignment of my Computer, Data & Network Security course, where I was supposed to demonstrate BufferOverflow.

It works just fine till 13 characters; 15 or more charters causes desired BufferOverflow. The disaster happens when I enter exactly 14 characters: the code starts behaving like a loop! It's like the main function is being called again and again-

screen shot of console

I am using CodeBlocks-16.01 and GNU GCC Compiler. I have also executed the code on TutorialsPoint but did not get this problem there.


Solution

  • Your code generates a buffer overflow--a real one. Going past the end of string can overwrite the return address [on the stack] that main should return to when done.

    If it is chosen correctly, it can loop back to main or jump just about anywhere in memory. What it actually does depends upon the compiler, the linker, the loader, the address the program was loaded at.

    And, the value of the string entered (i.e.) some strings will crash, others might loop, some might produce goofy results, but not loop. One string might do X behavior in a given environment and Y behavior in another. A different string might reverse these results.

    What you really want to do is demonstrate (i.e. simulate) buffer overflow, without doing anything that will crash your program.

    Here is a safe way to do this:

    #include <stdio.h>
    
    #define SEED     0xFF                   // sentinel value
    
    // NOTE: using a struct guarantees that over will appear directly after string
    // (i.e.) over is higher in memory than string
    struct buffer {
        char string[4];                     // buffer that can overflow
        unsigned char over[80];             // safe place for the overflow
    };
    
    int
    main(void)
    {
        struct buffer buf;
        int idx;
        int over;
    
        // prefill the "overflow detection buffer" with a sentinel value (e.g. one
        // that can't be input via fgets [under normal circumstances])
        for (idx = 0;  idx < sizeof(buf.over);  ++idx)
            buf.over[idx] = SEED;
    
        printf("Enter your string here: ");
        fflush(stdout);
    
        // NOTE: this fgets will never _really_ cause any harm -- the "10" slop
        // factor guarantees this
        fgets(buf.string,sizeof(buf) - 10,stdin);
    
        // overflow is anything that ran past string into over
        over = 0;
        for (idx = 0;  idx < sizeof(buf.over);  ++idx) {
            if (buf.over[idx] != SEED) {
                over = 1;
                break;
            }
        }
    
        if (over)
            printf("buffer overflowed\n");
        else
            printf("buffer did not overflow\n");
    
        return 0;
    }
    

    UPDATE:

    you should specifically admonish against the use of gets

    Ordinarily, I would have. Because of the special nature of this question, I was on the fence about this.

    (even though he wants a buffer overflow, you should add why he may very well get one he doesn't want using the removed gets function)

    IMO, this was implied by the use of fgets in my example code, but may not have been specifically inferred. So, fair enough ...

    In my example code, using gets(buf.string) instead of the fgets could/would produce the same [desired] effect. However, this would still be unsafe because there is still no limit on the length read. It could run past the total struct length sizeof(string) + sizeof(over) and produce a real buffer overflow, just as before.

    Since you were trying to cause a buffer overflow, it's easier to code with gets, but you get the undesired behavior.

    [As others have pointed out] gets is deprecated for that very reason. If you just wanted a normal usage, replace gets(string) with fgets(string,sizeof(string),stdin) So, never use gets and always use fgets.