Lista de verificação de segurança do driver
Neste artigo, você encontra uma lista de verificação de segurança de driver para desenvolvedores que desejam reduzir o risco de comprometimento dos drivers.
Visão geral da segurança do driver
Falha de segurança pode ser qualquer falha que possibilite a um invasor causar mau funcionamento de um driver de tal forma que faça com que o sistema trave ou se torne inutilizável. Além disso, vulnerabilidades no código do driver podem permitir que um invasor obtenha acesso ao kernel, possibilitando o comprometimento de todo o sistema operacional. O objetivo da maioria dos desenvolvedores que trabalham com drivers é fazer com que o driver funcione corretamente, e não se haverá um invasor mal-intencionado tentando explorar vulnerabilidades em seu código.
Entretanto, depois que um driver é lançado, os invasores podem tentar investigar e identificar falhas de segurança. Os desenvolvedores devem pensar nesses problemas durante a fase de design e implementação para minimizar a probabilidade dessas vulnerabilidades. O objetivo é eliminar todas as falhas de segurança conhecidas antes que o driver seja lançado.
A criação de drivers mais seguros exige cooperação do arquiteto do sistema (que pensa cuidadosamente nas possíveis ameaças ao driver), do desenvolvedor que implementa o código (que codifica defensivamente operações comuns que podem ser a causa das explorações) e da equipe de teste (que tenta proativamente encontrar pontos fracos e vulnerabilidades). Com a coordenação adequada de todas essas atividades, a segurança do driver aumenta drasticamente.
Além de evitar os problemas associados a um driver que está sendo alvo de ataque, muitas das etapas descritas, como o uso mais preciso da memória do kernel, aumentarão a confiabilidade do driver. Isso reduz os custos de suporte e aumenta a satisfação do cliente com seu produto. Realizar as tarefas da lista de verificação abaixo pode ajudar você a atingir todos esses objetivos.
Lista de verificação de segurança: Realize a tarefa de segurança descrita em cada um desses tópicos.
Confirme se há necessidade de um driver de kernel
Controle o acesso a drivers que são somente de software
Não assine o código do driver de teste em produção
Siga as diretrizes de codificação segura do driver
Implemente código compatível com HVCI
Siga as práticas recomendadas de código específicas da tecnologia
Faça revisão de código por pares
Gerencie o controle de acesso do driver
Aumente a segurança da instalação do dispositivo
Execute a assinatura adequada do driver da versão
Use o CodeQL para verificar o código do driver
Adicione anotações SAL ao código do driver
Use o Driver Verifier para verificar vulnerabilidades
Verifique o código com o BinSkim Binary Analyzer
Verifique o código com os testes do programa de compatibilidade de hardware
Analise técnicas e extensões do depurador
Examine os recursos de codificação segura
Leia o resumo das principais conclusões
Confirme se há necessidade de um driver de kernel
Item 1 da lista de verificação de segurança: Confirme se há necessidade de um driver de kernel e se uma abordagem de menor risco, como o serviço ou aplicativo do Windows, não é uma opção melhor.
Os drivers residem no kernel do Windows. Problemas ao executar no kernel podem expor todo o sistema operacional. Se houver qualquer outra opção disponível, provavelmente será de menor custo e terá menos riscos associados do que criar um novo driver de kernel. Para obter mais informações sobre como usar os drivers internos do Windows, consulte Precisa escrever um driver?.
Para obter informações sobre como usar tarefas em segundo plano, consulte Como dar suporte ao aplicativo com tarefas em segundo plano.
Para obter informações sobre como usar os serviços do Windows, consulte Serviços.
Use as estruturas do driver
Item 2 da lista de verificação de segurança: Use as estruturas do driver para reduzir o tamanho do código e aumentar a confiabilidade e a segurança dele.
Use o Windows Driver Frameworks para reduzir o tamanho do código e aumentar a confiabilidade e a segurança dele. Para começar, leia Como usar o WDF para desenvolver um driver. Para obter informações sobre como usar o UMDF (Driver de estrutura de modo de usuário) de baixo risco, consulte Como escolhendo um modelo de driver.
Escrever um driver de WDM (Windows Driver Model) antigo leva mais tempo, é caro e quase sempre envolve recriar o código disponível nas estruturas do driver.
O Windows Driver Framework tem código-fonte aberto e está disponível no GitHub. É o mesmo código-fonte usado para criar a biblioteca de runtime do WDF fornecida no Windows 10. Você poderá depurar seu driver com mais eficiência quando seguir as interações entre o driver e o WDF. Para baixá-lo, acesse https://github.com/Microsoft/Windows-Driver-Frameworks.
Controle o acesso a drivers que são somente de software
Item 3 da lista de verificação de segurança: Se um driver somente de software for criado, o controle de acesso adicional deverá ser implementado.
Os drivers de kernel somente de software não usam PnP (plug-and-play) para serem associados a IDs de hardware específicos. Eles podem ser executados em qualquer PC. Esse driver pode ser usado para outros fins, criando um vetor de ataque.
Como os drivers de kernel somente de software contêm risco adicional, eles devem ser limitados para serem executados em hardware específico (por exemplo, usando uma ID PnP exclusiva para habilitar a criação de um driver PnP ou procurando na tabela SMBIOS a presença de um hardware específico).
Por exemplo, imagine que o OEM Fabrikam queira distribuir um driver que habilite um utilitário de overclock para seus sistemas. Se esse driver somente de software for executado em um sistema de um OEM diferente, o sistema poderá sofrer danos ou instabilidade. Os sistemas da Fabrikam devem incluir uma ID PnP exclusiva para permitir a criação de um driver PnP que também possa ser atualizado por meio do Windows Update. Se isso não for possível e a Fabrikam criar um driver herdado, esse driver deverá encontrar outro método para verificar se está sendo executado em um sistema da Fabrikam (por exemplo, examinando a tabela SMBIOS antes de habilitar qualquer recurso).
Não assine o código do driver de teste em produção
Item 4 da lista de verificação de segurança: Não assine o código de driver de kernel em produção para desenvolvimento, teste e fabricação.
O código de driver de kernel usado para desenvolvimento, teste ou fabricação pode incluir recursos perigosos que representam um risco à segurança. Esse código perigoso nunca deve ser assinado com um certificado que seja confiável para o Windows. O mecanismo correto para executar o código de driver perigoso é desabilitar a Inicialização segura UEFI, habilitar o BCD "TESTSIGNING" e assinar o código de desenvolvimento, teste e fabricação usando um certificado não confiável (por exemplo, um que seja gerado pelo makecert.exe).
O código assinado por um SPC (Certificado de Editores de Software) ou WHQL (Windows Hardware Quality Labs) confiável não deve facilitar o desvio das tecnologias de segurança e integridade de código do Windows. Antes que o código seja assinado por um SPC ou WHQL confiável, primeiro verifique se ele está em conformidade com as diretrizes de Como criar drivers confiáveis no modo kernel. Além disso, o código não deve conter comportamentos perigosos, como descrito abaixo. Para obter mais informações sobre a assinatura do driver, consulte Assinatura de driver da versão mais adiante neste artigo.
Exemplos de comportamentos perigosos incluem o seguinte:
- Possibilitar o mapeamento de um kernel arbitrário, memória física ou de dispositivo para o modo de usuário.
- Possibilitar a leitura ou gravação de um kernel arbitrário, memória física ou dispositivo, incluindo entrada/saída (E/S) de porta.
- Conceder acesso ao armazenamento a ponto de ignorar o controle de acesso do Windows.
- Possibilitar a modificação de um hardware ou firmware que o driver não foi desenvolvido para gerenciar.
Faça uma análise de ameaças
Item 5 da lista de verificação de segurança: Modifique um modelo de ameaça de driver existente ou crie um modelo de ameaça personalizado para o driver.
Ao pensar em segurança, uma metodologia comum é criar modelos de ameaças específicos que tentam descrever os tipos de ataques possíveis. Essa técnica é útil ao projetar um driver, porque força o desenvolvedor a considerar com antecedência os possíveis vetores de ataque contra um driver. Depois de identificar possíveis ameaças, um desenvolvedor de driver pode considerar meios de se defender contra essas ameaças para reforçar a segurança geral do componente do driver.
Este artigo contém diretrizes específicas do driver para criar um modelo de ameaça leve: Modelagem de ameaças para drivers. O artigo mostra como exemplo um diagrama de modelo de ameaça de driver que pode ser usado como ponto de partida para o driver.
As práticas recomendadas de SDL (Ciclo de vida de desenvolvimento de segurança) e as ferramentas associadas podem ser usadas por IHVs e OEMs para melhorar a segurança de seus produtos. Para obter mais informações, consulte as Recomendações de SDL para OEMs.
Siga as diretrizes de codificação segura do driver
Item 6 da lista de verificação de segurança: Revise seu código e remova todas as vulnerabilidades de código conhecidas.
A atividade principal da criação de drivers seguros é identificar no código áreas que precisam ser alteradas para evitar vulnerabilidades de software conhecidas. Muitas dessas vulnerabilidades de software conhecidas lidam com o controle estrito do uso de memória para evitar problemas com casos de substituição ou abrangência de locais de memória que seu driver utiliza.
Ferramentas de varredura de código, como CodeQL e testes específicos de driver, podem ser usadas para ajudar a localizar algumas dessas vulnerabilidades (mas não todas). Essas ferramentas e testes são descritos posteriormente neste tópico.
Buffers de memória
Sempre verifique os tamanhos dos buffers de entrada e saída para saber se eles conseguem conter todos os dados solicitados. Para obter mais informações, consulte Falha ao verificar o tamanho dos buffers.
Inicialize corretamente todos os buffers de saída usando zeros antes de retorná-los ao chamador. Para obter mais informações, consulte Falha ao inicializar buffers de saída.
Valide buffers de comprimento variável. Para obter mais informações, consulte Falha ao validar buffers de comprimento variável. Para obter mais informações sobre como trabalhar com buffers e usar ProbeForRead e ProbeForWrite para validar o endereço de um buffer, consulte Processamento de buffer.
Use o método apropriado para acessar buffers de dados com IOCTLs
Uma das principais responsabilidades de um driver do Windows é transferir dados entre aplicativos no modo de usuário e os dispositivos de um sistema. Os três métodos para acessar buffers de dados são mostrados na tabela a seguir.
Tipo de buffer IOCTL | Resumo | Para obter mais informações |
---|---|---|
METHOD_BUFFERED | Recomendado para a maioria das situações | Usar E/S em buffer |
METHOD_IN_DIRECT ou METHOD_OUT_DIRECT | Usado em algumas E/S de HW de alta velocidade | Usar E/S direta |
METHOD_NEITHER | Se possível, evite | Usar E/S direta ou em buffer |
Em geral, a E/S em buffer é recomendada, pois fornece os métodos de buffer mais seguros. Mas mesmo ao usar E/S em buffer, existem riscos, como ponteiros inseridos, que devem ser atenuados.
Para obter mais informações sobre como trabalhar com buffers em IOCTLs, consulte Métodos para acessar buffers de dados.
Erros ao usar E/S em buffer IOCTL
Verifique o tamanho dos buffers relacionados a IOCTL. Para obter mais informações, consulte Falha ao verificar o tamanho dos buffers.
Inicialize corretamente os buffers de saída. Para obter mais informações, consulte Falha ao inicializar buffers de saída.
Valide corretamente os buffers de comprimento variável. Para obter mais informações, consulte Falha ao validar buffers de comprimento variável.
Ao usar E/S em buffer, retorne o comprimento adequado para OutputBuffer no campo Informações da estrutura de IO_STATUS_BLOCK. Não retorne diretamente o comprimento de uma solicitação READ. Por exemplo, pense em uma situação em que os dados retornados do espaço do usuário indicam que há um buffer de 4K. Se o driver realmente precisa retornar apenas 200 bytes, mas retorna 4K no campo Informações, então ocorreu uma vulnerabilidade de divulgação de informações. Esse problema ocorre porque, em versões anteriores do Windows, o buffer que o gerenciador de E/S usa para E/S em buffer não é zerado. Assim, o aplicativo do usuário recupera os 200 bytes originais de dados mais 4K-200 bytes de tudo o que estava no buffer (conteúdo do pool não paginado). Esse cenário pode ocorrer com todos os usos de E/S em buffer, não apenas com IOCTLs.
Erros na E/S direta de IOCTL
Gerencie corretamente buffers de comprimento zero. Para obter mais informações, consulte Erros de E/S direta.
Erros ao referenciar endereços de espaço do usuário
Valide ponteiros inseridos em solicitações de E/S em buffer. Para obter mais informações, consulte Erros ao referenciar endereços de espaço do usuário.
Valide qualquer endereço no espaço do usuário antes de tentar usá-lo. Para isso, use APIs como ProbeForRead e ProbeForWrite quando apropriado.
Leituras e gravações de registro específicas do modelo MSR
Os intrínsecos do compilador, como __readmsr e __writemsr, podem ser usados para acessar os registros específicos do modelo. Se esse acesso for necessário, o driver sempre deverá verificar se o registro de leitura ou gravação está restrito ao índice ou intervalo esperado.
Para obter mais informações e exemplos de código, consulte Possibilitar leitura e gravação de MSRs em Práticas recomendadas de segurança de desenvolvimento para desenvolvedores de driver do Windows.
Vulnerabilidades TOCTOU
Existe uma possível vulnerabilidade de tempo de verificação para tempo de uso (TOCTOU) ao usar E/S direta (para IOCTLs ou para leitura/gravação). Lembre-se de que o driver está acessando o buffer de dados do usuário, então o usuário pode acessá-lo simultaneamente.
Para gerenciar esse risco, copie todos os parâmetros que precisam ser validados do buffer de dados do usuário para a memória que só pode ser acessada no modo kernel (como a pilha ou o pool). Em seguida, quando os dados não puderem ser acessados pelo aplicativo do usuário, valide e opere nos dados que foram passados.
O código do driver deve fazer uso correto da memória
Todas as alocações de pool de drivers devem estar no pool NX (não executável). O uso de pools de memória NX é inerentemente mais seguro do que o uso de pools executáveis não paginados (NP), além de oferecer melhor proteção contra ataques de estouro.
Os drivers de dispositivo devem gerenciar adequadamente várias solicitações de E/S de modo de usuário, bem como de kernel para kernel.
Para permitir que os drivers permitam a virtualização HVCI, há requisitos adicionais de memória. Para obter mais informações, consulte Implementar código compatível com HVCI mais adiante neste artigo.
Alças
- Valide identificadores passados entre a memória do modo de usuário e do modo kernel. Para obter mais informações, consulte Gerenciamento de identificadores e Falha ao validar identificadores de objetos.
Objetos de dispositivo
Proteja objetos de dispositivo. Para obter mais informações, consulte Como proteger objetos de dispositivo.
Valide objetos de dispositivo. Para obter mais informações, consulte Falha ao validar objetos de dispositivo.
IRPs
WDF e IRPs
Uma vantagem de usar o WDF é que os drivers WDF normalmente não acessam IRPs diretamente. Por exemplo, a estrutura converte os IRPs do WDM que representam operações de leitura, gravação e controle de E/S do dispositivo em objetos de solicitação de estrutura que o KMDF/UMDF recebe em filas de E/S.
Se você estiver escrevendo um driver do WDM, leia as diretrizes a seguir.
Gerencie adequadamente os buffers de E/S de IRP
Os artigos a seguir contêm informações sobre como validar valores de entrada de IRP:
DispatchReadWrite usando E/S em buffer
DispatchReadWrite usando E/S direta
Problemas de segurança para códigos de controle de E/S
Valide os valores associados a um IRP, como endereços e comprimentos de buffer.
Se você optar por usar Nenhuma E/S, lembre-se de que, ao contrário de Leitura e Gravação, e ao contrário de E/S em buffer e E/S direta, ao usar Nenhuma E/S IOCTL, os ponteiros e comprimentos de buffer não são validados pelo Gerenciador de E/S.
Gerencie operações de conclusão de IRP corretamente
Um driver nunca deve concluir um IRP com um valor de status de STATUS_SUCCESS, a menos que ele realmente aceite e processe o IRP. Para obter informações sobre as formas corretas de gerenciar operações de conclusão de IRP, consulte Como concluir IRPs.
Gerencie o estado pendente de IRP do driver
O driver deve marcar o IRP pendente antes de salvar o IRP, considerando a inclusão da chamada para IoMarkIrpPending e a atribuição em uma sequência interligada. Para obter mais informações, consulte Falha ao verificar o estado de um driver e Como reter IRPs de entrada quando um dispositivo está pausado.
Gerencie operações de cancelamento de IRP corretamente
Pode ser difícil codificar corretamente as operações de cancelamento porque elas normalmente são executadas de forma assíncrona. Os problemas no código que gerencia operações de cancelamento podem passar despercebidos por muito tempo, pois esse código normalmente não é executado com frequência em um sistema em execução. Você deve ler e entender todas as informações apresentadas em Como cancelar IRPs. Preste atenção especial a Sincronização do cancelamento de IRP e Pontos a serem considerados ao cancelar IRPs.
Uma forma recomendada de minimizar os problemas de sincronização associados às operações de cancelamento é implementar uma fila IRP segura contra cancelamento.
Gerencie a limpeza do IRP e feche as operações corretamente
Entenda a diferença entre solicitações IRP_MJ_CLEANUP e IRP_MJ_CLOSE. As solicitações de limpeza chegam depois que um aplicativo fecha todos os identificadores em um objeto de arquivo, mas, às vezes, antes que todas as solicitações de E/S sejam concluídas. As solicitações de fechamento chegam depois que todas as solicitações de E/S para o objeto de arquivo são concluídas ou canceladas. Para obter mais informações, consulte os seguintes artigos:
Rotinas DispatchCreate, DispatchClose e DispatchCreateClose
Erros no tratamento de operações de limpeza e fechamento
Para obter mais informações sobre como gerenciar IRPs corretamente, consulte Erros adicionais ao gerenciar IRPs.
Outros problemas de segurança
Use um bloqueio ou uma sequência interligada para evitar condições de corrida. Para obter mais informações, consulte Erros em um ambiente multiprocessador.
Verifique se os drivers de dispositivo gerenciam adequadamente várias solicitações de E/S de modo de usuário, bem como de kernel para kernel.
Verifique se não há filtros TDI ou LSPs instalados pelo driver ou pacotes de software associados durante a instalação ou o uso.
Use funções seguras
Use funções seguras de cadeia de caracteres. Para obter mais informações, consulte Como usar funções seguras de cadeia de caracteres.
Use funções aritméticas seguras. Para obter mais informações, consulte Rotinas de bibliotecas seguras de números inteiros
Use funções de conversão seguras.
Vulnerabilidades adicionais de código
Além das possíveis vulnerabilidades abordadas aqui, este artigo contém informações adicionais sobre como aprimorar a segurança do código do driver do modo kernel: Como criar drivers confiáveis no modo kernel.
Para obter informações adicionais sobre a codificação segura de C e C++, consulte Recursos de codificação segura no final deste artigo.
Gerencie o controle de acesso do driver
Item 7 da lista de verificação de segurança: Revise o driver para saber se você está controlando corretamente o acesso.
Como gerenciar o controle de acesso do driver – WDF
A função dos drivers é impedir que os usuários acessem indevidamente os dispositivos e arquivos de um computador. Para impedir o acesso não autorizado a dispositivos e arquivos, você deve:
Nomear objetos de dispositivo somente quando necessário. Os objetos de dispositivo nomeados geralmente são necessários apenas por motivos herdados. Por exemplo, se você tiver um aplicativo que espera abrir o dispositivo usando um nome específico ou se estiver usando um dispositivo não PNP/dispositivo de controle. Os drivers WDF não precisam nomear seu dispositivo PnP FDO para criar um link simbólico usando WdfDeviceCreateSymbolicLink.
Proteger o acesso a objetos e interfaces do dispositivo.
Para permitir que aplicativos ou outros drivers WDF acessem o PDO do dispositivo PnP, você deve usar interfaces de dispositivo. Para obter mais informações, consulte Usar interfaces de dispositivo. Uma interface de dispositivo serve como um link simbólico para o PDO da pilha de dispositivos.
Uma das melhores maneiras de controlar o acesso ao PDO é especificando uma string SDDL em seu INF. Se a cadeia de caracteres SDDL não estiver no arquivo INF, o Windows aplicará um descritor de segurança padrão. Para obter mais informações, consulte Proteger objetos de dispositivo e SDDL para objetos de dispositivo.
Para obter mais informações sobre como controlar o acesso, consulte os seguintes artigos:
Controlar o acesso ao dispositivo em drivers KMDF
Nomes, descritores de segurança e classes de dispositivo - Como tornar os objetos de dispositivo acessíveis... e SAFE do The NT Insider Newsletter de janeiro de fevereiro de 2017, publicado pela OSR.
Como gerenciar o controle de acesso do driver – WDM
Se você estiver trabalhando com um driver WDM e tiver usado um objeto de dispositivo nomeado, poderá usar IoCreateDeviceSecure e especificar um SDDL para protegê-lo. Ao implementar IoCreateDeviceSecure, sempre especifique um GUID de classe personalizado para DeviceClassGuid. Você não deve especificar um GUID de classe existente aqui. Fazer isso pode interromper as configurações de segurança ou compatibilidade de outros dispositivos pertencentes a essa classe. Para obter mais informações, consulte WdmlibIoCreateDeviceSecure.
Para obter mais informações, consulte os seguintes artigos:
Controlar o acesso ao dispositivo
Controlar o acesso ao namespace do dispositivo
Modelo de segurança do Windows para desenvolvedores de drivers
Hierarquia de risco de SIDs (identificadores de segurança)
A seção a seguir descreve a hierarquia de risco dos SIDs comuns usados no código do driver. Para obter informações gerais sobre SDDL, consulte SDDL para objetos de dispositivo, Cadeias de caracteres SID e Sintaxe de cadeia de caracteres SDDL.
É importante entender que, se chamadores de privilégios mais baixos tiverem permissão para acessar o kernel, o risco de código aumentará. Neste diagrama resumido, o risco aumenta à medida que você permite que SIDs de privilégios mais baixos acessem as funcionalidades do driver.
SY (System)
\/
BA (Built-in Administrators)
\/
LS (Local Service)
\/
BU (Built-in User)
\/
AC (Application Container)
Seguindo o princípio geral de segurança de privilégios mínimos, configure apenas o nível mínimo de acesso necessário para que o driver funcione.
Controle de segurança IOCTL granular do WDM
Para gerenciar melhor a segurança quando IOCTLs são enviados por chamadores no modo de usuário, o código do driver pode incluir a função IoValidateDeviceIoControlAccess. Essa função permite que um driver verifique os direitos de acesso. Ao receber um IOCTL, um driver pode chamar IoValidateDeviceIoControlAccess, especificando FILE_READ_ACCESS, FILE_WRITE_ACCESS, ou ambos.
A implementação do controle de segurança IOCTL granular não substitui a necessidade de gerenciar o acesso do driver usando as técnicas abordadas anteriormente.
Para obter mais informações, consulte os seguintes artigos:
Definir códigos de controle de E/S
Implemente código compatível com HVCI
Item 8 da lista de verificação de segurança: Valide se o driver usa memória para que ele seja compatível com HVCI.
Uso de memória e compatibilidade com HVCI
O HVCI usa tecnologia de hardware e virtualização para isolar a função de tomada de decisão de CI (Integridade de código) do restante do sistema operacional. Ao usar a segurança baseada em virtualização para isolar a CI, a única maneira de a memória do kernel se tornar executável é por meio de uma verificação de CI. Isso significa que as páginas de memória do kernel nunca podem ser graváveis e executáveis (W + X), e o código executável não pode ser modificado diretamente.
Para implementar o código compatível com HVCI, verifique se o código do driver faz o seguinte:
- Aceita o NX por padrão
- Usa APIs/sinalizadores NX para alocação de memória (NonPagedPoolNx)
- Não usa seções que são graváveis e executáveis
- Não tenta modificar diretamente a memória do sistema executável
- Não usa código dinâmico no kernel
- Não carrega arquivos de dados como executáveis
- O alinhamento da seção é um múltiplo de 0x1000 (PAGE_SIZE). Por exemplo, DRIVER_ALIGNMENT=0x1000
Para obter mais informações sobre como usar a ferramenta e uma lista de chamadas de memória incompatíveis, consulte Implementar código compatível com HVCI.
Para obter mais informações sobre o teste de segurança de conceitos básicos do sistema relacionado, consulte Teste de preparação de integridade de código do hipervisor e HVCI (Integridade de código protegida por hipervisor).
Siga as práticas recomendadas de código específicas da tecnologia
Item 9 da lista de verificação de segurança: Analise as seguintes diretrizes específicas de tecnologia para o driver.
Sistemas de Arquivos
Para obter mais informações sobre a segurança do sistema de arquivos, consulte os seguintes artigos:
Introdução à segurança dos sistemas de arquivos
Problemas de segurança do sistema de arquivos
Recursos de segurança para sistemas de arquivos
Coexistência com outros drivers de filtro do sistema de arquivos
NDIS – Rede
Para obter informações sobre a segurança do driver NDIS, consulte Problemas de segurança para drivers de rede.
Exibição
Para obter informações sobre a segurança do driver de exibição, consulte <Conteúdo pendente>.
Impressoras
Para obter informações relacionadas à segurança do driver de impressora, consulte Considerações sobre segurança do driver de impressora V4.
Problemas de segurança para drivers WIA (Aquisição de imagem do Windows)
Para obter informações sobre a segurança de WIA, consulte Problemas de segurança para drivers WIA (Aquisição de imagem do Windows).
Aumente a segurança da instalação do dispositivo
Item 10 da lista de verificação de segurança: Analise as diretrizes de criação e instalação de informações de driver para saber se você está seguindo as práticas recomendadas.
Ao criar o código que instala seu driver, você deve sempre realizar a instalação do seu dispositivo de maneira segura. Uma instalação segura de dispositivo faz o seguinte:
- Limita o acesso ao dispositivo e suas classes de interface de dispositivo
- Limita o acesso aos serviços de driver que foram criados para o dispositivo
- Protege os arquivos de driver contra modificação ou exclusão
- Limita o acesso às entradas de registro do dispositivo
- Limita o acesso às classes WMI do dispositivo
- Usa as funções SetupAPI corretamente
Para obter mais informações, consulte os seguintes artigos:
Criar instalações de dispositivos seguros
Usar funções de instalação de dispositivo
Tópicos avançados de instalação de dispositivos e drivers
Faça revisão de código por pares
Item 11 da lista de verificação de segurança: Faça a revisão de código por pares para procurar problemas não identificados por outras ferramentas e processos
Busque revisores de código experientes que consigam detectar problemas que você possa ter deixado passar. Um segundo olhar geralmente encontra problemas que você pode ter esquecido.
Se você não tiver a equipe adequada para revisar seu código internamente, contrate ajuda externa para essa finalidade.
Execute a assinatura adequada do driver da versão
Item 12 da lista de verificação de segurança: Use o portal do parceiro do Windows para assinar corretamente o driver para distribuição.
Antes de disponibilizar publicamente um pacote de driver, recomendamos que você envie o pacote para certificação. Para obter mais informações, consulte Testar o desempenho e a compatibilidade, Introdução ao programa de hardware, Hardware Dashboard Services e Atestar a assinatura de um driver de kernel para versão pública.
Use o CodeQL para verificar o código do driver
Item 13 da lista de verificação de segurança: Use o CodeQL para verificar vulnerabilidades no código do driver.
CodeQL, do GitHub, é um poderoso mecanismo de análise de código semântico e uma combinação de um vasto conjunto de consultas de segurança com uma plataforma robusta, que o tornam uma ferramenta inestimável para proteger o código dos drivers. Para obter mais informações, consulte CodeQL e o teste de logotipo de ferramentas estáticas.
Adicione anotações SAL ao código do driver
Item 14 da lista de verificação de segurança: Adicione anotações SAL ao código do driver.
O SAL (linguagem de anotação de código-fonte) fornece um conjunto de anotações para descrever como uma função usa seus parâmetros, as suposições que ela faz sobre eles e as garantias obtidas ao concluir. As anotações são definidas no arquivo de cabeçalho sal.h
. A análise de código do Visual Studio para C++ usa anotações da SAL para modificar a análise de funções. Para obter mais informações sobre o desenvolvimento de driver SAL 2.0 para Windows, consulte Anotações SAL 2.0 para drivers Windows e Usando anotações de SAL para reduzir defeitos de código do C/C++.
Para obter informações gerais sobre SAL, consulte este artigo disponível na OSR. https://www.osr.com/blog/2015/02/23/sal-annotations-dont-hate-im-beautiful/
Use o Driver Verifier para verificar vulnerabilidades
Item 15 da lista de verificação de segurança: Use o Driver Verifier para verificar vulnerabilidades no código do driver.
O Driver Verifier usa um conjunto de regras de interface e um modelo do sistema operacional para determinar se o driver interage corretamente com o sistema operacional Windows. O DV encontra defeitos no código do driver que podem apontar possíveis bugs nos drivers.
O Driver Verifier possibilita testar o driver em tempo real. O Driver Verifier monitora drivers gráficos e drivers no modo kernel do Windows para detectar chamadas de função ilegais ou ações que podem corromper o sistema. O Driver Verifier pode submeter drivers do Windows a uma variedade de pressões e testes para encontrar comportamentos inadequados. Para obter mais informações, consulte Driver Verifier.
Observe que o DV suporta apenas determinados tipos de drivers. Para obter mais informações sobre os drivers que o DV pode verificar, consulte Drivers com suporte. Consulte as páginas a seguir para obter informações sobre os testes do DV disponíveis para o tipo de driver com o qual você está trabalhando.
- Regras para drivers WDM
- Regras para drivers KMDF
- Regras para drivers NDIS
- Regras para drivers Storport
- Regras para drivers de áudio
- Regras para drivers AVStream
Para se familiarizar com o DV, você pode usar um dos drivers de exemplo (por exemplo, o toaster em destaque: https://github.com/Microsoft/Windows-driver-samples/tree/main/general/toaster/toastDrv/kmdf/func/featured).
Verifique o código com o BinSkim Binary Analyzer
Item 16 da lista de verificação de segurança: Siga estas etapas para usar o BinSkim para verificar se as opções de compilação estão configuradas para minimizar problemas de segurança conhecidos.
Use o BinSkim para examinar arquivos binários e identificar práticas de codificação e compilação que podem tornar o binário vulnerável.
O BinSkim verifica:
- Uso de conjuntos de ferramentas do compilador desatualizados – os binários devem ser compilados em relação aos conjuntos de ferramentas do compilador mais recentes sempre que possível, pois isso maximiza o uso das mitigações de segurança atuais no nível do compilador e fornecidas pelo sistema operacional.
- Configurações de compilação desprotegidas – os binários devem ser compilados com as configurações mais seguras possíveis para habilitar mitigações de segurança fornecidas pelo sistema operacional, maximizar erros do compilador e relatórios de avisos acionáveis, entre outras coisas.
- Problemas de assinatura – os binários devem ser assinados com algoritmos de criptografia forte.
O BinSkim é uma ferramenta de código aberto e gera arquivos de saída que usam o formato SARIF (Static Analysis Results Interchange Format). O BinSkim substitui a antiga ferramenta BinScope.
Para obter mais informações sobre o BinSkim, consulte o Guia do usuário doBinSkim.
Siga estas etapas para validar se as opções de compilação de segurança estão configuradas corretamente no código que você está enviando.
Baixe e instale o SDK do .NET Core multiplataforma.
Confirme se o Visual Studio está instalado. Para obter informações sobre como fazer o download e instalar o Visual Studio, consulte Instalar o Visual Studio.
Há várias opções para baixar o BinSkim, como um pacote NuGet. Neste exemplo, usaremos a opção git clone para fazer o download aqui: https://github.com/microsoft/binskim e instalá-lo em um PC com Windows de 64 bits.
Abra uma janela do prompt de comando do Desenvolvedor do Visual Studio e crie um diretório, por exemplo
C:\binskim-master
.C:\> Md \binskim-master
Acesse o diretório que você acabou de criar.
C:\> Cd \binskim-master
Use o comando git clone para baixar todos os arquivos necessários.
C:\binskim-master> git clone --recurse-submodules https://github.com/microsoft/binskim.git
Acesse o novo diretório
binskim
criado pelo comando clone.C:\> Cd \binskim-master\binskim
Execute BuildAndTest.cmd para que a compilação de versão seja bem-sucedida e que todos os testes sejam aprovados.
C:\binskim-master\binskim> BuildAndTest.cmd Welcome to .NET Core 3.1! --------------------- SDK Version: 3.1.101 ... C:\binskim-master\binskim\bld\bin\AnyCPU_Release\Publish\netcoreapp2.0\win-x64\BinSkim.Sdk.dll 1 File(s) copied C:\binskim-master\binskim\bld\bin\AnyCPU_Release\Publish\netcoreapp2.0\linux-x64\BinSkim.Sdk.dll 1 File(s) copied ...
O processo de compilação cria um conjunto de diretórios com os executáveis BinSkim. Acesse o diretório de saída de build win-x64.
C:\binskim-master\binskim> Cd \binskim-master\bld\bin\AnyCPU_Release\Publish\netcoreapp2.0\win-x64>
Exiba a ajuda da opção de análise.
C:\binskim-master\binskim\bld\bin\AnyCPU_Release\Publish\netcoreapp2.0\win-x64> BinSkim help analyze
BinSkim PE/MSIL Analysis Driver 1.6.0.0
--sympath Symbols path value, e.g., SRV*http://msdl.microsoft.com/download/symbols or Cache*d:\symbols;Srv*http://symweb. See
https://learn.microsoft.com/windows-hardware/drivers/debugger/advanced-symsrv-use for syntax information. Note that BinSkim will clear the
_NT_SYMBOL_PATH environment variable at runtime. Use this argument for symbol information instead.
--local-symbol-directories A set of semicolon-delimited local directory paths that will be examined when attempting to locate PDBs.
-o, --output File path to which analysis output will be written.
--verbose Emit verbose output. The resulting comprehensive report is designed to provide appropriate evidence for compliance scenarios.
...
Definir o caminho do símbolo
Se você estiver criando todo o código que está analisando no mesmo computador em que está executando o BinSkim, normalmente não será necessário definir o caminho do símbolo. Isso ocorre porque seus arquivos de símbolo estão disponíveis na caixa local onde você compilou. Se você estiver usando um sistema de compilação mais complexo ou redirecionando seus símbolos para um local diferente (não ao lado do binário compilado), use --local-symbol-directories
para adicionar esses locais à pesquisa do arquivo de símbolo.
Se o código fizer referência a um binário compilado que não faz parte dele, o sympath do depurador do Windows poderá ser usado para recuperar símbolos para verificar a segurança dessas dependências de código. Se você encontrar um problema nessas dependências, talvez não consiga corrigi-lo. Mas pode ser útil entender todos os riscos possíveis de segurança que você esteja aceitando ao assumir essas dependências.
Dica
Ao adicionar um caminho de símbolo (que faz referência a um servidor de símbolos em rede), adicione um local de cache local para especificar um caminho local para armazenar os símbolos em cache. Não fazer isso pode comprometer muito o desempenho do BinSkim. O exemplo a seguir especifica um cache local em d:\symbols.
--sympath Cache*d:\symbols;Srv*http://symweb
Para saber mais sobre sympath, consulte Caminho de símbolo para depuradores do Windows.
Execute o comando a seguir para analisar um binário de driver compilado. Atualize o caminho de destino para apontar para o arquivo .sys do driver compilado.
C:\binskim-master\binskim\bld\bin\AnyCPU_Release\Publish\netcoreapp2.0\win-x64> BinSkim analyze "C:\Samples\KMDF_Echo_Driver\echo.sys"
Para obter informações adicionais, adicione a opção detalhada como esta.
C:\binskim-master\binskim\bld\bin\AnyCPU_Release\Publish\netcoreapp2.0\win-x64> BinSkim analyze "C:\Samples\KMDF_Echo_Driver\osrusbfx2.sys" --verbose
Observação
A opção --verbose produzirá resultados explícitos de aprovação/reprovação para cada verificação. Se você não fornecer detalhes, verá apenas os defeitos detectados pelo BinSkim. A opção --verbose normalmente não é recomendada para sistemas de automação reais, pois ela aumenta o tamanho dos arquivos de log e dificulta a detecção de falhas individuais quando elas ocorrem, pois elas serão incorporadas no meio de um grande número de resultados de "aprovação".
Revise a saída do comando para procurar possíveis problemas. Este exemplo de saída mostra três testes que passaram. Informações adicionais sobre as regras, como BA2002, estão disponíveis no Guia do usuário do BinSkim.
Analyzing... Analyzing 'osrusbfx2.sys'... ... C:\Samples\KMDF_Echo_Driver\osrusbfx2.sys\Debug\osrusbfx2.sys: pass BA2002: 'osrusbfx2.sys' does not incorporate any known vulnerable dependencies, as configured by current policy. C:\Samples\KMDF_Echo_Driver\Debug\osrusbfx2.sys: pass BA2005: 'osrusbfx2.sys' is not known to be an obsolete binary that is vulnerable to one or more security problems. C:\Samples\KMDF_Echo_Driver\osrusbfx2.sys: pass BA2006: All linked modules of 'osrusbfx2.sys' generated by the Microsoft front-end satisfy configured policy (compiler minimum version 17.0.65501.17013).
Esta saída mostra que o teste BA3001 não é executado, pois a ferramenta indica que o driver não é um binário ELF.
... C:\Samples\KMDF_Echo_Driver\Debug\osrusbfx2.sys: notapplicable BA3001: 'osrusbfx2.sys' was not evaluated for check 'EnablePositionIndependentExecutable' as the analysis is not relevant based on observed metadata: image is not an ELF binary.
Esta saída mostra um erro para o teste BA2007.
... C:\Samples\KMDF_Echo_Driver\Debug\osrusbfx2.sys: error BA2007: 'osrusbfx2.sys' disables compiler warning(s) which are required by policy. A compiler warning is typically required if it has a high likelihood of flagging memory corruption, information disclosure, or double-free vulnerabilities. To resolve this issue, enable the indicated warning(s) by removing /Wxxxx switches (where xxxx is a warning id indicated here) from your command line, and resolve any warnings subsequently raised during compilation.
Para habilitar esses avisos no Visual Studio, em C/C++ nas páginas de propriedades do projeto, remova os valores que você não deseja excluir em Desabilitar avisos específicos.
As opções de compilação padrão no Visual Studio para projetos de driver podem desabilitar avisos como os seguintes. Esses avisos serão relatados pelo BinSkim.
C4627 - 'description': ignorado ao procurar o uso de cabeçalho pré-compilado
C4986 - 'declaration': especificação de exceção não corresponde à declaração anterior
Para obter mais informações sobre os avisos do compilador, consulte Avisos do compilador por versão do compilador.
Verifique o código com os testes do programa de compatibilidade de hardware
Item 17 da lista de verificação de segurança: Use os testes do programa de compatibilidade de hardware relacionados à segurança para verificar se há problemas de segurança.
O programa de compatibilidade de hardware inclui testes relacionados à segurança que podem ser usados para procurar vulnerabilidades de código. O Programa de compatibilidade de hardware do Windows usa os testes no HLK (Windows Hardware Lab Kit). Os testes de conceitos básicos do dispositivo HLK podem ser usados na linha de comando para exercitar o código do driver e investigar se há pontos fracos. Para obter informações gerais sobre os testes de conceitos básicos do dispositivo e o programa de compatibilidade de hardware, consulte Windows Hardware Lab Kit.
Os testes a seguir são exemplos que podem ser úteis para buscar comportamentos associados a vulnerabilidades de código do driver:
DF: Teste de IOCTL aleatório de fuzzing (Confiabilidade)
DF: Teste subaberto de fuzzing (Confiabilidade)
DF: Teste de FSCTL de fuzzing do buffer de comprimento zero (Confiabilidade)
DF: Teste de FSCTL aleatório de fuzzing (Confiabilidade)
DF: Teste de API diverso de fuzzing (Confiabilidade)
Você também pode usar o fuzzing de atraso de sincronização do kernel, incluído no Driver Verifier.
Os testes de CHAOS (Concurrent Hardware and Operating System) verificam o driver PnP, testes de fuzzing de driver de dispositivo e testes de sistema de energia simultaneamente. Para obter mais informações, consulte Testes de CHAOS (fundamentos do dispositivo).
Os testes de penetração de fundamentos do dispositivo executam várias formas de ataques de entrada, que são um componente crítico dos testes de segurança. Os testes de ataque e penetração podem ajudar a identificar vulnerabilidades nas interfaces de software. Para obter mais informações, consulte Testes de penetração (fundamentos do dispositivo).
Use o Device Guard – Teste de conformidade, juntamente com as outras ferramentas descritas neste artigo, para confirmar se o driver é compatível com HVCI.
Ferramentas de teste personalizadas e específicas do domínio
Considere o desenvolvimento de testes de segurança personalizados específicos do domínio. Para desenvolver testes adicionais, reúna informações dos designers originais do software, bem como de recursos de desenvolvimento não relacionados familiarizados com o tipo específico de driver que está sendo desenvolvido e de uma ou mais pessoas que conheçam análise e prevenção de invasão de segurança.
Analise técnicas e extensões do depurador
Item 18 da lista de verificação de segurança: Analise essas ferramentas de depuração e considere seu uso no fluxo de trabalho de depuração de desenvolvimento.
Comandos do depurador relacionados à segurança
A extensão !acl formata e exibe o conteúdo de uma ACL (lista de controle de acesso). Para obter mais informações, consulte Determinar a ACL de um objeto e !acl.
A extensão !token mostra uma exibição formatada de um objeto de token de segurança. Para obter mais informações, consulte !token.
A extensão !tokenfields exibe os nomes e deslocamentos dos campos dentro do objeto de token de acesso (a estrutura TOKEN). Para obter mais informações, consulte !tokenfields.
A extensão !sid mostra o identificador de segurança (SID) no endereço especificado. Para obter mais informações, consulte !sid.
A extensão !sd exibe o descritor de segurança no endereço especificado. Para obter mais informações, consulte !sd.
Microsoft Vulnerable and Malicious Driver Reporting Center
Qualquer pessoa pode enviar um driver suspeito usando o Microsoft Vulnerable and Malicious Driver Reporting Center. Consulte esta entrada de blog para obter informações sobre como os drivers são enviados para análise - Melhore a segurança do kernel com o novo Microsoft Vulnerable and Malicious Driver Reporting Center
O Reporting Center pode verificar e analisar drivers do Windows criados para arquiteturas x86 e x64. Drivers verificados vulneráveis e mal-intencionados são sinalizados para análise e investigação pela equipe de Drivers vulneráveis da Microsoft. Depois que os drivers vulneráveis são confirmados, eles são notificados adequadamente e adicionados à lista de bloqueio de drivers vulneráveis. Para obter mais informações sobre esse assunto, consulte Regras de bloqueio de driver recomendadas pela Microsoft. Essas regras são aplicadas por padrão a dispositivos habilitados para HVCI (integridade de código protegida por hipervisor) e Windows 10 no modo S.
Examine os recursos de codificação segura
Item 19 da lista de verificação de segurança: Examine esses recursos para entender melhor as práticas recomendadas de codificação segura aplicáveis aos desenvolvedores de driver.
Analise estes recursos para saber mais sobre segurança do driver
Diretrizes de codificação de driver de modo kernel seguro
Criar drivers confiáveis no modo kernel
Organizações de codificação segura
Carnegie Mellon University SEI CERT
Carnegie Mellon University SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition).
MITRE - Weaknesses Addressed by the CERT C Secure Coding Standard
Building Security In Maturity Model (BSIMM) - https://www.bsimm.com/
SAFECode - https://safecode.org/
OSR
A OSR fornece treinamento de desenvolvimento de drivers e serviços de consultoria. Esses artigos do boletim informativo da OSR destacam os problemas de segurança do driver.
You've Gotta Use Protection -- Inside Driver & Device Security
Locking Down Drivers - A Survey of Techniques
Meltdown and Spectre: What about drivers?
Estudo de caso
Manuais
24 deadly sins of software security : programming flaws and how to fix them de Michael Howard, David LeBlanc e John Viega
The art of software security assessment : identifying and preventing software vulnerabilities, Mark Dowd, John McDonald e Justin Schuh
Writing Secure Software Second Edition, Michael Howard e David LeBlanc
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities, Mark Dowd e John McDonald
Secure Coding in C and C++ (SEI Series in Software Engineering) 2nd Edition, Robert C. Seacord
Programming the Microsoft Windows Driver Model (2nd Edition), Walter Oney
Developing Drivers with the Windows Driver Foundation (Developer Reference), Penny Orwick e Guy Smith
Treinamento
O treinamento presencial de driver do Windows está disponível em fornecedores como os seguintes:
O treinamento online de codificação segura está disponível em várias fontes. Por exemplo, este curso está disponível na Coursera em:
Identificando vulnerabilidades de segurança na programação do C/C++.
O SAFECode também oferece treinamento gratuito:
Certificação profissional
O CERT oferece uma Certificação profissional de codificação segura.
Resumo das principais conclusões
A segurança do driver é uma tarefa complexa que contém muitos elementos, mas aqui estão alguns pontos-chave a serem considerados:
Os drivers residem no kernel do Windows. Problemas ao executar no kernel podem expor todo o sistema operacional. Por isso, preste muita atenção à segurança do driver e projete sempre pensando em segurança.
Aplique o princípio de privilégios mínimos:
a. Use uma cadeia de caracteres SDDL estrita para restringir o acesso ao driver
b. Restrinja ainda mais os IOCTLs individuais
Crie um modelo de ameaça para identificar vetores de ataque e pense em outras restrições que podem ajudar.
Tenha cuidado com relação aos ponteiros inseridos que estão sendo passados do modo de usuário. Eles precisam ser investigados, acessados dentro de try except, e são propensos a problemas de tempo de verificação/tempo de uso (ToCToU), a menos que o valor do buffer seja capturado e comparado.
Se você não tiver certeza, use METHOD_BUFFERED como um método de buffer IOCTL.
Use utilitários de varredura de código para procurar vulnerabilidades de código conhecidas e corrigir possíveis problemas identificados.
Busque revisores de código experientes que consigam detectar problemas que você possa ter deixado passar.
Use o Driver Verifier e teste o driver com várias entradas, incluindo casos menos frequentes.