int func(char* str)
{
char buffer[100];
unsigned short len = strlen(str);
if(len >= 100)
{
return (-1);
}
strncpy(buffer,str,strlen(str));
return 0;
}
Esse código é vulnerável a um ataque de estouro de buffer e estou tentando descobrir o porquê. Estou pensando que tem a ver com len
ser declarado um em short
vez de um int
, mas não tenho muita certeza.
Alguma ideia?
c
security
buffer-overflow
Jason
fonte
fonte
strncpy
. Nesse caso, não é.strlen
é calculado, usado para a verificação de validade e , em seguida, é absurdamente calculado novamente - é uma falha DRY. Se o segundostrlen(str)
fosse substituído porlen
, não haveria possibilidade de estouro de buffer, independentemente do tipo delen
. As respostas não abordam esse ponto, elas apenas conseguem evitá-lo.Respostas:
Na maioria dos compiladores, o valor máximo de um
unsigned short
é 65535.Qualquer valor acima que é contornado, então 65536 se torna 0 e 65600 se torna 65.
Isso significa que seqüências longas do comprimento certo (por exemplo, 65600) passam na verificação e sobrecarregam o buffer.
Use
size_t
para armazenar o resultado destrlen()
, nãounsigned short
e compararlen
com uma expressão que codifica diretamente o tamanho debuffer
. Então, por exemplo:fonte
len
como o terceiro argumento do strncpy. Usar strlen novamente é burro em qualquer caso./ sizeof(buffer[0])
- observe quesizeof(char)
em C é sempre 1 (mesmo que um caractere contenha um zilhão de bits), então isso é supérfluo quando não há possibilidade de usar um tipo de dados diferente. Ainda assim ... parabéns por uma resposta completa (e obrigado por responder aos comentários).char[]
echar*
não são a mesma coisa. Existem muitas situações em que achar[]
será implicitamente convertido em achar*
. Por exemplo,char[]
é exatamente o mesmo quechar*
quando usado como o tipo para argumentos de função. No entanto, a conversão não ocorre parasizeof()
.buffer
em algum momento, a expressão será atualizada automaticamente. Isso é essencial para a segurança, porque a declaração debuffer
pode estar a algumas linhas da verificação do código real. Portanto, é fácil alterar o tamanho do buffer, mas esqueça de atualizar em todos os locais onde o tamanho é usado.O problema está aqui:
Se a string for maior que o comprimento do buffer de destino, o strncpy ainda a copiará. Você está baseando o número de caracteres da sequência como o número a ser copiado em vez do tamanho do buffer. A maneira correta de fazer isso é a seguinte:
O que isso faz é limitar a quantidade de dados copiados para o tamanho real do buffer menos um para o caractere final nulo. Em seguida, definimos o último byte no buffer para o caractere nulo como uma proteção adicional. A razão para isso é que o strncpy copiará até n bytes, incluindo o nulo final, se strlen (str) <len - 1. Caso contrário, o nulo não será copiado e você terá um cenário de falha, porque agora seu buffer não possui terminação. corda.
Espero que isto ajude.
EDIT: Após um exame mais aprofundado e contribuições de outras pessoas, segue uma codificação possível para a função:
Como já sabemos o comprimento da string, podemos usar o memcpy para copiar a string do local referenciado por str no buffer. Observe que, na página de manual do strlen (3) (em um sistema FreeBSD 9.3), o seguinte é indicado:
O que eu interpreto é que o comprimento da string não inclui o nulo. É por isso que copio len + 1 bytes para incluir o nulo, e o teste verifica se o comprimento <tamanho do buffer - 2. Menos um porque o buffer inicia na posição 0 e menos outro para garantir que haja espaço para o nulo.
EDIT: Acontece que o tamanho de algo começa com 1 enquanto o acesso começa com 0; portanto, o -2 anterior estava incorreto porque retornaria um erro para qualquer coisa> 98 bytes, mas deveria ser> 99 bytes.
EDIT: Embora a resposta sobre um curto não assinado esteja geralmente correta, já que o comprimento máximo que pode ser representado é 65.535 caracteres, isso realmente não importa, porque se a string for maior que isso, o valor será contornado. É como pegar 75.231 (que é 0x000125DF) e mascarar os 16 bits principais, fornecendo 9695 (0x000025DF). O único problema que vejo com isso são os primeiros 100 caracteres após 65.535, pois a verificação de comprimento permitirá a cópia, mas copiará até os 100 primeiros caracteres da string em todos os casos e terminará nula . Portanto, mesmo com o problema abrangente, o buffer ainda não será excedido.
Isso pode ou não representar, por si só, um risco de segurança, dependendo do conteúdo da string e para o que você a está usando. Se é apenas texto simples legível por humanos, geralmente não há problema. Você acabou de obter uma string truncada. No entanto, se for algo como uma URL ou mesmo uma sequência de comandos SQL, você poderá ter um problema.
fonte
func
... e todas as outras funções C já escritas que usam seqüências terminadas em NUL como argumentos. Expor a possibilidade de a entrada não ter terminação NUL é completamente sem noção.len >= 100
) com um valor, mas o comprimento da cópia com um valor diferente ... isso é uma violação do princípio DRY. Simplesmente chamarstrncpy(buffer, str, len)
evita a possibilidade de estouro de buffer e faz menos trabalho do questrncpy(buffer,str,sizeof(buffer) - 1)
... embora aqui seja apenas um equivalente mais lentomemcpy(buffer, str, len)
.Mesmo que você esteja usando
strncpy
, o comprimento do corte ainda depende do ponteiro da string passado. Você não tem idéia de quanto tempo essa cadeia é (a localização do terminador nulo em relação ao ponteiro, ou seja). Portanto, ligarstrlen
sozinho abre a vulnerabilidade. Se você quer ser mais seguro, usestrnlen(str, 100)
.O código completo corrigido seria:
fonte
strlen
Também não teria acesso depois do final do buffer?strnlen
que não resolve o problema se o que o orlp está sugerindo está supostamente correto de qualquer maneira.buffer
. "como str poderia apontar para um buffer de 2 bytes, nenhum dos quais é NUL." - isso é irrelevante, pois é verdade em qualquer implementação defunc
. A questão aqui é sobre o estouro de buffer, não o UB, porque a entrada não é terminada por NUL.A resposta com a embalagem está correta. Mas há um problema que acho que não foi mencionado se (len> = 100)
Bem, se Len tivesse 100, copiaríamos 100 elementos e não teríamos \ 0 à direita. Isso claramente significaria que qualquer outra função, dependendo da sequência final correta, andaria muito além da matriz original.
A string problemática de C é IMHO insolúvel. É melhor você sempre ter alguns limites antes da ligação, mas mesmo isso não ajudará. Não há verificação de limites e, portanto, os estouros de buffer sempre podem e infelizmente acontecerão ....
fonte
strncpy()
e amigos, mas a alocação de memória funciona comostrdup()
e amigos. Eles estão no padrão POSIX-2008, portanto são bastante portáteis, embora não estejam disponíveis em alguns sistemas proprietários.buffer
é local para esta função e não é usada em nenhum outro lugar. Em um programa real, teríamos que examinar como ele é usado ... às vezes a terminação NUL não está correta (o uso original do strncpy era criar entradas de diretório de 14 bytes do UNIX - preenchidas com NUL e não terminadas com NUL). "A string problemática de C é IMHO insolúvel" - enquanto C é uma linguagem incrível que foi superada por uma tecnologia muito melhor, um código seguro pode ser escrito nela se for usada disciplina suficiente.if (len >= 100)
é a condição para quando a verificação falha , não quando passa, o que significa que não há um caso em que exatamente 100 bytes sem terminador NUL sejam copiados, pois esse comprimento está incluído na condição de falha.Além dos problemas de segurança envolvidos na chamada
strlen
mais de uma vez, geralmente não se deve usar métodos de string em strings cujo comprimento é precisamente conhecido [para a maioria das funções de strings, existe apenas um caso muito restrito em que elas devem ser usadas - em strings para as quais um máximo o comprimento pode ser garantido, mas o comprimento exato não é conhecido]. Uma vez que o comprimento da string de entrada é conhecido e o tamanho do buffer de saída, deve-se descobrir qual o tamanho de uma região que deve ser copiada e, em seguida, usarmemcpy()
para realmente executar a cópia em questão. Embora seja possível questrcpy
tenha um desempenho superiormemcpy()
ao copiar uma sequência de apenas 1 a 3 bytes, em muitas plataformasmemcpy()
é provável que seja mais do que o dobro da velocidade ao lidar com sequências maiores.Embora existam algumas situações em que a segurança teria um custo de desempenho, essa é uma situação em que a abordagem segura também é a mais rápida. Em alguns casos, pode ser razoável escrever código que não seja seguro contra entradas que se comportam de maneira estranha, se o código que fornece as entradas pode garantir que elas serão bem comportadas e se a proteção contra entradas mal comportadas impediria o desempenho. Garantir que os comprimentos de sequência sejam verificados apenas uma vez melhora o desempenho e a segurança, embora uma coisa extra possa ser feita para ajudar a proteger a segurança, mesmo ao rastrear manualmente o comprimento da sequência: para cada sequência que se espera que tenha um nulo final, escreva o nulo final explicitamente. do que esperar que a string de origem a tenha. Assim, se alguém estivesse escrevendo um
strdup
equivalente:Observe que a última instrução geralmente poderia ser omitida se o memcpy tivesse processado
len+1
bytes, mas outro segmento era modificar a cadeia de origem, o resultado poderia ser uma cadeia de destino não terminada por NUL.fonte
strlen
mais de uma vez ?strlen
e toma alguma ação com base no valor retornado (que era provavelmente o motivo para chamá-lo em primeiro lugar), uma chamada repetida (1) sempre gera a mesma resposta que a primeira, nesse caso, é simplesmente um desperdício de trabalho, ou (2) às vezes (porque outra coisa - talvez outro segmento - modificou a sequência nesse meio tempo) produz uma resposta diferente; nesse caso, o código que faz algumas coisas com o comprimento (por exemplo, alocar um buffer) pode assumir um tamanho diferente do código que faz outras coisas (copiar para o buffer).