Executar o CodeQL em um banco de dados

Concluído

Com o código extraído em um banco de dados, agora você pode analisá-lo usando consultas CodeQL. Especialistas do GitHub, pesquisadores de segurança e colaboradores da comunidade escrevem e mantêm as consultas CodeQL padrão. Você também pode escrever suas próprias consultas.

Você pode usar consultas CodeQL na análise de verificação de código para encontrar problemas no código-fonte e encontrar possíveis vulnerabilidades de segurança. Você também pode escrever consultas personalizadas para identificar problemas para cada idioma que você está usando no código-fonte.

Há dois tipos importantes de consultas:

  • As consultas de alerta realçam problemas em locais específicos do código.
  • As consultas de caminho descrevem o fluxo de informações entre uma origem e um coletor em seu código.

Consulta CodeQL simples

A estrutura básica de consulta CodeQL tem a extensão .ql e contém a cláusula select. Aqui está um exemplo de estrutura de consulta:

/**
 *
 * Query metadata
 *
 */
import /* ... CodeQL libraries or modules ... */
/* ... Optional, define CodeQL classes and predicates ... */
from /* ... variable declarations ... /
where / ... logical formula ... /
select / ... expressions ... */

Consultar metadados

O uso do CodeQL com a verificação de código converte os resultados de uma maneira que realça os possíveis problemas que as consultas foram projetadas para localizar. As consultas contêm propriedades de metadados que indicam como os resultados devem ser interpretados. Utilize metadados das consultas para:

  • Identifique suas consultas personalizadas ao adicioná-las ao repositório GitHub.
  • Forneça informações sobre a finalidade da consulta.

As informações de metadados podem incluir uma descrição da consulta, uma ID exclusiva e o tipo de problema que é (alerta ou caminho). Os metadados também especificam como interpretar e exibir os resultados da consulta.

O GitHub tem um guia de estilo recomendado para metadados de consulta. Você pode encontrá-lo na documentação do CodeQL.

Este exemplo mostra metadados para uma das consultas Java padrão:

Captura de tela mostrando metadados de consulta.

O CodeQL não interpreta consultas que não têm metadados. Ele mostra esses resultados como uma tabela e não os exibe no código-fonte.

Sintaxe de QL

QL é uma linguagem de consulta declarativa orientada a objeto. Ele é otimizado para habilitar a análise eficiente de estruturas de dados hierárquicas e, em particular, bancos de dados que representam artefatos de software.

A sintaxe de QL é semelhante ao SQL, mas a semântica de QL é baseada no Datalog. O datalog é uma linguagem de programação lógica declarativa, que geralmente é usada como uma linguagem de consulta. Como a QL é principalmente uma linguagem lógica, todas as operações em QL são operações lógicas. A QL também herda predicados recursivos do Datalog. A QL adiciona suporte a agregações para tornar as consultas ainda complexas concisas e simples.

A linguagem QL consiste em fórmulas lógicas. Ele usa conectividades lógicas comuns, como and, ore not, juntamente com quantificadores como forall e exists. Como a QL herda predicados recursivos, você também pode escrever consultas recursivas complexas usando sintaxe de QL básica e agregações como count, sume average.

Para obter mais informações sobre o idioma QL, consulte a documentação do CodeQL.

Consultas de caminho

A maneira como as informações fluem por meio de um programa é importante. Dados que parecem benignos podem fluir de maneiras inesperadas que permitem que sejam usados de forma mal-intencionada.

A criação de consultas de caminho pode ajudá-lo a visualizar o fluxo de informações por meio de uma base de código. Uma consulta pode acompanhar o caminho que os dados levam de seus possíveis pontos de partida (source) para seus possíveis pontos de extremidade (sink). Para modelar caminhos, sua consulta deve fornecer informações sobre a origem, o coletor e as etapas de fluxo de dados que os vinculam.

A maneira mais fácil de começar a escrever sua própria consulta de caminho é usar uma das consultas existentes como modelo. Para obter essas consultas para idiomas com suporte, consulte a documentação do CodeQL.

Sua consulta de caminho requer determinados metadados, predicados de consulta e estruturas de instrução select. Muitas das consultas de caminho internas no CodeQL seguem uma estrutura básica. A estrutura depende de como o CodeQL modela o idioma que você está analisando.

Aqui está um modelo de exemplo para uma consulta de caminho:

/**
 * ...
 * @kind path-problem
 * ...
 */

import <language>
// For some languages (Java/C++/Python/Swift), you need to explicitly import the data-flow library, such as
// import semmle.code.java.dataflow.DataFlow or import codeql.swift.dataflow.DataFlow
...

module Flow = DataFlow::Global<MyConfiguration>;
import Flow::PathGraph

from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "<message>"

Nesse modelo:

  • MyConfiguration é um módulo que contém os predicados que definem como os dados fluem entre source e sink.
  • Flow é o resultado da computação de fluxo de dados com base em MyConfiguration.
  • Flow::Pathgraph é o módulo de grafo de fluxo de dados resultante que você precisa importar para incluir explicações de caminho na consulta.
  • source e sink são nós no grafo, conforme definido na configuração, e Flow::PathNode é o tipo deles.
  • DataFlow::Global<..> é uma invocação do fluxo de dados. Em vez disso, você pode usar TaintTracking::Global<..> para incluir um conjunto padrão de etapas de contaminação.

Como escrever uma consulta de caminho

Sua consulta precisa calcular um grafo de caminho para gerar explicações de caminho. Para fazer isso, defina um predicado de consulta chamado edges. Um predicado de consulta é um predicado não membro com uma anotação query. A anotação de consulta retorna todas as tuplas avaliadas pelo predicado.

O edges predicado define as relações de borda do grafo que você está computando. Ele é usado para calcular os caminhos relacionados a cada resultado gerado pela consulta. Você também pode importar um predicado predefinido edges de um módulo de grafo de trajetórias em uma das bibliotecas de fluxo de dados padrão.

As bibliotecas de fluxo de dados contêm as outras classes, predicados e módulos que geralmente são usados na análise de fluxo de dados, além do módulo de grafo de caminho. As bibliotecas de fluxo de dados codeQL funcionam modelando o grafo de fluxo de dados ou implementando a análise de fluxo de dados. Bibliotecas normais de fluxo de dados são usadas para analisar o fluxo de informações no qual os valores de dados são preservados em cada etapa.

Aqui está uma instrução de exemplo que importa o pathgraph módulo da biblioteca de fluxo de dados (DataFlow.qll), na qual edges é definido:

import DataFlow::PathGraph

Você pode importar muitas outras bibliotecas incluídas com o CodeQL. Você também pode importar bibliotecas projetadas especificamente para implementar a análise de fluxo de dados em várias estruturas e ambientes comuns.

A classe PathNode foi projetada para implementar a análise de fluxo de dados. É um Node aumentado com um contexto de chamada (exceto para coletores), um caminho de acesso e uma configuração. Somente os valores PathNode que são alcançáveis a partir de uma origem são gerados.

Aqui está um exemplo do caminho de importação:

import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl

Opcionalmente, você pode definir um nodes predicado de consulta, que especifica os nós do grafo de caminho para todos os idiomas. Quando você define nodes, os nós selecionados definem apenas bordas com pontos de extremidade. Quando você não define nodes, precisa selecionar todos os pontos de extremidade possíveis de edges.

Análise de banco de dados

Ao usar consultas para analisar um banco de dados CodeQL, você recebe resultados significativos no contexto do código-fonte. Os resultados são apresentados como alertas ou caminhos no formato SARIF ou em outro formato interpretado.

Aqui está um exemplo de um comando de banco de dados CodeQL que analisa o banco de dados executando consultas selecionadas nele e interpretando os resultados:

codeql database analyze --format=<format> ---output=<output> [--threads=<num>] [--ram=<MB>] <options>... -- <database> <query|dir|suite>...

Esse comando combina o efeito dos comandos codeql database run-queries e codeql database interpret-results de encanamento.

Como alternativa, você pode executar consultas que não atendem aos requisitos para serem interpretadas como alertas de código-fonte. Para fazer isso, use codeql-database run-queries ou codeql query run. Em seguida, use codeql bqrs decode para converter os resultados brutos em uma notação legível.

Você pode obter uma lista completa dos comandos disponíveis da CLI do CodeQL no manual da CLI do CodeQL.

Usar um arquivo SARIF com categorias

O CodeQL dá suporte a SARIF para compartilhar resultados de análise estática. O SARIF foi projetado para representar a saída de uma ampla gama de ferramentas de análise estática.

Você precisa especificar uma categoria ao usar a saída SARIF para análise de CodeQL. As categorias podem distinguir várias análises executadas no mesmo repositório de confirmação e em diferentes idiomas ou partes diferentes do código. No entanto, arquivos SARIF com a mesma categoria substituem uns aos outros.

Você pode verificar cada arquivo de saída SARIF usando CodeQL para analisar idiomas diferentes na mesma base de código quando o valor da categoria é consistente entre as execuções de análise. Recomendamos que você use o idioma que está sendo verificado como um identificador para a categoria.

Aqui está um exemplo. O valor da categoria é exibido (com uma barra à direita acrescentada se ainda não estiver presente) como a <run>.automationId propriedade em SARIF v1, a <run>.automationLogicalId propriedade em SARIF v2 e a <run>.automationDetails.id propriedade em SARIF v2.1.0.

Postar os resultados do SARIF no GitHub

Depois que o banco de dados estiver pronto, você poderá consultá-lo interativamente. Ou você pode executar um conjunto de consultas para gerar um conjunto de resultados no formato SARIF e carregar os resultados em um repositório de destino no GitHub.com:

codeql github upload-results --sarif=<file> [--github-auth-stdin] [--github-url=<url>] [--repository=<repository-name>] [--ref=<ref>] [--commit=<commit>] [--checkout-path=<path>] <options>...

Para carregar resultados no GitHub, verifique se cada servidor de CI (integração contínua) tem um aplicativo GitHub ou um token de acesso pessoal para a CLI do CodeQL usar. Você deve usar um token de acesso ou um aplicativo do GitHub com a security_events permissão de gravação.

Potencialmente, você poderia permitir que a CLI do CodeQL usasse o mesmo token se os servidores de CI já usassem um token com esse escopo para fazer check-out de repositórios do GitHub. Caso contrário, crie um novo token com a security_events permissão de gravação e adicione esse token ao repositório secreto do sistema de CI. Uma prática recomendada para a segurança é definir o --github-auth-stdin sinalizador e passar o token para o comando por meio da entrada padrão.

Carregar resultados do SARIF

Para que a verificação de código exiba resultados de uma ferramenta de análise estática não Microsoft em seu repositório GitHub, seus resultados devem ser armazenados em um arquivo SARIF que dê suporte a um subconjunto específico do esquema JSON SARIF 2.1.0. Você pode carregar os resultados usando a API de verificação de código ou a CLI do CodeQL.

Sempre que você carrega os resultados de uma nova verificação de código, o CodeQL processa os resultados e adiciona alertas ao repositório. Para evitar alertas duplicados para o mesmo problema, a verificação de código usa a propriedade SARIF partialFingerprints para fazer a correspondência de resultados em várias execuções para que eles apareçam apenas uma vez na última execução do branch selecionado. A eliminação de duplicatas possibilita a correspondência de alertas com a linha de código correta quando os arquivos são editados.

A ID da regra para um resultado deve ser a mesmo em todas as análises. Os dados de impressão digital são incluídos automaticamente em arquivos SARIF criados por meio do fluxo de trabalho de análise do CodeQL ou do executor do CodeQL.

As especificações SARIF usam o nome da propriedade JSON partialFingerprints, um dicionário de tipos de impressões digitais nomeados para a impressão digital. Essa propriedade contém, no mínimo, um valor para primaryLocationLineHash, que fornece uma impressão digital com base no contexto do local primário.

O GitHub tentará preencher o partialFingerprints campo dos arquivos de origem se você carregar um arquivo SARIF usando a ação upload-sarif e esses dados estiverem ausentes. Além disso, se você enviar um arquivo SARIF sem dados de impressão digital usando o ponto de extremidade da API /code-scanning/sarifs, os usuários podem ver alertas duplicados quando os alertas de verificação de código são processados e exibidos.

Para evitar ver alertas duplicados enquanto você trabalha com ferramentas de análise estática, calcule os dados de impressão digital e preencha a partialFingerprints propriedade antes de carregar o arquivo SARIF. Um ponto de partida útil é usar o mesmo script da ação upload-sarif .