Stack smashing détecté

j'exécute mon A. out file. Après l'exécution le programme tourne pendant un certain temps puis sort avec le message:

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

quelles pourraient être les raisons possibles de cette situation et comment puis-je y remédier?

177
demandé sur Sathya 2009-08-28 12:17:50

9 réponses

L'écrasement de la pile

est en fait causé par un mécanisme de protection utilisé par gcc pour détecter les erreurs de débordement de tampon. Par exemple dans l'extrait suivant:

#include <stdio.h>

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

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

le compilateur, (dans ce cas gcc) ajoute des variables de protection (appelées canaries) qui ont des valeurs connues. Une chaîne de caractères de taille supérieure à 10 provoque la corruption de cette variable, ce qui amène SIGABRT à mettre fin au programme.

pour obtenir un aperçu, vous pouvez essayer désactiver cette protection de gcc en utilisant l'option -fno-stack-protector pendant la compilation. Dans ce cas, vous obtiendrez une erreur différente, probablement une erreur de segmentation que vous tentez d'accéder illégale d'un emplacement de mémoire. Notez que -fstack-protector doit toujours être activé pour les constructions release car il s'agit d'une fonctionnalité de sécurité.

vous pouvez obtenir quelques informations sur le point de débordement en exécutant le programme avec un débogueur. Valgrind ne fonctionne pas bien avec les erreurs liées à la pile, mais comme un débogueur, il peut vous aider à identifier l'emplacement et la raison de l'accident.

256
répondu sud03r 2015-10-25 00:11:37

Veuillez jeter un oeil à la situation suivante:

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:$ 

lorsque j'ai désactivé le protecteur de rupture de pile, aucune erreur n'a été décelée, ce qui aurait dû se produire lorsque j'ai utilisé "./A. out wepasssssssssssssssssssss "

donc pour répondre à votre question ci-dessus, le message" * * pile smashing detected : xxx " a été affiché parce que votre protecteur de pile smashing était actif et a trouvé qu'il y a un débordement de pile dans votre programme.

trouvez juste où cela se produit, et réparez-le.

14
répondu wearetherock 2016-06-17 14:26:06

vous pouvez essayer de déboguer le problème en utilisant valgrind :

la distribution de Valgrind actuellement comprend six outils de production de qualité: un détecteur d'erreur de mémoire, deux thread des détecteurs d'erreurs, un cache et direction de la prévision du profileur, profileur de cache générateur d'appel-graphique, et un profileur de tas. Il comprend également deux outils expérimentaux: tas/pile/réseau mondial de dépassement détecteur , et un Bloc de base SimPoint vecteur générateur. Il s'exécute sur l' plates-formes suivantes: x86 / Linux, AMD64 / Linux, PPC32/ Linux, PPC64 / Linux, et X86 / Darwin (Mac OS X).

7
répondu hlovdal 2009-08-28 08:24:31

cela signifie que vous avez écrit à certaines variables sur la pile d'une manière illégale, très probablement à la suite d'un dépassement de tampon .

3
répondu starblue 2009-08-28 09:05:37

exemple Minimal avec analyse de démontage

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

compiler et exécuter:

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

échoue comme désiré:

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

démontage

Maintenant, nous regardons le démontage:

objdump -D a.out

qui contient:

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    "151940920"x10,%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   "151940920"x61,-0xc(%rbp)
  400594:       c6 45 f5 62             movb   "151940920"x62,-0xb(%rbp)
  400598:       c6 45 f6 63             movb   "151940920"x63,-0xa(%rbp)
  40059c:       c6 45 f7 64             movb   "151940920"x64,-0x9(%rbp)
    int len = sizeof(arr);
  4005a0:       c7 45 f0 04 00 00 00    movl   "151940920"x4,-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    "151940920"x0,%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 

noter les commentaires pratiques automatiquement ajoutés par objdump 's module d'intelligence artificielle .

Si vous exécutez ce programme plusieurs fois par GDB, vous verrez que:

  • le canari obtient une autre valeur aléatoire à chaque fois
  • la dernière boucle de myfunc est exactement ce qui modifie l'adresse du canari

maintenant le grand la question Est comment le canari est initialisé à %fs:0x28 pour commencer, donc je vous laisse à:

"tentatives de débogage

à partir de Maintenant on, on modifie le code:

    myfunc(arr, len + 1);

d'être à la place:

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

pour être plus intéressant.

nous allons ensuite essayer de voir si nous pouvons identifier le coupable + 1 appel avec une méthode plus automatisée que la simple lecture et la compréhension de l'ensemble du code source.

gcc -fsanitize=address

avec ce drapeau, il fonctionne brutalement, et les sorties:

#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

suivi d'une sortie plus colorée. Merci Google .

Valgrind SGCheck

comme mentionné par d'autres , Valgrind n'est pas bon à résoudre ce genre de problème.

il a un outil expérimental appelé SGCheck :

SGCheck est un outil pour trouver des dépassements de pile et de tableaux globaux. Il fonctionne en utilisant une approche heuristique dérivée d'une observation sur les formes probables des accès stack et Global array.

donc je n'ai pas été très surpris quand il n'a pas trouvé l'erreur:

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

le message d'erreur doit ressembler à ceci: Valgrind erreur manquante

GDB

une observation importante est que si vous exécutez le programme par GDB, ou examiner le core fichier après le fait:

gdb -nh -q a.out core

alors, comme nous l'avons vu sur l'Assemblée, GDB devrait vous indiquer la fin de la fonction qui a fait le contrôle canarien:

(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)

Et donc le problème est probablement dans l'un des appels cette fonction fait.

ensuite, nous essayons de mettre en évidence l'appel exact d'échec par Premier le pas à pas en place juste après l'archipel des canaries est défini:

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

et en regardant l'adresse:

(gdb) p $rbp - 0x8
 = (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 "*****?Vk6", <incomplete sequence 56>, len=5) at a.c:3
3           for (i = 0; i < len; ++i) {
(gdb) p len
 = 5
(gdb) p i
 = 4
(gdb) bt
#0  myfunc (src=0x7fffffffcf14 "*****?Vk6", <incomplete sequence 56>, len=5) at a.c:3
#1  0x00000000004005cc in main () at a.c:12

maintenant, cela nous laisse à droite l'instruction offensante: len = 5 et i = 4 , et dans ce cas particulier, nous a indiqué le coupable ligne 12.

cependant, le backtrace est corrompu, et contient des déchets. Une rétrotrace correcte ressemblerait à:

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

donc peut-être que cela pourrait corrompre la pile et vous empêcher de voir la trace.

aussi, cette méthode exige de savoir quel est le dernier appel de la fonction de vérification canari sinon vous aurez des faux positifs, ce qui ne sera pas toujours faisable, à moins que vous utiliser le débogage inversé .

testé sur Ubuntu 16.04, gcc 6.4.0.

3

quelles pourraient être les raisons possibles de ceci et comment puis-je le rectifier?

L'un des scénarios serait dans l'exemple suivant:

#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--;
    }
}

dans ce programme vous pouvez inverser une chaîne ou une partie de la chaîne si vous appelez par exemple reverse() avec quelque chose comme ceci:

reverse( arr + 2 );

Si vous décidez de passer la longueur du tableau comme ceci:

#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--;
    }
}

fonctionne très bien aussi.

mais quand vous faites cela:

revSTR( arr + 2, len );

Vous recevez:

==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)

et cela se produit parce que dans le premier code, la longueur de arr est cochée à l'intérieur de revSTR() qui est très bien, mais dans le deuxième code où vous passez la longueur:

revSTR( arr + 2, len );

la longueur est maintenant plus longue que la longueur réelle que vous passez quand vous dites arr + 2 .

Longueur de strlen ( arr + 2 ) != strlen ( arr ) .

1
répondu Michi 2018-01-20 13:46:55

corruptions de pile causées habituellement par des débordements de tampon. Vous pouvez vous défendre contre eux en programmant défensivement.

chaque fois que vous accédez à un tableau, mettez une assert devant elle pour s'assurer que l'accès n'est pas hors limite. Par exemple:

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

cela vous fait penser aux limites du tableau et vous fait aussi penser à ajouter des tests pour les déclencher si possible. Si certaines de ces assertions peuvent échouer au cours de l'utilisation normale les transformer en un if .

1
répondu Calmarius 2018-02-23 12:01:51

j'ai eu cette erreur en utilisant malloc() pour allouer un peu de mémoire à une structure * après en avoir dépensé un peu pour déboguer le code, j'ai finalement utilisé la fonction free() pour libérer la mémoire allouée et par la suite le message d'erreur parti:)

0
répondu djangodude 2017-09-03 17:16:48

L'utilisation de vfork() au lieu de fork() est une autre source de fracas de cheminée .

je viens de déboguer un cas de cela, où le processus enfant a été incapable de execve() l'exécutable cible et retourné un code d'erreur plutôt que d'appeler _exit() .

parce que vfork() avait engendré cet enfant, il est retourné alors qu'il exécutait toujours dans l'espace de processus du parent, non seulement corrompant le parent stack, mais provoquant deux ensembles disparates de diagnostics à être imprimé par le code "en aval".

, en remplaçant vfork() par fork() , a réglé les deux problèmes, tout comme le fait de remplacer l'énoncé return de l'enfant par _exit() .

mais comme le code enfant précède l'appel execve() avec des appels à d'autres routines( pour définir l'uid / gid, dans ce cas particulier), il ne répond pas techniquement aux exigences pour vfork() , donc le changer utiliser fork() est correct ici.

(notez que l'énoncé problématique return n'était pas en fait codé comme tel -- au lieu de cela, une macro a été invoquée, et cette macro a décidé si _exit() ou return basé sur une variable globale. Il n'était donc pas tout de suite évident que le code enfant n'était pas conforme à l'usage vfork() .)

pour plus d'informations, voir:

la différence entre fork(), vfork(), exec() et clone()

0
répondu James Craig Burley 2018-06-05 01:07:31