Pile empilée détectée

J’exécute mon fichier a.out. Après exécution, le programme s’exécute pendant un certain temps puis quitte 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 cela et comment puis-je le rectifier?

Stack Smashing est en fait dû à un mécanisme de protection utilisé par gcc pour détecter les erreurs de dépassement de mémoire tampon. Par exemple dans l’extrait de code suivant:

 #include  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 canaris) qui ont des valeurs connues. Une chaîne d’entrée de taille supérieure à 10 entraîne la corruption de cette variable, ce qui entraîne la fin du programme SIGABRT.

Pour avoir un aperçu, vous pouvez essayer de désactiver cette protection de gcc en utilisant l’option -fno-stack-protector lors de la compilation. Dans ce cas, vous obtiendrez une erreur différente, probablement une erreur de segmentation lorsque vous essayez d’accéder à un emplacement de mémoire illégal. Notez que l’ -fstack-protector doit toujours être activée pour les versions validées, car il s’agit d’une fonctionnalité de sécurité.

Vous pouvez obtenir des informations sur le sharepoint débordement en exécutant le programme avec un débogueur. Valgrind ne fonctionne pas bien avec les erreurs liées à la stack, mais comme un débogueur, il peut vous aider à identifier l’emplacement et la raison de la panne.

Veuillez regarder la situation suivante:

 ab@cd-x:$ cat test_overflow.c #include  #include  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 stack, aucune erreur n’a été détectée, ce qui aurait dû se produire lorsque j’ai utilisé “./a.out wepassssssssssssssssss”

Donc, pour répondre à votre question ci-dessus, le message “** stack smashing detecté: xxx” a été affiché car votre protecteur de stack était actif et a constaté un dépassement de stack dans votre programme.

Il suffit de savoir où cela se produit et de le réparer.

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

La dissortingbution Valgrind comprend actuellement six outils de qualité de production: un détecteur d’erreur de mémoire, deux détecteurs d’erreur de thread, un profileur de cache et de prédiction de twig, un profileur de cache générateur de graphe d’appel et un profileur de tas. Il comprend également deux outils expérimentaux: un détecteur de dépassement de stack / stack / masortingce globale et un générateur de vecteur de bloc de base SimPoint. Il s’exécute sur les plates-formes suivantes: X86 / Linux, AMD64 / Linux, PPC32 / Linux, PPC64 / Linux et X86 / Darwin (Mac OS X).

Cela signifie que vous avez écrit de manière illégale sur certaines variables de la stack, probablement à la suite d’un débordement de tampon .

Exemple minimal avec parsing de déassembly

ac:

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

Comstackr et exécuter:

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

échoue à volonté:

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

Déassembly

Maintenant, nous regardons le déassembly:

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

Notez les commentaires utiles ajoutés automatiquement par le module d'intelligence artificielle d' objdump .

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

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

Maintenant, la grande question est de savoir comment le canari est initialisé à %fs:0x28 pour commencer, alors je vous laisse à:

Tentatives de débogage

A partir de maintenant, nous modifions le code:

  myfunc(arr, len + 1); 

être à la place:

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

ê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 compréhension du code source complet.

gcc -fsanitize=address

Avec ce drapeau, ça marche brutalement et produit:

 #0 0x4008bf in myfunc /home/cirsan01/test/ac:4 #1 0x40099b in main /home/cirsan01/test/ac: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 pour résoudre ce type de problème.

Il possède un outil expérimental appelé SGCheck :

SGCheck est un outil de recherche de dépassements de stack et de tableaux globaux. Cela fonctionne en utilisant une approche heuristique dérivée d'une observation sur les formes probables d'access aux stacks et aux tableaux globaux.

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: Erreur manquante Valgrind

GDB

Une observation importante est que si vous exécutez le programme via GDB, ou examinez le fichier core après le fait:

 gdb -nh -q a.out core 

alors, comme nous l'avons vu sur l'assemblage, GDB devrait vous indiquer la fin de la fonction qui a effectué le contrôle canari:

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

Et par conséquent, le problème est probablement dû à l’un des appels effectués par cette fonction.

Ensuite, nous essayons de localiser exactement l’échec de l’appel par le premier simple passage juste après le réglage du canari:

  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 regarder l'adresse:

 (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", , len=5) at ac:3 3 for (i = 0; i < len; ++i) { (gdb) p len $2 = 5 (gdb) pi $3 = 4 (gdb) bt #0 myfunc (src=0x7fffffffcf14 "*****?Vk\266", , len=5) at ac:3 #1 0x00000000004005cc in main () at ac:12 

Maintenant, cela nous laisse à la bonne instruction fautive: len = 5 et i = 4 , et dans ce cas particulier, nous a indiqué la ligne 12.

Cependant, la trace est corrompue et contient des déchets. Un backtrace correct ressemblerait à ceci:

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

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

En outre, cette méthode nécessite 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 possible, sauf si vous utilisez le débogage inversé .

Testé sur Ubuntu 16.04, gcc 6.4.0.

Quelles pourraient être les raisons possibles de cela et comment puis-je le rectifier?

Un scénario serait dans l’exemple suivant:

 #include  #include  #include  void swap ( char *a , char *b ); void revSTR ( char *const src ); int main ( void ){ char arr[] = "ABCDE"; 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  #include  #include  void swap ( char *a , char *b ); void revSTR ( char *const src, size_t len ); int main ( void ){ char arr[] = "ABCDE"; 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 bien aussi.

Mais quand tu fais ça:

 revSTR( arr + 2, len ); 

Vous obtenez:

 ==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 car dans le premier code, la longueur de arr est vérifiée à l'intérieur de revSTR() ce qui est 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 ) .

Les corruptions de stacks sont généralement causées par des dépassements de tampon. Vous pouvez vous défendre contre eux en programmant défensivement.

Chaque fois que vous accédez à un tableau, mettez-le en avant pour vous assurer que l’access n’est pas hors limites. Par exemple:

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

Cela vous fait penser à des limites de tableau et vous fait aussi penser à append des tests pour les déclencher si possible. Si certaines de ces affirmations peuvent échouer lors d'une utilisation normale, transformez-les en un if normal.

J’ai eu cette erreur en utilisant malloc () pour allouer de la mémoire à une structure * après en avoir débogué le code, j’ai finalement utilisé la fonction free () pour libérer la mémoire allouée et le message d’erreur par la suite 🙂

Une autre source de destruction de stack est l’utilisation (incorrecte) de vfork() au lieu de fork() .

Je viens juste de déboguer un cas de ce type, où le processus enfant n’a pas pu execve() l’exécutable cible execve() et renvoyé un code d’erreur plutôt que d’appeler _exit() .

Comme vfork() avait engendré cet enfant, il vfork() tout en étant toujours en cours d’exécution dans l’espace de processus du parent, non seulement corrompant la stack du parent, mais provoquant l’impression de deux ensembles de diagnostics disparates par code “en aval”.

Changer vfork() en fork() corrigé les deux problèmes, tout comme le changement de l’instruction return l’enfant en _exit() .

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

(Notez que l’instruction return problématique n’était pas réellement codée en tant que telle – à la place, une macro a été invoquée, et cette macro a décidé s’il _exit() ou return fonction d’une variable globale. était non-conforme pour l’ vfork() de vfork() .)

Pour plus d’informations, voir:

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