Quais são as funções da biblioteca padrão que devem / devem ser evitadas?

Eu li no Stack Overflow que algumas funções C são “obsoletas” ou “devem ser evitadas”. Você pode, por favor, me dar alguns exemplos desse tipo de funções e por quê?

Quais alternativas dessas funções existem?

Podemos usá-los com segurança – quaisquer boas práticas?

Funções reprovadas
Inseguro
Um exemplo perfeito de tal function é gets () , porque não há como dizer qual é o tamanho do buffer de destino. Conseqüentemente, qualquer programa que lê input usando gets () possui uma vulnerabilidade de estouro de buffer . Por razões semelhantes, deve-se usar strncpy () no lugar de strcpy () e strncat () no lugar de strcat () .

No entanto, alguns outros exemplos incluem a function tmpfile () e mktemp () devido a possíveis problemas de segurança ao sobrescrever arquivos temporários e que são substituídos pela function mkstemp () mais segura.

Não Reentrante
Outros exemplos incluem gethostbyaddr () e gethostbyname () que são não-reentrantes (e, portanto, não garantimos que sejam thread – safe ) e foram substituídos pelos reentrant getaddrinfo () e freeaddrinfo () .

Você pode estar percebendo um padrão aqui … ou a falta de segurança (possivelmente por não include informações suficientes na assinatura para possivelmente implementá-lo com segurança) ou a não-reinput são fonts comuns de reprovação.

Desatualizado, não portátil
Algumas outras funções simplesmente se tornam obsoletas porque duplicam a funcionalidade e não são tão portáteis quanto outras variantes. Por exemplo, bzero () é preterido em favor de memset () .

Segurança de fio e reinput
Você perguntou, em seu post, sobre segurança e reinput de threads. Existe uma ligeira diferença. Uma function é reentrante se não usar nenhum estado mutável compartilhado. Portanto, por exemplo, se todas as informações necessárias forem passadas para a function, e quaisquer buffers necessários também forem passados ​​para a function (em vez de serem compartilhados por todas as chamadas para a function), ela será reentrante. Isso significa que segmentos diferentes, usando parâmetros independentes, não correm o risco de compartilhar o estado acidentalmente. A reinput é uma garantia mais forte do que a segurança de roscas. Uma function é thread-safe se pode ser usada por vários threads simultaneamente. Uma function é thread safe se:

  • É reentrante (isto é, não compartilha nenhum estado entre as chamadas) ou:
  • Ele não é reentrante, mas usa synchronization / bloqueio conforme necessário para o estado compartilhado.

Em geral, na especificação Single UNIX e no IEEE 1003.1 (ou seja, “POSIX”), não é garantido que qualquer function que não seja garantida como reentrante seja thread safe. Portanto, em outras palavras, apenas funções com garantia de reinput podem ser portualmente usadas em aplicativos multithread (sem bloqueio externo). Isso não significa, no entanto, que as implementações desses padrões não possam fazer uma function não reentrante thread-safe. Por exemplo, o Linux freqüentemente adiciona synchronization a funções não reentrantes para adicionar uma garantia (além da Especificação Single UNIX) de threadsafety.

Strings (e buffers de memory, em geral)
Você também perguntou se há alguma falha fundamental com strings / arrays. Alguns podem argumentar que este é o caso, mas eu diria que não, não há falha fundamental na linguagem. C e C ++ exigem que você passe o comprimento / capacidade de um array separadamente (não é uma propriedade “.length” como em outros idiomas). Isso não é uma falha, por si. Qualquer desenvolvedor C e C ++ pode escrever o código correto simplesmente passando o comprimento como um parâmetro onde necessário. O problema é que várias APIs que exigiam essa informação não conseguiram especificá-la como um parâmetro. Ou assumiu que alguma constante MAX_BUFFER_SIZE seria usada. Tais APIs foram substituídas e substituídas por APIs alternativas que permitem que os tamanhos de matriz / buffer / string sejam especificados.

Scanf (em resposta à sua última pergunta)
Pessoalmente, eu uso a biblioteca de iostreams C ++ (std :: cin, std :: cout, os operadores << e >>, std :: getline, std :: istringstream, std :: ostringstream, etc.), então eu não normalmente lidar com isso. Se eu fosse forçado a usar C puro, no entanto, eu usaria apenas fgetc () ou getchar () em combinação com strtol () , strtoul () , etc. e analisaria as coisas manualmente, já que eu não sou um grande fã de varargs ou strings de formato. Dito isto, tanto quanto sei, não há problema com [f] scanf () , [f] printf () , etc., desde que você mesmo crie as strings de formato, nunca passe strings de formato arbitrário ou permita ao usuário input para ser usado como strings de formato, e você usa as macros de formatação definidas em onde apropriado. (Nota, snprintf () deve ser usado no lugar de sprintf () , mas isso tem a ver com a falha em especificar o tamanho do buffer de destino e não o uso de strings de formato). Também devo salientar que, em C ++, o boost :: format fornece formatação semelhante a printf sem varargs.

Mais uma vez as pessoas estão repetindo, como um mantra, a ridícula afirmação de que a versão “n” das funções str são versões seguras.

Se era para isso que eles pretendiam, eles sempre anulariam as seqüências de caracteres.

As “n” versões das funções foram escritas para uso com campos de comprimento fixo (como inputs de diretório nos primeiros filesystems), em que o terminador nul é necessário apenas se a cadeia não preencher o campo. Esta é também a razão pela qual as funções têm efeitos colaterais estranhos que são inúteis se usados ​​como substitutos – use strncpy () por exemplo:

Se a matriz apontada por s2 for uma cadeia de caracteres menor que n bytes, os bytes nulos serão anexados à cópia na matriz apontada por s1, até que n bytes em todos sejam gravados.

Como buffers alocados para manipular nomes de arquivos são tipicamente 4kbytes, isso pode levar a uma deterioração maciça no desempenho.

Se você quer versões “supostamente” seguras, então obtenha – ou escreva suas próprias – strines (strlcpy, strlcat etc) que sempre nul terminam as strings e não possuem efeitos colaterais. Por favor, note que estes não são realmente seguros, pois eles podem silenciosamente truncar a string – este raramente é o melhor curso de ação em qualquer programa do mundo real. Há ocasiões em que isso é aceitável, mas também há muitas circunstâncias em que isso pode levar a resultados catastróficos (por exemplo, imprimir prescrições médicas).

Várias respostas aqui sugerem o uso de strncat() sobre strcat() ; Eu sugiro que strncat() (e strncpy() ) também devem ser evitados. Tem problemas que dificultam o uso correto e levam a erros:

  • o parâmetro de comprimento para strncat() está relacionado a (mas não exatamente – veja o terceiro ponto) o número máximo de caracteres que podem ser copiados para o destino em vez do tamanho do buffer de destino. Isso torna o strncat() mais difícil de usar do que deveria, especialmente se vários itens forem concatenados ao destino.
  • pode ser difícil determinar se o resultado foi truncado (o que pode ou não ser importante)
  • é fácil ter um erro off-by-one. Como o padrão C99 observa, “Assim, o número máximo de caracteres que podem acabar no array apontado por s1 é strlen(s1)+n+1 ” para uma chamada que se parece com strncat( s1, s2, n)

strncpy() também tem um problema que pode resultar em erros que você tenta usar de maneira intuitiva – ele não garante que o destino seja terminado com nulo. Para garantir que você tenha certeza de tratar especificamente esse caso de canto descartando um '\0' no último local do buffer (pelo menos em certas situações).

Eu sugiro usar algo como strlcat() e strlcpy() do OpenBSD (embora eu saiba que algumas pessoas não gostem dessas funções; acredito que elas são muito mais fáceis de usar com segurança que strncat() / strncpy() ).

Aqui está um pouco do que Todd Miller e Theo de Raadt tinham a dizer sobre problemas com strncat() e strncpy() :

Existem vários problemas encontrados quando strncpy() e strncat() são usados ​​como versões seguras de strcpy() e strcat() . Ambas as funções lidam com terminação NUL e o parâmetro length de maneiras diferentes e não intuitivas que confundem até mesmo programadores experientes. Eles também não fornecem uma maneira fácil de detectar quando ocorre o truncamento. … De todas essas questões, a confusão causada pelos parâmetros de tamanho e o problema relacionado à terminação NUL são os mais importantes. Quando auditamos a tree fonte do OpenBSD para potenciais falhas de segurança, encontramos um uso excessivo de strncpy() e strncat() . Embora nem todos tenham resultado em falhas de segurança exploráveis, eles deixaram claro que as regras para o uso de strncpy() e strncat() em operações de string seguras são amplamente mal compreendidas.

A auditoria de segurança do OpenBSD descobriu que bugs com essas funções eram “desenfreados”. Ao contrário de gets() , essas funções podem ser usadas com segurança, mas na prática existem muitos problemas porque a interface é confusa, não intuitiva e difícil de usar corretamente. Eu sei que a Microsoft também fez análises (embora eu não saiba quantos dos seus dados eles podem ter publicado), e como resultado baniram (ou pelo menos muito fortemente desencorajaram – a ‘proibição’ pode não ser absoluta) uso de strncat() e strncpy() (entre outras funções).

Alguns links com mais informações:

Algumas pessoas afirmam que strcpy e strcat devem ser evitados, em favor de strncpy e strncat . Isso é um pouco subjetivo, na minha opinião.

Eles definitivamente devem ser evitados quando se lida com a input do usuário – sem dúvida aqui.

No código “longe” do usuário, quando você sabe que os buffers são longos o suficiente, o strcpy e o strcat podem ser um pouco mais eficientes, pois calcular o n para passar para seus primos pode ser supérfluo.

Evitar

  • strtok para programas multithreaded como não é thread-safe.
  • gets como poderia causar estouro de buffer

Funções de biblioteca padrão que nunca devem ser usadas:

setjmp.h

  • setjmp() . Juntamente com longjmp() , essas funções são amplamente reconhecidas como incrivelmente perigosas de usar: elas levam a programação de espaguete, elas vêm com inúmeras formas de comportamento indefinido, elas podem causar efeitos colaterais indesejados no ambiente do programa, como afetar valores armazenados em a pilha. Referências: MISRA-C: regra de 2012 21.4, CERT C MSC22-C .
  • longjmp() . Veja setjmp() .

stdio.h

  • gets() . A function foi removida da linguagem C (conforme C11), já que não era seguro por design. A function já foi sinalizada como obsoleta em C99. Use fgets() vez disso. Referências: ISO 9899: 2011 K.3.5.4.1, veja também a nota 404).

stdlib.h

  • família de funções atoi() . Eles não têm tratamento de erros, mas invocam um comportamento indefinido sempre que ocorrerem erros. Funções completamente supérfluas que podem ser substituídas pela família de funções strtol() . Referências: MISRA-C: regra de 2012 21.7.

string.h

  • strncat() . Tem uma interface estranha que geralmente é mal utilizada. É principalmente uma function supérflua. Veja também observações para strncpy() .
  • strncpy() . A intenção dessa function nunca foi ser uma versão mais segura do strcpy() . Seu único objective era sempre manipular um antigo formato de string em sistemas Unix e incluí-lo na biblioteca padrão é um erro conhecido. Essa function é perigosa porque pode deixar a cadeia sem terminação nula e os programadores costumam usá-la incorretamente. Referências: Por que o strlcpy e o strlcat são considerados inseguros? .

Funções de biblioteca padrão que devem ser usadas com caucanvas:

assert.h

  • assert() . Vem com sobrecarga e geralmente não deve ser usado no código de produção. É melhor usar um manipulador de erros específico do aplicativo que exiba erros, mas não feche necessariamente todo o programa.

signal.h

  • signal() . Referências: MISRA-C: 2012 regra 21.5, CERT C SIG32-C .

stdarg.h

  • família de funções va_arg() . A presença de funções de comprimento variável em um programa C é quase sempre uma indicação de projeto de programa ruim. Deve ser evitado a menos que você tenha requisitos muito específicos.

stdio.h
Geralmente, essa biblioteca inteira não é recomendada para o código de produção , pois ela vem com vários casos de comportamento mal definido e segurança de tipo ruim.

  • fflush() . Perfeitamente bem para usar streams de saída. Invoca o comportamento indefinido, se usado para streams de input.
  • gets_s() . Versão segura de gets() incluída na interface de verificação de limites C11. É preferível usar fgets() vez disso, conforme a recomendação padrão C. Referências: ISO 9899: 2011 K.3.5.4.1.
  • família de funções printf() . Funções pesadas de resources que vêm com muito comportamento indefinido e segurança de tipo ruim. sprintf() também tem vulnerabilidades. Essas funções devem ser evitadas no código de produção. Referências: MISRA-C: regra de 2012 21.6.
  • família de funções scanf() . Veja observações sobre printf() . Além disso, – scanf() é vulnerável a overruns de buffer se não for usado corretamente. fgets() é preferível usar quando possível. Referências: CERT C INT05-C , MISRA-C: regra de 2012 21.6.
  • família de funções tmpfile() . Vem com vários problemas de vulnerabilidade. Referências: CERT C FIO21-C .

stdlib.h

  • família de funções malloc() . Perfeitamente bem para usar em sistemas hospedados, apesar de estar ciente de problemas bem conhecidos no C90 e, portanto , não lançar o resultado . A família de funções malloc() nunca deve ser usada em aplicativos autônomos. Referências: MISRA-C: regra de 2012 21.3.

    Observe também que realloc() é perigoso caso você sobrescreva o ponteiro antigo com o resultado de realloc() . Caso a function falhe, você cria um vazamento.

  • system() . Vem com muita sobrecarga e, embora portável, geralmente é melhor usar funções da API específicas do sistema. Vem com vários comportamentos mal definidos. Referências: CERT C ENV33-C .

string.h

  • strcat() . Veja observações para strcpy() .
  • strcpy() . Perfeitamente bem para usar, a menos que o tamanho dos dados a serem copiados seja desconhecido ou maior que o buffer de destino. Se nenhuma verificação do tamanho dos dados de input for feita, poderá haver excesso de buffer. O que não é culpa do próprio strcpy() , mas do aplicativo de chamada – que o strcpy() é inseguro é principalmente um mito criado pela Microsoft .
  • strtok() . Altera a string do chamador e usa variables ​​de estado internas, o que poderia torná-lo inseguro em um ambiente multi-threaded.

Vale a pena acrescentar novamente que o strncpy() não é o substituto de propósito geral do strcpy() que o nome pode sugerir. Ele é projetado para campos de comprimento fixo que não precisam de um nul-terminator (ele foi originalmente projetado para uso com inputs de diretório UNIX, mas pode ser útil para coisas como campos de chave de criptografia).

É fácil, no entanto, usar o strncat() como um substituto para o strcpy() :

 if (dest_size > 0) { dest[0] = '\0'; strncat(dest, source, dest_size - 1); } 

(O teste if pode obviamente ser descartado no caso comum, onde você sabe que o dest_size é definitivamente diferente de zero).

Confira também a lista da Microsoft de APIs banidas . Estas são APIs (incluindo muitas já listadas aqui) que são banidas do código da Microsoft porque geralmente são mal utilizadas e levam a problemas de segurança.

Você pode não concordar com todos eles, mas todos eles valem a pena considerar. Eles adicionam uma API à lista quando seu uso indevido levou a vários bugs de segurança.

Quase qualquer function que lida com sequências terminadas em NUL é potencialmente insegura. Se você está recebendo dados do mundo exterior e manipulando-os através das funções str * (), você se prepara para a catástrofe

Não se esqueça do sprintf – é a causa de muitos problemas. Isto é verdade porque a alternativa, o snprintf tem, por vezes, diferentes implementações que podem tornar o seu código não portável.

  1. linux: http://linux.die.net/man/3/snprintf

  2. windows: http://msdn.microsoft.com/pt-br/library/2ts7cx93%28VS.71%29.aspx

No caso 1 (linux), o valor de retorno é a quantidade de dados necessária para armazenar o buffer inteiro (se for menor que o tamanho do buffer fornecido, a saída foi truncada)

No caso 2 (windows), o valor de retorno é um número negativo, caso a saída seja truncada.

Geralmente você deve evitar funções que não são:

  1. buffer overflow safe (muitas funções já foram mencionadas aqui)

  2. fio seguro / não reentrante (strtok por exemplo)

No manual de cada function você deve procurar por palavras-chave como: safe, sync, async, thread, buffer, bugs

É muito difícil usar o scanf com segurança. O bom uso do scanf pode evitar estouro de buffer, mas você ainda está vulnerável a comportamentos indefinidos ao ler números que não se encheckboxm no tipo solicitado. Na maioria dos casos, fgets seguidos por auto-análise (usando sscanf , strchr , etc.) é uma opção melhor.

Mas eu não diria “evite o scanf o tempo todo”. scanf tem seus usos. Por exemplo, digamos que você queira ler a input do usuário em uma matriz de char 10 bytes de comprimento. Você deseja remover a nova linha à direita, se houver. Se o usuário digitar mais de 9 caracteres antes de uma nova linha, você deseja armazenar os primeiros 9 caracteres no buffer e descartar tudo até a próxima nova linha. Você pode fazer:

 char buf[10]; scanf("%9[^\n]%*[^\n]", buf)); getchar(); 

Uma vez que você se acostume com esse idioma, ele é mais curto e, de certa forma, mais limpo que:

 char buf[10]; if (fgets(buf, sizeof buf, stdin) != NULL) { char *nl; if ((nl = strrchr(buf, '\n')) == NULL) { int c; while ((c = getchar()) != EOF && c != '\n') { ; } } else { *nl = 0; } } 

Em todos os cenários de cópia de seqüência / movimentação – strcat (), strncat (), strcpy (), strncpy (), etc. – as coisas ficam muito melhores ( mais seguras ) se algumas heurísticas simples forem aplicadas:

1. Sempre encha o (s) seu (s) buffer (s) antes de adicionar dados.
2. Declare buffers de caracteres como [SIZE + 1], com uma macro-constante.

Por exemplo, dado:

#define BUFSIZE 10
char Buffer [BUFSIZE + 1] = {0x00}; / * O compilador NUL-preenche o resto * /

podemos usar código como:

memset (Buffer, 0x00, sizeof (Buffer));
strncpy (Buffer, BUFSIZE, “12345678901234567890”);

relativamente com segurança. O memset () deve aparecer antes do strncpy (), mesmo que tenhamos inicializado o Buffer em tempo de compilation, porque não sabemos o lixo que outro código colocou nele antes de nossa function ser chamada. O strncpy () irá truncar os dados copiados para “1234567890”, e não terminará NUL. No entanto, como já preenchemos NUL todo o buffer – sizeof (Buffer), em vez de BUFSIZE – é garantido que haverá um NUL final “fora do escopo”, desde que limitemos nossas gravações usando o BUFSIZE constante, em vez de sizeof (Buffer).

Buffer e BUFSIZE também funcionam bem para snprintf ():

memset (Buffer, 0x00, sizeof (Buffer));

if (snprintf (Buffer, BUFIZE, “Dados:% s”, “Dados em excesso”)> BUFSIZE) {
/ * Faça algum tratamento de erros /
} / Se estiver usando o MFC, você precisará if (… <0), instead * /

Mesmo que o snprintf () grave especificamente apenas caracteres BUFIZE-1, seguidos pelo NUL, isso funciona com segurança. Então, “perdemos” um byte NUL irrelevante no final do Buffer … impedimos as condições de buffer overflow e string não delimitado, por um custo de memory muito pequeno.

Minha chamada em strcat () e strncat () é mais hard-line: não os use. É difícil usar o strcat () com segurança, e a API do strncat () é tão contra-intuitiva que o esforço necessário para usá-lo elimina qualquer benefício. Eu proponho o seguinte drop-in:

#define strncat (target, source, bufsize) snprintf (alvo, fonte, “% s% s”, alvo, fonte)

É tentador criar um drop-in strcat (), mas não é uma boa ideia:

#define strcat (target, source), snprintf (alvo, sizeof (alvo), “% s% s”, alvo, fonte)

porque alvo pode ser um ponteiro (assim sizeof () não retorna a informação que precisamos). Eu não tenho uma boa solução “universal” para instâncias de strcat () no seu código.

Um problema que eu freqüentemente encontro de programadores “strFunc () – aware” é uma tentativa de proteger contra estouros de buffer usando strlen (). Isso é bom se o conteúdo tiver a garantia de ser terminado em NUL. Caso contrário, o próprio strlen () pode causar um erro de saturação de buffer (geralmente levando a uma violação de segmentação ou outra situação de core-dump), antes que você atinja o código “problemático” que está tentando proteger.

Atoi não é thread-safe. Eu uso o strtol, por recomendação da página man.

Intereting Posts