• Nenhum resultado encontrado

2.4. Análise Estática de Código

2.4.2. Análise Semântica

2.4.2.1. Verificação de Tipos

A verificação de tipos de dados (type cheking) está associada à verificação dos limites máximo e mínimo que uma variável pode tomar, dependendo do seu tipo.

Algumas linguagens de programação, como o Java, têm implementada a verificação de tipos, garantindo que os valores das variáveis não ultrapassam os limites das mesmas. No entanto, outras, como o C e o C++, não fazem essa verificação.

Como consequência da não verificação de tipos temos a ocorrência de vulnerabilidades de inteiros, nomeadamente, overflow de inteiro, underflow de inteiro,

signedness e truncamento, que podem estar associadas aos tamanhos dos tampões de

memória (buffers). Estas, se exploradas, originam, por exemplo, buffer overflow e negação de serviço (DoS) [17].

Uma das formas de se realizar verificação de tipos passa por colocar linhas de código, nos lugares correctos, que verifiquem os limites possíveis para as variáveis e por recorrer a qualificadores de tipo (type qualifiers) para anotar o código fonte.

Ferramentas de análise estática de código que realizam este tipo de análise têm de rastrear as variáveis inteiras, tendo continuamente presente os limites máximo e mínimo para estas mesmas variáveis e assegurar que na atribuição de um valor a uma variável o mesmo primeiramente é verificado para os limites da variável.

A CQual [50] é uma destas ferramentas que recorre a qualificadores de tipos para

rastrear as variáveis do tipo de dado do qualificador [47]. O seu modo de funcionamento passa por anotar o código fonte com os qualificadores de tipo estipulados. Estas anotações nada transmitem a um compilador, mas a CQual identifica as variáveis dos tipos a rastrear. De salientar, no entanto, que estes qualificadores de tipos somente tratam do tipo de dados por eles definidos, não garantindo a detecção ou ocorrência de vulnerabilidades em outros tipos de dados.

Em CQual [19], os qualificadores de tipos, definidos pelo programador, acabam

por ser uma extensão da linguagem de programação, criando, assim, inferências de qualificadores de tipos de dados e seus relacionamentos. Assim, uma função, que espera um dado tipo de dados num dos seus parâmetros, poderá também receber qualquer um dos sub-tipos de dados do tipo de dados do qualificador.

Uma outra ferramenta de verificação de tipos é a BOON (Buffer Overrun

Detection) [16][36][41][45][46], a qual detecta somente buffers overflow. Como grande

cada uma das strings com duas propriedades. A primeira é a quantidade de bytes alocada para determinada variável, a segunda é a quantidade de bytes actualmente em utilização. Todas as funções que manipulam estas strings são então modeladas pelos efeitos causados nas duas propriedades analisadas. Estes efeitos são utilizados na realidade para gerar o que é apelidado de restrições. Por fim, estas restrições são resolvidas, as quais são então confrontadas com os valores inicialmente declarados para as strings. Caso possa ocorrer um buffer overflow, as devidas mensagens de aviso são geradas.

Para além destas duas ferramentas, também o Lint e o Splint (Secure

Programming Lint) efectuam verificação de tipos.

A ferramenta Lint [22] está na origem de muitas outras ferramentas de análise

estática de código, incluindo as comerciais. Esta ferramenta de sistemas Unix originalmente foi desenvolvida para ajudar a garantir a consistência das chamadas de funções, pela verificação dos limites possíveis para as variáveis passadas nos parâmetros das funções. Esta verificação de limites é o que apelidamos de verificação de tipos. Nas chamadas das funçõoes são verificados se os tipos de dados das variáveis que são passadas são iguais aos tipos de dados requeridos pelos argumentos das funções. Também é verificado, na adaptação de código de ILP32 para LP64, a existência de truncamento na atribuição de uma variável inteira (int, long ou pointer) de tipo de dados de maior largura a outra de menor largura.

A outra ferramenta mencionada, o Splint [16][23][35][36][41], é uma versão melhorada da ferramenta LCLint, cujas funcionalidades foram herdadas de ferramenta

Lint. O Splint, comparativamente com o Lint, realiza as mesmas verificações de tipos,

com excepção da atribuição de ponteiros a variáveis inteiras de menor largura do que os ponteiros. Mas, a funcionalidade de signedness é mais efeciente do que no Lint. A metodologia do Splint é recorrer a anotações inseridas pelo programador, tanto no programa como nas funções das bibliotecas padrões. Esta metodologia permite ao programador especificar pré-condições e/ou pós-condições para as funções. Para que as vulnerabilidades possam ser detectadas, as restrições geradas a partir das anotações no código fonte do programa devem ser resolvidas. Caso algum problema seja identificado na resolução desta restrições, será emitida uma mensagem de aviso ao utilizador. Por exemplo, se para a função strcpy for especificada uma pré-condição de que o comprimento da string destino (buffer) tem de ser maior do que o tamanho da string origem, caso esta restrição não seja resolvida será emitida uma mensagem de aviso. Às

especificações criadas e resolução de restrição é o que denominamos por verificação de tipos, onde está patente a verificação dos limites possíveis para as variáveis. No entanto, é bom referir que por defeito (sem anotação ao código) esta ferramenta monitoriza a criação e o acesso a buffers, detectando, assim, a violação de limites. Querendo com isso dizer que o programador recorre a anotações ao código somente quando o Splint não faz verificação de tipos em certas situações.

Documentos relacionados