¿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 se bloquea, pero a veces solo se ejecuta, solo da 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 una memoria totalmente no relacionada, entonces me imagino que es muy malo. Leí mucho 'todo puede pasar', 'la segmentación podría ser el problema menos grave' , 'tu disco duro podría ponerse rosado y los unicornios podrían estar cantando debajo de tu ventana', lo cual es agradable, pero ¿cuál es realmente el peligro?
Mis preguntas:
- ¿La lectura de valores desde fuera de la matriz puede dañar algo aparte de mi programa? Me imagino que solo mirar las cosas no cambia nada, o ¿cambiaría, por ejemplo, el atributo 'última vez abierto' de un archivo al que llegué?
- ¿Establecer valores fuera de la matriz puede dañar algo aparte de mi programa? 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.
- Ahora ejecuto mis pequeños programas desde XCode. ¿Proporciona alguna protección adicional alrededor de mi programa donde no puede alcanzar fuera de su propia memoria? ¿Puede dañar XCode?
- ¿Alguna recomendación sobre cómo ejecutar mi código inherentemente con errores?
Yo uso OSX 10.7, Xcode 4.6.
Respuestas:
En lo que respecta al estándar ISO C (la definición oficial del lenguaje), acceder a una matriz fuera de sus límites tiene un " comportamiento indefinido ". El significado literal de esto es:
Una nota no normativa se expande sobre esto:
Entonces esa es la teoría. Cual es la realidad
En el "mejor" caso, accederá a un trozo de memoria que es propiedad de su programa actualmente en ejecución (lo que podría hacer que su programa se comporte mal), o que no sea propiedad de su programa actualmente en ejecución (lo que probablemente hará que su programa choque con algo así 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 su programa se bloquee.
Eso supone que su programa se ejecuta bajo un sistema operativo que intenta proteger los procesos que se ejecutan simultáneamente entre sí. Si su código se está ejecutando en el "bare 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, incluyendo, en algunos casos, daños físicos al hardware (o cosas o personas cercanas).
Incluso en un entorno de SO protegido, las protecciones no siempre son del 100%. Hay errores del sistema operativo que permiten a los programas no privilegiados obtener acceso raíz (administrativo), por ejemplo. Incluso con privilegios de usuario normales, un programa que no funciona correctamente puede consumir recursos excesivos (CPU, memoria, disco), posiblemente averiando todo el sistema. Una gran cantidad de malware (virus, etc.) explota el desbordamiento del búfer para obtener acceso no autorizado al sistema.
(Un ejemplo histórico: he oído que en algunos sistemas antiguos con memoria central , acceder repetidamente a una única ubicación de memoria en un bucle cerrado podría literalmente derretir esa porción de memoria. Otras posibilidades incluyen destruir una pantalla CRT y mover la lectura / escribo en la cabeza de una unidad de disco con la frecuencia armónica del gabinete de la unidad, lo que hace que cruce una mesa y caiga al suelo).
Y siempre hay que preocuparse por Skynet .
La conclusión es esta: si pudieras escribir un programa para hacer algo mal deliberadamente , al menos teóricamente es posible que un programa defectuoso pueda hacer lo mismo accidentalmente .
En la práctica, es muy poco probable que su programa defectuoso que se ejecuta en un sistema MacOS X vaya a hacer algo más serio que el bloqueo. Pero no es posible evitar por completo que el código defectuoso haga cosas realmente malas.
fuente
En general, los sistemas operativos de hoy (los más populares de todos modos) ejecutan todas las aplicaciones en regiones de memoria protegidas utilizando un administrador de memoria virtual. Resulta que no es terriblemente FÁCIL (por decir) simplemente leer o escribir en una ubicación que existe 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 CLAVE utilizado para cifrar, descifrar o validar un programa / proceso. Leer fuera de límites puede tener efectos algo adversos / inesperados en su código si está tomando decisiones basadas en los datos que está leyendo
2) La única forma en que realmente podría DAÑAR algo escribiendo 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 en realidad no es para el almacenamiento de datos sino para controlar alguna pieza de hardware) no es una ubicación RAM. De hecho, normalmente no dañará algo a menos que esté escribiendo una ubicación programable única que no sea regrabable (o algo por el estilo).
3) Generalmente, la ejecución desde el depurador ejecuta el código en modo de depuración. La ejecución en modo de depuración TENDE (pero no siempre) a detener su código más rápido cuando ha hecho algo considerado fuera de práctica o francamente ilegal.
4) Nunca use macros, use estructuras de datos que ya tengan una verificación de límites de índice de matriz incorporada, etc.
ADICIONAL Debería agregar que la información anterior es realmente solo para sistemas que usan un sistema operativo con ventanas de protección de memoria. Si escribe código para un sistema embebido 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 direccionadas virtuales), se debe tener mucha más precaución al leer y escribir en la memoria. También en estos casos, siempre deben emplearse prácticas de codificación SEGURAS y SEGURAS para evitar problemas de seguridad.
fuente
No verificar los límites puede provocar efectos secundarios feos, incluidos agujeros de seguridad. Uno de los feos es la ejecución de código arbitrario . En el ejemplo clásico: si tiene una matriz de tamaño fijo y la usa
strcpy()
para colocar una cadena suministrada por el usuario, el usuario puede darle una cadena que desborde el búfer y sobrescriba otras ubicaciones de memoria, incluida la dirección de código donde la CPU debería regresar cuando su función acabados.Lo que significa que su usuario puede enviarle una cadena que hará que su programa llame esencialmente
exec("/bin/sh")
, lo que lo convertirá en shell, ejecutando todo lo que quiera en su sistema, incluida la recolección de todos sus datos y convertir su máquina en un nodo botnet.Vea Smashing The Stack por diversión y ganancias para obtener detalles sobre cómo se puede hacer esto.
fuente
foo[0]
a travésfoo[len-1]
después de haber utilizado previamente un cheque delen
en contra de la longitud de la matriz o bien ejecutar o saltar una pieza de código, el compilador debe sentirse libre para ejecutar ese otro código incondicionalmente, incluso si la aplicación posee el almacenamiento más allá de la matriz y los efectos de la lectura hubiera sido benigno, pero el efecto de invocar el otro código no lo sería.Usted escribe:
Digámoslo así: cargue un arma. Apúntelo fuera de la ventana sin ningún objetivo particular y dispare. Cual 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? Conozco al menos un problema importante causado por tal desbordamiento. El problema estaba en una función de estadísticas aparentemente sin sentido que estropeó alguna tabla de conversión no relacionada para una base de datos de producción. El resultado fue una limpieza muy costosa después. En realidad, hubiera sido mucho más barato y 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 los límites.
fuente
No ejecutar su programa como usuario root o cualquier otro usuario privilegiado no dañará ninguno de su sistema, por lo que, en general, puede ser una buena idea.
Al escribir datos en alguna ubicación de memoria aleatoria, no "dañará" directamente ningún otro programa que se ejecute en su computadora, ya que cada proceso se ejecuta en su propio espacio de memoria.
Si intenta acceder a cualquier memoria no asignada a su proceso, el sistema operativo detendrá la ejecución de su programa con una falla de segmentación.
Entonces, directamente (sin ejecutarse como root y acceder directamente a archivos como / dev / mem) no hay peligro de que su programa interfiera con cualquier otro programa que se ejecute en su sistema operativo.
Sin embargo, y probablemente esto es de lo que ha oído hablar en términos de peligro, al escribir a ciegas datos aleatorios en ubicaciones de memoria aleatorias por accidente, puede dañar cualquier cosa que pueda dañar.
Por ejemplo, su programa puede querer eliminar un archivo específico dado por un nombre de archivo almacenado en algún lugar de su programa. Si por accidente simplemente sobrescribe la ubicación donde se almacena el nombre del archivo, puede eliminar un archivo muy diferente.
fuente
NSArray
s en Objective-C se les asigna un bloque específico de memoria. Exceder los límites de la matriz significa que accedería a la memoria que no está asignada a la matriz. Esto significa:Desde el aspecto de su programa, siempre quiere saber cuándo su código excede los límites de una matriz. Esto puede provocar que se devuelvan valores desconocidos, haciendo que su aplicación se bloquee o proporcione datos no válidos.
fuente
NSArrays
tener excepciones fuera de límites. Y estas preguntas parecen ser sobre la matriz C.Es posible que desee intentar usar la
memcheck
herramienta en Valgrind cuando pruebe su código: no detectará violaciones de límites de matriz individuales dentro de un marco de pila, pero debería detectar muchos otros tipos de problemas de memoria, incluidos los que causarían sutiles, más amplios problemas fuera del alcance de una sola función.Del manual:
ETA: Sin embargo, como dice la respuesta de Kaz, no es una panacea, y no siempre da el resultado más útil, especialmente cuando se utilizan patrones de acceso interesantes .
fuente
Si alguna vez realiza la programación a nivel de sistemas o la programación de sistemas integrados, pueden ocurrir cosas muy malas si escribe en ubicaciones de memoria aleatorias. Los sistemas más antiguos y muchos microcontroladores usan IO mapeada en memoria, por lo que escribir en una ubicación de memoria que se mapea en un registro periférico puede causar estragos, especialmente si se hace de forma asincrónica.
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 se escribiera en cualquier otra ubicación en el chip mientras eso sucedía, causaría que el ciclo de programación fallara.
En algunos casos, el hardware ajustará 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 dará como resultado que los datos se escriban justo en el medio de las cosas.
Y finalmente, las CPU más antiguas como el MC68000 pueden bloquearse hasta el punto de que solo un reinicio de hardware puede hacer que vuelvan a funcionar. No he trabajado en ellos durante un par de décadas, pero creo que es cuando se encontró con un error de bus (memoria inexistente) al tratar de manejar una excepción, simplemente se detendría hasta que se confirmara el restablecimiento del hardware.
Mi mayor recomendación es un enchufe descarado para un producto, pero no tengo ningún interés personal en él y no estoy afiliado a ellos de ninguna manera, pero basado en un par de décadas de programación en C y sistemas integrados donde la confiabilidad era crítica, la PC de Gimpel Lint no solo detectará ese tipo de errores, sino que también lo convertirá en un mejor programador de C / C ++ al insistir constantemente sobre los malos hábitos.
También recomiendo leer el estándar de codificación MISRA C, si puede enganchar una copia de alguien. No he visto ninguno reciente, pero en los viejos tiempos daban una buena explicación de por qué deberías / no deberías hacer las cosas que cubren.
No sé sobre ti, pero sobre la segunda o tercera vez que recibo un coredump o un bloqueo de cualquier aplicación, mi opinión sobre cualquier compañía que la produzca se reduce a la mitad. La 4ta o 5ta 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 solo para asegurarme de que nunca vuelva a perseguirme.
fuente
Estoy trabajando con un compilador para un chip DSP que genera deliberadamente código 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 modo que el final de una iteración capta previamente algunos datos para la siguiente iteración. Por lo tanto, el datum captado previamente 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 estándar que se ocupa de la máxima portabilidad.
Más a menudo que no, un programa que accede fuera de los límites no está inteligentemente optimizado. Es simplemente con errores. El código obtiene algún valor de basura y, a diferencia de los bucles optimizados del compilador mencionado anteriormente, el código luego usa el valor en cálculos posteriores, corrompiéndolos.
Vale la pena detectar errores como ese, por lo que vale la pena hacer que el comportamiento sea indefinido, incluso por esa sola razón: para que el tiempo de ejecución pueda producir un mensaje de diagnóstico como "desbordamiento de matriz en la línea 42 de main.c".
En los sistemas con memoria virtual, se podría asignar una matriz de manera que la dirección que sigue esté en un área no asignada de memoria virtual. El acceso luego bombardeará el programa.
Sin embargo, el acceso a valores no inicializados o fuera de los límites a veces es una técnica de optimización válida, incluso si no es máximamente portátil. Esta es, por ejemplo, la razón por la cual la herramienta Valgrind no informa accesos a datos no inicializados cuando esos accesos suceden, sino solo cuando el valor se usa más tarde de alguna manera que podría afectar el resultado del programa. Obtiene 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 del 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 transfirió a Linux y se ejecutó bajo Valgrind. Pero el vendedor me convenció de que solo varios bitsdel valor que se usa en realidad proviene de la memoria no inicializada, y esos bits fueron cuidadosamente evitados por la lógica. Solo se estaban usando los bits buenos del valor y Valgrind no tiene la capacidad de rastrear el bit individual. El material no inicializado 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 hay. 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 mapeados en memoria y la dirección no se ajusta) es una técnica de optimización válida.
"Comportamiento indefinido" en realidad 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 propio programa o el estándar C, son ejemplos de indefinido 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, el comportamiento a veces sin definir realmente es absolutamente no definido por cualquiera.
fuente
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 núcleo no asignó a sus procesos, generando la excepción adecuada y ser asesinado (quiero decir, su proceso).
fuente