Обнаружено разрушение стека - PullRequest
203 голосов
/ 28 августа 2009

Я выполняю свой файл a.out. После выполнения программа работает некоторое время, затем завершается с сообщением:

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

Какие могут быть возможные причины для этого и как мне это исправить?

Ответы [ 9 ]

294 голосов
/ 28 августа 2009

Сбой стека здесь на самом деле вызван механизмом защиты, используемым gcc для обнаружения ошибок переполнения буфера. Например, в следующем фрагменте:

#include <stdio.h>

void func()
{
    char array[10];
    gets(array);
}

int main(int argc, char **argv)
{
    func();
}

Компилятор (в данном случае gcc) добавляет переменные защиты (называемые канареями), которые имеют известные значения. Входная строка размером больше 10 приводит к повреждению этой переменной, в результате чего SIGABRT завершает программу.

Чтобы получить представление, вы можете попробовать отключить эту защиту gcc, используя опцию -fno-stack-protector во время компиляции. В этом случае вы получите другую ошибку, скорее всего, ошибку сегментации, когда вы пытаетесь получить доступ к неправильной ячейке памяти. Обратите внимание, что -fstack-protector всегда должен быть включен для сборок релиза, так как это функция безопасности.

Вы можете получить некоторую информацию о точке переполнения, запустив программу с отладчиком. Valgrind плохо работает с ошибками, связанными со стеком, но, как и отладчик, он может помочь вам точно определить местоположение и причину сбоя.

20 голосов

Пример минимального воспроизведения с анализом разборки

main.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;
}

Скомпилируйте и запустите:

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

терпит неудачу по желанию:

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

Протестировано на Ubuntu 16.04, GCC 6.4.0.

Демонтажные

Теперь посмотрим на разборку:

objdump -D a.out

который содержит:

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 

Обратите внимание на удобные комментарии, автоматически добавляемые objdump модулем искусственного интеллекта .

Если вы запустите эту программу несколько раз через GDB, вы увидите, что:

  • канарейка каждый раз получает другое случайное значение
  • последний цикл myfunc - это именно то, что изменяет адрес канарейки

Канарейка рандомизируется, устанавливая ее с помощью %fs:0x28, которая содержит случайное значение, как описано в:

Попытки отладки

С этого момента мы модифицируем код:

    myfunc(arr, len + 1);

вместо:

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

чтобы быть более интересным.

Затем мы попытаемся выяснить, можем ли мы точно определить виновника + 1 вызова с помощью метода, более автоматизированного, чем простое чтение и понимание всего исходного кода.

gcc -fsanitize=address

Если вы перекомпилируете с этим флагом и запустите программу, она выдаст:

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

с последующим более цветным выводом.

Это четко указывает на проблемную строку 12.

Спасибо, Google .

Valgrind SGCheck

Как и , упомянутый другими , Вальгринд плохо справляется с такой задачей.

У него есть экспериментальный инструмент , который называется SGCheck :

SGCheck - инструмент для поиска переполнения стека и глобальных массивов. Он работает с использованием эвристического подхода, основанного на наблюдении за вероятными формами доступа к стеку и глобальным массивам.

Так что я не очень удивился, когда не нашел ошибку:

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

Сообщение об ошибке должно выглядеть примерно так: Ошибка Valgrind

GDB

Важным замечанием является то, что если вы запускаете программу через GDB или просматриваете файл core после факта:

gdb -nh -q a.out core

затем, как мы видели на сборке, GDB должен указать вам на конец функции, которая выполняла канарейку:

(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 main.c:15
(gdb) f 5
#5  0x00000000004005f6 in main () at main.c:15
15      }
(gdb)

И, следовательно, проблема, вероятно, связана с одним из вызовов этой функции.

Далее мы попытаемся точно определить точный сбойный вызов, сделав первый шаг сразу после установки канарейки:

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

и просмотр адреса:

(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 main.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 main.c:3
#1  0x00000000004005cc in main () at main.c:12

Теперь, это оставляет нас в правильном нарушении инструкции: len = 5 и i = 4, и в данном конкретном случае указало нам на виновную линию 12.

Однако обратная трассировка повреждена и содержит некоторое количество мусора. Правильная обратная трассировка будет выглядеть так:

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

так что, возможно, это может повредить стек и помешать вам увидеть след.

Кроме того, этот метод требует знания того, что является последним вызовом функции проверки канарейки, в противном случае у вас будут ложные срабатывания, что не всегда возможно, если только вы не используете обратную отладку .

16 голосов
/ 06 марта 2010

Пожалуйста, посмотрите на следующую ситуацию:

ab@cd-x:$ cat test_overflow.c 
#include <stdio.h>
#include <string.h>

int check_password(char *password){
    int flag = 0;
    char buffer[20];
    strcpy(buffer, password);

    if(strcmp(buffer, "mypass") == 0){
        flag = 1;
    }
    if(strcmp(buffer, "yourpass") == 0){
        flag = 1;
    }
    return flag;
}

int main(int argc, char *argv[]){
    if(argc >= 2){
        if(check_password(argv[1])){
            printf("%s", "Access granted\n");
        }else{
            printf("%s", "Access denied\n");
        }
    }else{
        printf("%s", "Please enter password!\n");
    }
}
ab@cd-x:$ gcc -g -fno-stack-protector test_overflow.c 
ab@cd-x:$ ./a.out mypass
Access granted
ab@cd-x:$ ./a.out yourpass
Access granted
ab@cd-x:$ ./a.out wepass
Access denied
ab@cd-x:$ ./a.out wepassssssssssssssssss
Access granted

ab@cd-x:$ gcc -g -fstack-protector test_overflow.c 
ab@cd-x:$ ./a.out wepass
Access denied
ab@cd-x:$ ./a.out mypass
Access granted
ab@cd-x:$ ./a.out yourpass
Access granted
ab@cd-x:$ ./a.out wepassssssssssssssssss
*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0xce0ed8]
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0xce0e90]
./a.out[0x8048524]
./a.out[0x8048545]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xc16b56]
./a.out[0x8048411]
======= Memory map: ========
007d9000-007f5000 r-xp 00000000 08:06 5776       /lib/libgcc_s.so.1
007f5000-007f6000 r--p 0001b000 08:06 5776       /lib/libgcc_s.so.1
007f6000-007f7000 rw-p 0001c000 08:06 5776       /lib/libgcc_s.so.1
0090a000-0090b000 r-xp 00000000 00:00 0          [vdso]
00c00000-00d3e000 r-xp 00000000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d3e000-00d3f000 ---p 0013e000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d3f000-00d41000 r--p 0013e000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d41000-00d42000 rw-p 00140000 08:06 1183       /lib/tls/i686/cmov/libc-2.10.1.so
00d42000-00d45000 rw-p 00000000 00:00 0 
00e0c000-00e27000 r-xp 00000000 08:06 4213       /lib/ld-2.10.1.so
00e27000-00e28000 r--p 0001a000 08:06 4213       /lib/ld-2.10.1.so
00e28000-00e29000 rw-p 0001b000 08:06 4213       /lib/ld-2.10.1.so
08048000-08049000 r-xp 00000000 08:05 1056811    /dos/hacking/test/a.out
08049000-0804a000 r--p 00000000 08:05 1056811    /dos/hacking/test/a.out
0804a000-0804b000 rw-p 00001000 08:05 1056811    /dos/hacking/test/a.out
08675000-08696000 rw-p 00000000 00:00 0          [heap]
b76fe000-b76ff000 rw-p 00000000 00:00 0 
b7717000-b7719000 rw-p 00000000 00:00 0 
bfc1c000-bfc31000 rw-p 00000000 00:00 0          [stack]
Aborted
ab@cd-x:$ 

Когда я отключил защиту от разрушения стека, ошибок не было обнаружено, что должно было произойти, когда я использовал "./a.out wepassssssssssssssssss"

Таким образом, чтобы ответить на ваш вопрос выше, было отображено сообщение «** smashing dismashing обнаружено: xxx», потому что ваш защитный механизм стека был деактивирован и обнаружил, что в вашей программе переполнение стека.

Просто выясните, где это происходит, и исправьте.

7 голосов
/ 28 августа 2009

Вы можете попытаться отладить проблему, используя valgrind :

В настоящее время дистрибутив Valgrind включает в себя шесть инструментов качества производства: детектор ошибок памяти, два потока детекторы ошибок, кеш и профилировщик прогноза ветвления, профилировщик кеша, генерирующий граф вызовов, и профилировщик кучи. Это также включает два экспериментальных инструмента: переполнение кучи / стека / глобального массива детектор и базовый блок SimPoint векторный генератор. Это работает на следующие платформы: X86 / Linux, AMD64 / Linux, PPC32 / Linux, PPC64 / Linux, и X86 / Darwin (Mac OS X).

3 голосов
/ 28 августа 2009

Это означает, что вы записали в некоторые переменные в стеке недопустимым образом, скорее всего, в результате переполнения буфера .

2 голосов
/ 20 января 2018

Какие могут быть возможные причины для этого и как мне это исправить?

Один сценарий будет в следующем примере:

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

void swap ( char *a , char *b );
void revSTR ( char *const src );

int main ( void ){
    char arr[] = "A-B-C-D-E";

    revSTR( arr );
    printf("ARR = %s\n", arr );
}

void swap ( char *a , char *b ){
    char tmp = *a;
    *a = *b;
    *b = tmp;
}

void revSTR ( char *const src ){
    char *start = src;
    char *end   = start + ( strlen( src ) - 1 );

    while ( start < end ){
        swap( &( *start ) , &( *end ) );
        start++;
        end--;
    }
}

В этой программе вы можете перевернуть строку или ее часть, если, например, вы вызываете reverse() с чем-то вроде этого:

reverse( arr + 2 );

Если вы решите передать длину массива следующим образом:

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

void swap ( char *a , char *b );
void revSTR ( char *const src, size_t len );

int main ( void ){
    char arr[] = "A-B-C-D-E";
    size_t len = strlen( arr );

    revSTR( arr, len );
    printf("ARR = %s\n", arr );
}

void swap ( char *a , char *b ){
    char tmp = *a;
    *a = *b;
    *b = tmp;
}

void revSTR ( char *const src, size_t len ){
    char *start = src;
    char *end   = start + ( len - 1 );

    while ( start < end ){
        swap( &( *start ) , &( *end ) );
        start++;
        end--;
    }
}

Работает нормально тоже.

Но когда вы сделаете это:

revSTR( arr + 2, len );

Вы получаете:

==7125== Command: ./program
==7125== 
ARR = A-
*** stack smashing detected ***: ./program terminated
==7125== 
==7125== Process terminating with default action of signal 6 (SIGABRT)
==7125==    at 0x4E6F428: raise (raise.c:54)
==7125==    by 0x4E71029: abort (abort.c:89)
==7125==    by 0x4EB17E9: __libc_message (libc_fatal.c:175)
==7125==    by 0x4F5311B: __fortify_fail (fortify_fail.c:37)
==7125==    by 0x4F530BF: __stack_chk_fail (stack_chk_fail.c:28)
==7125==    by 0x400637: main (program.c:14)

И это происходит потому, что в первом коде длина arr проверяется внутри revSTR(), что хорошо, но во втором коде, где вы передаете длину:

revSTR( arr + 2, len );

Длина теперь больше, чем фактическая длина, которую вы передаете, когда говорите arr + 2.

Длина strlen ( arr + 2 )! = strlen ( arr ).

1 голос
/ 23 февраля 2018

Повреждения стека, обычно вызванные переполнением буфера. Вы можете защищаться от них, программируя в обороне.

Всякий раз, когда вы обращаетесь к массиву, ставьте перед ним assert, чтобы гарантировать, что доступ не выходит за пределы. Например:

assert(i + 1 < N);
assert(i < N);
a[i + 1] = a[i];

Это заставляет вас думать о границах массива, а также о добавлении тестов для их запуска, если это возможно. Если некоторые из этих утверждений могут потерпеть неудачу при обычном использовании, превратите их в обычный if.

0 голосов
/ 05 июня 2018

Другим источником разрушения стека является (неправильное) использование vfork() вместо fork().

Я только что отладил случай, когда дочерний процесс не смог execve() целевой исполняемый файл и возвратил код ошибки вместо вызова _exit().

Поскольку vfork() порождал этого потомка, он вернулся, фактически все еще работая в пространстве процесса родителя, не только повреждая стек родителя, но и вызывая печать двух несопоставимых наборов диагностики с помощью "нисходящего" кода.

Изменение vfork() на fork() устранило обе проблемы, равно как и изменение оператора return ребенка на _exit().

Но поскольку дочерний код предшествует вызову execve() вызовами других подпрограмм (для установки uid / gid, в данном конкретном случае), он технически не соответствует требованиям для vfork(), поэтому изменив его на использование fork() здесь правильно.

(Обратите внимание, что проблемный оператор return на самом деле не был закодирован как таковой - вместо этого был вызван макрос, и этот макрос решил, следует ли _exit() или return на основе глобальной переменной. Сразу видно, что дочерний код не соответствует vfork() использованию.)

Для получения дополнительной информации см .:

Разница между fork (), vfork (), exec () и clone ()

0 голосов
/ 03 сентября 2017

Я получил эту ошибку при использовании malloc () для выделения некоторой памяти для структуры *, потратив некоторое время на отладку кода, я, наконец, использовал функцию free (), чтобы освободить выделенную память, и впоследствии сообщение об ошибке исчезло:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...