Comece a coletar erros nas funções de cópia

memcpy

Notei várias vezes que os programadores cometem erros em funções simples de cópia de dados. Este tópico exigirá muito mais tempo no futuro para estudar e selecionar material, a fim de escrever um artigo completo. Mas eu queria compartilhar alguns exemplos que notei recentemente.

O fenômeno Baadera-Meinhof? Não, eu acho que não


Como membro da equipe do PVS-Studio, encontro um grande número de erros que descobrimos em vários projetos. Como o DevRel - eu gosto de falar sobre isso :). Hoje eu decidi falar sobre funções de cópia de dados implementadas incorretamente.

Já deparei com essas funções malsucedidas mais de uma vez. Mas não as escrevi, porque não dei importância a isso. No entanto, desde que notei essa tendência, é hora de começar a colecioná-las. Para começar, vou compartilhar os dois últimos casos notados.

Alguém pode argumentar que dois casos - isso não é uma regularidade. E isso, talvez, chamei a atenção para eles apenas porque eles me conheceram após um curto período de tempo e desencadearam o fenômeno Baader-Meinhof .

O fenômeno Baader-Meinhof, também uma ilusão de frequência, é uma distorção cognitiva na qual as informações recentemente descobertas que aparecem novamente após um curto período de tempo são percebidas como repetidas com frequência incomum.

Eu acho que isso não é verdade. Eu já tive a experiência de uma observação dessas sobre as funções de comparação, que foi confirmada pelo material coletado: "O mal vive nas funções de comparação ".

Ok, vamos direto ao ponto. A introdução para dar apenas dois exemplos até agora tem sido muito longa :).

Exemplo N1


No artigo sobre a verificação Zephyr RTOS, descrevi apenas uma tentativa malsucedida de implementar o análogo da função strdup :

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}

PVS-Studio Warning: V575 [CWE-628] A função 'memcpy' não copia toda a string. Use a função 'strcpy / strcpy_s' para preservar o terminal nulo. shell.c 427

Os relatórios de analisador que os memcpy função copia a linha, mas não copia o terminal de zero, e isso é muito suspeito. Parece que este terminal 0 é copiado aqui:

((u8_t *)mntpt)[strlen(mntpt)] = '\0';

Não, há um erro de digitação aqui, pelo qual o terminal zero é copiado para si mesmo. Observe que a gravação na matriz mntpt , não cpy_mntpt . Como resultado, a função mntpt_prepare retorna uma string incompleta com um terminal zero.

De fato, o programador queria escrever assim:

((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';

Não está claro por que o código foi escrito de maneira confusa e fora do padrão. Como resultado, um erro grave foi cometido em uma função pequena e descomplicada. Este código pode ser simplificado para a seguinte opção:

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}

Exemplo N2


void myMemCpy(void *dest, void *src, size_t n) 
{ 
   char *csrc = (char *)src; 
   char *cdest = (char *)dest; 
   for (int i=0; i<n; i++) 
     cdest[i] = csrc[i]; 
}

Nós não identificamos esse código usando o PVS-Studio, mas o encontrei acidentalmente no site StackOverflow: C e análise estática de código: isso é mais seguro que o memcpy?

No entanto, se você verificar esta função usando o analisador PVS-Studio, ele notará corretamente:

  • V104 Conversão implícita de 'i' para tipo memsize em uma expressão aritmética: i <n test.cpp 26
  • V108 Tipo de índice incorreto: cdest [não é do tipo memsize]. Use o tipo memsize. test.cpp 27
  • V108 Tipo de índice incorreto: csrc [não é do tipo memsize]. Use o tipo memsize. test.cpp 27

De fato, esse código contém uma falha, conforme indicado nas respostas ao StackOverflow. Você não pode usar uma variável int como um índice . Em um programa de 64 bits, quase certamente (não consideramos arquiteturas exóticas), a variável int será de 32 bits e a função poderá copiar não mais que INT_MAX bytes. Essa. não mais que 2 gigabytes.

Com um buffer maior a ser copiado, ocorrerá um estouro da variável sign, que do ponto de vista de C e C ++ é um comportamento indefinido. E, a propósito, não tente adivinhar exatamente como o erro se manifestará. Este é realmente um tópico difícil, sobre o qual você pode ler no artigo "O comportamento indefinido está mais próximo do que você pensa ".

É especialmente engraçado que esse código tenha aparecido como uma tentativa de remover algum tipo de aviso do analisador Checkmarx que ocorreu quando a função memcpy foi chamada . O programador não apresentou nada melhor do que fabricar sua própria bicicleta. E, apesar da simplicidade da função de cópia, ela ainda estava errada. Na verdade, é provável que a pessoa tenha se saído ainda pior do que era. Em vez de entender o motivo do aviso, ele mascarou o problema escrevendo sua própria função (confundindo o analisador). Além disso, foi adicionado um erro usando int para o contador . Ah, sim, esse código ainda pode prejudicar a otimização. É ineficiente usar seu próprio código em vez da função memcpy otimizada e eficiente . Não faça isso :)

Conclusão


Bem, estou apenas no início da jornada e, provavelmente, levará mais de um ano para acumular materiais para uma publicação completa sobre esse tópico. Na verdade, só agora vou começar a escrever esses casos. Agradecemos sua atenção e veja o que o analisador PVS-Studio encontrará interessante em seu código C / C ++ / C # / Java.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Andrey Karpov. Iniciando minha coleção de erros encontrados nas funções de cópia .

All Articles