Stack smashing detectado

Estoy ejecutando mi archivo a.out. Después de la ejecución, el progtwig se ejecuta durante un tiempo y luego sale con el mensaje:

**** stack smashing detected ***: ./a.out terminated* *======= Backtrace: =========* */lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted* 

¿Cuáles podrían ser las posibles razones para esto y cómo lo rectifico?

Stack Smashing aquí en realidad se debe a un mecanismo de protección utilizado por gcc para detectar errores de desbordamiento de búfer. Por ejemplo en el siguiente fragmento:

 #include  void func() { char array[10]; gets(array); } int main(int argc, char **argv) { func(); } 

El comstackdor (en este caso, gcc) agrega variables de protección (llamadas canarios) que tienen valores conocidos. Una cadena de entrada de tamaño superior a 10 causa corrupción de esta variable, lo que hace que SIGABRT finalice el progtwig.

Para obtener una idea, puede intentar deshabilitar esta protección de gcc utilizando la opción -fno-stack-protector durante la comstackción. En ese caso, obtendrá un error diferente, muy probablemente un error de segmentación, ya que está tratando de acceder a una ubicación de memoria ilegal. Tenga en cuenta que -fstack-protector siempre debe estar activado para comstackciones de lanzamiento ya que es una característica de seguridad.

Puede obtener información sobre el punto de desbordamiento ejecutando el progtwig con un depurador. Valgrind no funciona bien con errores relacionados con la stack, pero al igual que un depurador, puede ayudarlo a identificar la ubicación y el motivo del locking.

Por favor mira la siguiente situación:

 ab@cd-x:$ cat test_overflow.c #include  #include  int check_password(char *password){ int flag = 0; char buffer[20]; strcpy(buffer, password); if(strcmp(buffer, "mypass") == 0){ flag = 1; } if(strcmp(buffer, "yourpass") == 0){ flag = 1; } return flag; } int main(int argc, char *argv[]){ if(argc >= 2){ if(check_password(argv[1])){ printf("%s", "Access granted\n"); }else{ printf("%s", "Access denied\n"); } }else{ printf("%s", "Please enter password!\n"); } } ab@cd-x:$ gcc -g -fno-stack-protector test_overflow.c ab@cd-x:$ ./a.out mypass Access granted ab@cd-x:$ ./a.out yourpass Access granted ab@cd-x:$ ./a.out wepass Access denied ab@cd-x:$ ./a.out wepassssssssssssssssss Access granted ab@cd-x:$ gcc -g -fstack-protector test_overflow.c ab@cd-x:$ ./a.out wepass Access denied ab@cd-x:$ ./a.out mypass Access granted ab@cd-x:$ ./a.out yourpass Access granted ab@cd-x:$ ./a.out wepassssssssssssssssss *** stack smashing detected ***: ./a.out terminated ======= Backtrace: ========= /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0xce0ed8] /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0xce0e90] ./a.out[0x8048524] ./a.out[0x8048545] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xc16b56] ./a.out[0x8048411] ======= Memory map: ======== 007d9000-007f5000 r-xp 00000000 08:06 5776 /lib/libgcc_s.so.1 007f5000-007f6000 r--p 0001b000 08:06 5776 /lib/libgcc_s.so.1 007f6000-007f7000 rw-p 0001c000 08:06 5776 /lib/libgcc_s.so.1 0090a000-0090b000 r-xp 00000000 00:00 0 [vdso] 00c00000-00d3e000 r-xp 00000000 08:06 1183 /lib/tls/i686/cmov/libc-2.10.1.so 00d3e000-00d3f000 ---p 0013e000 08:06 1183 /lib/tls/i686/cmov/libc-2.10.1.so 00d3f000-00d41000 r--p 0013e000 08:06 1183 /lib/tls/i686/cmov/libc-2.10.1.so 00d41000-00d42000 rw-p 00140000 08:06 1183 /lib/tls/i686/cmov/libc-2.10.1.so 00d42000-00d45000 rw-p 00000000 00:00 0 00e0c000-00e27000 r-xp 00000000 08:06 4213 /lib/ld-2.10.1.so 00e27000-00e28000 r--p 0001a000 08:06 4213 /lib/ld-2.10.1.so 00e28000-00e29000 rw-p 0001b000 08:06 4213 /lib/ld-2.10.1.so 08048000-08049000 r-xp 00000000 08:05 1056811 /dos/hacking/test/a.out 08049000-0804a000 r--p 00000000 08:05 1056811 /dos/hacking/test/a.out 0804a000-0804b000 rw-p 00001000 08:05 1056811 /dos/hacking/test/a.out 08675000-08696000 rw-p 00000000 00:00 0 [heap] b76fe000-b76ff000 rw-p 00000000 00:00 0 b7717000-b7719000 rw-p 00000000 00:00 0 bfc1c000-bfc31000 rw-p 00000000 00:00 0 [stack] Aborted ab@cd-x:$ 

Cuando deshabilité el protector de destrucción de stack, no se detectaron errores, lo que debería haber sucedido cuando usé “./a.out wepassssssssssssssssss”

Entonces, para responder a su pregunta anterior, se mostró el mensaje “** aplastamiento de stack detectado: xxx” porque su protector de destrucción de stack estaba activo y descubrió que hay un desbordamiento de stack en su progtwig.

Simplemente averigua dónde ocurre eso y corrígelo.

Podría intentar solucionar el problema usando valgrind :

La distribución de Valgrind actualmente incluye seis herramientas de calidad de producción: un detector de errores de memoria, dos detectores de errores de subprocesos, un generador de perfiles de caché y predicción de ramificaciones, un generador de perfiles de caché de generación de gráficos y un generador de perfiles de montículos. También incluye dos herramientas experimentales: un detector de desbordamiento de matriz global / stack / global y un generador de vector de bloque básico SimPoint. Se ejecuta en las siguientes plataformas: X86 / Linux, AMD64 / Linux, PPC32 / Linux, PPC64 / Linux y X86 / Darwin (Mac OS X).

Significa que escribiste algunas variables en la stack de forma ilegal, muy probablemente como resultado de un desbordamiento del búfer .

Ejemplo mínimo con análisis de desassembly

C.A:

 void myfunc(char *const src, int len) { int i; for (i = 0; i < len; ++i) { src[i] = 42; } } int main(void) { char arr[] = {'a', 'b', 'c', 'd'}; int len = sizeof(arr); myfunc(arr, len + 1); return 0; } 

Comstackr y ejecutar:

 gcc -fstack-protector -g -O0 -std=c99 ac ulimit -c unlimited && rm -f core ./a.out 

falla como se desea

 *** stack smashing detected ***: ./a.out terminated Aborted (core dumped) 

Desassembly

Ahora miramos el desassembly:

 objdump -D a.out 

que contiene:

 int main (void){ 400579: 55 push %rbp 40057a: 48 89 e5 mov %rsp,%rbp # Allocate 0x10 of stack space. 40057d: 48 83 ec 10 sub $0x10,%rsp # Put the 8 byte canary from %fs:0x28 to -0x8(%rbp), # which is right at the bottom of the stack. 400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 400588: 00 00 40058a: 48 89 45 f8 mov %rax,-0x8(%rbp) 40058e: 31 c0 xor %eax,%eax char arr[] = {'a', 'b', 'c', 'd'}; 400590: c6 45 f4 61 movb $0x61,-0xc(%rbp) 400594: c6 45 f5 62 movb $0x62,-0xb(%rbp) 400598: c6 45 f6 63 movb $0x63,-0xa(%rbp) 40059c: c6 45 f7 64 movb $0x64,-0x9(%rbp) int len = sizeof(arr); 4005a0: c7 45 f0 04 00 00 00 movl $0x4,-0x10(%rbp) myfunc(arr, len + 1); 4005a7: 8b 45 f0 mov -0x10(%rbp),%eax 4005aa: 8d 50 01 lea 0x1(%rax),%edx 4005ad: 48 8d 45 f4 lea -0xc(%rbp),%rax 4005b1: 89 d6 mov %edx,%esi 4005b3: 48 89 c7 mov %rax,%rdi 4005b6: e8 8b ff ff ff callq 400546  return 0; 4005bb: b8 00 00 00 00 mov $0x0,%eax } 
  # Check that the canary at -0x8(%rbp) hasn't changed after calling myfunc. # If it has, jump to the failure point __stack_chk_fail. 4005c0: 48 8b 4d f8 mov -0x8(%rbp),%rcx 4005c4: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx 4005cb: 00 00 4005cd: 74 05 je 4005d4 
4005cf: e8 4c fe ff ff callq 400420 <__stack_chk_fail @plt> # Otherwise, exit normally. 4005d4: c9 leaveq 4005d5: c3 retq 4005d6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4005dd: 00 00 00

Observe los útiles comentarios agregados automáticamente por el módulo de inteligencia artificial de objdump .

Si ejecuta este progtwig varias veces a través de GDB, verá que:

  • el canario obtiene un valor aleatorio diferente cada vez
  • el último ciclo de myfunc es exactamente lo que modifica la dirección del canario

Ahora la gran pregunta es cómo el canario se inicializa en %fs:0x28 para empezar, así que te dejo para:

Intentos de depuración

A partir de ahora, modificamos el código:

  myfunc(arr, len + 1); 

ser en cambio:

  myfunc(arr); myfunc(arr, len + 1); /* line 12 */ myfunc(arr); 

ser más interesante

Luego trataremos de ver si podemos identificar la llamada culpable + 1 con un método más automatizado que solo leer y comprender todo el código fuente.

gcc -fsanitize=address

Con esta bandera, simplemente funciona brutalmente y produce:

 #0 0x4008bf in myfunc /home/cirsan01/test/ac:4 #1 0x40099b in main /home/cirsan01/test/ac:12 #2 0x7fcd2e13d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f) #3 0x400798 in _start (/home/cirsan01/test/a.out+0x40079 

seguido por una salida más coloreada. Gracias Google

Valgrind SGCheck

Como mencionaron otros , Valgrind no es bueno para resolver este tipo de problema.

Tiene una herramienta experimental llamada SGCheck :

SGCheck es una herramienta para encontrar los excesos de la stack y las matrices globales. Funciona mediante el uso de un enfoque heurístico derivado de una observación acerca de las formas probables de acceso a conjuntos y conjuntos globales.

Así que no me sorprendió mucho cuando no encontró el error:

 valgrind --tool=exp-sgcheck ./a.out 

El mensaje de error debería verse así: Valgrind missing error

GDB

Una observación importante es que si ejecuta el progtwig a través de GDB o examina el archivo core después del hecho:

 gdb -nh -q a.out core 

entonces, como vimos en la asamblea, GDB debería indicarle el final de la función que hizo la verificación canaria:

 (gdb) bt #0 0x00007f0f66e20428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 #1 0x00007f0f66e2202a in __GI_abort () at abort.c:89 #2 0x00007f0f66e627ea in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7f0f66f7a49f "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:175 #3 0x00007f0f66f0415c in __GI___fortify_fail (msg=, msg@entry=0x7f0f66f7a481 "stack smashing detected") at fortify_fail.c:37 #4 0x00007f0f66f04100 in __stack_chk_fail () at stack_chk_fail.c:28 #5 0x00000000004005f6 in main () at ac:15 (gdb) f 5 #5 0x00000000004005f6 in main () at ac:15 15 } (gdb) 

Y, por lo tanto, el problema es probable en una de las llamadas que hizo esta función.

A continuación, tratamos de identificar la llamada que falla exactamente al primer incremento individual justo después de configurar el canario:

  400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 400588: 00 00 40058a: 48 89 45 f8 mov %rax,-0x8(%rbp) 

y mirando la dirección:

 (gdb) p $rbp - 0x8 $1 = (void *) 0x7fffffffcf18 (gdb) watch 0x7fffffffcf18 Hardware watchpoint 2: *0x7fffffffcf18 (gdb) c Continuing. Hardware watchpoint 2: *0x7fffffffcf18 Old value = 1800814336 New value = 1800814378 myfunc (src=0x7fffffffcf14 "*****?Vk\266", , len=5) at ac:3 3 for (i = 0; i < len; ++i) { (gdb) p len $2 = 5 (gdb) pi $3 = 4 (gdb) bt #0 myfunc (src=0x7fffffffcf14 "*****?Vk\266", , len=5) at ac:3 #1 0x00000000004005cc in main () at ac:12 

Ahora, esto nos deja a la derecha de la instrucción ofensiva: len = 5 e i = 4 , y en este caso particular, nos señaló la línea culpable 12.

Sin embargo, la traza inversa está dañada y contiene algo de basura. Una traza inversa correcta se vería así:

 #0 myfunc (src=0x7fffffffcf14 "abcd", len=4) at ac:3 #1 0x00000000004005b8 in main () at ac:11 

así que tal vez esto podría dañar la stack y evitar que veas el rastro.

Además, este método requiere saber cuál es la última llamada de la función de verificación canaria; de lo contrario, tendrá falsos positivos, lo que no siempre será factible, a menos que use una depuración inversa .

Probado en Ubuntu 16.04, gcc 6.4.0.

¿Cuáles podrían ser las posibles razones para esto y cómo lo rectifico?

Un escenario sería en el siguiente ejemplo:

 #include  #include  #include  void swap ( char *a , char *b ); void revSTR ( char *const src ); int main ( void ){ char arr[] = "ABCDE"; revSTR( arr ); printf("ARR = %s\n", arr ); } void swap ( char *a , char *b ){ char tmp = *a; *a = *b; *b = tmp; } void revSTR ( char *const src ){ char *start = src; char *end = start + ( strlen( src ) - 1 ); while ( start < end ){ swap( &( *start ) , &( *end ) ); start++; end--; } } 

En este progtwig, puede invertir una Cadena o una parte de la cadena si, por ejemplo, llama a reverse() con algo como esto:

 reverse( arr + 2 ); 

Si decide pasar la longitud de la matriz de esta manera:

 #include  #include  #include  void swap ( char *a , char *b ); void revSTR ( char *const src, size_t len ); int main ( void ){ char arr[] = "ABCDE"; size_t len = strlen( arr ); revSTR( arr, len ); printf("ARR = %s\n", arr ); } void swap ( char *a , char *b ){ char tmp = *a; *a = *b; *b = tmp; } void revSTR ( char *const src, size_t len ){ char *start = src; char *end = start + ( len - 1 ); while ( start < end ){ swap( &( *start ) , &( *end ) ); start++; end--; } } 

Funciona bien también

Pero cuando haces esto:

 revSTR( arr + 2, len ); 

Obtiene obtener:

 ==7125== Command: ./program ==7125== ARR = A- *** stack smashing detected ***: ./program terminated ==7125== ==7125== Process terminating with default action of signal 6 (SIGABRT) ==7125== at 0x4E6F428: raise (raise.c:54) ==7125== by 0x4E71029: abort (abort.c:89) ==7125== by 0x4EB17E9: __libc_message (libc_fatal.c:175) ==7125== by 0x4F5311B: __fortify_fail (fortify_fail.c:37) ==7125== by 0x4F530BF: __stack_chk_fail (stack_chk_fail.c:28) ==7125== by 0x400637: main (program.c:14) 

Y esto sucede porque en el primer código, la longitud de arr se verifica dentro de revSTR() que está bien, pero en el segundo código donde se pasa la longitud:

 revSTR( arr + 2, len ); 

la longitud ahora es más larga que la longitud real que pasa cuando dices arr + 2 .

Longitud de strlen ( arr + 2 ) ! = strlen ( arr ) .

Corrosión de stack usualmente causada por desbordamientos de búfer. Puedes defenderte contra ellos progtwigndo a la defensiva.

Siempre que acceda a una matriz, coloque una afirmación antes para asegurarse de que el acceso no esté fuera de límites. Por ejemplo:

 assert(i + 1 < N); assert(i < N); a[i + 1] = a[i]; 

Esto te hace pensar en límites de matriz y también te hace pensar en agregar pruebas para activarlas si es posible. Si algunos de estos atributos pueden fallar durante el uso normal, conviértelos en un if regular.

Obtuve este error al usar malloc () para asignar algo de memoria a una estructura * después de gastar algo de esto depurando el código, finalmente utilicé la función free () para liberar la memoria asignada y posteriormente el mensaje de error se fue 🙂

Otra fuente de vfork() de stack es el uso (incorrecto) de vfork() lugar de fork() .

Acabo de depurar un caso de esto, donde el proceso hijo no pudo execve() el ejecutable de destino y devolvió un código de error en lugar de llamar a _exit() .

Debido a que vfork() generó ese hijo, regresó mientras todavía se ejecutaba dentro del espacio de proceso del padre, no solo corrompiendo la stack de los padres, sino causando que se imprimieran dos conjuntos dispares de diagnósticos por código “descendente”.

El cambio de vfork() a fork() solucionó ambos problemas, al igual que el cambio de la statement de return del hijo a _exit() lugar.

Pero dado que el código hijo precede a la llamada execve() con llamadas a otras rutinas (para establecer el uid / gid, en este caso particular), técnicamente no cumple los requisitos para vfork() , por lo que cambiarlo para usar fork() es correcto aquí

(Tenga en cuenta que la statement de return problemática no estaba realmente codificada como tal; en su lugar, se invocaba una macro, y esa macro decidía si _exit() o return función de una variable global. Por lo tanto, no era inmediatamente obvio que el código secundario no estaba conforme con el vfork() de vfork() .)

Para más información, ver:

La diferencia entre fork (), vfork (), exec () y clone ()