stringtranslate.com

puntero colgando

puntero colgando

Los punteros colgantes y los punteros salvajes en la programación informática son punteros que no apuntan a un objeto válido del tipo apropiado. Estos son casos especiales de violaciones de seguridad de la memoria . De manera más general, las referencias colgantes y las referencias salvajes son referencias que no se resuelven en un destino válido.

Los punteros colgantes surgen durante la destrucción de objetos , cuando un objeto que tiene una referencia entrante se elimina o se desasigna, sin modificar el valor del puntero, de modo que el puntero aún apunta a la ubicación de la memoria desasignada. El sistema puede reasignar la memoria previamente liberada, y si el programa luego elimina la referencia al puntero (ahora) colgante, puede resultar en un comportamiento impredecible , ya que la memoria ahora puede contener datos completamente diferentes. Si el programa escribe en la memoria a la que hace referencia un puntero colgante, puede producirse una corrupción silenciosa de datos no relacionados, lo que genera errores sutiles que pueden ser extremadamente difíciles de encontrar. Si la memoria se ha reasignado a otro proceso, intentar eliminar la referencia al puntero colgante puede causar fallas de segmentación (UNIX, Linux) o fallas de protección general (Windows). Si el programa tiene privilegios suficientes para permitirle sobrescribir los datos contables utilizados por el asignador de memoria del núcleo, la corrupción puede causar inestabilidades en el sistema. En los lenguajes orientados a objetos con recolección de basura , las referencias colgantes se evitan destruyendo únicamente los objetos que son inalcanzables, lo que significa que no tienen ningún puntero entrante; esto se garantiza mediante el rastreo o el recuento 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 la inicialización a algún estado conocido, lo cual es posible en algunos lenguajes de programación. Muestran el mismo comportamiento errático que los punteros colgantes, aunque es menos probable que pasen desapercibidos porque muchos compiladores generarán una advertencia en el momento de la compilación si se accede a las variables declaradas antes de ser inicializadas. [1]

Causa de los punteros colgantes

En muchos lenguajes (por ejemplo, el lenguaje de programación C ), eliminar un objeto de la memoria explícitamente o destruir el marco de la pila al regresar no altera los punteros asociados. El puntero todavía apunta a la misma ubicación en la memoria aunque ahora pueda usarse para otros propósitos.

A continuación se muestra un ejemplo sencillo:

{ char * dp = NULL ; /* ... */ { char c ; dp = & c ; } /* c queda fuera del alcance */ /* dp ahora es un puntero colgante */ }               

Si el sistema operativo es capaz de detectar referencias en tiempo de ejecución a punteros nulos , una solución a lo anterior es 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 utilizar sin una inicialización adicional.

Otra fuente frecuente de punteros colgantes es una combinación confusa de llamadas malloc()a free()bibliotecas y: un puntero queda 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.

#incluir <stdlib.h> función vacía () { char * dp = malloc ( A_CONST ); /* ... */ libre ( dp ); /* dp ahora se convierte en un puntero colgante */ dp = NULL ; /* dp ya no está colgando */ /* ... */ }             

Un paso en falso muy común es devolver direcciones de una variable local asignada por la pila: una vez que regresa una función llamada, el espacio para estas variables se desasigna y técnicamente tienen "valores basura".

int * func ( vacío ) { int número = 1234 ; /* ... */ return & núm ; }        

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 numcon otros valores y el puntero ya no funcionará correctamente. Si numse debe devolver un puntero a , numdebe tener un alcance más allá de la función; podría declararse como static.

Desasignación manual sin referencia colgante

Antoni Kreczmar  [pl] (1945-1996) ha creado un completo sistema de gestión de objetos libre de fenómenos de referencias colgantes. [2] Fisher y LeBlanc [3] propusieron un enfoque similar bajo el nombre Locks-and-keys .

Causa de punteros salvajes

Los punteros salvajes se crean omitiendo la inicialización necesaria antes del primer uso. Por lo tanto, estrictamente hablando, cada puntero en los lenguajes de programación que no imponen la inicialización comienza como un puntero salvaje.

Esto ocurre con mayor frecuencia debido a que se salta la inicialización, no al omitirla. La mayoría de los compiladores pueden advertir sobre esto.

int f ( int i ) { char * dp ; /* dp es un puntero salvaje */ static char * scp ; /* scp no es un puntero salvaje:  * las variables estáticas se inicializan a 0  * al inicio y conservan sus valores desde  * la última llamada posterior.  * El uso de esta función puede considerarse de mal  * estilo si no se comenta */ }         

Agujeros de seguridad relacionados con punteros colgantes

Al igual que los errores de desbordamiento del búfer , los errores de punteros colgantes/salvajes con frecuencia se convierten en agujeros de seguridad. Por ejemplo, si el puntero se utiliza para realizar una llamada a una función virtual , se puede llamar a una dirección diferente (posiblemente apuntando al código de explotación) debido a que se sobrescribe el puntero de vtable . Alternativamente, si el puntero se utiliza para escribir en la memoria, es posible que alguna otra estructura de datos esté dañada. Incluso si la memoria sólo se lee una vez que el puntero queda colgando, puede provocar fugas de información (si se colocan datos interesantes en la siguiente estructura asignada allí) o una escalada de privilegios (si la memoria ahora no válida se utiliza en controles de seguridad). Cuando se utiliza un puntero colgante después de haber sido liberado sin asignarle una nueva porción de memoria, esto se conoce como vulnerabilidad de "uso después de la liberación". [4] Por ejemplo, CVE - 2014-1776 es una vulnerabilidad de uso después de la liberación en Microsoft Internet Explorer 6 a 11 [5] que es utilizada por ataques de día cero por parte de una amenaza persistente avanzada . [6]

Evitar errores de puntero colgante

En C, la técnica más sencilla es implementar una versión alternativa de la free()función (o similar) que garantice el reinicio del puntero. Sin embargo, esta técnica no borrará otras variables de puntero que puedan contener una copia del puntero.

#incluir <assert.h> #incluir <stdlib.h>  /* Versión alternativa para 'free()' */ static void safefree ( void ** pp ) { /* en modo de depuración, cancelar si pp es NULL */ afirmar ( pp ); /* free(NULL) funciona correctamente, por lo que no se requiere ninguna verificación además de afirmar en modo de depuración */ free ( * pp ); /* desasignar fragmento, tenga en cuenta que free(NULL) es válido */ * pp = NULL ; /* restablecer el puntero original */ }            int f ( int i ) { char * p = NULL , * p2 ; p = malloc ( 1000 ); /* obtener un trozo */ p2 = p ; /* copia el puntero */ /* usa el fragmento aquí */ safefree (( void ** ) & p ); /* liberación de seguridad; no afecta 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 pendiente, por lo que este es un comportamiento indefinido. */ devolver i + c ; }                               

La versión alternativa se puede utilizar incluso para garantizar la validez de un puntero vacío antes de llamar malloc():

 libre de seguridad ( & p ); /* No estoy seguro de si se ha liberado el fragmento */ p = malloc ( 1000 ); /* asignar ahora */     

Estos usos pueden enmascararse mediante #definedirectivas para construir macros útiles (una común es #define XFREE(ptr) safefree((void **)&(ptr))), creando algo así como un metalenguaje o pueden integrarse en una biblioteca de herramientas aparte. En todos los casos, los programadores que utilicen esta técnica deben utilizar las versiones seguras en todos los casos en los que free()se utilicen; no hacerlo conduce nuevamente al problema. Además, esta solución se limita al alcance de un único programa o proyecto y debe documentarse adecuadamente.

Entre las soluciones más estructuradas, una técnica popular para evitar punteros colgantes en C++ es utilizar punteros inteligentes . Un puntero inteligente normalmente utiliza el recuento de referencias para recuperar objetos. Algunas otras técnicas incluyen el método de las lápidas y el método de cerraduras y llaves . [3]

Otro enfoque es utilizar el recolector de basura Boehm , un recolector de basura conservador que reemplaza las funciones de asignación de memoria estándar en C y C++ con un recolector de basura. Este enfoque elimina por completo los errores de puntero colgante al deshabilitar las liberaciones y recuperar objetos mediante la recolección de basura.

En lenguajes como Java, los punteros colgantes no pueden ocurrir porque no existe ningún mecanismo para desasignar memoria explícitamente. Más bien, el recolector de basura puede desasignar memoria, pero solo cuando ya no se pueda acceder al objeto desde ninguna referencia.

En el lenguaje Rust , el sistema de tipos se ha ampliado para incluir también las variables de duración y adquisición de recursos e inicialización . A menos que se desactiven las funciones del lenguaje, los punteros colgantes se detectarán en el momento de la compilación y se informarán como errores de programación.

Detección de puntero colgante

Para exponer errores de punteros pendientes, una técnica de programación común es establecer punteros en el puntero nulo o en una dirección no válida una vez que se haya liberado el almacenamiento al que apuntan. Cuando se elimina la referencia al puntero nulo (en la mayoría de los idiomas), el programa finalizará inmediatamente; no hay posibilidad de corrupción de datos o comportamiento impredecible. Esto hace que el error de programación subyacente sea más fácil de encontrar y resolver. Esta técnica no ayuda cuando hay varias copias del puntero.

Algunos depuradores sobrescribirán y destruirán automáticamente los datos que se han liberado, 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 haciéndolos inútiles y además muy destacados (el patrón sirve para mostrarle al programador que la memoria ya ha sido liberada).

También se pueden utilizar herramientas como Polyspace , TotalView , Valgrind , Mudflap, [8] AddressSanitizer o herramientas basadas en LLVM [9] para detectar usos de punteros colgantes.

Otras herramientas (SoftBound, Insure++ y CheckPointer) instrumentan el código fuente para recopilar y rastrear valores legítimos de punteros ("metadatos") y verificar la validez de cada acceso al puntero con los metadatos.

Otra estrategia, cuando se sospecha de un pequeño conjunto de clases, es hacer temporalmente todas sus funciones miembro virtuales : después de que la instancia de clase ha sido destruida/liberada, su puntero a la tabla de métodos virtuales se establece en NULLy cualquier llamada a una función miembro se activará. Bloquee el programa y mostrará el código culpable en el depurador.

Ver también

Referencias

  1. ^ "Opciones de advertencia: uso de la colección de compiladores GNU (GCC)".
  2. ^ Gianna Cioni, Antoni Kreczmar, Desasignación programada sin referencias colgantes , Cartas de procesamiento de información , v. 18, 1984 , págs.
  3. ^ ab CN 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.
  4. ^ Dalci, Eric; autor anónimo; Equipo de contenido de CWE (11 de mayo de 2012). "CWE-416: uso gratuito". Enumeración de debilidades comunes . Corporación Mitre . Consultado el 28 de abril de 2014 . {{cite web}}: |author2=tiene nombre genérico ( ayuda )
  5. ^ "CVE-2014-1776". Vulnerabilidades y exposiciones comunes (CVE) . 2014-01-29. Archivado desde el original el 30 de abril de 2017 . Consultado el 16 de mayo de 2017 .
  6. ^ Chen, Xiaobo; Caselden, Dan; Scott, Mike (26 de abril de 2014). "Nuevo exploit de día cero dirigido a las versiones 9 a 11 de Internet Explorer identificado en ataques dirigidos". Blog de FireEye . Ojo de fuego . Consultado el 28 de abril de 2014 .
  7. ^ Patrones de llenado de memoria de Visual C++ 6.0
  8. ^ Depuración del puntero guardabarros
  9. ^ Dhurjati, D. y Adve, V. Detección eficiente de todos los usos del puntero colgante en servidores de producción