Dans quelle mesure est-il dangereux d'accéder à un tableau en dehors de ses limites (en C)? Il peut parfois arriver que je lise à l'extérieur du tableau (je comprends maintenant que j'accède ensuite à la mémoire utilisée par d'autres parties de mon programme ou même au-delà) ou que j'essaie de définir une valeur pour un index en dehors du tableau. Le programme se bloque parfois, mais parfois il s'exécute, ne donnant que des résultats inattendus.
Maintenant, ce que je voudrais savoir, c'est à quel point est-ce vraiment dangereux? Si cela endommage mon programme, ce n'est pas si mal. Si d'un autre côté, cela casse quelque chose en dehors de mon programme, parce que j'ai réussi à accéder à une mémoire totalement indépendante, alors c'est très mauvais, j'imagine. J'ai lu beaucoup de "tout peut arriver", "la segmentation pourrait être le moindre problème" , "votre disque dur pourrait devenir rose et les licornes pourraient chanter sous votre fenêtre", ce qui est bien, mais quel est vraiment le danger?
Mes questions:
- La lecture de valeurs de l'extérieur du tableau peut-elle endommager autre chose que mon programme? J'imagine que le simple fait de regarder les choses ne change rien, ou changerait-il par exemple l'attribut «dernière ouverture» d'un fichier que j'ai atteint?
- La définition de valeurs en dehors du tableau peut-elle endommager autre chose que mon programme? De cette question de débordement de pile, je suppose qu'il est possible d'accéder à n'importe quel emplacement de mémoire, qu'il n'y a aucune garantie de sécurité.
- J'exécute maintenant mes petits programmes à partir de XCode. Cela fournit-il une protection supplémentaire autour de mon programme lorsqu'il ne peut pas atteindre en dehors de sa propre mémoire? Peut-il nuire à XCode?
- Des recommandations sur la façon d'exécuter mon code intrinsèquement buggé en toute sécurité?
J'utilise OSX 10.7, Xcode 4.6.
Réponses:
En ce qui concerne la norme ISO C (la définition officielle du langage), l'accès à un tableau en dehors de ses limites a " comportement indéfini ". La signification littérale de ceci est:
Une note non normative développe ce point:
Voilà donc la théorie. Quelle est la réalité?
Dans le "meilleur" cas, vous accéderez à une partie de la mémoire appartenant à votre programme en cours d'exécution (ce qui pourrait entraîner un mauvais comportement de votre programme), ou ce n'est pas le cas. à votre programme en cours d'exécution (ce qui entraînera probablement votre programme à planter avec quelque chose comme un défaut de segmentation). Ou vous pouvez essayer d'écrire dans la mémoire que votre programme possède, mais qui est marqué en lecture seule; cela entraînera probablement également le plantage de votre programme.
Cela suppose que votre programme s'exécute sous un système d'exploitation qui tente de protéger les processus simultanément. Si votre code s'exécute sur le "bare metal", par exemple s'il fait partie d'un noyau de système d'exploitation ou d'un système embarqué, alors il n'y a pas une telle protection; votre code de mauvaise conduite était censé fournir cette protection. Dans ce cas, les possibilités de dommages sont considérablement plus importantes, y compris, dans certains cas, les dommages physiques au matériel (ou aux choses ou aux personnes à proximité).
Même dans un environnement de système d'exploitation protégé, les protections ne sont pas toujours à 100%. Il existe des bogues du système d'exploitation qui permettent aux programmes non privilégiés d'obtenir un accès root (administratif), par exemple. Même avec des privilèges utilisateur ordinaires, un programme défectueux peut consommer des ressources excessives (CPU, mémoire, disque), entraînant éventuellement la panne de l'ensemble du système. De nombreux logiciels malveillants (virus, etc.) exploitent les dépassements de mémoire tampon pour obtenir un accès non autorisé au système.
(Un exemple historique: j'ai entendu dire que sur certains anciens systèmes avec mémoire de base , l'accès répétitif à un seul emplacement de mémoire dans une boucle étroite pouvait littéralement faire fondre ce morceau de mémoire. D'autres possibilités incluent la destruction d'un écran CRT et le déplacement de la lecture / tête d'écriture d'un lecteur de disque avec la fréquence harmonique de l'armoire du lecteur, ce qui le fait marcher sur une table et tomber sur le sol.)
Et Skynet doit toujours s'inquiéter.
L'essentiel est le suivant: si vous pouviez écrire un programme pour faire quelque chose de mal délibérément , il est au moins théoriquement possible qu'un programme buggy puisse faire la même chose accidentellement .
Dans la pratique, il est très peu probable que votre programme de buggy fonctionnant sur un système MacOS X fasse quelque chose de plus grave qu'un crash. Mais il n'est pas possible d' empêcher complètement le code buggé de faire de très mauvaises choses.
la source
En général, les systèmes d'exploitation d'aujourd'hui (les plus populaires de toute façon) exécutent toutes les applications dans les régions de mémoire protégées à l'aide d'un gestionnaire de mémoire virtuelle. Il s'avère qu'il n'est pas terriblement FACILE (disons) de simplement lire ou écrire dans un emplacement qui existe dans de VRAIS espaces en dehors des régions qui ont été attribuées / allouées à votre processus.
Réponses directes:
1) La lecture n'endommagera presque jamais directement un autre processus, mais elle peut indirectement endommager un processus si vous lisez une valeur KEY utilisée pour chiffrer, déchiffrer ou valider un programme / processus. La lecture hors limites peut avoir des effets quelque peu négatifs / inattendus sur votre code si vous prenez des décisions en fonction des données que vous lisez
2) La seule façon dont vous pourriez réellement ENDOMMAGER quelque chose en écrivant dans un emplacement accessible par une adresse mémoire est si cette adresse mémoire sur laquelle vous écrivez est en fait un registre matériel (un emplacement qui n'est en fait pas pour le stockage de données mais pour contrôler une pièce) du matériel) pas un emplacement RAM. En fait, vous n'endommagerez toujours normalement pas quelque chose, sauf si vous écrivez un emplacement programmable unique qui n'est pas réinscriptible (ou quelque chose de cette nature).
3) Généralement exécuté à partir du débogueur exécute le code en mode débogage. L'exécution en mode débogage a tendance à (mais pas toujours) à arrêter votre code plus rapidement lorsque vous avez fait quelque chose de hors d'usage ou carrément illégal.
4) N'utilisez jamais de macros, utilisez des structures de données qui ont déjà des limites d'index de tableau intégrées, etc ...
SUPPLÉMENTAIRE Je dois ajouter que les informations ci-dessus ne concernent vraiment que les systèmes utilisant un système d'exploitation avec des fenêtres de protection de la mémoire. Si vous écrivez du code pour un système embarqué ou même un système utilisant un système d'exploitation (en temps réel ou autre) qui n'a pas de fenêtres de protection de la mémoire (ou des fenêtres adressées virtuelles), il faut faire preuve de beaucoup plus de prudence lors de la lecture et de l'écriture dans la mémoire. Dans ces cas également, des pratiques de codage SAFE et SECURE doivent toujours être utilisées pour éviter les problèmes de sécurité.
la source
Ne pas vérifier les limites peut entraîner des effets secondaires laids, y compris des failles de sécurité. L'une des plus laides est l'exécution de code arbitraire . Dans l'exemple classique: si vous avez un tableau de taille fixe et que vous utilisez
strcpy()
pour y placer une chaîne fournie par l'utilisateur, l'utilisateur peut vous donner une chaîne qui déborde le tampon et écrase d'autres emplacements de mémoire, y compris l'adresse de code où le CPU doit retourner lorsque votre fonction finitions.Ce qui signifie que votre utilisateur peut vous envoyer une chaîne qui fera essentiellement appeler votre programme
exec("/bin/sh")
, ce qui le transformera en shell, exécutant tout ce qu'il veut sur votre système, y compris la récolte de toutes vos données et la transformation de votre machine en nœud de botnet.Voir Briser la pile pour le plaisir et le profit pour plus de détails sur la façon dont cela peut être fait.
la source
foo[0]
traversfoo[len-1]
après avoir déjà utilisé un chèque delen
contre la longueur du tableau soit exécuter ou sauter un morceau de code, le compilateur doit se sentir libre d'exécuter cette autre code sans condition même si l'application possède le stockage passé le tableau et les effets de la lecture, il aurait été bénin, mais l'effet de l'invocation de l'autre code ne le serait pas.Vous écrivez:
Disons-le de cette façon: charger un pistolet. Pointez-le à l'extérieur de la fenêtre sans but ni tir particuliers. Quel est le danger?
Le problème est que vous ne savez pas. Si votre code écrase quelque chose qui bloque votre programme, tout va bien car il l'arrêtera dans un état défini. Cependant, s'il ne plante pas, les problèmes commencent à se poser. Quelles ressources sont sous le contrôle de votre programme et que pourrait-il leur apporter? Quelles ressources pourraient être contrôlées par votre programme et que pourraient-elles leur apporter? Je connais au moins un problème majeur causé par un tel débordement. Le problème était dans une fonction de statistiques apparemment dénuée de sens qui a gâché une table de conversion non liée pour une base de données de production. Le résultat a été un nettoyage très coûteux par la suite. En fait, il aurait été beaucoup moins cher et plus facile à gérer si ce problème avait formaté les disques durs ... avec d'autres mots: les licornes roses pourraient être votre moindre problème.
L'idée que votre système d'exploitation vous protégera est optimiste. Si possible, essayez d'éviter d'écrire hors des limites.
la source
Ne pas exécuter votre programme en tant que root ou tout autre utilisateur privilégié ne nuira à aucun de votre système, donc généralement cela peut être une bonne idée.
En écrivant des données dans un emplacement de mémoire aléatoire, vous n'endommagerez directement aucun autre programme en cours d'exécution sur votre ordinateur car chaque processus s'exécute dans son propre espace mémoire.
Si vous essayez d'accéder à une mémoire non allouée à votre processus, le système d'exploitation arrêtera l'exécution de votre programme avec une erreur de segmentation.
Donc, directement (sans exécuter en tant que root et accéder directement à des fichiers comme / dev / mem), il n'y a aucun danger que votre programme interfère avec tout autre programme exécuté sur votre système d'exploitation.
Néanmoins - et c'est probablement ce dont vous avez entendu parler en termes de danger - en écrivant aveuglément des données aléatoires dans des emplacements de mémoire aléatoires par accident, vous pouvez certainement endommager tout ce que vous pouvez endommager.
Par exemple, votre programme peut vouloir supprimer un fichier spécifique donné par un nom de fichier stocké quelque part dans votre programme. Si, par accident, vous écrasez simplement l'emplacement où le nom de fichier est stocké, vous pouvez supprimer un fichier très différent à la place.
la source
NSArray
s dans Objective-C se voient attribuer un bloc de mémoire spécifique. Le dépassement des limites du tableau signifie que vous accéderez à la mémoire qui n'est pas affectée au tableau. Ça signifie:De l'aspect de votre programme, vous voulez toujours savoir quand votre code dépasse les limites d'un tableau. Cela peut entraîner le renvoi de valeurs inconnues, provoquant le plantage de votre application ou la fourniture de données non valides.
la source
NSArrays
ont des exceptions hors limites. Et cette question semble concerner le tableau C.Vous voudrez peut-être essayer d'utiliser l'
memcheck
outil dans Valgrind lorsque vous testez votre code - il ne détectera pas les violations de limites de tableau individuelles dans un cadre de pile, mais il devrait détecter de nombreux autres types de problèmes de mémoire, y compris ceux qui causeraient des subtilités plus larges problèmes hors du cadre d'une seule fonction.Du manuel:
ETA: Cependant, comme le dit la réponse de Kaz, ce n'est pas une panacée et ne donne pas toujours les résultats les plus utiles, surtout lorsque vous utilisez des modèles d'accès passionnants .
la source
Si vous faites de la programmation au niveau des systèmes ou de la programmation de systèmes intégrés, de très mauvaises choses peuvent se produire si vous écrivez dans des emplacements de mémoire aléatoires. Les systèmes plus anciens et de nombreux microcontrôleurs utilisent des E / S mappées en mémoire, de sorte que l'écriture dans un emplacement mémoire mappé sur un registre périphérique peut faire des ravages, surtout si elle est effectuée de manière asynchrone.
Un exemple est la programmation de la mémoire flash. Le mode de programmation sur les puces de mémoire est activé en écrivant une séquence spécifique de valeurs à des emplacements spécifiques à l'intérieur de la plage d'adresses de la puce. Si un autre processus devait écrire dans un autre emplacement de la puce pendant ce temps, cela entraînerait l'échec du cycle de programmation.
Dans certains cas, le matériel encapsulera les adresses (les bits / octets d'adresse les plus importants sont ignorés), donc l'écriture dans une adresse au-delà de la fin de l'espace d'adressage physique entraînera en fait l'écriture des données en plein milieu des choses.
Et enfin, les processeurs plus anciens comme le MC68000 peuvent être verrouillés au point que seule une réinitialisation matérielle peut les remettre en marche. Je n'y ai pas travaillé depuis quelques décennies, mais je pense que c'est quand il a rencontré une erreur de bus (mémoire inexistante) en essayant de gérer une exception, il s'arrêterait simplement jusqu'à ce que la réinitialisation matérielle soit confirmée.
Ma plus grande recommandation est une prise flagrante pour un produit, mais je n'y ai aucun intérêt personnel et je ne suis aucunement affilié à eux - mais basé sur quelques décennies de programmation C et de systèmes embarqués où la fiabilité était critique, le PC de Gimpel Lint ne détectera pas seulement ce genre d'erreurs, il fera de vous un meilleur programmeur C / C ++ en constamment harcelant au sujet des mauvaises habitudes.
Je recommanderais également de lire la norme de codage MISRA C, si vous pouvez attraper une copie de quelqu'un. Je n'en ai pas vu de récents, mais dans le temps, ils m'ont expliqué pourquoi vous devriez / ne devriez pas faire les choses qu'ils couvrent.
Je ne sais pas pour vous, mais à propos de la 2e ou de la 3e fois que je reçois un coredump ou un raccrochage de n'importe quelle application, mon opinion sur la société qui l'a produite diminue de moitié. La 4e ou la 5e fois et quel que soit le paquet devient étagère et je conduis un piquet en bois au centre du paquet / disque dans lequel il est entré juste pour m'assurer qu'il ne revienne jamais me hanter.
la source
Je travaille avec un compilateur pour une puce DSP qui génère délibérément du code qui accède à un après la fin d'un tableau en code C qui ne le fait pas!
Cela est dû au fait que les boucles sont structurées de sorte que la fin d'une itération prélève certaines données pour l'itération suivante. Ainsi, la donnée extraite à la fin de la dernière itération n'est jamais réellement utilisée.
Écrire du code C comme ça invoque un comportement indéfini, mais ce n'est qu'une formalité d'un document de standards qui se préoccupe d'une portabilité maximale.
Plus souvent qu'autrement, un programme qui accède hors des limites n'est pas habilement optimisé. C'est tout simplement buggé. Le code récupère une certaine valeur de déchets et, contrairement aux boucles optimisées du compilateur susmentionné, le code utilise ensuite la valeur dans les calculs suivants, les corrompant ainsi.
Cela vaut la peine d'attraper des bogues comme ça, et il vaut donc la peine de rendre le comportement non défini, même pour cette seule raison: pour que l'exécution puisse produire un message de diagnostic comme "dépassement de la ligne à la ligne 42 de main.c".
Sur les systèmes avec mémoire virtuelle, il se peut qu'un tableau soit alloué de telle sorte que l'adresse qui suit se trouve dans une zone non mappée de mémoire virtuelle. L'accès bombardera alors le programme.
Néanmoins, l'accès à des valeurs non initialisées ou hors limites est parfois une technique d'optimisation valide, même s'il n'est pas portable au maximum. C'est par exemple pourquoi l'outil Valgrind ne signale pas les accès aux données non initialisées lorsque ces accès se produisent, mais uniquement lorsque la valeur est utilisée ultérieurement d'une manière qui pourrait affecter le résultat du programme. Vous obtenez un diagnostic comme «branche conditionnelle dans xxx: nnn dépend de la valeur non initialisée» et il peut parfois être difficile de localiser son origine. Si tous ces accès étaient immédiatement bloqués, il y aurait beaucoup de faux positifs provenant du code optimisé par le compilateur ainsi que du code correctement optimisé à la main.
En parlant de cela, je travaillais avec un codec d'un fournisseur qui dégageait ces erreurs lorsqu'il était porté sur Linux et exécuté sous Valgrind. Mais le vendeur m'a convaincu que seulement plusieurs bitsde la valeur utilisée provenait en fait de la mémoire non initialisée, et ces bits ont été soigneusement évités par la logique. Seuls les bons bits de la valeur étaient utilisés et Valgrind n'a pas la possibilité de retrouver le bit individuel. Le matériel non initialisé provient de la lecture d'un mot après la fin d'un flux binaire de données codées, mais le code sait combien de bits se trouvent dans le flux et n'utilisera pas plus de bits qu'il n'y en a réellement. Étant donné que l'accès au-delà de la fin de la matrice de flux binaires ne cause aucun dommage à l'architecture DSP (il n'y a pas de mémoire virtuelle après la matrice, pas de ports mappés en mémoire et l'adresse ne se termine pas), c'est une technique d'optimisation valide.
"Comportement indéfini" ne signifie pas vraiment grand-chose, car selon ISO C, simplement inclure un en-tête qui n'est pas défini dans la norme C, ou appeler une fonction qui n'est pas définie dans le programme lui-même ou la norme C, sont des exemples d'indéfini comportement. Un comportement indéfini ne signifie pas "non défini par quiconque sur la planète" simplement "non défini par la norme ISO C". Mais bien sûr, un comportement parfois indéfini n'est vraiment absolument pas défini par quiconque.
la source
Outre votre propre programme, je ne pense pas que vous casserez quoi que ce soit, dans le pire des cas, vous essayerez de lire ou d'écrire à partir d'une adresse mémoire qui correspond à une page que le noyau n'a pas assignée à vos processus, générant l'exception appropriée et d'être tué (je veux dire, votre processus).
la source