Why doesn't my program crash when I write past the end of an array?

前端 未结 9 1242
-上瘾入骨i
-上瘾入骨i 2020-11-22 12:47

Why does the code below work without any crash @ runtime ?

And also the size is completely dependent on machine/platform/compiler!!. I can even give upto 200 in a 64

9条回答
  •  清歌不尽
    2020-11-22 13:12

    Regarding exactly when / where a local variable buffer overflow crashes depends on a few factors:

    1. The amount of data on the stack already at the time the function is called which contains the overflowing variable access
    2. The amount of data written into the overflowing variable/array in total

    Remember that stacks grow downwards. I.e. process execution starts with a stackpointer close to the end of the memory to-be-used as stack. It doesn't start at the last mapped word though, and that's because the system's initialization code may decide to pass some sort of "startup info" to the process at creation time, and often do so on the stack.

    That is the usual failure mode - a crash when returning from the function that contained the overflow code.

    If the total amount of data written into a buffer on the stack is larger than the total amount of stackspace used previously (by callers / initialization code / other variables) then you'll get a crash at whatever memory access first runs beyond the top (beginning) of the stack. The crashing address will be just past a page boundary - SIGSEGV due to accessing memory beyond the top of the stack, where nothing is mapped.

    If that total is less than the size of the used part of the stack at this time, then it'll work just ok and crash later - in fact, on platforms that store return addresses on the stack (which is true for x86/x64), when returning from your function. That's because the CPU instruction ret actually takes a word from the stack (the return address) and redirects execution there. If instead of the expected code location this address contains whatever garbage, an exception occurs and your program dies.

    To illustrate this: When main() is called, the stack looks like this (on a 32bit x86 UNIX program):

    [ esp          ]  (which exits/terminates process)
    [ esp + 4      ] argc
    [ esp + 8      ] argv
    [ esp + 12     ] envp 
    [ ...          ]
    [ ...          ] 

    When main() starts, it will allocate space on the stack for various purposes, amongst others to host your to-be-overflowed array. This will make it look like:

    [ esp          ] 
    [ ...          ] 
    [ esp + X      ] arr[0]
    [ esp + X + 4  ] arr[1]
    [ esp + X + 8  ] arr[2]
    [ esp + X + 12 ] 
    [ ...          ] 
    
    [ old esp      ]  (which exits/terminates process)
    [ old esp + 4  ] argc
    [ old esp + 8  ] argv
    [ old esp + 12 ] envp 
    [ ...          ]
    [ ...          ] 

    This means you can happily access way beyond arr[2].

    For a taster of different crashes resulting from buffer overflows, attempt this one:

    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        int i, arr[3];
    
        for (i = 0; i < atoi(argv[1]); i++)
            arr[i] = i;
    
        do {
            printf("argv[%d] = %s\n", argc, argv[argc]);
        } while (--argc);
    
        return 0;
    }
    

    and see how different the crash will be when you overflow the buffer by a little (say, 10) bit, compared to when you overflow it beyond the end of the stack. Try it with different optimization levels and different compilers. Quite illustrative, as it shows both misbehaviour (won't always print all argv[] correctly) as well as crashes in various places, maybe even endless loops (if, e.g., the compiler places i or argc into the stack and the code overwrites it during the loop).

提交回复
热议问题