Top Qs
Chronologie
Chat
Contexte
Dangling pointer
notion de programmation informatique De Wikipédia, l'encyclopédie libre
Remove ads
Dangling pointer (traduction littérale : « pointeur pendouillant » ou « pointeur sautillant ») est un terme anglais de programmation informatique désignant un pointeur qui ne pointe pas vers un type d'objet approprié.

Les dangling pointers sont créés lors de la destruction de l'objet.
Cause de pointeurs pendouillant
Résumé
Contexte
Dans de nombreux langages (par exemple, le langage C ), la suppression explicite d'un objet de la mémoire ou la destruction de son bloc d'activation lors du retour d'une instruction ne modifie pas les pointeurs associés. Dans ce cas, le pointeur continue de pointer vers le même emplacement mémoire, alors que cet emplacement peut désormais être utilisé à d'autres fins.
Un exemple simple est présenté ci-dessous :
{
char* dp = NULL;
// ...
{
char c;
dp = &c;
}
// c sort de la portée
// dp est désormais un pointeur pendouillant
}
Si le système d'exploitation est capable de détecter les références à des pointeurs nuls lors de l'exécution, une solution consiste à affecter la valeur 0 (NULL) à dp juste avant la sortie du bloc interne. Une autre solution serait de garantir d'une façon ou d'une autre que dp ne soit pas réutilisé sans une nouvelle initialisation.
Une autre cause fréquente de pointeurs pendouillant est une combinaison erronée d'appels aux fonctions malloc() et free() : un pointeur devient pendouillant si le bloc de mémoire qu'il pointe est libéré. Comme dans l'exemple précédent, une solution consiste à réinitialiser le pointeur à NULL après avoir libéré sa référence, comme illustré ci-dessous.
#include <stdlib.h>
void func() {
char* dp = (char*)malloc(sizeof(char) * 10);
// ...
free(dp); // dp devient pendouillant à cet instant
dp = NULL; // dp cesse d'être pendouillant
// ...
}
Une erreur trop fréquente consiste à renvoyer l'adresse d'une variable locale allouée sur la pile : une fois qu'une fonction appelée a renvoyé une valeur, l'espace alloué à ces variables sur la pile est libéré et, techniquement, elles contiennent des valeurs indéfinies.
int* func(void) {
int num = 1234;
// ...
return #
}
Les tentatives de lecture à partir du pointeur peuvent encore renvoyer la valeur correcte (1234) pendant un certain temps après l'appel func, mais toute fonction appelée plus tard risque d'écraser avec d'autres valeurs l'espace mémoire qui était alloué à num sur la pile , et le pointeur ne fonctionnera alors plus correctement. Si un pointeur vers num doit être renvoyé, num doit avoir une portée au-delà de la fonction ; il peut, par exemple, être déclaré comme static .
Remove ads
Causes de pointeurs non initialisés
Les pointeurs non initialisés sont créés quand l'initialisation nécessaire avant leur première utilisation est omise. Ainsi, à proprement parler, tout pointeur dans les langages de programmation qui n'imposent pas d'initialisation est au départ un pointeur non initialisé.
Cela se produit le plus souvent lorsqu'on saute l'initialisation et non pas lorsqu'on l'omet. La plupart des compilateurs sont capables d'avertir au sujet de ce problème.
int f(int i) {
char* dp; // dp est un pointeur non initialisé
static char* scp; /* scp n'est pas un pointeur non initialisé:
* Les variables static sont initialisées à 0
* au début, et conservent leurs valeurs du
* dernier appel ensuite.
* Cependant, utiliser cette fonctionnalité peut
* être considéré comme une mauvaise pratique
* s'il n'y a pas de commentaire*/
}
Remove ads
Failles de sécurité impliquant des pointeurs pendouillant
Résumé
Contexte
Tout comme les failles de dépassement de tampon, les failles liées aux pointeurs pendouillant/invalides ou non initialisés constituent fréquemment des failles de sécurité. Par exemple, si le pointeur est utilisé pour appeler une fonction virtuelle, une autre adresse (qui peut pointer vers du code d'exploitation) peut être appelée, suite à l'écrasement du pointeur de la table virtuelle . De plus, si le pointeur est utilisé pour écrire en mémoire, une autre structure de données peut être corrompue. Même si la mémoire n'est lue qu'une fois que le pointeur est devenu invalide, il peut y avoir des fuites d'informations (si des données sensibles sont placées dans la structure allouée à cet emplacement plus tard) ou à une élévation de privilèges (si la mémoire désormais invalide est utilisée lors de contrôles de sécurité). Lorsqu'un pointeur invalide est utilisé après avoir été libéré sans qu'un nouveau bloc de mémoire lui ait été alloué, on parle de vulnérabilité « use after free »[1]. Par exemple, CVE 2014-1776 est une vulnérabilité de type « use after free » sur Microsoft Internet Explorer 6 à 11 qui était utilisée dans des attaques zero-day par une Advanced Persistent Threat (menace persistante avancée).
Éviter les erreurs de pointeur pendouillant
Résumé
Contexte
En C, la technique la plus simple est d'implémenter une version alternative de la fonction free() (ou équivalente) qui garantit la réinitialisation du pointeur. Cependant, cette technique ne libère pas les autres variables de type pointeur qui peuvent contenir une copie de ce pointeur.
#include <assert.h>
#include <stdlib.h>
// Version sécurisée de free()
static void safeFree(void** pp) {
// pour débuguer, abandonner si pp est NULL
assert(pp);
// free(NULL) fonctionne comme il faut, donc aucune vérification n'est nécessaire en plus du assert
free(*pp); // désallouer
*pp = NULL; // réinitialiser
}
int f(int i) {
char* p = NULL;
char* p2;
p = (char*)malloc(1000); // obtenir un emplacement mémoire
p2 = p; // copier le pointeur
// ici, utiliser l'emplacement mémoire puis ensuite :
safeFree((void**)&p); // free sécurisé ; n'affecte pas p2
safeFree((void**)&p); // le réappeler ne pose pas de problème comme p est à NULL
char c = *p2; // p2 est en revanche toujours pendouillant, donc ce n'est pas valide.
return i + c;
}
La version alternative peut même être utilisée pour garantir la validité d'un pointeur vide avant d'appeler malloc() :
safeFree(&p); // Si je ne suis pas sûr.e que l'emplacement ait été libéré, maintenant c'est bon
p = (char*)malloc(1000); // On peut maintenant allouer
Ces usages peuvent être masqués par des directives #define pour construire des macros utiles (par exemple : #define XFREE(ptr) safeFree((void**)&(ptr)) ), créant ainsi une sorte de métalangage, ou encore intégrés à une bibliothèque d'outils distincte. Dans tous les cas, les programmeurs utilisant cette technique doivent impérativement utiliser les versions sécurisées de free() ; sinon, le problème est toujours là. De plus, cette solution est limitée au cadre d'un seul programme ou projet, et doit être correctement documentée.
Parmi les solutions plus structurées, une technique courante pour éviter les pointeurs pendouillant en C++ consiste à utiliser des pointeurs intelligents . Un pointeur intelligent utilise généralement le comptage de références pour récupérer les objets. D'autres techniques existent, comme la méthode des pierres tombales et la méthode des clés et des verrous .
Une autre approche consiste à utiliser le ramasse-miettes Boehm, un ramasse-miettes conservateur qui remplace les fonctions d'allocation mémoire standard en C et C++ par un ramasse-miettes. Cette approche élimine toutes les erreurs de pointeurs non initialisés en désactivant les libérations de mémoire et en récupérant les objets via le ramasse-miettes.
Une autre approche consiste à utiliser un système tel que CHERI, qui stocke les pointeurs avec des métadonnées supplémentaires susceptibles d'empêcher les accès invalides en incluant des informations sur leur durée de vie. CHERI nécessite généralement une prise en charge par le processeur afin d'effectuer ces vérifications supplémentaires.
Dans les langages comme Java, les pointeurs non initialisés ne peuvent pas car il n'existe aucun mécanisme pour libérer explicitement la mémoire. C'est le ramasse-miettes qui peut libérer la mémoire, mais seulement lorsque l'objet n'est plus accessible par aucune référence.
En Rust, le système de types a été étendu dans le but d'inclure la durée de vie des variables et l'initialisation des ressources . À moins de désactiver ces fonctionnalités, les pointeurs non initialisés sont détectés à la compilation et signalés comme des erreurs de programmation.
Remove ads
Libération manuelle de mémoire sans référence pendouillante
Antoni Kreczmar (pl) (1945–1996) a créé un système de gestion d'objets complet, libre de tout phénomène de référence pendouillante[2]. Fisher et Leblanc[3] ont proposé une approche similaire, appelée Locks-and-keys (Verrous et clés en français).
Détection de pointeurs pendouillant
Résumé
Contexte
Pour détecter les erreurs de pointeurs pendouillant, une technique courante consiste à affecter une valeur nulle au pointeur, ou bien une adresse invalide une fois que ce pointeur est libéré. Dans la plupart des langages, quand le pointeur nul est déréférencé, le programme s'arrête immédiatement, éliminant ainsi tout risque de corruption des données, ou de comportement imprévisible. Cela facilite la détection et la résolution des potentielles erreurs de programmation. Cette technique est inefficace lorsqu'il y a plusieurs copies du pointeur.
Certains débogueurs suppriment automatiquement les données libérées, généralement à l'aide d'un motif spécifique, tel que 0xDEADBEEF (le débogueur Visual C/C++ de Microsoft, par exemple, utilise 0xCC, 0xCD ou 0xDD selon les données libérées ). Ceci empêche généralement la réutilisation des données en les rendant inutilisables et en les signalant clairement (le motif indique au programmeur que la mémoire a déjà été libérée).
Des outils tels que Polyspace, TotalView, Valgrind, Mudflap, AddressSanitizer, ou des outils basés sur LLVM[4] peuvent également être utilisés pour détecter les utilisations de pointeurs pendouillants.
D'autres outils (SoftBound, Insure++ et CheckPointer) instrumentent le code source pour collecter et suivre les valeurs légitimes des pointeurs (les « métadonnées »), et vérifier la validité de chaque accès du pointeur par rapport aux métadonnées.
Une autre stratégie, lorsqu'on soupçonne un petit ensemble de classes, consiste à rendre temporairement toutes leurs fonctions virtuelles : une fois l'instance de classe détruite ou libérée, son pointeur vers la table des méthodes virtuelles est défini sur NULL, et tout appel à une fonction membre de la classe fera planter le programme, et affichera le code concerné dans le débogueur.
L'extension de balisage de la mémoire ARM64 (MTE, pour memory tagging extension en anglais), désactivée par défaut sur les systèmes Linux, mais pouvant être activée sur Android 16, déclenche une erreur de segmentation lorsqu'elle détecte une utilisation après libération et un dépassement de tampon[5],[6].
Remove ads
Notes et références
Liens externes
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads
