Visící ukazatel - Dangling pointer

Visící ukazatel

Visící ukazatele a divoké ukazatele v počítačovém programování jsou ukazatele , které neodkazují na platný objekt příslušného typu. Jedná se o speciální případy narušení bezpečnosti paměti . Obecněji platí, že visící odkazy a divoké odkazy jsou odkazy , které se nevyřeší na platné místo určení, a zahrnují takové jevy, jako je odkazová hniloba na internetu.

Visící ukazatele vznikají během ničení objektů , kdy je objekt, který má příchozí referenci, odstraněn nebo uvolněn, aniž by došlo ke změně hodnoty ukazatele, takže ukazatel stále ukazuje na umístění paměti v uvolněné paměti. Systém může dříve uvolněnou paměť znovu přidělit, a pokud pak program dereferences (nyní) visící ukazatel, může dojít k nepředvídatelnému chování , protože paměť nyní může obsahovat úplně jiná data. Pokud program zapisuje do paměti odkazované visícím ukazatelem, může dojít k tichému poškození nesouvisejících dat, což vede k jemným chybám, které lze extrémně obtížně najít. Pokud byla paměť realokována na jiný proces, pak pokus o dereference visícího ukazatele může způsobit chyby segmentace (UNIX, Linux) nebo obecné chyby ochrany (Windows). Pokud má program dostatečná oprávnění, která mu umožňují přepsat data z účetnictví používaná alokátorem paměti jádra, může poškození způsobit nestabilitu systému. V objektově orientovaných jazycích se sběrem odpadků brání visícím odkazům pouze ničení objektů, které jsou nedostupné, což znamená, že nemají žádné příchozí ukazatele; to je zajištěno sledováním nebo počítáním referencí . Nicméně, finalizer může vytvořit nové odkazy na objekt, vyžadující objekt vzkříšení , aby se zabránilo odkaz visící.

Divoké ukazatele vznikají, když je ukazatel použit před inicializací do nějakého známého stavu, což je v některých programovacích jazycích možné. Vykazují stejné nevyrovnané chování jako visící ukazatele, i když je méně pravděpodobné, že zůstanou nezjištěny, protože mnoho kompilátorů vyvolá varování v době kompilace, pokud jsou před inicializací zpřístupněny deklarované proměnné.

Příčina visících ukazatelů

V mnoha jazycích (např. Programovací jazyk C ) odstranění objektu z paměti explicitně nebo zničením rámce zásobníku při návratu nemění přidružené ukazatele. Ukazatel stále ukazuje na stejné místo v paměti, i když jej nyní lze použít k jiným účelům.

Níže je uveden přímočarý příklad:

{
   char *dp = NULL;
   /* ... */
   {
       char c;
       dp = &c;
   } 
     /* c falls out of scope */
     /* dp is now a dangling pointer */
}

Pokud je operační systém schopen detekovat za běhu odkazy na nulové ukazatele , výše uvedeným řešením je přiřadit 0 (null) dp bezprostředně před výstupem z vnitřního bloku. Dalším řešením by bylo nějakým způsobem zaručit, že se dp znovu nepoužije bez další inicializace.

Dalším častým zdrojem visících ukazatelů je neuspořádaná kombinace volání knihovny malloc()a free()volání: ukazatel se visí, když se uvolní blok paměti, na který ukazuje. Stejně jako v předchozím příkladu je jedním ze způsobů, jak se tomu vyhnout, zajistit resetování ukazatele na nulu po uvolnění jeho odkazu - jak je ukázáno níže.

#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 */
    /* ... */
}

Příliš častým chybným krokem je vracení adres lokální proměnné přidělené zásobníkem: jakmile se vrátí volaná funkce, prostor pro tyto proměnné se uvolní a technicky budou mít „odpadkové hodnoty“.

int *func(void)
{
    int num = 1234;
    /* ... */
    return &num;
}

Pokusy o čtení z ukazatele mohou ještě chvíli po volání vrátit správnou hodnotu (1234) func, ale všechny funkce, které budou poté vyvolány, mohou přepsat úložiště zásobníku přidělené numjiným hodnotám a ukazatel by již nefungoval správně. Pokud nummusí být vrácen ukazatel na , nummusí mít rozsah mimo funkci - může být deklarován jako static.

Ruční deallokace bez visící reference

Antoni Kreczmar  [ pl ] (1945-1996) vytvořil kompletní systém správy objektů, který je bez houpajících se referenčních jevů, viz.

Schéma axiomů operace zabít
Nechť x 1 , ..., x n jsou proměnné, n> 0, 1≤i≤n. Každý vzorec následujícího schématu je větou virtuálního stroje vytvořeného Kreczmarem.
číst jako : pokud objekt o je hodnota n proměnných, pak po provedení instrukce kill (x i ) společná hodnota těchto proměnných není žádná (to znamená, že od tohoto okamžiku je objekt o nedosažitelný a následně část paměť, kterou zabírá, lze stejnou operací zabít recyklovat bez jakéhokoli poškození).

Tudíž:

  • není třeba opakovat operaci zabít (x 1 ), zabít (x 2 ), ...
  • neexistuje žádný fenomén visící reference ,
  • jakýkoli pokus o přístup k odstraněnému objektu bude detekován a signalizován jako výjimka „ odkaz na žádný “.

Poznámka: náklady na zabití jsou konstantní .

Podobný přístup navrhli Fisher a LeBlanc pod názvem Zámky a klíče .

Příčina divokých ukazatelů

Divoké ukazatele se vytvářejí vynecháním nutné inicializace před prvním použitím. Přesně řečeno, každý ukazatel v programovacích jazycích, které nevynucují inicializaci, začíná jako divoký ukazatel.

K tomu nejčastěji dochází kvůli přeskočení inicializace, nikoli jejím vynecháním. Většina kompilátorů na to dokáže upozornit.

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 */
}

Bezpečnostní díry zahrnující visící ukazatele

Stejně jako chyby přetečení vyrovnávací paměti se z chyb visících/divokých ukazatelů často stávají bezpečnostní díry. Pokud se například ukazatel používá k volání virtuální funkce , může být kvůli přepsání ukazatele vtable volána jiná adresa (možná směřující na kód exploitu) . Alternativně, pokud se ukazatel používá pro zápis do paměti, může dojít k poškození některé jiné datové struktury. I když je paměť načtena pouze tehdy, když se ukazatel pohupuje, může to vést k úniku informací (pokud jsou do další struktury, která je tam přidělena, vložena zajímavá data) nebo k eskalaci privilegií (pokud je při kontrolách zabezpečení použita nyní neplatná paměť). Když se po uvolnění uvolní visící ukazatel, aniž by mu byl přidělen nový kus paměti, stane se to známým jako zranitelnost „použití po volném“. Například CVE - 2014-1776 je chyba zabezpečení, kterou lze v aplikaci Microsoft Internet Explorer 6 až 11 použít po použití a která je využívána útoky nultého dne pomocí pokročilé trvalé hrozby .

Vyhněte se visícím chybám ukazatele

V jazyce C je nejjednodušší implementovat alternativní verzi funkce free()(nebo podobné), která zaručuje reset ukazatele. Tato technika však nevymaže jiné proměnné ukazatele, které mohou obsahovat kopii ukazatele.

#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;
}

Alternativní verzi lze použít i k zajištění platnosti prázdného ukazatele před voláním malloc():

    safefree(&p);        /* i'm not sure if chunk has been released */
    p = malloc(1000);    /* allocate now */

Tato použití lze maskovat pomocí #definesměrnic pro konstrukci užitečných maker (běžná bytost #define XFREE(ptr) safefree((void **)&(ptr))), vytvářet něco jako metajazyk nebo je lze vložit do knihovny nástrojů samostatně. V každém případě by programátoři používající tuto techniku ​​měli používat bezpečné verze v každém případě, kde free()by byly použity; pokud tak neučiníte, vede to opět k problému. Toto řešení je také omezeno na rozsah jednoho programu nebo projektu a mělo by být řádně zdokumentováno.

Mezi strukturovanější řešení patří oblíbenou technikou, jak se v C ++ vyhnout visícím ukazatelům, používání chytrých ukazatelů . Chytrý ukazatel obvykle používá počítání odkazů k získání objektů. Mezi další techniky patří metoda náhrobků a metoda zámků a klíčů .

Dalším přístupem je použít Boehm garbage collector , konzervativní garbage collector, který nahrazuje standardní funkce alokace paměti v C a C ++ popelářem. Tento přístup zcela eliminuje chyby visícího ukazatele deaktivací uvolňování a rekultivací objektů uvolňováním odpadu.

V jazycích, jako je Java, se visící ukazatele nemohou vyskytovat, protože neexistuje žádný mechanismus pro explicitní uvolnění paměti. Uvolňovač paměti může místo toho uvolnit paměť, ale pouze v případě, že objekt již není dostupný z žádných odkazů.

V jazyce Rust byl typový systém rozšířen tak, aby zahrnoval také životnost proměnných a získávání zdrojů je inicializace . Pokud nedeaktivujete funkce jazyka, budou visící ukazatele zachyceny v době kompilace a hlášeny jako programovací chyby.

Detekce visícího ukazatele

Chcete -li vystavit chyby visícího ukazatele, jednou z běžných technik programování je nastavit ukazatele na nulový ukazatel nebo na neplatnou adresu, jakmile bude uvolněno úložiště, na které ukazují. Když je nulový ukazatel zrušen (ve většině jazyků), program se okamžitě ukončí - neexistuje žádný potenciál pro poškození dat nebo nepředvídatelné chování. To usnadňuje hledání a řešení základní chyby programování. Tato technika nepomáhá, pokud existuje více kopií ukazatele.

Některé ladicí programy automaticky přepíší a zničí data, která byla uvolněna, obvykle s konkrétním vzorem, například 0xDEADBEEF(například používá ladicí program Visual C/C ++ společnosti Microsoft 0xCC, 0xCDnebo v 0xDDzávislosti na tom, co bylo uvolněno). To obvykle brání opětovnému použití dat tím, že jsou zbytečná a také velmi nápadná (vzor slouží k ukázání programátoru, že paměť již byla uvolněna).

K detekci použití visících ukazatelů lze také použít nástroje jako Polyspace , TotalView , Valgrind , Mudflap, AddressSanitizer nebo nástroje založené na LLVM .

Jiné nástroje ( SoftBound , Insure ++ a CheckPointer ) instrumentují zdrojový kód ke shromažďování a sledování oprávněných hodnot ukazatelů („metadata“) a kontrolují platnost každého ukazatele vůči metadatům.

Další strategií, při podezření na malou sadu tříd, je dočasně virtualizovat všechny jejich členské funkce : poté, co byla instance třídy zničena/uvolněna, její ukazatel na tabulku virtuální metody je nastaven na NULLa každé volání členské funkce bude havarujte program a v debuggeru se zobrazí vinný kód.

Jiné použití

Termín visící ukazatel může být také použit v jiných kontextech než programování, zejména technickými lidmi. Například telefonní číslo na osobu, která od té doby změnila telefony, je skutečným příkladem visícího ukazatele. Dalším příkladem je záznam v online encyklopedii, který odkazuje na jiný záznam, jehož název byl změněn, a mění všechny dříve existující odkazy na tento záznam na visící ukazatele.

Viz také

Reference