Stack smashing detected c как исправить

Minimal reproduction example with disassembly analysis

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); /* Cause smashing by writing one byte too many. */
    return 0;
}

GitHub upstream.

Compile and run:

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

fails as desired:

*** stack smashing detected ***: terminated
Aborted (core dumped)

Tested on Ubuntu 20.04, GCC 10.2.0.

On Ubuntu 16.04, GCC 6.4.0, I could reproduce with -fstack-protector instead of -fstack-protector-all, but it stopped blowing up when I tested on GCC 10.2.0 as per Geng Jiawen’s comment. man gcc clarifies that as suggested by the option name, the -all version adds checks more aggressively, and therefore presumably incurs a larger performance loss:

-fstack-protector

Emit extra code to check for buffer overflows, such as stack smashing attacks. This is done by adding a guard variable to functions with vulnerable objects. This includes functions that call “alloca”, and functions with buffers larger than or equal to 8 bytes. The guards are initialized when a function is entered and then checked when the function exits. If a guard check fails, an error message is printed and the program exits. Only variables that are actually allocated on the stack are considered, optimized away variables or variables allocated in registers don’t count.

-fstack-protector-all

Like -fstack-protector except that all functions are protected.

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:

  • https://unix.stackexchange.com/questions/453749/what-sets-fs0x28-stack-canary
  • Why does this memory address %fs:0x28 ( fs[0x28] ) have a random value?

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 to enable Google’s Address Sanitizer (ASan)

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

#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

followed by some more colored output.

This clearly pinpoints the problematic line 12.

The source code for this is at: https://github.com/google/sanitizers but as we saw from the example it is already upstreamed into GCC.

ASan can also detect other memory problems such as memory leaks: How to find memory leak in a C++ code/project?

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 terminatedn") 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)

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 "*****?Vk266", <incomplete sequence 355216>, 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 "*****?Vk266", <incomplete sequence 355216>, len=5) at main.c:3
#1  0x00000000004005cc in main () at main.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 main.c:3
#1  0x00000000004005b8 in main () at main.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.

ks10ks

0 / 0 / 0

Регистрация: 12.05.2015

Сообщений: 4

1

12.07.2019, 19:51. Показов 6683. Ответов 5

Метки нет (Все метки)


Студворк — интернет-сервис помощи студентам

Здравствуйте!
Пробую сделать программу, которая заполняет массив случайными числами от 10 до 100 и вычисляет строку с самой большой суммой.
Написал заполнение массива, но программа при работе с циклом пишет:
*** stack smashing detected ***: <unknown> terminated
и завершает работу. Не получается избавиться от этой ошибки.

Код следующий:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <iomanip>
 
using namespace std;
 
int main()
{
    int const array_rows = 3;
    int const array_columns = 4;
    int array1[array_rows][array_columns] = {
        {0,0,0,0},
        {0,0,0,0},
        {0,0,0,0}
    };
    int row_sum = 0; // Для вычисления суммы чисел в ячейках строк
 
    srand(time(NULL));
    cout << "Заполняем массив случайными числами от 10 до 100 и выводим его на экран:" << endl;
    for (int i = 0;i<array_rows;i++) {
        for (int j = 0;j<array_columns;j++) {
            array1[j][i] = rand()%91+10; // задаем ячейке массива случайное значение от 10 до 100
            cout << setw(4) << array1[j][i];
        }
    }
 
    cout << "Теперь посчи";
    return 0;
}



0



Programming

Эксперт

94731 / 64177 / 26122

Регистрация: 12.04.2006

Сообщений: 116,782

12.07.2019, 19:51

5

“C with Classes”

1634 / 1392 / 521

Регистрация: 16.08.2014

Сообщений: 5,824

Записей в блоге: 1

12.07.2019, 19:59

2

Лучший ответ Сообщение было отмечено ks10ks как решение

Решение

ks10ks, по ходу нарушение границ стека, вместо array1[j][i] попробуй array1[i][j]



1



Эксперт по математике/физикеЭксперт С++

2013 / 1342 / 382

Регистрация: 16.05.2013

Сообщений: 3,463

Записей в блоге: 6

12.07.2019, 22:23

3

С точки зрения памяти размер памяти выделяемый для array[j][i] и array[i][j] одинаков и непрерывен так что ошибка должно быть это только сообщение конкретного компилятора.



0



Неэпический

17813 / 10585 / 2043

Регистрация: 27.09.2012

Сообщений: 26,625

Записей в блоге: 1

12.07.2019, 22:30

4

Ilot, там массив 3×4.
Так что при j == 3 вылезем за пределы памяти массива.



0



Эксперт по математике/физикеЭксперт С++

2013 / 1342 / 382

Регистрация: 16.05.2013

Сообщений: 3,463

Записей в блоге: 6

12.07.2019, 22:43

5

Croessmah, истину глаголишь котяра. Посыпаю голову пеплом.



0



0 / 0 / 0

Регистрация: 12.05.2015

Сообщений: 4

13.07.2019, 13:10

 [ТС]

6

Цитата
Сообщение от _stanislav
Посмотреть сообщение

ks10ks, по ходу нарушение границ стека, вместо array1[j][i] попробуй array1[i][j]

Спасибо за помощь!



0



#include <stdio.h>

int get_max(int [], int );


int main(){
const int N = 14;
int  arr[] = {};
int  l;
l = get_max(arr, N);
return 0;
}




int get_max(int arr[], int size)
{
for (int i = 0; i < size; i++){
	scanf("%d", &arr[i]);
	}
int max = 0;
for (int i = 0; i < size; i++){
        if(arr[i] > max){
                max = arr[i];
        }

}
printf("%dn", max);
}

После выполнения программы, написанной на Си, выдаёт ошибку:
*** stack smashing detected ***: terminated
Аварийный останов (стек памяти сброшен на диск)


  • Вопрос задан

    более года назад

  • 3303 просмотра

Офрмляйте код нормально, пожалуйста, с отступами. Массиву нужен размер, у вас же он равен нулю. Функция get_max ничего не возвращает, хотя заявлен тип int.

Пригласить эксперта

Вышел за границу локального массива (описанного в теле функции без ключевого слова static) и затер адрес возврата мусорным значением, результат – передача управления по завершении функции get_max неизвестно куда, по итогу ось гасит процесс к чертовой матери, не давая ему даже шанса понять что произошло на данной нам господом богом планете земля, притом, что он всего лишь выполнялся и ничего плохо не сделал никому, это жестоко.


  • Показать ещё
    Загружается…

24 мая 2023, в 17:41

300 руб./за проект

24 мая 2023, в 17:38

7000 руб./за проект

24 мая 2023, в 17:29

5000 руб./за проект

Минуточку внимания

Федор Новиков

Искусственный Интеллект

(314854)


1 год назад

В языках С, С++ и многих других массивы индексируются с 0,а не с 1, как в Паскале. Выход за пределы массива ломает содержимое стека

Владимира АбрамоваУченик (94)

1 год назад

Двумерный массив разве не начинается с 1?

Федор Новиков
Искусственный Интеллект
(314854)
Владимира Абрамова, от размерности не зависит. int o[10][4] – элементы от o[0][0] до o[9][3]

Peer-2-PeerМудрец (16176)

1 год назад

Не понял!! Где задаётся и меняется значение переменной sum?

Что-то стёрлось?

Федор Новиков
Искусственный Интеллект
(314854)
Peer-2-Peer, похоже, что про суммирование забыли. Но вопрос был про развал стека

  1. the stack smashing detected Error in C
  2. Fix the stack smashing detected Error in C

Stack Smashing Detected Error in C

This tutorial highlights the cause and solves the stack smashing detected error in C.

Anyone facing this issue should understand the division of the memory and stack layout. It will help identify the causes of this error while coding in C programming.

the stack smashing detected Error in C

Usually, the compiler (we are talking about the GCC compiler here) generates this error to defend against the buffer overflow. The buffer overflow happens whenever the user input exceeds the capacity of the buffer (a temporary storage area).

In that case, the compiler throws an error saying stack smashing detected. See the following code snippet where we get the stack smashing detected error if the user inputs more than 10 characters.

Example Code:

#include<stdio.h>

int main(int argc, char **argv) {
    char array[10];
    printf("Please enter your username.");
    gets(array);
    return 0;
}

As this error surrounds the buffer overflow and stack concepts, it is crucial to understand the division of the memory and stack layout here.

The buffer overflow is a dangerous process, so the compiler gets the advantage of different protection mechanisms to guard against this error. One of the mechanisms is known as a canary, a randomly generated value.

The compiler (GCC) adds the protection variables (called canaries) that have known values. The canary is overwritten if the buffer overflows.

Further, the compiler identifies by comparing with known values that the stack is compromised and generates an error saying: stack smashing detected.

To prevent the buffer overflow protection variable and have some insights, we can disable the GCC’s protection using the -fno-stack-protector while compiling. For instance, $ gcc -o filename -fno-stack-protector.

In this way, we most likely will get the segmentation fault error because we will be trying to access the illegal memory location. Remember that the fstack-protector must be turned on for the release builds because it’s a security feature.

Fix the stack smashing detected Error in C

Now, how do we rectify this error and execute the program smoothly?

We can not say that preventing the stack protector is a solution to this problem, but it helps find some insights that we can use to rectify this error. We can also get some details about the cause of overflow by executing the program with a debugger.

The Valgrind will not work well with the stack-related issues and errors, but a debugger may help us pinpoint the reason and location for the crash. Find that buggy code and fix it to avoid severe security vulnerabilities.

Добавить комментарий