Baumelnder Zeiger - Dangling pointer
Dangling-Zeiger und Wild-Zeiger in der Computerprogrammierung sind Zeiger , die nicht auf ein gültiges Objekt des entsprechenden Typs zeigen. Dies sind Sonderfälle von Speichersicherheitsverletzungen . Allgemeiner gesagt , baumeln Referenzen und wilde Referenzen sind Referenzen , die auf ein gültiges Ziel nicht beheben, und solche Phänomene wie umfassen Toter Link im Internet.
Hängende Zeiger entstehen während der Objektzerstörung , wenn ein Objekt, das eine eingehende Referenz hat, gelöscht oder freigegeben wird, ohne den Wert des Zeigers zu ändern, so dass der Zeiger immer noch auf die Speicherstelle des freigegebenen Speichers zeigt. Das System kann den zuvor freigegebenen Speicher neu zuordnen , und wenn das Programm dann den (jetzt) baumelnden Zeiger dereferenziert , kann ein unvorhersehbares Verhalten resultieren , da der Speicher jetzt völlig andere Daten enthalten kann. Wenn das Programm in den Speicher schreibt, der von einem baumelnden Zeiger referenziert wird, kann es zu einer stillschweigenden Beschädigung nicht verwandter Daten kommen, was zu subtilen Fehlern führt , die extrem schwer zu finden sind. Wenn der Speicher einem anderen Prozess zugewiesen wurde, kann der Versuch, den Dangling-Pointer zu dereferenzieren, zu Segmentierungsfehlern (UNIX, Linux) oder allgemeinen Schutzfehlern (Windows) führen. Wenn das Programm über ausreichende Berechtigungen verfügt, um die vom Speicherzuordner des Kernels verwendeten Buchhaltungsdaten zu überschreiben, kann die Beschädigung Systeminstabilitäten verursachen. In objektorientierten Sprachen mit Garbage Collection werden Dangling-Referenzen verhindert, indem nur Objekte zerstört werden, die nicht erreichbar sind, das heißt, sie haben keine eingehenden Zeiger; dies wird entweder durch Tracing oder Referenzzählung sichergestellt . Jedoch ist ein Finalizerthread kann auf ein Objekt neue Referenzen erzeugt, erfordern Objekt Auferstehung einen baumelnde Bezug zu verhindern.
Wildzeiger entstehen, wenn ein Zeiger vor der Initialisierung auf einen bekannten Zustand verwendet wird, was in einigen Programmiersprachen möglich ist. Sie zeigen das gleiche unberechenbare Verhalten wie baumelnde Zeiger, bleiben jedoch mit geringerer Wahrscheinlichkeit unentdeckt, da viele Compiler zur Kompilierzeit eine Warnung ausgeben, wenn auf deklarierte Variablen vor der Initialisierung zugegriffen wird.
Ursache für baumelnde Zeiger
In vielen Sprachen (z. B. der Programmiersprache C ) ändert das explizite Löschen eines Objekts aus dem Speicher oder durch das Zerstören des Stapelrahmens bei der Rückkehr die zugehörigen Zeiger nicht. Der Zeiger zeigt immer noch auf dieselbe Stelle im Speicher, obwohl er jetzt für andere Zwecke verwendet werden kann.
Ein einfaches Beispiel ist unten gezeigt:
{
char *dp = NULL;
/* ... */
{
char c;
dp = &c;
}
/* c falls out of scope */
/* dp is now a dangling pointer */
}
Wenn das Betriebssystem Laufzeitreferenzen auf Nullzeiger erkennen kann , besteht eine Lösung für das obige darin, dp unmittelbar vor dem Verlassen des inneren Blocks 0 (null) zuzuweisen. Eine andere Lösung wäre, irgendwie sicherzustellen, dass dp nicht ohne weitere Initialisierung erneut verwendet wird.
Eine weitere häufige Quelle für baumelnde Zeiger ist eine durcheinandergebrachte Kombination von malloc()und free()Bibliotheksaufrufen: Ein Zeiger wird baumeln, wenn der Speicherblock, auf den er zeigt, freigegeben wird. Wie im vorherigen Beispiel können Sie dies vermeiden, indem Sie sicherstellen, dass der Zeiger nach dem Freigeben seiner Referenz auf null zurückgesetzt wird – wie unten gezeigt.
#include <stdlib.h>
void func()
{
char *dp = malloc(A_CONST);
/* ... */
free(dp); /* dp now becomes a dangling pointer */
dp = NULL; /* dp is no longer dangling */
/* ... */
}
Ein allzu häufiger Fehler ist die Rückgabe von Adressen einer dem Stack zugewiesenen lokalen Variablen: Sobald eine aufgerufene Funktion zurückkehrt, wird der Platz für diese Variablen freigegeben und technisch haben sie "Müllwerte".
int *func(void)
{
int num = 1234;
/* ... */
return #
}
Versuche, vom Zeiger zu lesen, können nach dem Aufruf noch eine Weile den richtigen Wert (1234) zurückgeben func, aber alle danach aufgerufenen Funktionen können den zugewiesenen Stapelspeicher nummit anderen Werten überschreiben und der Zeiger würde nicht mehr richtig funktionieren. Wenn ein Zeiger auf numzurückgegeben werden muss, nummuss er einen Gültigkeitsbereich haben, der über die Funktion hinausgeht – er könnte als deklariert werden static.
Manuelle Freigabe ohne baumelnde Referenz
Antoni Kreczmar (1945-1996) hat ein komplettes Objektverwaltungssystem geschaffen, das frei von Dangling Reference-Phänomenen ist, siehe
- Schema der Axiome der Operation kill
- Seien x 1 , ... ,x n Variablen, n > 0, 1≤i≤n. Jede Formel des folgenden Schemas ist ein Satz der von Kreczmar konstruierten virtuellen Maschine.
- Seien x 1 , ... ,x n Variablen, n > 0, 1≤i≤n. Jede Formel des folgenden Schemas ist ein Satz der von Kreczmar konstruierten virtuellen Maschine.
- gelesen als : Wenn ein Objekt o der Wert von n Variablen ist, dann ist nach der Ausführung der Anweisung kill(x i ) der gemeinsame Wert dieser Variablen keins (das bedeutet, dass das Objekt o ab diesem Momentnicht erreichbar ist und folglich der Teil der der von ihm belegte Speicher kann durch die gleiche Operation kill wiederverwertet werden, ohne Schaden zu nehmen).
Folglich:
- Die Operation kill(x 1 ), kill(x 2 ), ... muss nicht wiederholt werden.
- es gibt kein Phänomen der baumelnden Referenz ,
- jeder Versuch, auf das gelöschte Objekt zuzugreifen, wird erkannt und als Ausnahme „ Referenz auf keine “ signalisiert .
Hinweis: Die Tötungskosten sind konstant .
Ein ähnlicher Ansatz wurde von Fisher und LeBlanc unter dem Namen Locks-and-Keys vorgeschlagen .
Ursache für wilde Zeiger
Wild-Zeiger werden erzeugt, indem die notwendige Initialisierung vor der ersten Verwendung weggelassen wird. Streng genommen beginnt also jeder Zeiger in Programmiersprachen, die keine Initialisierung erzwingen, als wilder Zeiger.
Dies geschieht meistens durch Überspringen der Initialisierung, nicht durch Auslassen. Die meisten Compiler können davor warnen.
int f(int i)
{
char *dp; /* dp is a wild pointer */
static char *scp; /* scp is not a wild pointer:
* static variables are initialized to 0
* at start and retain their values from
* the last call afterwards.
* Using this feature may be considered bad
* style if not commented */
}
Sicherheitslücken mit baumelnden Zeigern
Wie Buffer-Overflow- Bugs werden Dangling/Wild-Pointer-Bugs häufig zu Sicherheitslücken. Wenn der Zeiger beispielsweise verwendet wird, um einen virtuellen Funktionsaufruf durchzuführen , kann eine andere Adresse (die möglicherweise auf Exploit-Code zeigt) aufgerufen werden, weil der vtable- Zeiger überschrieben wird. Alternativ kann, wenn der Zeiger zum Schreiben in den Speicher verwendet wird, eine andere Datenstruktur beschädigt werden. Auch wenn der Speicher erst gelesen wird, wenn der Zeiger baumelt, kann es zu Informationslecks (wenn interessante Daten in die nächste dort zugewiesene Struktur eingefügt werden) oder zur Privilegieneskalation (wenn der nun ungültige Speicher bei Sicherheitsüberprüfungen verwendet wird) führen. Wenn ein Dangling-Pointer verwendet wird, nachdem er freigegeben wurde, ohne ihm einen neuen Speicherblock zuzuweisen, wird dies als "Use after free"-Schwachstelle bezeichnet. Zum Beispiel CVE - 2014-1776 ist eine use-after-free - Sicherheitsanfälligkeit in Microsoft Internet Explorer 6 bis 11 verwendet wird , Zero-Day - Angriffe durch eine Advanced Persistent Threat .
Fehler bei baumelnden Zeigern vermeiden
In C besteht die einfachste Technik darin, eine alternative Version der free()(oder einer ähnlichen) Funktion zu implementieren, die das Zurücksetzen des Zeigers garantiert. Diese Technik löscht jedoch keine anderen Zeigervariablen, die eine Kopie des Zeigers enthalten können.
#include <assert.h>
#include <stdlib.h>
/* Alternative version for 'free()' */
static void safefree(void **pp)
{
/* in debug mode, abort if pp is NULL */
assert(pp);
/* free(NULL) works properly, so no check is required besides the assert in debug mode */
free(*pp); /* deallocate chunk, note that free(NULL) is valid */
*pp = NULL; /* reset original pointer */
}
int f(int i)
{
char *p = NULL, *p2;
p = malloc(1000); /* get a chunk */
p2 = p; /* copy the pointer */
/* use the chunk here */
safefree((void **)&p); /* safety freeing; does not affect p2 variable */
safefree((void **)&p); /* this second call won't fail as p is reset to NULL */
char c = *p2; /* p2 is still a dangling pointer, so this is undefined behavior. */
return i + c;
}
Die alternative Version kann sogar verwendet werden, um die Gültigkeit eines leeren Zeigers vor dem Aufruf zu garantieren malloc():
safefree(&p); /* i'm not sure if chunk has been released */
p = malloc(1000); /* allocate now */
Diese Verwendungen können durch #defineDirektiven maskiert werden , um nützliche Makros zu konstruieren (ein übliches ist #define XFREE(ptr) safefree((void **)&(ptr))), um so etwas wie eine Metasprache zu erstellen, oder sie können separat in eine Werkzeugbibliothek eingebettet werden. In jedem Fall sollten Programmierer, die diese Technik verwenden, die sicheren Versionen in jedem Fall verwenden, in dem free()sie verwendet werden würden; Andernfalls führt dies erneut zu dem Problem. Außerdem ist diese Lösung auf den Umfang eines einzelnen Programms oder Projekts beschränkt und sollte ordnungsgemäß dokumentiert werden.
Unter den strukturierteren Lösungen ist die Verwendung von intelligenten Zeigern eine beliebte Technik, um baumelnde Zeiger in C++ zu vermeiden . Ein intelligenter Zeiger verwendet normalerweise Referenzzählungen , um Objekte zurückzufordern. Einige andere Techniken umfassen die Tombstones- Methode und die Locks-and-Keys- Methode.
Ein anderer Ansatz besteht darin, den Boehm Garbage Collector zu verwenden , einen konservativen Garbage Collector , der Standardfunktionen zur Speicherzuordnung in C und C++ durch einen Garbage Collector ersetzt. Dieser Ansatz eliminiert Dangling-Pointer-Fehler vollständig, indem er Frees deaktiviert und Objekte durch Garbage Collection zurückfordert.
In Sprachen wie Java können keine freien Zeiger auftreten, da es keinen Mechanismus gibt, um Speicher explizit freizugeben. Stattdessen kann der Garbage Collector Speicher freigeben, jedoch nur, wenn das Objekt nicht mehr über Referenzen erreichbar ist.
In der Sprache Rust wurde das Typsystem um die Variablen Lifetimes und Resource Acquisition is Initialization erweitert . Wenn man die Funktionen der Sprache nicht deaktiviert, werden baumelnde Zeiger zur Kompilierzeit abgefangen und als Programmierfehler gemeldet.
Erkennung baumelnder Zeiger
Um Fehler durch dangling-pointer aufzudecken, besteht eine übliche Programmiertechnik darin, Pointer auf den Null-Pointer oder auf eine ungültige Adresse zu setzen, sobald der Speicher, auf den sie zeigen, freigegeben wurde. Wenn der Nullzeiger dereferenziert wird (in den meisten Sprachen), wird das Programm sofort beendet – es besteht keine Möglichkeit für Datenbeschädigung oder unvorhersehbares Verhalten. Dies macht den zugrunde liegenden Programmierfehler leichter zu finden und zu beheben. Diese Technik hilft nicht, wenn mehrere Kopien des Zeigers vorhanden sind.
Einige Debugger wird automatisch überschreiben und zerstören Daten, die in der Regel mit einem bestimmten Muster befreit wurde, wie 0xDEADBEEF(Microsoft Visual C / C ++ Debugger zum Beispiel Anwendungen 0xCC, 0xCDoder 0xDDje nachdem , was befreit wurde). Dies verhindert in der Regel die Wiederverwendung der Daten, indem es sie unbrauchbar und auch sehr auffällig macht (das Muster dient dazu, dem Programmierer zu zeigen, dass der Speicher bereits freigegeben wurde).
Tools wie Polyspace , TotalView , Valgrind , Mudflap, AddressSanitizer oder auf LLVM basierende Tools können ebenfalls verwendet werden, um die Verwendung von Dangling Pointern zu erkennen.
Andere Tools ( SoftBound , Insure++ und CheckPointer ) instrumentieren den Quellcode, um legitime Werte für Zeiger ("Metadaten") zu sammeln und zu verfolgen und jeden Zeigerzugriff anhand der Metadaten auf Gültigkeit zu prüfen.
Wenn Sie eine kleine Menge von Klassen vermuten, besteht eine andere Strategie darin, alle ihre Memberfunktionen vorübergehend virtuell zu machen : Nachdem die Klasseninstanz zerstört/freigegeben wurde, wird ihr Zeiger auf die virtuelle Methodentabelle auf gesetzt NULL, und jeder Aufruf einer Memberfunktion wird stürzt das Programm ab und es wird der schuldige Code im Debugger angezeigt.
Andere Verwendungen
Der Begriff Dangling Pointer kann auch in anderen Kontexten als der Programmierung verwendet werden, insbesondere von Technikern. Zum Beispiel ist eine Telefonnummer für eine Person, die seitdem das Telefon gewechselt hat, ein reales Beispiel für einen baumelnden Zeiger. Ein weiteres Beispiel ist ein Eintrag in einer Online-Enzyklopädie , der auf einen anderen Eintrag verweist, dessen Titel geändert wurde, wodurch alle zuvor vorhandenen Verweise auf diesen Eintrag in baumelnde Zeiger geändert werden.