Je fais un cours à l'université, où l'un des laboratoires est d'effectuer des exploits de débordement de tampon sur le code qu'ils nous donnent. Cela va de simples exploits comme changer l'adresse de retour d'une fonction sur une pile pour revenir à une fonction différente, jusqu'au code qui modifie un registre de programmes / un état de mémoire mais revient ensuite à la fonction que vous avez appelée, ce qui signifie que le la fonction que vous avez appelée est complètement inconsciente de l'exploit.
J'ai fait des recherches à ce sujet, et ces types d'exploits sont utilisés à peu près partout, même maintenant, dans des choses comme l' exécution de homebrew sur la Wii et le jailbreak sans connexion pour iOS 4.3.1
Ma question est pourquoi ce problème est-il si difficile à résoudre? Il est évident que c'est un exploit majeur utilisé pour pirater des centaines de choses, mais il semble qu'il serait assez facile à corriger en tronquant simplement toute entrée au-delà de la longueur autorisée et en purifiant simplement toutes les entrées que vous prenez.
EDIT: Une autre perspective que j'aimerais que les réponses prennent en compte - pourquoi les créateurs de C ne résolvent-ils pas ces problèmes en réimplémentant les bibliothèques?
la source
Il n'est pas vraiment inexact de dire que C est en fait "sujet aux erreurs" de par sa conception . Mis à part quelques erreurs graves comme
gets
, le langage C ne peut pas vraiment être autrement sans perdre la fonctionnalité principale qui attire les gens vers C en premier lieu.C a été conçu comme un langage système pour agir comme une sorte d '«assemblage portable». Une caractéristique majeure du langage C est que, contrairement aux langages de niveau supérieur, le code C correspond souvent très étroitement au code machine réel. En d'autres termes, ce
++i
n'est généralement qu'uneinc
instruction, et vous pouvez souvent avoir une idée générale de ce que le processeur fera au moment de l'exécution en consultant le code C.Mais l'ajout de la vérification implicite des limites ajoute beaucoup de surcharge supplémentaire - surcharge que le programmeur n'a pas demandée et pourrait ne pas vouloir. Cette surcharge va bien au-delà du stockage supplémentaire requis pour stocker la longueur de chaque baie, ou des instructions supplémentaires pour vérifier les limites de la baie à chaque accès à la baie. Et l'arithmétique des pointeurs? Ou si vous avez une fonction qui prend un pointeur? L'environnement d'exécution n'a aucun moyen de savoir si ce pointeur tombe dans les limites d'un bloc de mémoire légitimement alloué. Afin de garder une trace de cela, vous auriez besoin d'une architecture d'exécution sérieuse qui puisse comparer chaque pointeur par rapport à une table de blocs de mémoire actuellement alloués, moment auquel nous entrons déjà dans le territoire d'exécution géré de style Java / C #.
la source
Je pense que le vrai problème n'est pas que ces types de bugs sont difficiles à corriger, mais qu'ils sont si faciles à faire: si vous utilisez
strcpy
,sprintf
et vos amis de la manière (apparemment) la plus simple qui puisse fonctionner, alors vous avez probablement ouvert la porte pour un débordement de tampon. Et personne ne le remarquera jusqu'à ce que quelqu'un l'exploite (sauf si vous avez de très bonnes critiques de code). Ajoutez maintenant le fait qu'il existe de nombreux programmeurs médiocres et qu'ils sont sous la pression du temps la plupart du temps - et vous avez une recette de code qui est tellement criblée de débordements de tampon qu'il sera difficile de les corriger tous simplement parce qu'il y a tant d'entre eux et ils se cachent si bien.la source
sizeof(ptr)
vaut 4 ou 8, en général. C'est une autre limitation en C: il n'y a aucun moyen de déterminer la longueur d'un tableau, étant donné juste le pointeur vers celui-ci.#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))
déclencher une division par zéro au moment de la compilation. Un autre astucieux que j'ai vu pour la première fois dans Chromium est celui#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))
qui échange la poignée de faux positifs pour certains faux négatifs - malheureusement, il est inutile pour char []. Vous pouvez utiliser diverses extensions du compilateur pour le rendre encore plus fiable, par exemple blogs.msdn.com/b/ce_base/archive/2007/05/08/… .Il est difficile de corriger les dépassements de tampon car C ne fournit pratiquement aucun outil utile pour résoudre le problème. C'est un défaut de langage fondamental que les tampons natifs n'offrent aucune protection et il est pratiquement, sinon complètement, impossible de les remplacer par un produit supérieur, comme C ++ l'a fait avec
std::vector
etstd::array
, et il est difficile, même en mode débogage, de trouver des débordements de tampon.la source
std::vector
d'être mises en œuvre efficacement. Etvector::operator[]
fait le même choix pour la vitesse sur la sécurité. La sécuritévector
réside dans le fait de faciliter le cadrage autour de la taille, ce qui est la même approche que les bibliothèques C modernes.realloc
(C99 vous permet également de dimensionner les tableaux de pile en utilisant une taille déterminée par l'exécution mais constante via n'importe quelle variable automatique, presque toujours préférable àchar buf[1024]
). Deuxièmement, le problème n'a rien à voir avec l'expansion des tampons, il a à voir avec le fait que les tampons portent ou non la taille avec eux et vérifient cette taille lorsque vous y accédez.vector::operator[]
vérifie les limites en mode débogage - quelque chose que les tableaux natifs ne peuvent pas faire - et deuxièmement, il n'y a aucun moyen en C d'échanger le type de tableau natif avec un qui peut faire la vérification des limites, car il n'y a pas de modèles et aucun opérateur surcharge. En C ++, si vous souhaitez passer deT[]
àstd::array
, vous pouvez pratiquement échanger un typedef. En C, il n'y a aucun moyen d'y parvenir, et aucun moyen d'écrire une classe avec des fonctionnalités équivalentes, et encore moins une interface.std::vector<T>
etstd::array<T, N>
faire en C ++. Il n'y aurait aucun moyen de concevoir et de spécifier une bibliothèque, pas même une bibliothèque standard, qui pourrait le faire.std::vector
ne peut également jamais être de taille statique. Quant au générique, vous pouvez le rendre aussi générique que le bon C en a besoin - un petit nombre d'opérations fondamentales sur void * (ajouter, supprimer, redimensionner) et tout le reste écrit spécifiquement. Si vous allez vous plaindre que C n'a pas de génériques de style C ++, c'est bien en dehors de la portée de la gestion sécurisée des tampons.Le problème est pas avec la C langue .
OMI, le seul obstacle majeur à surmonter est que le C est tout simplement mal enseigné . Des décennies de mauvaises pratiques et d'informations erronées ont été institutionnalisées dans les manuels de référence et les notes de cours, empoisonnant dès le départ l'esprit de chaque nouvelle génération de programmeurs. Les étudiants reçoivent une brève description des fonctions d'E / S «faciles» comme
gets
1 ouscanf
puis sont laissés à eux-mêmes. On ne leur dit pas où ni comment ces outils peuvent échouer, ni comment prévenir ces échecs. On ne leur dit pas d'utiliserfgets
etstrtol/strtod
parce que ceux-ci sont considérés comme des outils "avancés". Ensuite, ils se déchaînent sur le monde professionnel pour faire des ravages. Ce n'est pas que beaucoup des programmeurs les plus expérimentés connaissent mieux, car ils ont reçu la même éducation cérébrale. C'est exaspérant. Je vois tellement de questions ici et sur Stack Overflow et sur d'autres sites où il est clair que la personne qui pose la question est enseignée par quelqu'un qui ne sait tout simplement pas de quoi il parle , et bien sûr, vous ne pouvez pas simplement dire "votre professeur a tort", car il est professeur et vous n'êtes qu'un gars sur Internet.Et puis vous avez la foule qui dédaigne toute réponse commençant par, "bien, selon la norme de langue ..." parce qu'ils travaillent dans le monde réel et selon eux, la norme ne s'applique pas au monde réel . Je peux traiter avec quelqu'un qui a juste une mauvaise éducation, mais quiconque insiste pour être ignorant n'est qu'un fléau pour l'industrie.
Il n'y aurait pas de problèmes de dépassement de tampon si la langue était enseignée correctement en mettant l'accent sur l'écriture de code sécurisé. Ce n'est pas "dur", ce n'est pas "avancé", c'est juste d'être prudent.
Oui, cela a été une diatribe.
1 Ce qui, heureusement, a finalement été retiré de la spécification du langage, bien qu'il se cache à jamais dans 40 ans de code hérité.
la source
sprintf
, mais cela ne signifie pas que le langage n'était pas défectueux. C était défectueux et est défectueux - comme n'importe quel langage - et il est important que nous admettions ces défauts afin que nous puissions continuer à les corriger.Le problème tient autant à la myopie managériale qu'à l'incompétence des programmeurs. N'oubliez pas qu'une application de 90 000 lignes n'a besoin que d' une seule opération non sécurisée pour être totalement non sécurisée. Il est presque hors de portée que toute application écrite au-dessus d'une gestion de chaîne fondamentalement non sécurisée soit 100% parfaite - ce qui signifie qu'elle sera non sécurisée.
Le problème est que les coûts liés à l'insécurité ne sont pas facturés au bon destinataire (la société qui vend l'application ne devra presque jamais rembourser le prix d'achat), ou ne sont pas clairement visibles au moment où les décisions sont prises ("Nous devons expédier en mars quoi qu'il arrive! "). Je suis à peu près certain que si vous preniez en compte les coûts à long terme et les coûts pour vos utilisateurs plutôt que pour le profit de votre entreprise, l'écriture en C ou dans des langues apparentées serait beaucoup plus chère, probablement si chère que ce n'est clairement pas le bon choix dans de nombreux pays. des domaines où de nos jours la sagesse conventionnelle dit que c'est une nécessité. Mais cela ne changera que si une responsabilité logicielle beaucoup plus stricte est introduite - ce que personne dans l'industrie ne veut.
la source
L'un des grands pouvoirs de l'utilisation de C est qu'il vous permet de manipuler la mémoire comme bon vous semble.
L'une des grandes faiblesses de l'utilisation de C est qu'elle vous permet de manipuler la mémoire comme bon vous semble.
Il existe des versions sûres de toutes les fonctions dangereuses. Cependant, les programmeurs et le compilateur n'appliquent pas strictement leur utilisation.
la source
Probablement parce que C ++ l'a déjà fait et est rétrocompatible avec le code C. Donc, si vous voulez un type de chaîne sûr dans votre code C, vous utilisez simplement std :: string et écrivez votre code C à l'aide d'un compilateur C ++.
Le sous-système de mémoire sous-jacent peut aider à empêcher les débordements de tampon en introduisant des blocs de garde et en vérifiant leur validité - de sorte que toutes les allocations ont 4 octets de `` fefefefe '' ajoutés, lorsque ces blocs sont écrits, le système peut lancer un wobbler. Ce n'est pas garanti d'empêcher une écriture en mémoire, mais cela montrera que quelque chose s'est mal passé et doit être corrigé.
Je pense que le problème est que les anciennes routines strcpy etc. sont toujours présentes. S'ils étaient supprimés en faveur de strncpy, etc., cela aiderait.
la source
Il est simple de comprendre pourquoi le problème de débordement n'est pas résolu. C était défectueux dans quelques domaines. À l'époque, ces défauts étaient considérés comme tolérables ou même comme une caractéristique. Maintenant, des décennies plus tard, ces défauts ne peuvent pas être corrigés.
Certaines parties de la communauté de programmation ne veulent pas que ces trous soient bouchés. Il suffit de regarder toutes les guerres de flammes qui recommencent sur les chaînes, les tableaux, les pointeurs, la collecte des ordures ...
la source
memcpy()
disponibilité et la seule utilisation standard de la copie efficace d'un segment de tableau.