
En programación informática, los punteros colgantes y los punteros comodín son punteros que no apuntan a un objeto válido del tipo apropiado. Estos son casos especiales de violaciones de seguridad de memoria . En términos más generales, las referencias colgantes y las referencias comodín son referencias que no se resuelven en un destino válido.
Los punteros colgantes surgen durante la destrucción de objetos , cuando un objeto al que apunta un puntero determinado se elimina o se desasigna sin modificar el valor de dicho puntero, de modo que este sigue apuntando a la ubicación de memoria desasignada. El sistema puede reasignar la memoria previamente liberada, y si el programa desreferencia el puntero (ahora) colgante, puede producirse un comportamiento impredecible , ya que la memoria puede contener datos completamente diferentes. Si el programa escribe en la memoria a la que apunta un puntero colgante, puede producirse una corrupción silenciosa de datos no relacionados, lo que da lugar a errores sutiles que pueden ser extremadamente difíciles de detectar. Si la memoria se ha reasignado a otro proceso, intentar desreferenciar el puntero colgante puede provocar fallos de segmentación (UNIX, Linux) o fallos de protección general (Windows). Si el programa tiene privilegios suficientes para sobrescribir los datos de contabilidad utilizados por el asignador de memoria del kernel, la corrupción puede provocar inestabilidades en el sistema. En los lenguajes orientados a objetos con recolección de basura , las referencias colgantes se evitan destruyendo únicamente los objetos inaccesibles, es decir, aquellos que no reciben ningún puntero entrante; esto se garantiza mediante el rastreo o el conteo de referencias . Sin embargo, un finalizador puede crear nuevas referencias a un objeto, lo que requiere la resurrección del objeto para evitar una referencia colgante.
Los punteros salvajes, también llamados punteros no inicializados, surgen cuando se utiliza un puntero antes de su inicialización a un estado conocido, lo cual es posible en algunos lenguajes de programación. Presentan el mismo comportamiento errático que los punteros colgantes, aunque es menos probable que pasen desapercibidos, ya que muchos compiladores generan una advertencia en tiempo de compilación si se accede a las variables declaradas antes de su inicialización. [ 1 ]
Causa de punteros colgantes
En muchos lenguajes de programación (por ejemplo, el lenguaje C ), eliminar un objeto de la memoria explícitamente o destruir el marco de pila al regresar no altera los punteros asociados. El puntero sigue apuntando a la misma ubicación en la memoria, aunque esa ubicación ahora pueda utilizarse para otros fines.
A continuación se muestra un ejemplo sencillo:
{ char * dp = NULL ; // ... { char c ; dp = & c ; } // c queda fuera del ámbito // dp ahora es un puntero colgante }Si el sistema operativo puede detectar referencias en tiempo de ejecución a punteros nulos , una solución consiste en asignar 0 (nulo) a dp inmediatamente antes de salir del bloque interno. Otra solución sería garantizar de alguna manera que dp no se vuelva a usar sin una inicialización adicional.
Otra causa frecuente de punteros colgantes es una combinación desordenada de llamadas malloc()a free()bibliotecas: un puntero se vuelve colgante cuando se libera el bloque de memoria al que apunta. Al igual que en el ejemplo anterior, una forma de evitar esto es asegurarse de restablecer el puntero a nulo después de liberar su referencia, como se muestra a continuación.
#include <stdlib.h>void func () { char * dp = ( char * ) malloc ( sizeof ( char ) * 10 ); // ... free ( dp ); // dp ahora se convierte en un puntero colgante dp = NULL ; // dp ya no es colgante // ... }Un error demasiado común es devolver direcciones de variables locales asignadas en la pila: una vez que una función llamada regresa, el espacio para estas variables se libera y, técnicamente, tienen "valores basura".
int * func ( void ) { int num = 1234 ; // ... return & num ; }Los intentos de leer desde el puntero aún pueden devolver el valor correcto (1234) durante un tiempo después de llamar a func, pero cualquier función llamada posteriormente puede sobrescribir el almacenamiento de pila asignado para numcon otros valores y el puntero dejaría de funcionar correctamente. Si numse debe devolver un puntero a , numdebe tener un ámbito más allá de la función; podría declararse como static.
Desasignación manual sin referencia colgante
Antoni Kreczmar (1945–1996) creó un sistema completo de gestión de objetos que está libre del fenómeno de referencias colgantes. [ 2 ] Un enfoque similar fue propuesto por Fisher y LeBlanc [ 3 ] bajo el nombre de Locks-and-keys .
Causa de punteros salvajes
Wild pointers are created by omitting necessary initialization prior to first use. Thus, strictly speaking, every pointer in programming languages which do not enforce initialization begins as a wild pointer.
This most often occurs due to jumping over the initialization, not by omitting it. Most compilers are able to warn about this.
intf(inti){char*dp;// dp is a wild pointer staticchar*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 */}Security holes involving dangling pointers
Like buffer-overflow bugs, dangling/wild pointer bugs frequently become security holes. For example, if the pointer is used to make a virtual function call, a different address (possibly pointing at exploit code) may be called due to the vtable pointer being overwritten. Alternatively, if the pointer is used for writing to memory, some other data structure may be corrupted. Even if the memory is only read once the pointer becomes dangling, it can lead to information leaks (if interesting data is put in the next structure allocated there) or to privilege escalation (if the now-invalid memory is used in security checks). When a dangling pointer is used after it has been freed without allocating a new chunk of memory to it, this becomes known as a "use after free" vulnerability.[4] For example, CVE-2014-1776 is a use-after-free vulnerability in Microsoft Internet Explorer 6 through 11[5] that was used by zero-day attacks by an advanced persistent threat.[6]
Avoiding dangling pointer errors
In C, the simplest technique is to implement an alternative version of the free() (or alike) function which guarantees the reset of the pointer. However, this technique will not clear other pointer variables which may contain a copy of the pointer.
#include<assert.h>#include<stdlib.h>// Versión segura de free() static void safeFree ( void ** pp ) { // en modo depuración, abortar si pp es NULL assert ( pp ); // free(NULL) funciona correctamente, por lo que no se requiere ninguna comprobación aparte de la assert en modo depuración free ( * pp ); // liberar el bloque, tenga en cuenta que free(NULL) es válido * pp = NULL ; // restablecer el puntero original }int f ( int i ) { char * p = NULL ; char * p2 ; p = ( char * ) malloc ( 1000 ); // obtener un fragmento p2 = p ; // copiar el puntero // usar el fragmento aquí safeFree (( void ** ) & p ); // liberación segura; no afecta a la variable p2 safeFree (( void ** ) & p ); // esta segunda llamada no fallará ya que p se restablece a NULL char c = * p2 ; // p2 sigue siendo un puntero colgante, por lo que este es un comportamiento indefinido. return i + c ; }La versión alternativa puede utilizarse incluso para garantizar la validez de un puntero vacío antes de llamar a malloc():
safeFree ( & p ); // No estoy seguro de si el fragmento ha sido liberado */ p = ( char * ) malloc ( 1000 ); // asignar ahoraEstos usos pueden ocultarse mediante #definedirectivas para construir macros útiles (una común es #define XFREE(ptr) safeFree((void**)&(ptr))), creando algo parecido a un metalenguaje o pueden integrarse en una biblioteca de herramientas aparte. En todos los casos, los programadores que utilicen esta técnica deben usar las versiones seguras en cada instancia donde free()se usaría ; no hacerlo conduce nuevamente al problema. Además, esta solución se limita al ámbito de un solo programa o proyecto, y debe estar debidamente documentada.
Entre las soluciones más estructuradas, una técnica popular para evitar punteros colgantes en C++ es el uso de punteros inteligentes . Un puntero inteligente normalmente utiliza el conteo de referencias para recuperar objetos. Otras técnicas incluyen el método de marcadores de posición y el método de llaves y candados . [ 3 ]
Otro enfoque consiste en utilizar el recolector de basura Boehm , un recolector de basura conservador que reemplaza las funciones estándar de asignación de memoria en C y C++ con un recolector de basura. Este enfoque elimina por completo los errores de punteros colgantes al deshabilitar las liberaciones y recuperar los objetos mediante la recolección de basura.
Otro enfoque consiste en utilizar un sistema como CHERI , que almacena punteros con metadatos adicionales que pueden prevenir accesos no válidos al incluir información sobre el tiempo de vida de los punteros. CHERI generalmente requiere soporte de la CPU para realizar estas comprobaciones adicionales.
En lenguajes como Java, no se producen punteros colgantes porque no existe un mecanismo para liberar explícitamente la memoria. En cambio, el recolector de basura puede liberar la memoria, pero solo cuando el objeto ya no es accesible desde ninguna referencia.
En el lenguaje Rust , el sistema de tipos se ha ampliado para incluir también la duración de las variables y la adquisición de recursos durante la inicialización . A menos que se desactiven estas características del lenguaje, los punteros colgantes se detectarán en tiempo de compilación y se reportarán como errores de programación.
Detección de puntero colgante
Para detectar errores de punteros colgantes, una técnica de programación común consiste en asignar punteros al puntero nulo o a una dirección no válida una vez que se haya liberado la memoria a la que apuntan. Al desreferenciar el puntero nulo (en la mayoría de los lenguajes), el programa finaliza inmediatamente, sin riesgo de corrupción de datos ni comportamiento impredecible. Esto facilita la detección y resolución del error de programación subyacente. Sin embargo, esta técnica no es útil cuando existen varias copias del puntero.
Algunos depuradores sobrescriben y destruyen automáticamente los datos liberados, generalmente con un patrón específico, como 0xDEADBEEF(el depurador Visual C/C++ de Microsoft, por ejemplo, usa 0xCC, 0xCDo 0xDDdependiendo de lo que se haya liberado [ 7 ] ). Esto suele impedir que los datos se reutilicen al hacerlos inútiles y muy visibles (el patrón sirve para mostrar al programador que la memoria ya se ha liberado).
También se pueden utilizar herramientas como Polyspace , TotalView , Valgrind , Mudflap, [ 8 ] AddressSanitizer o herramientas basadas en LLVM [ 9 ] para detectar el uso de punteros colgantes.
Otras herramientas ( SoftBound , Insure++ y CheckPointer ) instrumentan el código fuente para recopilar y rastrear valores legítimos para los punteros ("metadatos") y comprueban la validez de cada acceso a un puntero comparándolo con los metadatos.
Otra estrategia, cuando se sospecha de un pequeño conjunto de clases, es hacer que todas sus funciones miembro sean virtuales temporalmente : después de que la instancia de la clase se haya destruido/liberado, su puntero a la Tabla de Métodos Virtuales se establece en NULL, y cualquier llamada a una función miembro hará que el programa falle y mostrará el código culpable en el depurador.
La extensión de etiquetado de memoria ARM64 (MTE), deshabilitada por defecto en sistemas Linux, pero que puede habilitarse en Android 16 , provoca un fallo de segmentación cuando detecta uso de memoria liberada y desbordamiento de búfer . [ 10 ] [ 11 ]
Véase también
Referencias
- ↑ "Opciones de advertencia: uso de la colección de compiladores GNU (GCC)" .
- ↑ Gianna Cioni, Antoni Kreczmar, Desasignación programada sin referencia colgante , Information Processing Letters , vol. 18, 1984 , págs. 179–185
- 1 2 C. N. Fisher, RJ Leblanc, La implementación de diagnósticos en tiempo de ejecución en Pascal , IEEE Transactions on Software Engineering , 6(4):313–319, 1980.
- ↑ Dalci, Eric; autor anónimo; Equipo de contenido de CWE (11 de mayo de 2012). "CWE-416: Uso después de liberación" . Enumeración de debilidades comunes . Mitre Corporation . Recuperado el 28 de abril de 2014 .
{{cite web}}:|author2=tiene nombre genérico ( ayuda ) - ↑ "CVE-2014-1776" . Vulnerabilidades y exposiciones comunes (CVE) . 29/01/2014. Archivado del original el 30/04/2017 . Consultado el 16/05/2017 .
- ↑ Chen, Xiaobo; Caselden, Dan; Scott, Mike (26 de abril de 2014). "Se identifica una nueva vulnerabilidad de día cero dirigida a las versiones 9 a 11 de Internet Explorer en ataques dirigidos" . Blog de FireEye . FireEye . Consultado el 28 de abril de 2014 .
- ↑ Patrones de llenado de memoria de Visual C++ 6.0
- ↑ Depuración de punteros de Mudflap
- ↑ Dhurjati, D. y Adve, V. Detección eficiente de todos los usos de punteros colgantes en servidores de producción
- ↑ "Extensión de etiquetado de memoria ARM" . Proyecto de código abierto de Android . Consultado el 11 de junio de 2025 .
- ↑ Goodin, Dan (13 de mayo de 2025). "Google introduce el modo de protección avanzada para sus usuarios de Android más vulnerables" . Ars Technica . Consultado el 11 de junio de 2025 .
- Errores de software
- vulnerabilidades de seguridad informática
- Punteros (programación informática)