J'exécute mon fichier a.out. Après l'exécution, le programme s'exécute pendant un certain temps puis se termine 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 y remédier?
Réponses:
Le smashing de pile ici est en fait dû à un mécanisme de protection utilisé par gcc pour détecter les erreurs de dépassement de tampon. Par exemple dans l'extrait de code suivant:
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 provoque la corruption de cette variable, ce qui entraîne SIGABRT pour terminer le programme.
Pour obtenir des informations, vous pouvez essayer de désactiver cette protection de gcc à l'aide de 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 cette option-fstack-protector
doit toujours être activée pour les versions, car il s'agit d'une fonction de sécurité.Vous pouvez obtenir des informations sur le point de dépassement 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.
la source
Exemple de reproduction minimale avec analyse de démontage
principal c
GitHub en amont .
Compiler et exécuter:
échoue comme souhaité:
Testé sur Ubuntu 16.04, GCC 6.4.0.
Démontage
Maintenant, nous regardons le démontage:
qui contient:
Remarquez les commentaires pratiques ajoutés automatiquement par
objdump
le module d'intelligence artificielle de .Si vous exécutez ce programme plusieurs fois via GDB, vous verrez que:
myfunc
est exactement ce qui modifie l'adresse du canariLe canari randomisé en le définissant avec
%fs:0x28
, qui contient une valeur aléatoire comme expliqué à:Tentatives de débogage
Désormais, nous modifions le code:
être à la place:
pour être plus intéressant.
Nous essaierons ensuite de voir si nous pouvons localiser l'
+ 1
appel coupable avec une méthode plus automatisée que la simple lecture et compréhension de tout le code source.gcc -fsanitize=address
pour activer l'assainissement d'adresse de Google (ASan)Si vous recompilez avec cet indicateur et exécutez le programme, il génère:
suivi d'une sortie plus colorée.
Cela identifie clairement la ligne problématique 12.
Le code source pour cela est à: https://github.com/google/sanitizers mais comme nous l'avons vu dans l'exemple, il est déjà en amont dans GCC.
ASan peut également détecter d'autres problèmes de mémoire tels que des fuites de mémoire: Comment trouver une fuite de mémoire dans un code / projet C ++?
Valgrind SGCheck
Comme mentionné par d'autres , Valgrind n'est pas bon pour résoudre ce genre de problème.
Il dispose d'un outil expérimental appelé SGCheck :
Je n'ai donc pas été très surpris quand il n'a pas trouvé l'erreur:
Le message d'erreur devrait ressembler à ceci: Erreur manquante de Valgrind
GDB
Une observation importante est que si vous exécutez le programme via GDB ou examinez le
core
fichier après coup:puis, comme nous l'avons vu sur l'assemblage, GDB devrait vous indiquer la fin de la fonction qui a effectué la vérification des canaris:
Et donc le problème est probable dans l'un des appels que cette fonction a fait.
Ensuite, nous essayons de localiser exactement l'appel défaillant en intensifiant le premier single juste après la mise en place du canari:
et regarder l'adresse:
Maintenant, cela nous laisse à la bonne instruction fautive:
len = 5
eti = 4
, dans ce cas particulier, nous a pointés vers la ligne coupable 12.Cependant, la trace est corrompue et contient des déchets. Une trace correcte ressemblerait à ceci:
alors peut-être que cela pourrait corrompre la pile et vous empêcher de voir la trace.
De plus, cette méthode nécessite de savoir quel est le dernier appel de la fonction de vérification des canaris, sinon vous aurez des faux positifs, ce qui ne sera pas toujours possible, sauf si vous utilisez le débogage inverse .
la source
Veuillez regarder la situation suivante:
Lorsque j'ai désactivé le protecteur de suppression de pile, 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 "** smashing de pile détecté: xxx" a été affiché car votre protecteur de smashing de pile était actif et a constaté un débordement de pile dans votre programme.
Découvrez où cela se produit et corrigez-le.
la source
Vous pouvez essayer de déboguer le problème à l'aide de valgrind :
la source
Cela signifie que vous avez écrit sur certaines variables de la pile de manière illégale, probablement à la suite d'un débordement de tampon .
la source
Un scénario serait dans l'exemple suivant:
Dans ce programme, vous pouvez inverser une chaîne ou une partie de la chaîne si vous appelez
reverse()
par exemple avec quelque chose comme ceci:Si vous décidez de transmettre la longueur du tableau comme ceci:
Fonctionne bien aussi.
Mais quand vous faites cela:
Vous obtenez:
Et cela se produit car dans le premier code, la longueur de
arr
est vérifiée à l'intérieur derevSTR()
ce qui est bien, mais dans le deuxième code où vous passez la longueur:la longueur est maintenant plus longue que la longueur réelle que vous passez lorsque vous dites
arr + 2
.Longueur de
strlen ( arr + 2 )
! =strlen ( arr )
.la source
gets
etscrcpy
. Je me demande si nous pourrions minimiser davantage. Je m'en débarrasserais au moinsstring.h
avecsize_t len = sizeof( arr );
. Testé sur gcc 6.4, Ubuntu 16.04. Je donnerais également l'exemple de l'échec avecarr + 2
pour minimiser le collage de copie.Les corruptions de pile 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, placez une assertion avant pour vous assurer que l'accès n'est pas hors limites. Par exemple:
Cela vous fait penser aux limites du tableau et vous fait également penser à ajouter des tests pour les déclencher si possible. Si certains de ces assertions peuvent échouer lors d'une utilisation normale, transformez-les en un régulier
if
.la source
J'ai obtenu cette erreur en utilisant malloc () pour allouer de la mémoire à une structure * après avoir dépensé un peu de débogage du code, j'ai finalement utilisé la fonction free () pour libérer la mémoire allouée et par la suite le message d'erreur a disparu :)
la source
Une autre source d'écrasement de pile est l'utilisation (incorrecte) de
vfork()
au lieu defork()
.Je viens de déboguer un cas de cela, où le processus enfant n'a pas pu
execve()
exécuter l'exécutable cible et a renvoyé un code d'erreur plutôt que d'appeler_exit()
.Parce qu'il
vfork()
avait engendré cet enfant, il est revenu alors qu'il s'exécutait toujours dans l'espace de processus du parent, non seulement corrompant la pile du parent, mais provoquant l'impression de deux ensembles de diagnostics disparates par du code "en aval".Changer
vfork()
pourfork()
résoudre les deux problèmes, tout comme changer lareturn
déclaration de l'enfant à la_exit()
place.Mais puisque le code enfant précède l'
execve()
appel avec des appels à d'autres routines (pour définir l'uid / gid, dans ce cas particulier), il ne répond techniquement pas aux exigences devfork()
, donc le changer pour l'utiliserfork()
est correct ici.(Notez que l'
return
énoncé problématique n'a pas été réellement codé en tant que tel - à la place, une macro a été invoquée, et cette macro a décidé si elle devait être basée sur une variable globale_exit()
ou enreturn
fonction de celle-ci. Il n'était donc pas immédiatement évident que le code enfant n'était pas conforme pour l'vfork()
utilisation. )Pour plus d'informations, voir:
La différence entre fork (), vfork (), exec () et clone ()
la source