Empilhamento de pilha detectado

Estou executando meu arquivo a.out. Após a execução, o programa é executado por algum tempo e sai com a mensagem:

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

Quais poderiam ser as possíveis razões para isso e como posso corrigi-lo?

Stack Smashing aqui é causado devido a um mecanismo de proteção usado pelo gcc para detectar erros de estouro de buffer. Por exemplo, no seguinte trecho:

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

O compilador (neste caso o gcc) adiciona variables ​​de proteção (chamadas canários) que possuem valores conhecidos. Uma string de input de tamanho maior que 10 causa corrupção desta variável, resultando em SIGABRT para finalizar o programa.

Para obter algumas informações, você pode tentar desabilitar essa proteção do gcc usando a opção -fno-stack-protector durante a compilation. Nesse caso, você receberá um erro diferente, provavelmente uma falha de segmentação quando estiver tentando acessar um local de memory ilegal. Observe que -fstack-protector sempre deve ser ativado para versões de lançamento, pois é um recurso de segurança.

Você pode obter algumas informações sobre o ponto de estouro executando o programa com um depurador. O Valgrind não funciona bem com erros relacionados à pilha, mas, como um depurador, pode ajudá-lo a apontar a localização e o motivo da falha.

Por favor, olhe a seguinte situação:

 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:$ 

Quando desativei o protetor de esmagamento de pilha, nenhum erro foi detectado, o que deveria ter acontecido quando eu usei “./a.out wepassssssssssssssssss”

Então, para responder à sua pergunta acima, a mensagem “** stack smashing detected: xxx” foi exibida porque seu protetor de stack staming estava ativo e descobriu que há um estouro de pilha em seu programa.

Apenas descubra onde isso ocorre e conserte-o.

Você poderia tentar depurar o problema usando o valgrind :

A distribuição Valgrind inclui atualmente seis ferramentas de qualidade de produção: um detector de erros de memory, dois detectores de erros de thread, um profiler de cache e predição de ramificação, um gerador de perfil gerador de cache de chamada e um profiler de heap. Ele também inclui duas ferramentas experimentais: um detector de sobrecarga de pilha / pilha / matriz global e um gerador de vetor de bloco básico SimPoint. Ele é executado nas seguintes plataformas: X86 / Linux, AMD64 / Linux, PPC32 / Linux, PPC64 / Linux e X86 / Darwin (Mac OS X).

Isso significa que você escreveu para algumas variables ​​na pilha de maneira ilegal, provavelmente como resultado de um estouro de buffer .

Exemplo mínimo com análise de desassembly

ac:

 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; } 

Compile e execute:

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

falha conforme desejado:

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

Desassembly

Agora olhamos para a desassembly:

 objdump -D a.out 

que contém:

 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 os comentários úteis adicionados automaticamente pelo módulo de inteligência artificial do objdump .

Se você executar este programa várias vezes através do GDB, verá que:

  • o canário recebe um valor random diferente a cada vez
  • o último loop do myfunc é exatamente o que modifica o endereço do canário

Agora a grande questão é como o canário é inicializado em %fs:0x28 para começar, então deixo você para:

Tentativas de debugging

De agora em diante, modificamos o código:

  myfunc(arr, len + 1); 

ser em vez disso:

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

ser mais interessante.

Vamos então tentar ver se podemos identificar a chamada + 1 culpada com um método mais automatizado do que apenas ler e entender todo o código-fonte.

gcc -fsanitize=address

Com este sinalizador, ele brutalmente funciona e produz:

 #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 mais alguma saída colorida. Obrigado Google .

Valgrind SGCheck

Como mencionado por outros , Valgrind não é bom em resolver esse tipo de problema.

Ele possui uma ferramenta experimental chamada SGCheck :

O SGCheck é uma ferramenta para encontrar derrapagens de conjuntos globais e de pilha. Ele funciona usando uma abordagem heurística derivada de uma observação sobre as formas prováveis ​​de access de pilha e matriz global.

Então eu não fiquei muito surpreso quando não encontrei o erro:

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

A mensagem de erro deve ficar assim: Valgrind faltando erro

GDB

Uma observação importante é que, se você executar o programa por meio do GDB ou examinar o arquivo core após o fato:

 gdb -nh -q a.out core 

então, como vimos na assembly, o GDB deve apontar para o final da function que fez a verificação do canário:

 (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) 

E, portanto, o problema é provável em uma das chamadas que essa function fez.

Em seguida, tentamos identificar a chamada com falha exata pelo primeiro passo único logo após o canário ser definido:

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

e vendo o endereço:

 (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 

Agora, isso nos deixa com a correta instrução ofensiva: len = 5 i = 4 , e neste caso particular, nos apontou para a linha culpada 12.

No entanto, o backtrace está corrompido e contém algum lixo. Um backtrace correto seria parecido com:

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

então talvez isso possa corromper a pilha e impedir que você veja o traço.

Além disso, este método requer saber qual é a última chamada da function de verificação canária, caso contrário você terá falsos positivos, o que nem sempre será viável, a menos que você use a debugging reversa .

Testado no Ubuntu 16.04, gcc 6.4.0.

Quais poderiam ser as possíveis razões para isso e como posso corrigi-lo?

Um cenário seria no seguinte exemplo:

 #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--; } } 

Neste programa você pode inverter uma String ou uma parte da string se, por exemplo, chamar reverse() com algo assim:

 reverse( arr + 2 ); 

Se você decidir passar o comprimento da matriz assim:

 #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 bem também.

Mas quando você faz isso:

 revSTR( arr + 2, len ); 

Você consegue:

 ==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) 

E isso acontece porque no primeiro código, o comprimento de arr é verificado dentro de revSTR() que é bom, mas no segundo código onde você passa o comprimento:

 revSTR( arr + 2, len ); 

o comprimento é agora mais longo que o comprimento real que você passa quando diz arr + 2 .

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

Empilhar corrupções ususally causadas por overflows de buffer. Você pode se defender contra eles programando defensivamente.

Sempre que você acessar uma matriz, coloque uma declaração antes para garantir que o access não esteja fora dos limites. Por exemplo:

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

Isso faz você pensar nos limites de array e também faz você pensar em adicionar testes para ativá-los, se possível. Se algumas dessas afirmações falharem durante o uso normal, torne-as regulares.

Eu recebi este erro ao usar malloc () para alocar alguma memory para uma struct * depois de gastar alguns esta debugging do código, eu finalmente usei a function free () para liberar a memory alocada e, posteriormente, a mensagem de erro foi 🙂

Outra fonte de esmagamento de pilha é o uso (incorreto) de vfork() vez de fork() .

Eu apenas depurei um caso disso, em que o processo filho não conseguiu execve() o executável de destino e retornou um código de erro em vez de chamar _exit() .

Como vfork() gerou esse filho, ele retornou enquanto ainda estava sendo executado no espaço de processo do pai, corrompendo não apenas a pilha do pai, mas fazendo com que dois conjuntos diferentes de diagnósticos fossem impressos pelo código “downstream”.

Mudar vfork() para fork() corrigiu ambos os problemas, assim como mudou a declaração de return da criança para _exit() .

Mas como o código filho precede a chamada execve() com chamadas para outras rotinas (para definir o uid / gid, neste caso particular), tecnicamente ele não atende aos requisitos para vfork() , então alterá-lo para usar fork() está correto aqui.

(Observe que a declaração de return problemático não foi realmente codificada como tal – em vez disso, uma macro foi invocada e essa macro decidiu se _exit() ou return base em uma variável global. Portanto, não ficou imediatamente óbvio que o código filho foi não-conforme para o uso do vfork() .

Para mais informações, veja:

A diferença entre fork (), vfork (), exec () e clone ()