- O projeto cURL, depois de já ter removido
strncpy(), agora também proibiu completamente strcpy() da base de código
strcpy() tem uma API simples, mas há o risco de a validação do tamanho do buffer ficar separada dela, o que a torna insegura na manutenção de longo prazo
- Para substituí-la, foi introduzida uma nova função chamada
curlx_strcopy(), que recebe tanto o tamanho do buffer de destino quanto o comprimento da string como argumentos e verifica se a cópia é possível antes de executá-la
- A função usa
memcpy() internamente e garante também o tratamento do caractere de terminação nula
- Com essa mudança, é possível aumentar a segurança e a consistência do código, além de reduzir o problema de IAs gerarem relatórios incorretos de vulnerabilidade
Contexto da remoção de strcpy
- No passado, o cURL já removeu todas as chamadas de
strncpy(), apontando os problemas de API pouco intuitiva, falha em garantir terminação nula e preenchimento desnecessário com zeros dessa função
- Nos casos em que era necessária a cópia de parte da string, a implementação foi alterada para usar
memcpy() e tratar manualmente a terminação nula
strcpy() tem uma API simples, mas não explicita o tamanho do buffer, o que cria o risco de, durante a manutenção, o código de validação ficar separado da chamada de cópia
- Quando o código é modificado ao longo de décadas por vários desenvolvedores, existe a possibilidade de a validação do tamanho do buffer ser neutralizada
Introdução de uma nova função de cópia de strings
- Para evitar esse risco, foi introduzida uma função alternativa chamada
curlx_strcopy()
- Ela recebe como argumentos o buffer de destino, o tamanho do buffer, o buffer de origem e o comprimento da string de origem
- A cópia com
memcpy() só é realizada quando tanto a cópia quanto a terminação nula são possíveis
- Em caso de falha, o buffer de destino é inicializado como string vazia
- Essa função exige mais argumentos e mais código do que
strcpy(), mas garante segurança ao acoplar de forma mais rígida a validação do buffer à operação de cópia
- O uso de
strcpy() foi completamente proibido na base de código do cURL, assim como já havia acontecido com strncpy()
Detalhes da implementação
- Um exemplo da definição da função é o seguinte
void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen)
{
DEBUGASSERT(slen < dsize);
if(slen < dsize) {
memcpy(dest, src, slen);
dest[slen] = 0;
}
else if(dsize)
dest[0] = 0;
}
- Com
DEBUGASSERT, é possível detectar erros mais cedo durante o desenvolvimento, e o código foi projetado para sempre ter sucesso em ambiente de produção
- Assim como
strcpy, ela não possui valor de retorno e adota uma abordagem de capturar erros nas etapas de teste e fuzzing
Reação da comunidade
- Alguns desenvolvedores apontaram que ela se parece com
strcpy_s() (C11 Annex K), mas o cURL ainda usa o padrão C89
- Outras opiniões sugeriram a necessidade de adicionar um valor de retorno ou de melhorar a forma de tratamento quando o buffer falha
- Em resposta, o cURL explicou que “como a função foi projetada para sempre ter sucesso, um valor de retorno é desnecessário”
Efeito colateral relacionado à IA
- Com essa mudança, também é possível evitar o problema de chatbots de IA detectarem erroneamente o uso de
strcpy no código do cURL e afirmarem que ele é vulnerável
- Ainda assim, o autor observou que “a IA continua podendo gerar outros relatórios falsos”, mencionando assim as limitações da análise de código baseada em IA
5 comentários
O certo é usar
snprintfno lugar destrcpy. Se houverstrcpyno código, tem que descobrir onde mora o desenvolvedor que fez aquilo.Esse era o jeito como eu trabalhava com código de depuração quando eu estava numa empresa de jogos há 25 anos, e não era só o
strcpy, né. No release, isso voltava a ficar liberado para ganhar desempenho e assim era colocado em produção. Na verdade, no lado de jogos, colisão de memória é uma das coisas mais sensíveis, então o trabalho também era feito com muita cautela e atenção, a ponto de criarmos e usarmos nosso próprio depurador de memória. Mas hoje, olhando para trás, vejo que aquilo estava basicamente criando um garbage collector. Que lembrança nostálgica.Erro C4996
'strcpy': esta função ou variável pode não ser segura. Considere usarstrcpy_sem vez disso. Para desativar a descontinuação, use_CRT_SECURE_NO_WARNINGS. Veja a ajuda online para mais detalhes.Comentários do Hacker News
strcpy()não é ruim apenas do ponto de vista de segurança, mas também de desempenhoAntigamente se pensava que
strcpy()era eficiente quando o tamanho da string era desconhecido, mas na prática, por copiar um byte por vez, a CPU precisa fazer previsão de desvios, o que é ineficientestrcpyusando laço escalar. Fico curioso se isso acontece só na arquitetura ARMSempre achei que as rotinas de string de C, sem exceção, têm grandes limitações, então acabam sendo pouco úteis
Por isso, acho que faz falta uma biblioteca que registre, junto com o ponteiro da string, o tamanho da memória alocada
Como exemplo, vale consultar a biblioteca bstring
strncpysurgiu para copiar nomes de arquivo de tamanho fixo. Para mais detalhes, veja esta resposta no StackOverflowstrncpyera originalmente uma função para lidar com campos de string de largura fixa. Por exemplo, servia para preencher com NUL um campo comochar username[20]. Veja a documentação relacionada no manual string_copying.7Acho estranho que
curlx_strcopynão retorne indicação de sucesso ou falhaDá para verificar
dest[0], mas isso tem alto potencial de gerar erro e não é intuitivoDEBUGASSERT(slen < dsize);passa, mas em builds release o assert pode ser removido. Acho melhor ter um código de erro explícitostrncpy()originalmente não era para strings terminadas em nulo, mas para campos de tamanho fixoO problema começou quando analisadores estáticos passaram a recomendar
strncpyno lugar destrcpy. As alternativas reais eramsnprintfoustrlcpystrlcpyé uma função da família BSD, então não faz parte do POSIX. A recomendação oficial éstpecpy, mas quase não há implementações reais. Veja a documentação relacionadastrncpypreencher após o nulo era permitir comparações eficientes em campos de nomes de tamanho fixo, como entradas de diretório. Isso também está explicitado no documento de fundamentação do padrão ANSI CEssa API parece algo no estilo do Annex-K. O tamanho do buffer de destino inclui espaço para o NUL, mas o tamanho da origem não inclui
Acho que seria melhor simplesmente usar
memcpydiretamenteNo artigo, chamou atenção a frase de que “
strcpyé uma isca para a IA gerar relatórios de vulnerabilidade incorretos”strcpycomo problema, mas cria provas complexas com erro lógico, fazendo os mantenedores perderem tempo verificando issoO princípio de “verifique perto do código” é bom, mas fica ambíguo quando é preciso verificar no início do ciclo de vida dos dados
Acho que seria bom poder distinguir por tipo algo como “dado validado”, como acontece com o tipo
Resultem RustResultapenas representa sucesso/falha, e não garante um estado validado. Em vez disso, seria melhor ter um tipo separado que só possa ser criado após passar pelo processo de validação. Essa é a filosofia de “parse, don’t validate”A diferença de off-by-one entre tamanho do buffer e comprimento da string é um problema de usabilidade terrível. Tem grande chance de continuar gerando erros
A nova função de cópia de string proposta esvazia o buffer de destino e retorna
voidquando a cópia não é possívelMas acho melhor tratar esse caso como erro e não mexer no buffer. Confiar apenas em
DEBUGASSERTpassa pouca segurançaParabéns por concluir o projeto. Mesmo em C/C++, com esforço, dá para alcançar segurança de memória
Só que, em ambiente móvel, o tamanho da fonte dos gráficos é pequeno demais, o que prejudica a legibilidade
strcpynão torna o código automaticamente seguro em memóriaTambém é uma boa migrar de vez para a linguagem C3. Como é um projeto que mantém a sintaxe de C com mudanças mínimas e adiciona recursos modernos, a transição também é fácil.