Estou fazendo um curso na faculdade, onde um dos laboratórios é realizar explorações de buffer overflow no código que eles nos fornecem. Isso varia de explorações simples, como alterar o endereço de retorno de uma função em uma pilha para retornar a uma função diferente, até o código que altera um estado de registro / memória de programas, mas depois retorna à função que você chamou, o que significa que o A função que você chamou é completamente alheia à exploração.
Eu fiz algumas pesquisas sobre isso, e esses tipos de explorações são usadas em praticamente todos os lugares até agora, em coisas como rodar homebrew no Wii e o jailbreak sem restrições para iOS 4.3.1
Minha pergunta é por que esse problema é tão difícil de resolver? É óbvio que esta é uma das principais explorações usadas para hackear centenas de coisas, mas parece que seria muito fácil de corrigir simplesmente truncando qualquer entrada além do comprimento permitido e limpando toda a entrada que você receber.
EDIT: Outra perspectiva que eu gostaria que as respostas fossem consideradas - por que os criadores de C não corrigem esses problemas reimplementando as bibliotecas?
fonte
Não é realmente impreciso dizer que C é realmente "propenso a erros" por design . Além de alguns erros graves como
gets
, a linguagem C não pode realmente ser de outra maneira sem perder o recurso principal que atrai as pessoas para C em primeiro lugar.C foi projetado como uma linguagem de sistemas para atuar como uma espécie de "montagem portátil". Uma característica importante da linguagem C é que, diferentemente das linguagens de nível superior, o código C geralmente mapeia muito de perto o código de máquina real. Em outras palavras,
++i
geralmente é apenas umainc
instrução, e muitas vezes você pode ter uma idéia geral do que o processador estará fazendo em tempo de execução, observando o código C.Mas a adição de verificação implícita de limites acrescenta uma sobrecarga extra - que o programador não solicitou e pode não querer. Essa sobrecarga vai muito além do armazenamento extra necessário para armazenar o comprimento de cada matriz ou das instruções extras para verificar os limites da matriz em todos os acessos à matriz. E a aritmética dos ponteiros? Ou então, se você tem uma função que recebe um ponteiro? O ambiente de tempo de execução não tem como saber se esse ponteiro se enquadra nos limites de um bloco de memória legitimamente alocado. Para acompanhar isso, você precisará de uma arquitetura de tempo de execução séria que possa verificar cada ponteiro em uma tabela de blocos de memória alocados no momento; nesse momento, já estamos entrando no território de tempo de execução gerenciado no estilo Java / C #.
fonte
Eu acho que o verdadeiro problema não é que estes tipos de erros são difíceis de corrigir, mas que eles são tão fáceis de fazer: Se você usar
strcpy
,sprintf
e amigos no caminho (aparentemente) simples que o trabalho pode, então você provavelmente abriu a porta para um estouro de buffer. E ninguém notará isso até que alguém o explore (a menos que você tenha ótimas análises de código). Agora, adicione o fato de que existem muitos programadores medíocres e que estão sob pressão de tempo a maior parte do tempo - e você tem uma receita para um código tão cheio de estouros de buffer que será difícil corrigi-los, simplesmente porque há muitos deles e eles estão se escondendo tão bem.fonte
sizeof(ptr)
é 4 ou 8, geralmente. Essa é outra limitação em C: não há como determinar o comprimento de uma matriz, dado apenas o ponteiro para ela.#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))
que acionará uma divisão do tempo de compilação por zero. Outro inteligente que vi pela primeira vez no Chromium é o#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))
que troca alguns poucos falsos positivos por alguns falsos negativos - infelizmente é inútil para char []. Você pode usar várias extensões do compilador para torná-lo ainda mais confiável, por exemplo, blogs.msdn.com/b/ce_base/archive/2007/05/08/… .É difícil corrigir estouros de buffer porque C praticamente não fornece ferramentas úteis para solucionar o problema. É uma falha de linguagem fundamental que os buffers nativos não oferecem proteção e é praticamente, se não completamente, impossível substituí-los por um produto superior, como o C ++ fez com
std::vector
estd::array
, e é difícil, mesmo no modo de depuração, encontrar estouros de buffer.fonte
std::vector
sejam implementadas com eficiência. Evector::operator[]
faz a mesma escolha por velocidade sobre segurança. A segurançavector
vem de facilitar a distribuição do tamanho, que é a mesma abordagem adotada pelas bibliotecas C modernas.realloc
(C99 também permite dimensionar matrizes de pilha usando um tamanho determinado por tempo de execução, mas constante, por qualquer variável automática, quase sempre preferívelchar buf[1024]
). Segundo, o problema não tem nada a ver com a expansão de buffers, tem a ver se os buffers carregam ou não tamanho com eles e verificam esse tamanho quando você os acessa.vector::operator[]
faz checagem de limites no modo de depuração - algo que matrizes nativas não podem fazer - e, em segundo lugar, não há como C trocar o tipo de matriz nativa por outro que possa fazer checagem de limites, porque não há modelos e nenhum operador sobrecarga. No C ++, se você quiser passar deT[]
parastd::array
, você pode praticamente trocar apenas um typedef. Em C, não há como conseguir isso, nem escrever uma classe com funcionalidade equivalente, muito menos interface.std::vector<T>
estd::array<T, N>
fazer em C ++. Não haveria maneira de projetar e especificar qualquer biblioteca, nem mesmo a Standard, que pudesse fazer isso.std::vector
também nunca pode ser estaticamente dimensionado. Quanto ao genérico, você pode torná-lo tão genérico quanto o bom C precisar ser - um pequeno número de operações fundamentais no void * (adicionar, remover, redimensionar) e tudo o mais escrito especificamente. Se você vai reclamar que C não possui genéricos no estilo C ++, isso está muito fora do escopo do manuseio seguro de buffer.O problema não é com o C linguagem .
Na IMO, o único grande obstáculo a ser superado é que C é simplesmente ensinado mal . Décadas de más práticas e informações erradas foram institucionalizadas em manuais de referência e notas de aula, envenenando as mentes de cada nova geração de programadores desde o início. Os alunos recebem uma breve descrição das funções de E / S "fáceis", como
gets
1 ou,scanf
e então são deixadas para seus próprios dispositivos. Eles não são informados sobre onde ou como essas ferramentas podem falhar ou como evitar essas falhas. Eles não são informados sobre o usofgets
estrtol/strtod
porque essas são consideradas ferramentas "avançadas". Então eles são desencadeados no mundo profissional para causar estragos. Não que muitos dos programadores mais experientes saibam melhor, porque receberam a mesma educação com danos cerebrais. É enlouquecedor. Eu vejo muitas perguntas aqui e no Stack Overflow e em outros sites onde fica claro que a pessoa que está fazendo a pergunta está sendo ensinada por alguém que simplesmente não sabe do que está falando , e é claro que você não pode simplesmente dizer "seu professor está errado", porque ele é professor e você é apenas um cara na Internet.E então você tem a multidão que despreza qualquer resposta começando com "bem, de acordo com o padrão do idioma ..." porque eles estão trabalhando no mundo real e, segundo eles, o padrão não se aplica ao mundo real . Eu posso lidar com alguém que apenas tem uma educação ruim, mas quem insiste em ser ignorante é apenas uma desgraça para a indústria.
Não haveria problemas de estouro de buffer se o idioma fosse ensinado corretamente, com ênfase na escrita de código seguro. Não é "difícil", não é "avançado", é apenas ter cuidado.
Sim, isso tem sido um discurso retórico.
1 Que, felizmente, finalmente foi retirado da especificação da linguagem, embora ocorra para sempre 40 anos de código legado.
fonte
sprintf
, mas isso não significa que o idioma não tenha falhas. C era falho e é falho - como qualquer idioma - e é importante admitirmos essas falhas para que possamos continuar a corrigi-las.O problema é tanto a falta de visão gerencial quanto a incompetência do programador. Lembre-se, um aplicativo de 90.000 linhas precisa de apenas uma operação insegura para ser completamente insegura. Está quase além da possibilidade que qualquer aplicativo escrito sobre o manuseio de strings fundamentalmente inseguro seja 100% perfeito - o que significa que será inseguro.
O problema é que os custos de insegurança não são cobrados do destinatário certo (a empresa que vende o aplicativo quase nunca precisará reembolsar o preço de compra) ou não são claramente visíveis no momento em que as decisões são tomadas ("Temos que enviar em março, não importa o quê! "). Estou bastante certo de que se você considerasse os custos e os custos de longo prazo para os usuários, e não para o lucro da sua empresa, escrever em C ou em idiomas relacionados seria muito mais caro, provavelmente tão caro que é claramente a escolha errada em muitos campos onde hoje em dia a sabedoria convencional diz que é uma necessidade. Mas isso não mudará a menos que seja introduzida uma responsabilidade muito mais rigorosa por software - que ninguém na indústria deseja.
fonte
Um dos grandes poderes do uso de C é que ele permite manipular a memória da maneira que você achar melhor.
Uma das grandes fraquezas do uso de C é que ele permite manipular a memória da maneira que você achar melhor.
Existem versões seguras de quaisquer funções não seguras. No entanto, programadores e compiladores não impõem estritamente seu uso.
fonte
Provavelmente porque o C ++ já fez isso e é compatível com o código C. Portanto, se você deseja um tipo de string seguro no seu código C, basta usar std :: string e escrever seu código C usando um compilador C ++.
O subsistema de memória subjacente pode ajudar a evitar estouros de buffer, introduzindo blocos de proteção e verificação de validade deles - para que todas as alocações tenham 4 bytes de 'fefefefe' adicionados, quando esses blocos são gravados, o sistema pode lançar um wobbler. Não é garantido impedir uma gravação na memória, mas mostrará que algo deu errado e precisa ser corrigido.
Eu acho que o problema é que as velhas rotinas strcpy etc ainda estão presentes. Se eles foram removidos em favor do strncpy etc, isso ajudaria.
fonte
É simples entender por que o problema do estouro não está resolvido. C foi falho em algumas áreas. Na época, essas falhas eram vistas como toleráveis ou até mesmo como um recurso. Agora, décadas depois, essas falhas não podem ser corrigidas.
Algumas partes da comunidade de programação não querem esses buracos. Basta olhar para todas as guerras de chamas que começam com strings, matrizes, ponteiros, coleta de lixo ...
fonte
memcpy()
disponível e ser apenas um meio padrão de copiar eficientemente um segmento de matriz.