arrays matrices - ¿Qué tan peligroso es acceder a una matriz fuera de los límites?




en studio (10)

NSArray s en Objective-C se les asigna un bloque específico de memoria. Superar los límites de la matriz significa que estaría accediendo a la memoria que no está asignada a la matriz. Esto significa:

  1. Esta memoria puede tener cualquier valor. No hay forma de saber si los datos son válidos según su tipo de datos.
  2. Esta memoria puede contener información confidencial, como claves privadas u otras credenciales de usuario.
  3. La dirección de memoria puede ser inválida o protegida.
  4. La memoria puede tener un valor cambiante porque otro programa o subproceso está accediendo a ella.
  5. Otras cosas utilizan el espacio de direcciones de memoria, como los puertos asignados en memoria.
  6. Escribir datos en una dirección de memoria desconocida puede bloquear su programa, sobrescribir el espacio de memoria del sistema operativo y, en general, hacer que el sol implote.

Desde el aspecto de su programa, siempre desea saber cuándo su código está excediendo los límites de una matriz. Esto puede hacer que se devuelvan valores desconocidos, lo que provoca que la aplicación se bloquee o proporcione datos no válidos.

¿Qué tan peligroso es acceder a una matriz fuera de sus límites (en C)? A veces puede suceder que lea desde fuera de la matriz (ahora entiendo que luego accedo a la memoria utilizada por otras partes de mi programa o incluso más allá de eso) o estoy tratando de establecer un valor en un índice fuera de la matriz. El programa a veces falla, pero a veces solo se ejecuta, dando solo resultados inesperados.

Ahora, lo que me gustaría saber es, ¿qué tan peligroso es esto realmente? Si daña mi programa, no es tan malo. Si, por otro lado, rompe algo fuera de mi programa, porque de alguna manera logré acceder a alguna memoria totalmente no relacionada, entonces es muy malo, me imagino. Leí un montón de "cualquier cosa puede pasar", "la segmentación podría ser el problema menos grave" , "su disco duro podría volverse rosado y los unicornios podrían estar cantando bajo su ventana", lo cual es bueno, pero ¿cuál es realmente el peligro?

Mis preguntas:

  1. ¿Puede la lectura de valores desde fuera de la matriz dañar algo aparte de mi programa? Me imagino que el solo hecho de ver las cosas no cambia nada, o, por ejemplo, ¿cambiaría el atributo "la última vez que se abrió" de un archivo al que llegué?
  2. ¿Los valores de configuración fuera de la matriz pueden dañar algo aparte de mi programa? A partir de esta pregunta de desbordamiento de pila, deduzco que es posible acceder a cualquier ubicación de memoria, que no hay garantía de seguridad.
  3. Ahora ejecuto mis pequeños programas desde XCode. ¿Proporciona eso alguna protección adicional alrededor de mi programa donde no puede salir de su propia memoria? ¿Puede dañar XCode?
  4. ¿Alguna recomendación sobre cómo ejecutar mi código intrínsecamente con errores?

Yo uso OSX 10.7, Xcode 4.6.


Si alguna vez realiza la programación a nivel de sistemas o la programación de sistemas integrados, pueden suceder cosas muy graves si escribe en ubicaciones de memoria aleatorias. Los sistemas más antiguos y muchos microcontroladores utilizan IO de memoria asignada, por lo que escribir en una ubicación de memoria que se asigna a un registro periférico puede causar estragos, especialmente si se realiza de forma asíncrona.

Un ejemplo es la programación de memoria flash. El modo de programación en los chips de memoria se habilita escribiendo una secuencia específica de valores en ubicaciones específicas dentro del rango de direcciones del chip. Si otro proceso fuera a escribir en cualquier otra ubicación en el chip mientras eso ocurría, causaría que el ciclo de programación fallara.

En algunos casos, el hardware envolverá las direcciones (se ignoran los bits / bytes más significativos de la dirección), por lo que escribir en una dirección más allá del final del espacio de direcciones físicas resultará en que los datos se escriban en el centro de las cosas.

Y finalmente, las CPU más antiguas como la MC68000 pueden bloquearse hasta el punto de que solo un reinicio de hardware puede hacer que vuelvan a funcionar. No he trabajado con ellos durante un par de décadas, pero creo que cuando se produjo un error de bus (memoria inexistente) al intentar manejar una excepción, simplemente se detendría hasta que se confirmara el reinicio del hardware.

Mi mayor recomendación es un descarado complemento para un producto, pero no tengo ningún interés personal en él y no estoy afiliado a ellos de ninguna manera, pero estoy basado en un par de décadas de programación en C y sistemas integrados donde la confiabilidad era fundamental, la PC de Gimpel Lint no solo detectará ese tipo de errores, sino que hará de usted un mejor programador de C / C ++ al insistirle constantemente con los malos hábitos.

También recomendaría leer el estándar de codificación MISRA C, si puede obtener una copia de alguien. No he visto ninguno reciente, pero en los últimos días me dieron una buena explicación de por qué deberías / no deberías hacer las cosas que cubren.

No sé nada de ti, pero la segunda o tercera vez que recibo un coro o colgarme de cualquier aplicación, mi opinión de la compañía que la produce se reduce a la mitad. La 4ª o 5ª vez, y cualquiera que sea el paquete, se convierte en estantería y conduzco una estaca de madera a través del centro del paquete / disco en el que vino para asegurarme de que nunca vuelva a atormentarme.


Si es un programa de espacio de usuario y se ejecuta en un sistema operativo protegido como Linux, lo peor que vería es un fallo de segmentación.


Además de su propio programa, no creo que rompa nada, en el peor de los casos intentará leer o escribir desde una dirección de memoria que corresponda a una página que el kernel no asignó a sus procesos, generando la excepción adecuada y ser asesinado (me refiero a tu proceso).


Estoy trabajando con un compilador para un chip DSP que genera código deliberadamente que accede a uno más allá del final de una matriz de código C que no lo hace.

Esto se debe a que los bucles están estructurados de manera que al final de una iteración se obtienen algunos datos para la siguiente iteración. Por lo tanto, la referencia previa al final de la última iteración nunca se usa realmente.

Escribir código C como ese invoca un comportamiento indefinido, pero eso es solo una formalidad de un documento de estándares que se ocupa de la máxima portabilidad.

Más a menudo que no, un programa que accede fuera de límites no está optimizado inteligentemente. Es simplemente buggy. El código recupera algún valor de basura y, a diferencia de los bucles optimizados del compilador mencionado anteriormente, el código utiliza el valor en cálculos posteriores, lo que lo corrompe.

Vale la pena capturar errores de esa manera, y por lo tanto, vale la pena hacer que el comportamiento quede indefinido solo por ese motivo: para que el tiempo de ejecución pueda producir un mensaje de diagnóstico como "saturación de la matriz en la línea 42 de main.c".

En los sistemas con memoria virtual, se podría asignar una matriz de tal manera que la dirección que sigue se encuentre en un área no asignada de la memoria virtual. El acceso luego bombardeará el programa.

Como nota al margen, tenga en cuenta que en C se nos permite crear un puntero que está más allá del final de una matriz. Y este puntero tiene que comparar más que cualquier otro puntero con el interior de una matriz. Esto significa que una implementación de C no puede colocar una matriz justo al final de la memoria, donde la dirección de una más se envolvería y se vería más pequeña que otras direcciones en la matriz.

Sin embargo, el acceso a valores sin inicializar o fuera de límites a veces es una técnica de optimización válida, incluso si no es lo más portátil. Esto es, por ejemplo, el motivo por el que la herramienta Valgrind no informa los accesos a datos no inicializados cuando se producen esos accesos, sino solo cuando el valor se usa más tarde de alguna manera que podría afectar el resultado del programa. Obtienes un diagnóstico como "rama condicional en xxx: nnn depende del valor no inicializado" y a veces puede ser difícil rastrear dónde se origina. Si todos estos accesos quedaran atrapados de inmediato, habría muchos falsos positivos derivados del código optimizado del compilador, así como el código correctamente optimizado a mano.

Hablando de eso, estaba trabajando con un códec de un proveedor que emitía estos errores cuando se portaba a Linux y se ejecutaba bajo Valgrind. Pero el proveedor me convenció de que solo algunos bits del valor que se usaba en realidad provenían de la memoria sin inicializar, y esos bits fueron evitados cuidadosamente por la lógica. Sólo se estaban usando los bits buenos del valor y Valgrind no tiene la capacidad de Rastrear hasta el bit individual. El material sin inicializar proviene de leer una palabra más allá del final de un flujo de bits de datos codificados, pero el código sabe cuántos bits hay en el flujo y no utilizará más bits de los que realmente existen. Dado que el acceso más allá del final de la matriz de flujo de bits no causa ningún daño en la arquitectura DSP (no hay memoria virtual después de la matriz, no hay puertos asignados a la memoria y la dirección no se ajusta) es una técnica de optimización válida.

El "comportamiento indefinido" realmente no significa mucho, porque de acuerdo con ISO C, simplemente incluir un encabezado que no está definido en el estándar C, o llamar a una función que no está definida en el programa mismo o el estándar C, son ejemplos de undefined comportamiento. El comportamiento indefinido no significa "no definido por nadie en el planeta" simplemente "no definido por el estándar ISO C". Pero, por supuesto, a veces el comportamiento indefinido no está definido por nadie.


En general, los sistemas operativos de hoy (los más populares de todos modos) ejecutan todas las aplicaciones en regiones de memoria protegidas usando un administrador de memoria virtual. Resulta que no es terriblemente FÁCIL (por ejemplo) simplemente leer o escribir en una ubicación que exista en un espacio REAL fuera de las regiones que se han asignado / asignado a su proceso.

Respuestas directas:

1) La lectura casi nunca dañará directamente otro proceso, sin embargo, puede dañar indirectamente un proceso si lees un valor KEY utilizado para cifrar, descifrar o validar un programa / proceso. La lectura fuera de límites puede tener efectos adversos / inesperados en su código si toma decisiones basadas en los datos que está leyendo.

2) La única forma en que realmente podría DAÑAR algo al escribir en una ubicación accesible por una dirección de memoria es si esa dirección de memoria a la que está escribiendo es en realidad un registro de hardware (una ubicación que no es para el almacenamiento de datos sino para controlar una parte de hardware) no es una ubicación de RAM. De hecho, normalmente no dañará algo a menos que esté escribiendo una ubicación programable que no sea regrabable (o algo por el estilo).

3) Generalmente ejecutándose desde dentro del depurador ejecuta el código en modo de depuración. La ejecución en el modo de depuración TENDRA (pero no siempre) detiene su código más rápido cuando ha hecho algo que se considera fuera de práctica o es absolutamente ilegal.

4) Nunca use macros, use estructuras de datos que ya tengan la verificación de límites de índice de matriz incorporada, etc.

ADICIONAL Debo agregar que la información anterior es realmente solo para sistemas que utilizan un sistema operativo con ventanas de protección de memoria. Si está escribiendo código para un sistema integrado o incluso un sistema que utiliza un sistema operativo (en tiempo real u otro) que no tiene ventanas de protección de memoria (o ventanas virtuales), debería tener mucha más precaución al leer y escribir en la memoria. También en estos casos, siempre se deben emplear prácticas de codificación SEGURAS y SEGURAS para evitar problemas de seguridad.


En lo que respecta a la norma ISO C (la definición oficial del idioma), el acceso a una matriz fuera de sus límites tiene un " comportamiento indefinido ". El significado literal de esto es:

comportamiento, en el uso de una construcción de programa no portátil o errónea o de datos erróneos, para los cuales esta Norma Internacional no impone requisitos

Una nota no normativa expande sobre esto:

El posible comportamiento indefinido abarca desde ignorar la situación completamente con resultados impredecibles, hasta comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

Así que esa es la teoría. Cual es la realidad

En el "mejor" caso, accederá a una parte de la memoria que pertenece a su programa que se está ejecutando actualmente (lo que podría causar que su programa se comporte mal), o que no pertenece a su programa que se está ejecutando actualmente (lo que probablemente hará que su programa chocar con algo como una falla de segmentación). O puede intentar escribir en la memoria que posee su programa, pero eso está marcado como de solo lectura; esto probablemente también hará que tu programa se bloquee.

Eso es asumiendo que su programa se está ejecutando bajo un sistema operativo que intenta proteger los procesos que se ejecutan de forma concurrente entre sí. Si su código se ejecuta en el "metal", diga si es parte de un kernel del sistema operativo o un sistema integrado, entonces no existe tal protección; su código de mal comportamiento es lo que se suponía que debía proporcionar esa protección. En ese caso, las posibilidades de daños son considerablemente mayores, incluido, en algunos casos, daños físicos en el hardware (o en cosas o personas cercanas).

Incluso en un entorno de sistema operativo protegido, las protecciones no siempre son del 100%. Hay errores en el sistema operativo que permiten que los programas sin privilegios obtengan acceso de raíz (administrativo), por ejemplo. Incluso con los privilegios de los usuarios normales, un programa que funciona mal puede consumir recursos excesivos (CPU, memoria, disco), posiblemente derribando todo el sistema. Una gran cantidad de malware (virus, etc.) explota las saturaciones de búfer para obtener acceso no autorizado al sistema.

(Un ejemplo histórico: he escuchado que en algunos sistemas antiguos con memoria central , el acceso repetido a una única ubicación de la memoria en un circuito cerrado podría, literalmente, hacer que esa parte de la memoria se derrita. Otras posibilidades incluyen destruir una pantalla CRT y mover la lectura / escriba el encabezado de una unidad de disco con la frecuencia armónica del gabinete de la unidad, haciendo que camine sobre una mesa y caiga al piso.)

Y siempre hay que preocuparse por Skynet .

La conclusión es esta: si pudieras escribir un programa para hacer algo mal deliberadamente , es al menos teóricamente posible que un programa con errores pueda hacer lo mismo accidentalmente .

En la práctica, es muy poco probable que su programa de buggy que se ejecuta en un sistema MacOS X haga algo más serio que un fallo. Pero no es posible evitar completamente que el código de buggy haga cosas realmente malas.


Es posible que desee probar el uso de la herramienta memcheck en Valgrind cuando pruebe su código, ya que no detectará las violaciones de los límites de la matriz individual dentro de un marco de pila, pero debería detectar muchos otros tipos de problemas de memoria, incluidos los que podrían causar problemas sutiles. Problemas más amplios fuera del alcance de una sola función.

Del manual:

Memcheck es un detector de errores de memoria. Puede detectar los siguientes problemas que son comunes en los programas C y C ++.

  • No debe acceder a la memoria, por ejemplo, sobrepasar y subutilizar los bloques del montón, sobrepasar la parte superior de la pila y acceder a la memoria después de que se haya liberado.
  • Uso de valores no definidos, es decir, valores que no se han inicializado o que se han derivado de otros valores no definidos.
  • Liberación incorrecta de la memoria del montón, como bloques de pila de doble liberación, o uso no coincidente de malloc / new / new [] frente a free / delete / delete []
  • Superposición de punteros src y dst en memcpy y funciones relacionadas.
  • Pérdidas de memoria.

ETA: Aunque, como dice la respuesta de Kaz, no es una panacea, y no siempre da el resultado más útil, especialmente cuando se usan patrones de acceso emocionantes .


Usted escribe:

Leí un montón de "cualquier cosa puede pasar", "la segmentación podría ser el problema menos grave", "su disco duro puede volverse rosado y los unicornios podrían estar cantando bajo su ventana", lo cual es bueno, pero ¿cuál es realmente el peligro?

Vamos a ponerlo así: cargar un arma. Apúntalo fuera de la ventana sin ningún objetivo particular y dispara. ¿Cuál es el peligro?

El problema es que no lo sabes. Si su código sobrescribe algo que bloquea su programa, está bien porque lo detendrá en un estado definido. Sin embargo, si no se bloquea, los problemas comienzan a surgir. ¿Qué recursos están bajo el control de su programa y qué podría hacerles? ¿Qué recursos podrían estar bajo el control de su programa y qué podría hacerles? Sé que al menos un problema importante fue causado por tal desbordamiento. El problema estaba en una función de estadísticas aparentemente sin sentido que arruinó algunas tablas de conversión no relacionadas para una base de datos de producción. El resultado fue una limpieza muy costosa después. En realidad, habría sido mucho más barato y más fácil de manejar si este problema hubiera formateado los discos duros ... con otras palabras: los unicornios rosados ​​podrían ser su menor problema.

La idea de que su sistema operativo lo protegerá es optimista. Si es posible, trate de evitar escribir fuera de límites.


Hago esto de una manera muy simple. Funciona para mi. ¿Algún inconveniente?

Array.prototype.isArray = true;

a=[]; b={};
a.isArray  // true
b.isArray  // (undefined -> false)






c arrays memory