Stack smashing detected

2019-01-01 12:17发布

I am executing my a.out file. After execution the program runs for some time then exits with the message:

**** stack smashing detected ***: ./a.out terminated*
*======= Backtrace: =========*
*/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted*

What could be the possible reasons for this and how do I rectify it?

9条回答
孤独总比滥情好
2楼-- · 2019-01-01 12:47

It means that you wrote to some variables on the stack in an illegal way, most likely as the result of a Buffer overflow.

查看更多
柔情千种
3楼-- · 2019-01-01 12:48

You could try to debug the problem using valgrind:

The Valgrind distribution currently includes six production-quality tools: a memory error detector, two thread error detectors, a cache and branch-prediction profiler, a call-graph generating cache profiler, and a heap profiler. It also includes two experimental tools: a heap/stack/global array overrun detector, and a SimPoint basic block vector generator. It runs on the following platforms: X86/Linux, AMD64/Linux, PPC32/Linux, PPC64/Linux, and X86/Darwin (Mac OS X).

查看更多
忆尘夕之涩
4楼-- · 2019-01-01 12:59

Minimal example with disassembly analysis

a.c:

void myfunc(char *const src, int len) {
    int i;
    for (i = 0; i < len; ++i) {
        src[i] = 42;
    }
}

int main(void) {
    char arr[] = {'a', 'b', 'c', 'd'};
    int len = sizeof(arr);
    myfunc(arr, len + 1);
    return 0;
}

Compile and run:

gcc -fstack-protector -g -O0 -std=c99  a.c
ulimit -c unlimited && rm -f core
./a.out

fails as desired:

*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)

Tested on Ubuntu 16.04, GCC 6.4.0.

Disassembly

Now we look at the disassembly:

objdump -D a.out

which contains:

int main (void){
  400579:       55                      push   %rbp
  40057a:       48 89 e5                mov    %rsp,%rbp

  # Allocate 0x10 of stack space.
  40057d:       48 83 ec 10             sub    $0x10,%rsp

  # Put the 8 byte canary from %fs:0x28 to -0x8(%rbp),
  # which is right at the bottom of the stack.
  400581:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  400588:       00 00 
  40058a:       48 89 45 f8             mov    %rax,-0x8(%rbp)

  40058e:       31 c0                   xor    %eax,%eax
    char arr[] = {'a', 'b', 'c', 'd'};
  400590:       c6 45 f4 61             movb   $0x61,-0xc(%rbp)
  400594:       c6 45 f5 62             movb   $0x62,-0xb(%rbp)
  400598:       c6 45 f6 63             movb   $0x63,-0xa(%rbp)
  40059c:       c6 45 f7 64             movb   $0x64,-0x9(%rbp)
    int len = sizeof(arr);
  4005a0:       c7 45 f0 04 00 00 00    movl   $0x4,-0x10(%rbp)
    myfunc(arr, len + 1);
  4005a7:       8b 45 f0                mov    -0x10(%rbp),%eax
  4005aa:       8d 50 01                lea    0x1(%rax),%edx
  4005ad:       48 8d 45 f4             lea    -0xc(%rbp),%rax
  4005b1:       89 d6                   mov    %edx,%esi
  4005b3:       48 89 c7                mov    %rax,%rdi
  4005b6:       e8 8b ff ff ff          callq  400546 <myfunc>
    return 0;
  4005bb:       b8 00 00 00 00          mov    $0x0,%eax
}
  # Check that the canary at -0x8(%rbp) hasn't changed after calling myfunc.
  # If it has, jump to the failure point __stack_chk_fail.
  4005c0:       48 8b 4d f8             mov    -0x8(%rbp),%rcx
  4005c4:       64 48 33 0c 25 28 00    xor    %fs:0x28,%rcx
  4005cb:       00 00 
  4005cd:       74 05                   je     4005d4 <main+0x5b>
  4005cf:       e8 4c fe ff ff          callq  400420 <__stack_chk_fail@plt>

  # Otherwise, exit normally.
  4005d4:       c9                      leaveq 
  4005d5:       c3                      retq   
  4005d6:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  4005dd:       00 00 00 

Notice the handy comments automatically added by objdump's artificial intelligence module.

If you run this program multiple times through GDB, you will see that:

  • the canary gets a different random value every time
  • the last loop of myfunc is exactly what modifies the address of the canary

The canary randomized by setting it with %fs:0x28, which contains a random value as explained at:

Debug attempts

From now on, we modify the code:

    myfunc(arr, len + 1);

to be instead:

    myfunc(arr, len);
    myfunc(arr, len + 1); /* line 12 */
    myfunc(arr, len);

to be more interesting.

We will then try to see if we can pinpoint the culprit + 1 call with a method more automated than just reading and understanding the entire source code.

gcc -fsanitize=address

If you recompile with this flag and run the program, it outputs:

#0 0x4008bf in myfunc /home/cirsan01/test/a.c:4
#1 0x40099b in main /home/cirsan01/test/a.c:12
#2 0x7fcd2e13d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#3 0x400798 in _start (/home/cirsan01/test/a.out+0x40079

followed by some more colored output.

This clearly pinpoints the problematic line 12.

Thank Google.

Valgrind SGCheck

As mentioned by others, Valgrind is not good at solving this kind of problem.

It does have an experimental tool called SGCheck:

SGCheck is a tool for finding overruns of stack and global arrays. It works by using a heuristic approach derived from an observation about the likely forms of stack and global array accesses.

So I was not very surprised when it did not find the error:

valgrind --tool=exp-sgcheck ./a.out

The error message should look like this apparently: Valgrind missing error

GDB

An important observation is that if you run the program through GDB, or examine the core file after the fact:

gdb -nh -q a.out core

then, as we saw on the assembly, GDB should point you to the end of the function that did the canary check:

(gdb) bt
#0  0x00007f0f66e20428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007f0f66e2202a in __GI_abort () at abort.c:89
#2  0x00007f0f66e627ea in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7f0f66f7a49f "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007f0f66f0415c in __GI___fortify_fail (msg=<optimized out>, msg@entry=0x7f0f66f7a481 "stack smashing detected") at fortify_fail.c:37
#4  0x00007f0f66f04100 in __stack_chk_fail () at stack_chk_fail.c:28
#5  0x00000000004005f6 in main () at a.c:15
(gdb) f 5
#5  0x00000000004005f6 in main () at a.c:15
15      }
(gdb)

And therefore the problem is likely in one of the calls that this function made.

Next we try to pinpoint the exact failing call by first single stepping up just after the canary is set:

  400581:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  400588:       00 00 
  40058a:       48 89 45 f8             mov    %rax,-0x8(%rbp)

and watching the address:

(gdb) p $rbp - 0x8
$1 = (void *) 0x7fffffffcf18
(gdb) watch 0x7fffffffcf18
Hardware watchpoint 2: *0x7fffffffcf18
(gdb) c
Continuing.

Hardware watchpoint 2: *0x7fffffffcf18

Old value = 1800814336
New value = 1800814378
myfunc (src=0x7fffffffcf14 "*****?Vk\266", <incomplete sequence \355\216>, len=5) at a.c:3
3           for (i = 0; i < len; ++i) {
(gdb) p len
$2 = 5
(gdb) p i
$3 = 4
(gdb) bt
#0  myfunc (src=0x7fffffffcf14 "*****?Vk\266", <incomplete sequence \355\216>, len=5) at a.c:3
#1  0x00000000004005cc in main () at a.c:12

Now, this does leaves us at the right offending instruction: len = 5 and i = 4, and in this particular case, did point us to the culprit line 12.

However, the backtrace is corrupted, and contains some trash. A correct backtrace would look like:

#0  myfunc (src=0x7fffffffcf14 "abcd", len=4) at a.c:3
#1  0x00000000004005b8 in main () at a.c:11

so maybe this could corrupt the stack and prevent you from seeing the trace.

Also, this method requires knowing what is the last call of the canary checking function otherwise you will have false positives, which will not always be feasible, unless you use reverse debugging.

查看更多
登录 后发表回答