cstackbuffer-overflowvirtual-address-spacestack-smash

Does a segmentation fault in gdb show the physical or virtual address?


I tried to smash the stack:

int main (void) {

    int ar[5] = {1,2,3,4,5};
    for(int i =0; i<255 ; i++)
        ar[i] = 10;

    return 0;
}

with gcc -fno-stack-protector somefile.c. First question: why is there difference with fault with (SIGABRT) and without (SIGSEGV) protector, when both are accessing ilegal memory (so should have the same fault, i think). Second when objdump:

0000000000001125 <main>:
    1125:   55                      push   %rbp
    1126:   48 89 e5                mov    %rsp,%rbp
    1129:   c7 45 e0 01 00 00 00    movl   $0x1,-0x20(%rbp)
...

The start of main address is virtual 0000000000001125

BUT when compiled without protector:

Program received signal SIGSEGV, Segmentation fault.
0x0000000af7be5b6b in ?? ()

The address (0x0000000af7be5b6b) is what? virtual, physical? I can see it nowhere in disassambled (as shown above) file, so where does the address come from?

EDIT: with protector (and fault is therefor SIGABRT), the output I do not understand as well:

Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50

What is __GI_raise a macro? and what is in the parenthesis sig=sig@entry=6, does gcc add a table for faults, or what are these marks from linker?


Solution

  • why is there difference with fault with (SIGABRT) and without (SIGSEGV) protector, when both are accessing ilegal memory (so should have the same fault, i think).

    When you compile without stack protector, you simply end up overwriting some part of memory that you are not allowed to, and the program gets killed by the operating system through a SIGSEGV signal.

    However, when you compile with a stack protector, the error can be detected by the libc and in that case the __stack_chk_fail() function is called. You can see this through objdump:

     72d:   b8 00 00 00 00          mov    eax,0x0
     732:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
     736:   64 48 33 14 25 28 00    xor    rdx,QWORD PTR fs:0x28
     73d:   00 00
     73f:   74 05                   je     746 <main+0x76>
     741:   e8 3a fe ff ff          call   580 <__stack_chk_fail@plt>
     746:   c9                      leave
     747:   c3                      ret
    

    The __stack_chk_fail() function then calls __fortify_fail_abort(), which calls __libc_message() printing the error message and finally executing abort(), which sends a SIGABRT signal to the process through raise(), killing it.

    The start of main address is virtual 0000000000001125

    Wrong. That is not a virtual address, that is merely an offset inside the binary, meaning that the code you see starts at byte 0x1125 in the binary itself. When the binary is then executed, a virtual memory region is created for the program starting at some (usually randomized) base virtual address. The base virtual address will then determine the position of main() and everything else. For example main() will be at base_virtual_addr + 0x1125. You have no way of determining the real, physical address where your program is loaded in RAM, only the operating system (i.e. the kernel) knows this, and an user space program does not need to.

    You can take a look at the virtual memory map of your program while it is running under GDB with the command info proc mappings, the result is something like the following:

    (gdb) info proc mappings
    process 11084
    Mapped address spaces:
    
              Start Addr           End Addr       Size     Offset objfile
          0x555555554000     0x555555555000     0x1000        0x0 /home/marco/Desktop/a.out
          0x555555754000     0x555555755000     0x1000        0x0 /home/marco/Desktop/a.out
          0x555555755000     0x555555756000     0x1000     0x1000 /home/marco/Desktop/a.out
          0x7ffff7a3a000     0x7ffff7bcf000   0x195000        0x0 /lib/x86_64-linux-gnu/libc-2.24.so
          0x7ffff7bcf000     0x7ffff7dcf000   0x200000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
          0x7ffff7dcf000     0x7ffff7dd3000     0x4000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
          0x7ffff7dd3000     0x7ffff7dd5000     0x2000   0x199000 /lib/x86_64-linux-gnu/libc-2.24.so
          0x7ffff7dd5000     0x7ffff7dd9000     0x4000        0x0
          0x7ffff7dd9000     0x7ffff7dfc000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.24.so
          0x7ffff7fcf000     0x7ffff7fd1000     0x2000        0x0
          0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
          0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
          0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x23000 /lib/x86_64-linux-gnu/ld-2.24.so
          0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x24000 /lib/x86_64-linux-gnu/ld-2.24.so
          0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
          0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
      0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]
    

    In this case, you can see that my program (/home/marco/Desktop/a.out) starts at the virtual base address 0x555555554000.

    What is __GI_raise a macro? and what is in the parenthesis sig=sig@entry=6, does gcc add a table for faults, or what are these marks from linker?

    __GI_raise() is an implementation of the raise() function. Remember? I just mentioned it earlier when talking about the stack protector. In your case, gdb stops after the program is killed by raise(SIGABRT), and shows the exact point at which the program died, which is inside the __GI_raise() internal function used by libc to deliver the signal.

    The string sig=sig@entry=6 is just a nice way for GDB to tell you that the function was called with its only argument set to 6, which is the signal number for SIGABRT.