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 retificá-lo?
Respostas:
O Stack Smashing aqui é realmente causado devido a um mecanismo de proteção usado pelo gcc para detectar erros de estouro de buffer. Por exemplo, no seguinte snippet:
O compilador (neste caso o gcc) adiciona variáveis de proteção (chamadas canaries) que possuem valores conhecidos. Uma sequência de entrada de tamanho maior que 10 causa corrupção nessa variável, resultando em SIGABRT para finalizar o programa.
Para obter algumas dicas, tente desativar essa proteção do gcc usando a opção
-fno-stack-protector
durante a compilação. Nesse caso, você receberá um erro diferente, provavelmente uma falha de segmentação, ao tentar acessar um local de memória ilegal. Observe que-fstack-protector
sempre deve estar ativado para compilações de versão, 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 identificar o local e o motivo da falha.
fonte
Exemplo de reprodução mínima com análise de desmontagem
main.c
GitHub upstream .
Compile e execute:
falha conforme desejado:
Testado no Ubuntu 16.04, GCC 6.4.0.
Desmontagem
Agora olhamos para a desmontagem:
que contém:
Observe os comentários úteis adicionados automaticamente pelo
objdump
's módulo de inteligência artificial .Se você executar este programa várias vezes através do GDB, verá que:
myfunc
é exatamente o que modifica o endereço do canárioO canário foi randomizado definindo-o com
%fs:0x28
, que contém um valor aleatório, conforme explicado em:Tentativas de depuração
A partir de agora, modificamos o código:
em vez disso:
para ser mais interessante.
Em seguida, tentaremos verificar se podemos identificar a
+ 1
chamada culpada com um método mais automatizado do que apenas ler e entender todo o código-fonte.gcc -fsanitize=address
para ativar o Sananizador de endereços do Google (ASan)Se você recompilar com esse sinalizador e executar o programa, ele produzirá:
seguido por uma saída mais colorida.
Isso indica claramente a linha problemática 12.
O código-fonte para isso está em: https://github.com/google/sanitizers, mas, como vimos no exemplo, ele já está incorporado no GCC.
O ASan também pode detectar outros problemas de memória, como vazamentos de memória: Como encontrar vazamento de memória em um código / projeto C ++?
Valgrind SGCheck
Como mencionado por outros , Valgrind não é bom em resolver esse tipo de problema.
Possui uma ferramenta experimental chamada SGCheck :
Portanto, não fiquei muito surpreso quando não encontrou o erro:
Aparentemente, a mensagem de erro deve ser semelhante a esta: Erro ausente do Valgrind
GDB
Uma observação importante é que, se você executar o programa através do GDB, ou examinar o
core
arquivo após o fato:então, como vimos na montagem, o GDB deve apontar o final da função que fez a verificação do canário:
E, portanto, o problema provavelmente ocorre em uma das chamadas que essa função fez.
Em seguida, tentamos identificar a chamada com falha exata, primeiro aumentando rapidamente após o canário ser definido:
e assistindo o endereço:
Agora, isso nos deixa com as instruções ofensivas corretas:
len = 5
ei = 4
, nesse caso em particular, nos apontou a linha culpada 12.No entanto, o backtrace está corrompido e contém algum lixo. Um backtrace correto seria semelhante a:
talvez isso possa corromper a pilha e impedir que você veja o rastreio.
Além disso, esse método requer saber qual é a última chamada da função de verificação de canário, caso contrário, você terá falsos positivos, que nem sempre serão viáveis, a menos que você use a depuração reversa .
fonte
Por favor, observe a seguinte situação:
Quando desativei o protetor de quebra de pilha, nenhum erro foi detectado, o que deveria ter acontecido quando usei "./a.out wepassssssssssssssssss"
Portanto, para responder à sua pergunta acima, a mensagem "** esmagamento de pilha detectado: xxx" foi exibida porque seu protetor de esmagamento de pilha estava ativo e descobriu que há excesso de pilha no seu programa.
Apenas descubra onde isso ocorre e corrija-o.
fonte
Você pode tentar depurar o problema usando o valgrind :
fonte
Isso significa que você escreveu para algumas variáveis na pilha de maneira ilegal, provavelmente como resultado de um estouro de buffer .
fonte
Um cenário seria o seguinte exemplo:
Neste programa, você pode reverter uma String ou uma parte da string se, por exemplo, chamar
reverse()
com algo como isto:Se você decidir passar o comprimento da matriz assim:
Funciona bem também.
Mas quando você faz isso:
Você obtém:
E isso acontece porque no primeiro código, o comprimento de
arr
é verificado dentro dorevSTR()
qual está correto, mas no segundo código em que você passa o comprimento:agora o comprimento é maior que o comprimento que você passa quando diz
arr + 2
.Comprimento de
strlen ( arr + 2 )
! =strlen ( arr )
.fonte
gets
escrcpy
. Gostaria de saber se poderíamos minimizar se ainda mais. Eu, pelo menos, se livrar destring.h
comsize_t len = sizeof( arr );
. Testado no gcc 6.4, Ubuntu 16.04. Eu também daria o exemplo com falhaarr + 2
para minimizar a colagem de cópias.Corrupções de pilha geralmente causadas por estouros de buffer. Você pode se defender contra eles programando defensivamente.
Sempre que você acessar uma matriz, coloque uma declaração antes dela para garantir que o acesso não esteja fora dos limites. Por exemplo:
Isso faz você pensar nos limites da matriz e também na adição de testes para acioná-los, se possível. Se algumas dessas afirmações falharem durante o uso normal, transforme-as em regulares
if
.fonte
Eu recebi esse erro ao usar malloc () para alocar alguma memória para uma estrutura * depois de gastar um pouco dessa depuração do código, finalmente usei a função free () para liberar a memória alocada e, posteriormente, a mensagem de erro desapareceu :)
fonte
Outra fonte de esmagamento de pilha é o uso (incorreto) de em
vfork()
vez defork()
.Acabei de depurar um caso disso, em que o processo filho não pôde
execve()
executar 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 em execução no espaço de processo do pai, não apenas corrompendo a pilha do pai, mas fazendo com que dois conjuntos diferentes de diagnósticos fossem impressos pelo código "downstream".Mudar
vfork()
parafork()
corrigir os dois problemas, assim como mudar areturn
declaração da criança para_exit()
.Mas como o código filho precede a
execve()
chamada com chamadas para outras rotinas (para definir o uid / gid, nesse caso em particular), tecnicamente não atende aos requisitosvfork()
, portanto, a alteração para usofork()
está correta aqui.(Observe que a
return
declaração problemática não era realmente codificada como tal - em vez disso, uma macro foi chamada e essa macro decidiu se baseava_exit()
oureturn
baseava-se em uma variável global. Portanto, não ficou imediatamente óbvio que o código filho não era conforme aovfork()
uso. )Para mais informações, veja:
A diferença entre fork (), vfork (), exec () e clone ()
fonte