Я выполняю свой файл .out. После выполнения программа работает некоторое время, а затем завершается с сообщением:
**** stack smashing detected ***: ./a.out terminated*
*======= Backtrace: =========*
*/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted*
Каковы могут быть возможные причины этого и как мне это исправить?
Ответы:
Разрушение стека здесь на самом деле вызвано механизмом защиты, используемым gcc для обнаружения ошибок переполнения буфера. Например, в следующем фрагменте:
Компилятор (в данном случае gcc) добавляет переменные защиты (называемые канареями), которые имеют известные значения. Входная строка размером больше 10 приводит к повреждению этой переменной, в результате чего SIGABRT завершает программу.
Чтобы получить представление, вы можете попробовать отключить эту защиту gcc используя опцию
-fno-stack-protector
во время компиляции. В этом случае вы получите другую ошибку, скорее всего, ошибку сегментации, когда вы пытаетесь получить доступ к неправильной ячейке памяти. Обратите внимание, что-fstack-protector
всегда следует включать для сборок релиза, так как это функция безопасности.Вы можете получить некоторую информацию о точке переполнения, запустив программу с отладчиком. Valgrind плохо работает с ошибками, связанными со стеком, но, как и отладчик, он может помочь вам точно определить местоположение и причину сбоя.
источник
Пример минимального воспроизведения с анализом разборки
main.c
GitHub вверх по течению .
Скомпилируйте и запустите:
терпит неудачу по желанию:
Протестировано на Ubuntu 16.04, GCC 6.4.0.
разборка
Теперь посмотрим на разборку:
который содержит:
Обратите внимание на полезные комментарии автоматически добавлен
objdump
«S искусственного интеллекта модуль .Если вы запустите эту программу несколько раз через GDB, вы увидите, что:
myfunc
именно то, что изменяет адрес канарейкиКанарейка рандомизируется путем установки его с помощью
%fs:0x28
, который содержит случайное значение, как описано в:Попытки отладки
С этого момента мы модифицируем код:
быть вместо:
быть более интересным
Затем мы попытаемся выяснить, сможем ли мы точно определить
+ 1
вызов преступника с помощью более автоматизированного метода, чем просто чтение и понимание всего исходного кода.gcc -fsanitize=address
включить Google Sanitizer адреса (ASan)Если вы перекомпилируете с этим флагом и запустите программу, она выдаст:
с последующим более цветным выводом.
Это ясно указывает на проблемную линию 12.
Исходный код для этого находится по адресу: https://github.com/google/sanitizers, но, как мы видели из примера, он уже передан в GCC.
ASan также может обнаруживать другие проблемы с памятью, такие как утечки памяти: как найти утечку памяти в коде / проекте C ++?
Valgrind SGCheck
Как уже упоминалось , Вальгринд не очень хорошо решает подобные проблемы.
У него есть экспериментальный инструмент под названием SGCheck :
Поэтому я не очень удивился, когда не нашел ошибку:
Сообщение об ошибке должно выглядеть следующим образом: ошибка Valgrind отсутствует
GDB
Важным наблюдением является то, что если вы запускаете программу через GDB или просматриваете
core
файл после факта:затем, как мы видели на сборке, GDB должен указать вам на конец функции, которая выполняла канарейку:
И, следовательно, проблема, скорее всего, в одном из вызовов этой функции.
Затем мы попытаемся точно определить точный сбойный вызов, сделав первый шаг сразу после установки канарейки:
и смотреть адрес:
Теперь, это оставляет нас в правильном нарушении инструкции:
len = 5
иi = 4
, и в данном конкретном случае, указало нам на виновную линию 12.Однако обратная трассировка повреждена и содержит некоторое количество мусора. Правильная обратная трассировка будет выглядеть так:
так что, возможно, это может повредить стек и помешать вам увидеть след.
Кроме того, этот метод требует знания того, что является последним вызовом функции проверки канарейки, в противном случае у вас будут ложные срабатывания, что не всегда возможно, если вы не используете обратную отладку .
источник
Пожалуйста, посмотрите на следующую ситуацию:
Когда я отключил защиту от разрушения стека, ошибок не обнаружено, что должно было произойти, когда я использовал «./a.out wepassssssssssssssssss»
Таким образом, чтобы ответить на ваш вопрос выше, было отображено сообщение «** smashing dismashing: xxx», потому что ваш защитник разрушения стека был активен и обнаружил, что в вашей программе переполнение стека.
Просто выясните, где это происходит, и исправьте это.
источник
Вы можете попытаться отладить проблему, используя valgrind :
источник
Это означает, что вы записали некоторые переменные в стеке недопустимым образом, скорее всего, в результате переполнения буфера .
источник
Один сценарий будет в следующем примере:
В этой программе вы можете перевернуть строку или ее часть, если вы, например, вызываете
reverse()
что-то вроде этого:Если вы решили передать длину массива следующим образом:
Тоже работает нормально.
Но когда вы делаете это:
Вы получаете:
И это происходит потому, что в первом коде длина
arr
проверяется внутри,revSTR()
что хорошо, но во втором коде, где вы передаете длину:Длина теперь больше, чем фактическая длина, которую вы передаете, когда говорите
arr + 2
.Длина
strlen ( arr + 2 )
! =strlen ( arr )
.источник
gets
иscrcpy
. Интересно, могли бы мы минимизировать, если дальше. Я бы хотя бы избавился отstring.h
сsize_t len = sizeof( arr );
. Протестировано на gcc 6.4, Ubuntu 16.04. Я также привел бы неудачный пример с тем,arr + 2
чтобы минимизировать вставку копий.Повреждения стека, обычно вызванные переполнением буфера. Вы можете защищаться от них, программируя в обороне.
Всякий раз, когда вы обращаетесь к массиву, ставьте перед ним assert, чтобы гарантировать, что доступ не выходит за пределы. Например:
Это заставляет вас думать о границах массива, а также о добавлении тестов для их запуска, если это возможно. Если некоторые из этих утверждений могут потерпеть неудачу при обычном использовании, превратите их в регулярные
if
.источник
Я получил эту ошибку при использовании malloc () для выделения некоторой памяти для struct *. После некоторой отладки кода я, наконец, использовал функцию free (), чтобы освободить выделенную память, и впоследствии сообщение об ошибке исчезло :)
источник
Другим источником разрушения стека является (неправильное) использование
vfork()
вместоfork()
.Я только что отладил случай, когда дочерний процесс не смог выполнить
execve()
целевой исполняемый файл и возвратил код ошибки, а не вызов_exit()
.Поскольку
vfork()
порождал этого потомка, он возвращался, в то время как фактически все еще выполнялся в пространстве процесса родителя, не только повреждая стек родителя, но и вызывал печать двух несопоставимых наборов диагностики с помощью «нисходящего» кода.Изменение
vfork()
вfork()
фиксированные оба проблемы, как и изменение ребенкаreturn
заявления_exit()
вместо этого.Но поскольку дочерний код предшествует
execve()
вызову вызовами других подпрограмм (для установки uid / gid, в данном конкретном случае), он технически не соответствует требованиям дляvfork()
, поэтому изменение его использованияfork()
здесь правильно.(Обратите внимание, что проблемный
return
оператор на самом деле не был закодирован как таковой - вместо этого был вызван макрос, и этот макрос решил, следует ли использовать глобальную переменную_exit()
илиreturn
основываться на ней. Поэтому не сразу было очевидно, что дочерний код не соответствуетvfork()
использования. )Для получения дополнительной информации см .:
Разница между fork (), vfork (), exec () и clone ()
источник