¿Por qué los comstackdores no advierten sobre índices de matrices estáticas fuera de límites?

Un colega mío se mordió recientemente al escribir fuera de los límites de una matriz estática en la stack (le agregó un elemento sin boost el tamaño de la matriz). ¿No debería el comstackdor atrapar este tipo de error? El siguiente código comstack limpiamente con gcc, incluso con las opciones -Wall -Wextra , y sin embargo es claramente erróneo:

 int main(void) { int a[10]; a[13] = 3; // oops, overwrote the return address return 0; } 

Estoy seguro de que este es un comportamiento indefinido, aunque no puedo encontrar un extracto del estándar C99 que lo diga en este momento. Pero en el caso más simple, donde el tamaño de una matriz se conoce como tiempo de comstackción y los índices se conocen en el momento de la comstackción, ¿no debería el comstackdor emitir una advertencia por lo menos?

GCC advierte sobre esto Pero hay que hacer dos cosas:

  1. Habilitar la optimización. Sin al menos -O2, GCC no está haciendo un análisis suficiente para saber qué es y cómo se ejecutó fuera del borde.
  2. Cambie su ejemplo para que realmente se use []; de lo contrario, GCC genera un progtwig sin operación y ha descartado completamente su asignación.

.

 $ cat foo.c int main(void) { int a[10]; a[13] = 3; // oops, overwrote the return address return a[1]; } $ gcc -Wall -Wextra -O2 -c foo.c foo.c: In function 'main': foo.c:4: warning: array subscript is above array bounds 

Por cierto: si devolviste un [13] en tu progtwig de prueba, tampoco funcionaría, ya que GCC optimiza la matriz de nuevo.

¿Has probado -fmudflap con GCC? Estas son verificaciones de tiempo de ejecución pero son útiles, ya que la mayoría de las veces tiene que ver con índices calculados de tiempo de ejecución de todos modos. En lugar de continuar trabajando silenciosamente, le notificará sobre esos errores.

-fmudflap -fmudflapth -fmudflapir Para los front-ends que lo soportan (C y C ++), instrumentar todas las operaciones de desreferenciación de punteros / matrices de riesgo, algunas funciones estándar de cadena / montón de biblioteca y otras construcciones asociadas con pruebas de rango / validez. Los módulos así instrumentados deben ser inmunes a los desbordamientos de búfer, el uso no válido del montón y algunas otras clases de errores de progtwigción de C / C ++. La instrumentación se basa en una biblioteca de tiempo de ejecución separada (libmudflap), que se vinculará a un progtwig si se proporciona -fmudflap en el momento del enlace. El comportamiento en tiempo de ejecución del progtwig instrumentado está controlado por la variable de entorno MUDFLAP_OPTIONS. Consulte “env MUDFLAP_OPTIONS = -help a.out” para conocer sus opciones.

Utilice -fmudflapth en lugar de -fmudflap para comstackr y vincular si su progtwig es multihebra. Utilice -fmudflapir, además de -fmudflap o -fmudflapth, si la instrumentación debe ignorar las lecturas de puntero. Esto produce menos instrumentación (y, por lo tanto, una ejecución más rápida) y aún así proporciona cierta protección contra escrituras corruptas en la memoria, pero permite que los datos leídos erróneamente se propaguen dentro de un progtwig.

Esto es lo que me ofrece mudflap para tu ejemplo:

 [js@HOST2 cpp]$ gcc -fstack-protector-all -fmudflap -lmudflap mudf.c [js@HOST2 cpp]$ ./a.out ******* mudflap violation 1 (check/write): time=1229801723.191441 ptr=0xbfdd9c04 size=56 pc=0xb7fb126d location=`mudf.c:4:3 (main)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7fb126d] ./a.out(main+0xb9) [0x804887d] /usr/lib/libmudflap.so.0(__wrap_main+0x4f) [0xb7fb0a5f] Nearby object 1: checked region begins 0B into and ends 16B after mudflap object 0x8509cd8: name=`mudf.c:3:7 (main) a' bounds=[0xbfdd9c04,0xbfdd9c2b] size=40 area=stack check=0r/3w liveness=3 alloc time=1229801723.191433 pc=0xb7fb09fd number of nearby objects: 1 [js@HOST2 cpp]$ 

Tiene un montón de opciones. Por ejemplo, puede desencadenar un proceso gdb en caso de violaciones, puede mostrarle dónde se filtró su progtwig (utilizando -print-leaks ) o detectar lecturas de variables no inicializadas. Use MUDFLAP_OPTIONS=-help ./a.out para obtener una lista de opciones. Dado que mudflap solo genera direcciones y no nombres de archivos y líneas de la fuente, escribí un pequeño script gawk:

 /^ / { file = gensub(/([^(]*).*/, "\\1", 1); addr = gensub(/.*\[([x[:xdigit:]]*)\]$/, "\\1", 1); if(file && addr) { cmd = "addr2line -e " file " " addr cmd | getline laddr print $0 " (" laddr ")" close (cmd) next; } } 1 # print all other lines 

Instale la salida de mudflap en ella, y mostrará el archivo de origen y la línea de cada entrada de seguimiento.

También -fstack-protector[-all] :

-fstack-protector código extra para comprobar si hay desbordamientos de búfer, como ataques de astackmiento de stack. Esto se hace agregando una variable de guarda a las funciones con objetos vulnerables. Esto incluye funciones que llaman a alloca, y funciones con buffers de más de 8 bytes. Las guardas se inicializan cuando se ingresa a una función y luego se verifican cuando la función sale. Si falla una comprobación de protección, se imprime un mensaje de error y el progtwig sale.

-fstack-protector-all Me gusta -fstack-protector excepto que todas las funciones están protegidas.

Tienes razón, el comportamiento es indefinido . Los punteros C99 deben apuntar dentro o solo un elemento más allá de las estructuras de datos declaradas o asignadas en el montón.

Nunca he podido averiguar cómo la gente de gcc decide cuándo avisar. Me sorprendió saber que: la -Wall por sí sola no avisará de las variables no inicializadas; como mínimo, necesita -O , e incluso entonces a veces se omite la advertencia.

Supongo que debido a que las matrices sin límites son tan comunes en C, el comstackdor probablemente no tiene una forma en sus árboles de expresión para representar una matriz que tenga un tamaño conocido en el momento de la comstackción. Entonces, aunque la información está presente en la statement, conjeturo que con el uso ya está perdida.

Segundo la recomendación de valgrind . Si está progtwigndo en C, debe ejecutar valgrind en todos los progtwigs, todo el tiempo hasta que ya no pueda recibir el impacto de rendimiento.

No es una matriz estática.

Comportamiento indefinido o no, se escribe en una dirección 13 enteros desde el principio de la matriz. Lo que hay es tu responsabilidad. Existen varias técnicas de C que intencionalmente asignan incorrectamente las matrices por razones razonables. Y esta situación no es inusual en unidades de comstackción incompletas.

Dependiendo de la configuración de su marca, hay varias características de este progtwig que se marcarían, como el hecho de que la matriz nunca se usa. Y el comstackdor podría optimizarlo de la existencia y no decirle, un árbol que cae en el bosque.

Es la manera C Es tu matriz, tu memoria, haz lo que quieras con ella. 🙂

(Hay una cantidad de herramientas de pelusas para ayudarlo a encontrar este tipo de cosas, y debe usarlas generosamente. Sin embargo, no todas funcionan a través del comstackdor. La comstackción y el enlace son a menudo lo suficientemente tediosos como son).

La razón por la que C no lo hace es porque C no tiene la información. Una statement como

 int a[10]; 

hace dos cosas: asigna sizeof(int)*10 bytes de espacio (más, potencialmente, un poco de espacio muerto para la alineación), y coloca una entrada en la tabla de símbolos que lee, conceptualmente,

 a : address of a[0] 

o en términos de C

 a : &a[0] 

y eso es todo. De hecho, en C puede intercambiar *(a+i) con a[i] en (casi *) todos los casos sin efecto POR DEFINICIÓN. Entonces, su pregunta es equivalente a preguntar “¿por qué puedo agregar un entero a este valor (dirección)?”

* Pop quiz: ¿Cuál es el único caso en esto, esto no es cierto?

La filosofía de C es que el progtwigdor siempre tiene la razón . Por lo tanto, le permitirá acceder de manera silenciosa a cualquier dirección de memoria que proporcione allí, asumiendo que siempre sabe lo que está haciendo y no le molestará con una advertencia.

¿No debería el comstackdor emitir una advertencia por lo menos?

No; Los comstackdores de C generalmente no realizan las comprobaciones de los límites del arreglo. El efecto negativo obvio de esto es, como usted menciona, un error con un comportamiento indefinido, que puede ser muy difícil de encontrar.

El lado positivo de esto es una posible pequeña ventaja de rendimiento en ciertos casos.

Creo que algunos comstackdores lo hacen en ciertos casos. Por ejemplo, si mi memoria me sirve correctamente, los comstackdores más nuevos de Microsoft tienen una opción de “Comprobación de seguridad de búfer” que detectará casos triviales de sobrecargas de búfer.

¿Por qué no todos los comstackdores hacen esto? O bien (como se mencionó anteriormente) la representación interna utilizada por el comstackdor no se presta a este tipo de análisis estático o simplemente no es lo suficientemente alta en la lista de prioridades de los escritores. Lo que para ser honesto, es una vergüenza de cualquier manera.

Hay alguna extensión en gcc para eso (desde el lado del comstackdor) http://www.doc.ic.ac.uk/~awl03/projects/miro/

por otro lado, férula, rata y muchas otras herramientas de análisis de código estático habrían encontrado eso.

También puede usar valgrind en su código y ver el resultado. http://valgrind.org/

Otra biblioteca muy usada parece ser la libertad

Es simplemente una decisión de diseño hecha. Lo que ahora conduce a estas cosas.

Saludos Friedrich

La opción de verificación de límites está disponible con gcc.

Vale la pena leer este artículo http://www.doc.ic.ac.uk/~phjk/BoundsChecking.html

Sin embargo, ‘le dorfier’ ha dado una respuesta adecuada a su pregunta, es su progtwig y es la forma en que C se comporta.