Eu li no Stack Overflow que algumas funções C são "obsoletas" ou "devem ser evitadas". Você pode me dar alguns exemplos desse tipo de função e o motivo?
Que alternativas existem para essas funções?
Podemos usá-los com segurança - alguma boa prática?
c
standard-library
obsolete
Andrei Ciobanu
fonte
fonte
strncpy()
como um substituto geral parastrcpy()
, estrncat()
nunca usaria porque tem a interface menos intuitiva que se possa imaginar - VOCÊ sabe o que especifica o parâmetro de comprimento?Respostas:
Funções
obsoletas Inseguras
Um exemplo perfeito de tal função é gets () , porque não há como dizer o quão grande é o buffer de destino. Consequentemente, qualquer programa que leia a entrada usando gets () tem uma vulnerabilidade de estouro de buffer . Por razões semelhantes, deve-se usar strncpy () no lugar de strcpy () e strncat () no lugar de strcat () .
Ainda, mais alguns exemplos incluem a função tmpfile () e mktemp () devido a possíveis problemas de segurança com a substituição de arquivos temporários e que são substituídos pela função mkstemp () mais segura .
Não-reentrante
Outros exemplos incluem gethostbyaddr () e gethostbyname () que são não-reentrantes (e, portanto, não têm garantia de threadsafe) e foram substituídos pelo getaddrinfo () e freeaddrinfo () reentrantes .
Você pode estar notando um padrão aqui ... ou a falta de segurança (possivelmente por não incluir informações suficientes na assinatura para possivelmente implementá-la com segurança) ou a não reentrada são fontes comuns de reprovação.
Desatualizado, Não-portátil
Algumas outras funções simplesmente se tornaram obsoletas porque duplicam a funcionalidade e não são tão portáveis quanto outras variantes. Por exemplo, bzero () está obsoleto em favor de memset () .
Thread Safety e Reentrance
Você perguntou, em sua postagem, sobre thread safety e reentrance. Existe uma pequena diferença. Uma função é reentrante se não usar nenhum estado mutável compartilhado. Portanto, por exemplo, se todas as informações necessárias forem passadas para a função e quaisquer buffers necessários também forem passados para a função (em vez de serem compartilhados por todas as chamadas para a função), então ela é reentrante. Isso significa que threads diferentes, usando parâmetros independentes, não correm o risco de compartilhar o estado acidentalmente. A reentrada é uma garantia mais forte do que a segurança do thread. Uma função é segura para thread se puder ser usada por vários threads simultaneamente. Uma função é thread-safe se:
Em geral, na Single UNIX Specification e IEEE 1003.1 (ou seja, "POSIX"), qualquer função que não seja garantida como reentrante não tem garantia de thread safe. Portanto, em outras palavras, apenas as funções que são garantidas como reentrantes podem ser usadas portavelmente em aplicativos multithread (sem bloqueio externo). Isso não significa, no entanto, que as implementações desses padrões não possam optar por tornar uma função não reentrante threadsafe. Por exemplo, o Linux frequentemente adiciona sincronização a funções não reentrantes para adicionar uma garantia (além daquela da Especificação Única do UNIX) de threadsafety.
Strings (e buffers de memória, em geral)
Você também perguntou se há alguma falha fundamental com strings / matrizes. Alguns podem argumentar que este é o caso, mas eu diria que não, não há nenhuma 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 algumas outras linguagens). Isso não é uma falha per se. Qualquer desenvolvedor C e C ++ pode escrever código correto simplesmente passando o comprimento como um parâmetro quando necessário. O problema é que várias APIs que exigiam essas informações não conseguiram especificá-las como um parâmetro. Ou presumiu que alguma constante MAX_BUFFER_SIZE seria usada. Essas APIs foram reprovadas e substituídas por APIs alternativas que permitem que os tamanhos de array / buffer / string sejam especificados.
Scanf (em resposta à sua última pergunta)
Pessoalmente, eu uso a biblioteca C ++ iostreams (std :: cin, std :: cout, os operadores << e >>, std :: getline, std :: istringstream, std :: ostringstream , etc.), então normalmente não lido com isso. Se eu fosse forçado a usar C puro, no entanto, pessoalmente apenas usaria fgetc () ou getchar () em combinação com strtol () , strtoul () , etc. e analisaria as coisas manualmente, já que não sou um grande fã de varargs ou strings de formato. Dito isso, até onde 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árias ou permita que a entrada do usuário seja usada como strings de formato e use as macros de formatação definidas em <inttypes.h> quando apropriado. (Observe, 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 com o uso de strings de formato). Devo também apontar que, em C ++, boost :: format fornece formatação semelhante a printf sem varargs.
fonte
strncpy
geralmente deve ser evitado também. Ele não faz o que a maioria dos programadores presume que ele faz. Ele não garante o encerramento (levando a saturações do buffer) e preenche strings mais curtas (possivelmente degradando o desempenho em alguns casos).strncpy()
nem o piorstrncat()
é um substituto sensato para as variantes n-less.Mais uma vez as pessoas estão repetindo, como um mantra, a afirmação ridícula de que a versão "n" das funções str são versões seguras.
Se fosse para isso que eles se destinavam, eles sempre encerrariam as strings com um valor nulo.
As versões "n" das funções foram escritas para uso com campos de comprimento fixo (como entradas de diretório em sistemas de arquivos anteriores), onde o terminador nul só é necessário se a string não preencher o campo. Esta é também a razão pela qual as funções têm efeitos colaterais estranhos que são ineficientes se usados apenas como substitutos - pegue strncpy () por exemplo:
Como os buffers alocados para lidar com nomes de arquivos são normalmente de 4 kbytes, isso pode levar a uma grande deterioração no desempenho.
Se você quiser versões "supostamente" seguras, obtenha - ou escreva suas próprias - rotinas strl (strlcpy, strlcat etc) que sempre terminam as strings e não têm efeitos colaterais. No entanto, observe que eles não são realmente seguros, pois podem truncar silenciosamente a string - raramente é o melhor curso de ação em qualquer programa do mundo real. Há ocasiões em que isso é OK, mas também há muitas circunstâncias em que pode levar a resultados catastróficos (por exemplo, impressão de receitas médicas).
fonte
strncpy()
, mas erradostrncat()
.strncat()
não foi projetado para uso com campos de comprimento fixo - na verdade, foi projetado parastrcat()
limitar o número de caracteres concatenados. É muito fácil usar isso como um "segurostrcat()
", mantendo o controle do espaço restante no buffer ao fazer várias concatenações, e ainda mais fácil de usá-lo como um "segurostrcpy()
" (definindo o primeiro caractere do buffer de destino'\0'
antes chamando).strncat()
sempre termina a string de destino e não grava'\0'
s extras .strncat()
nem sempre anula o destino e que ele foi projetado para uso com campos de comprimento fixo, ambos errados.strncat()
funcionará corretamente independentemente do comprimento da string de origem, enquantostrcat()
não. O problemastrlcat()
aqui é que não é uma função C padrão.Várias respostas aqui sugerem usar
strncat()
overstrcat()
; Eu sugeriria questrncat()
(estrncpy()
) também deve ser evitado. Tem problemas que dificultam o uso correto e levam a bugs:strncat()
está relacionado (mas não exatamente - veja o terceiro ponto) ao número máximo de caracteres que podem ser copiados para o destino ao invés do tamanho do buffer de destino. Isso tornastrncat()
mais difícil de usar do que deveria ser, principalmente se vários itens forem concatenados ao destino.s1
éstrlen(s1)+n+1
" para uma chamada que se parece comstrncat( s1, s2, n)
strncpy()
também tem um problema que pode resultar em bugs que você tenta usar de forma intuitiva - não garante que o destino seja terminado em nulo. Para garantir isso, você precisa ter certeza de lidar especificamente com esse caso de canto, deixando você mesmo um'\0'
no último local do buffer (pelo menos em certas situações).Eu sugiro usar algo como o OpenBSD
strlcat()
estrlcpy()
(embora eu saiba que algumas pessoas não gostam dessas funções; acredito que são muito mais fáceis de usar com segurança do questrncat()
/strncpy()
).Aqui está um pouco do que Todd Miller e Theo de Raadt têm a dizer sobre os problemas com
strncat()
estrncpy()
:A auditoria de segurança do OpenBSD descobriu que os bugs com essas funções eram "excessivos". Ao contrário
gets()
, essas funções podem ser usadas com segurança, mas na prática há 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 o quanto de seus dados eles podem ter publicado) e, como resultado, baniu (ou pelo menos desencorajou fortemente - o 'ban' pode não ser absoluto) o uso destrncat()
estrncpy()
(entre outras funções).Alguns links com mais informações:
fonte
memmove()
. (Bem, você pode usarmemcpy()
no caso normal quando as cordas são independentes.)strncat()
sempre termina a string de destino.strncat()
. No entanto, é correto parastrncpy()
, que tem alguns outros problemas.strncat()
é uma substituição razoável parastrcat()
, masstrncpy()
não é uma substituição razoável parastrcpy()
.strncat()
nem sempre termina nulo. Eu estava confundindo um pouco os comportamentos destrncat()
estrncpy()
(outra razão pela qual são funções a serem evitadas - eles têm nomes que implicam em comportamentos semelhantes, mas na verdade se comportam de maneiras diferentes de maneiras importantes ...). Eu alterei minha resposta para corrigir isso e também adicionar informações adicionais.char str[N] = ""; strncat(str, "long string", sizeof(str));
é um estouro de buffer se N não for grande o suficiente. Astrncat()
função é muito fácil de ser mal utilizada; não deve ser usado. Se você pode usarstrncat()
com segurança, você poderia ter usadomemmove()
ou emmemcpy()
vez disso (e aqueles seriam mais eficientes).Funções de biblioteca padrão que nunca devem ser usadas:
setjmp.h
setjmp()
. Junto comlongjmp()
, essas funções são amplamente reconhecidas como incrivelmente perigosas de usar: elas levam à programação espaguete, vêm com várias formas de comportamento indefinido, podem causar efeitos colaterais indesejados no ambiente do programa, como afetar os valores armazenados na pilha. Referências: MISRA-C: 2012 regra 21.4, CERT C MSC22-C .longjmp()
. Vejasetjmp()
.stdio.h
gets()
. A função foi removida da linguagem C (conforme C11), pois não era segura conforme o design. A função já foi marcada como obsoleta no C99. Use em seufgets()
lugar. Referências: ISO 9899: 2011 K.3.5.4.1, consulte também a nota 404.stdlib.h
atoi()
família de funções. Eles não têm tratamento de erros, mas invocam um comportamento indefinido sempre que ocorrem erros. Funções completamente supérfluas que podem ser substituídas pelastrtol()
família de funções. Referências: MISRA-C: 2012 regra 21.7.string.h
strncat()
. Possui uma interface estranha que costuma ser mal utilizada. É principalmente uma função supérflua. Veja também as observações parastrncpy()
.strncpy()
. A intenção dessa função nunca foi ser uma versão mais segura dostrcpy()
. Seu único propósito sempre foi lidar com um formato de string antigo em sistemas Unix, e o fato de ter sido incluído na biblioteca padrão é um erro conhecido. Esta função é perigosa porque pode deixar a string sem terminação nula e os programadores costumam usá-la incorretamente. Referências: Por que strlcpy e strlcat são considerados inseguros? .Funções de biblioteca padrão que devem ser usadas com cuidado:
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 exibe erros, mas não necessariamente fecha todo o programa.sinal.h
signal()
. Referências: MISRA-C: 2012 regra 21.5, CERT C SIG32-C .stdarg.h
va_arg()
família de funções. A presença de funções de comprimento variável em um programa C quase sempre é uma indicação de um projeto de programa ruim. Deve ser evitado, a menos que você tenha requisitos muito específicos.stdio.h
Geralmente, toda essa biblioteca não é recomendada para código de produção , pois ela vem com vários casos de comportamento mal definido e segurança de tipo pobre.
fflush()
. Perfeitamente bom para usar em fluxos de saída. Invoca um comportamento indefinido se usado para fluxos de entrada.gets_s()
. Versão seguragets()
incluída na interface de verificação de limites C11. É preferível usar emfgets()
vez disso, de acordo com a recomendação do padrão C. Referências: ISO 9899: 2011 K.3.5.4.1.printf()
família de funções. Funções pesadas de recursos que vêm com muito comportamento indefinido e segurança de tipo pobre.sprintf()
também tem vulnerabilidades. Essas funções devem ser evitadas no código de produção. Referências: MISRA-C: 2012 regra 21.6.scanf()
família de funções. Veja as observações sobreprintf()
. Além disso, -scanf()
é vulnerável a estouros de buffer se não for usado corretamente.fgets()
é preferível usar quando possível. Referências: CERT C INT05-C , MISRA-C: 2012 regra 21.6.tmpfile()
família de funções. Vem com vários problemas de vulnerabilidade. Referências: CERT C FIO21-C .stdlib.h
malloc()
família de funções. Perfeitamente bom para usar em sistemas hospedados, embora esteja ciente dos problemas conhecidos no C90 e, portanto , não lance o resultado . Amalloc()
família de funções nunca deve ser usada em aplicativos independentes. Referências: MISRA-C: 2012 regra 21.3.Observe também que
realloc()
é perigoso se você substituir o ponteiro antigo com o resultado derealloc()
. Caso a função falhe, você cria um vazamento.system()
. Vem com muita sobrecarga e, embora portátil, geralmente é melhor usar funções de API específicas do sistema. Vem com vários comportamentos mal definidos. Referências: CERT C ENV33-C .string.h
strcat()
. Veja as observações parastrcpy()
.strcpy()
. Muito bem de 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 entrada for feita, pode haver saturação de buffer. O que não é culpa delestrcpy()
mesmo, mas do aplicativo de chamada - questrcpy()
não é seguro é principalmente um mito criado pela Microsoft .strtok()
. Altera a string do chamador e usa variáveis de estado internas, o que pode torná-lo inseguro em um ambiente multi-thread.fonte
static_assert()
no lugar deassert()
, se a condição puder ser resolvida em tempo de compilação. Além disso,sprintf()
quase sempre pode ser substituído, osnprintf()
que é um pouco mais seguro.strtok()
em uma função A significa que (a) a função não pode chamar qualquer outra função que também usestrtok()
enquanto A está usando, e (b) significa que nenhuma função que chama A pode estar usandostrtok()
quando ela chama A. Em outras palavras, usandostrtok()
envenena a cadeia de chamadas; ele não pode ser usado com segurança no código da biblioteca porque deve documentar que ele usastrtok()
para impedir que outros usuáriosstrtok()
chamem o código da biblioteca.strncpy
é a função apropriada para usar ao gravar um buffer de string preenchido com zero com dados retirados de uma string terminada com zero ou de um buffer preenchido com zero cujo tamanho é pelo menos tão grande quanto o destino. Buffers preenchidos com zeros não são terrivelmente comuns, mas também não são exatamente obscuros.Algumas pessoas afirmam que
strcpy
estrcat
deve ser evitado, em favor destrncpy
estrncat
. Isso é um tanto subjetivo, na minha opinião.Definitivamente, eles devem ser evitados ao lidar com a entrada do usuário - sem dúvida aqui.
No código "longe" do usuário, quando você só sabe os buffers são suficientemente longo,
strcpy
estrcat
pode ser um pouco mais eficiente, pois o cálculo dan
para passar para seus primos pode ser supérfluo.fonte
strlcat
estrlcpy
, já que a versão 'n' não garante terminação NULL da string de destino.strncpy()
escreverá exatamenten
bytes, usando caracteres nul se necessário. Conforme Dan, usar uma versão segura é a melhor escolha da IMO.strncat()
pode ser difícil de usar corretamente e com segurança (e deve ser evitado) está no assunto.Evitar
strtok
para programas multithread porque não é seguro para thread.gets
pois pode causar estouro de bufferfonte
strtok()
vai um pouco além da segurança de thread - não é seguro nem mesmo em um único programa de thread a menos que você tenha certeza de que nenhuma função que seu código possa chamar enquanto usastrtok()
não o use (ou eles bagunçarão ostrtok()
estado certo abaixo de você). Na verdade, a maioria dos compiladores que visam plataformas multi-threaded cuidam dosstrtok()
problemas potenciais de até onde vão os threads usando armazenamento local de thread parastrtok()
os dados estáticos de. Mas isso ainda não resolve o problema de outras funções usá-lo enquanto você está (no mesmo segmento).strtok()
é que ele mantém precisamente um buffer para trabalhar, então a maioria das boas substituições exige manter um valor de buffer e transmiti-lo.strcspn
faz a maior parte do que você precisa - encontre o próximo separador de token. Você pode reimplementar uma variante sensata destrtok
com ele.Provavelmente, vale a pena adicionar novamente que
strncpy()
não é a substituição de propósito geral para ostrcpy()
que seu nome pode sugerir. Ele é projetado para campos de comprimento fixo que não precisam de um terminador nul (ele foi originalmente projetado para uso com entradas de diretório UNIX, mas pode ser útil para coisas como campos de chave de criptografia).É fácil, no entanto, usar
strncat()
como substituto parastrcpy()
:(O
if
teste pode obviamente ser descartado no caso comum, onde você sabe quedest_size
é definitivamente diferente de zero).fonte
Verifique também a lista de APIs proibidas da Microsoft . Essas são APIs (incluindo muitas já listadas aqui) que são banidas do código da Microsoft porque muitas vezes são mal utilizadas e levam a problemas de segurança.
Você pode não concordar com todos eles, mas vale a pena considerar todos eles. Eles adicionam uma API à lista quando seu uso indevido leva a uma série de bugs de segurança.
fonte
Quase todas as funções que lidam com strings terminadas em NUL são potencialmente inseguras. Se você está recebendo dados do mundo externo e os manipulando por meio das funções str * (), então você se preparou para a catástrofe
fonte
Não se esqueça do sprintf - ele é a causa de muitos problemas. Isso é verdade porque a alternativa, snprintf, às vezes tem implementações diferentes que podem tornar seu código não portável.
linux: http://linux.die.net/man/3/snprintf
windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx
No caso 1 (linux), o valor de retorno é a quantidade de dados necessária para armazenar todo o buffer (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:
buffer overflow safe (muitas funções já são mencionadas aqui)
thread seguro / não reentrante (strtok, por exemplo)
No manual de cada função você deve procurar por palavras-chave como: seguro, sincronização, async, thread, buffer, bugs
fonte
_sprintf()
foi criado pela Microsoft antes dasnprintf()
chegada do padrão , o IIRC. ´StringCbPrintf () ´ é bastante semelhante asnprintf()
embora.sprintf
alguma forma com segurança em alguns casos:sprintf(buffer,"%10s",input);
limita os nb bytes copiados a 10 (sebuffer
forchar buffer[11]
, é seguro, mesmo que os dados possam acabar truncados.É muito difícil de usar
scanf
com segurança. O bom uso descanf
pode evitar estouros de buffer, mas você ainda está vulnerável a um comportamento indefinido ao ler números que não se encaixam no tipo solicitado. Na maioria dos casos,fgets
seguido por auto-análise (usandosscanf
,strchr
, etc.) é a melhor opção.Mas eu não diria "evite
scanf
o tempo todo".scanf
tem seus usos. Como exemplo, digamos que você queira ler a entrada do usuário em umachar
matriz de 10 bytes. Você deseja remover a nova linha final, se houver. Se o usuário inserir 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:Depois que você se acostuma com esse idioma, ele é mais curto e, de certa forma, mais limpo que:
fonte
Em todos os cenários de cópia / movimentação de string - strcat (), strncat (), strcpy (), strncpy (), etc. - as coisas vão muito melhor ( mais seguras ) se algumas heurísticas simples forem aplicadas:
1. Sempre preencher NUL seu (s) buffer (es) antes de adicionar dados.
2. Declare buffers de caracteres como [SIZE + 1], com uma constante de macro.
Por exemplo, dado:
podemos usar códigos como:
com relativa segurança. O memset () deve aparecer antes de strncpy (), mesmo que tenhamos inicializado o Buffer em tempo de compilação, porque não sabemos que lixo o outro código colocou nele antes de nossa função ser chamada. O strncpy () truncará os dados copiados para "1234567890" e não os encerrará em NUL. No entanto, uma vez que já preenchemos o NUL de todo o buffer - sizeof (Buffer), em vez de BUFSIZE - é garantido que haverá um final "fora do escopo" finalizando o NUL de qualquer maneira, contanto que restrinjam nossas gravações usando o BUFSIZE constante, em vez de sizeof (Buffer).
Buffer e BUFSIZE também funcionam bem para snprintf ():
Embora snprintf () grave especificamente apenas caracteres BUFIZE-1, seguidos de NUL, isso funciona com segurança. Portanto, "desperdiçamos" um byte NUL estranho no final do Buffer ... evitamos o estouro do buffer e as condições de string não terminadas, por um custo de memória bem pequeno.
Minha chamada em strcat () e strncat () é mais rígida: não os use. É difícil usar strcat () com segurança, e a API para strncat () é tão contra-intuitiva que o esforço necessário para usá-la corretamente anula qualquer benefício. Proponho a seguinte visita:
É tentador criar um drop-in strcat (), mas não é uma boa ideia:
porque target pode ser um ponteiro (portanto, sizeof () não retorna as informações de que precisamos). Não tenho uma boa solução "universal" para instâncias de strcat () em seu código.
Um problema que encontro frequentemente com programadores "cientes de strFunc ()" é uma tentativa de proteção contra estouros de buffer usando strlen (). Isso é bom se o conteúdo tiver a garantia de terminação NUL. Caso contrário, strlen () em si pode causar um erro de saturação de buffer (geralmente levando a uma violação de segmentação ou outra situação de despejo de núcleo), antes mesmo de você alcançar o código "problemático" que está tentando proteger.
fonte
atoi não é thread-safe. Eu uso strtol em vez disso, por recomendação da página de manual.
fonte
strtol()
que seja thread-safe eatoi()
não seja.man atoi
(no entanto, deveria haver).