Problemas de segurança para drivers de rede

Para obter uma discussão geral sobre como escrever drivers seguros, consulte Criando drivers de Kernel-Mode confiáveis.

Além das seguintes práticas de codificação seguras e das diretrizes gerais do driver de dispositivo, os drivers de rede devem fazer o seguinte para aprimorar a segurança:

  • Todos os drivers de rede devem validar os valores que eles leem do registro. Especificamente, o chamador de NdisReadConfiguration ou NdisReadNetworkAddress não deve fazer suposições sobre valores lidos do Registro e deve validar cada valor do Registro que ele lê. Se o chamador de NdisReadConfiguration determinar que um valor está fora dos limites, ele deverá usar um valor padrão. Se o chamador de NdisReadNetworkAddress determinar que um valor está fora dos limites, ele deverá usar o endereço MAC (controle de acesso médio) permanente ou um endereço padrão.

Problemas específicos do OID

Diretrizes de segurança de OID de consulta

A maioria dos OIDs de Consulta pode ser emitida por qualquer aplicativo de modelo de usuário no sistema. Siga estas diretrizes específicas para OIDs de Consulta.

  1. Sempre valide se o tamanho do buffer é grande o suficiente para a saída. Qualquer manipulador de OID de consulta sem um tamanho de buffer de saída marcar tem um bug de segurança.

    if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG);
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Sempre escreva um valor correto e mínimo em BytesWritten. É um sinalizador vermelho a ser atribuído oid->BytesWritten = oid->InformationBufferLength como o exemplo a seguir.

    // ALWAYS WRONG
    oid->DATA.QUERY_INFORMATION.BytesWritten = DATA.QUERY_INFORMATION.InformationBufferLength; 
    

    O sistema operacional copiará bytes BytesWritten de volta para um aplicativo de modelo de usuário. Se BytesWritten for maior que o número de bytes que o driver realmente escreveu, o sistema operacional poderá acabar copiando de volta a memória de kernel não inicializada para o modelo de usuário, o que seria uma vulnerabilidade de divulgação de informações. Em vez disso, use um código semelhante a este:

    oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);
    
  3. Nunca leia os valores de volta do buffer. Em alguns casos, o buffer de saída de um OID é mapeado diretamente para um processo de usermode hostil. O processo hostil pode alterar o buffer de saída depois de gravar nele. Por exemplo, o código abaixo pode ser atacado, porque um invasor pode alterar NumElements depois que ele é gravado:

    output->NumElements = 4;
    for (i = 0 ; i < output->NumElements ; i++) {
        output->Element[i] = . . .;
    }
    

    Para evitar a leitura do buffer, mantenha uma cópia local. Por exemplo, para corrigir o exemplo acima, introduza uma nova variável de pilha:

    ULONG num = 4;
    output->NumElements = num;
    for (i = 0 ; i < num; i++) {
        output->Element[i] = . . .;
    }
    

    Com essa abordagem, o loop for lê de volta da variável num de pilha do driver e não de seu buffer de saída. O driver também deve marcar o buffer de saída com o volatile palavra-chave, para impedir que o compilador desfaça silenciosamente essa correção.

Definir diretrizes de segurança de OID

A maioria dos OIDs set pode ser emitida por um aplicativo de modelo de usuário em execução nos grupos de segurança Administradores ou Sistema. Embora sejam aplicativos geralmente confiáveis, o driver de miniporta ainda não deve permitir corrupção de memória ou injeção de código kernel. Siga estas regras específicas para Definir OIDs:

  1. Sempre valide se a entrada é grande o suficiente. Qualquer manipulador de conjunto de OID sem um tamanho de buffer de entrada marcar tem uma vulnerabilidade de segurança.

    if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Sempre que validar um OID com um deslocamento inserido, você deverá validar se o buffer inserido está dentro do conteúdo do OID. Isso requer várias verificações. Por exemplo, OID_PM_ADD_WOL_PATTERN pode fornecer um padrão inserido, que precisa ser verificado. A validação correta requer verificação:

    1. InformationBufferSize >= sizeof(NDIS_PM_PACKET_PATTERN)

      PmPattern = (PNDIS_PM_PACKET_PATTERN) InformationBuffer;
      if (InformationBufferLength < sizeof(NDIS_PM_PACKET_PATTERN))
      {
          Status = NDIS_STATUS_BUFFER_TOO_SHORT;
          *BytesNeeded = sizeof(NDIS_PM_PACKET_PATTERN);
          break;
      }
      
    2. Pattern-PatternOffset> + Pattern-PatternSize> não estoura

      ULONG TotalSize = 0;
      if (!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternSize, &TotalSize) ||
          TotalSize > InformationBufferLength) 
      {
          return NDIS_STATUS_INVALID_LENGTH;
      }
      

      Essas duas verificações podem ser combinadas usando código como o exemplo a seguir:

      ULONG TotalSize = 0;
      if (InformationBufferLength < sizeof(NDIS_PM_PACKET_PATTERN) ||
          !NT_SUCCESS(RtlUlongAdd(Pattern->PatternSize, Pattern->PatternOffset, &TotalSize) ||
          TotalSize > InformationBufferLength) 
      {
          return NDIS_STATUS_INVALID_LENGTH;
      }
      
    3. InformationBuffer + Pattern-PatternOffset> + Pattern-PatternLength> não estoura

      ULONG TotalSize = 0;
      if (!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternLength, &TotalSize) ||
          (!NT_SUCCESS(RtlUlongAdd(TotalSize, InformationBuffer, &TotalSize) ||
          TotalSize > InformationBufferLength) 
      {
          return NDIS_STATUS_INVALID_LENGTH;
      }
      
    4. Pattern-PatternOffset> + Pattern-PatternLength <>= InformationBufferSize

      ULONG TotalSize = 0;
      if(!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternLength, &TotalSize) ||
          TotalSize > InformationBufferLength)) 
      {
          return NDIS_STATUS_INVALID_LENGTH;
      }
      

Diretrizes de segurança OID do método

Os OIDs de método podem ser emitidos por um aplicativo de modelo de usuário em execução nos grupos de segurança Administradores ou Sistema. Eles são uma combinação de um Set e uma Consulta, portanto, ambas as listas anteriores de diretrizes também se aplicam a OIDs de método.

Outros problemas de segurança do driver de rede

  • Muitos drivers de miniporto NDIS expõem um dispositivo de controle usando NdisRegisterDeviceEx. Aqueles que fazem isso devem auditar seus manipuladores IOCTL, com todas as mesmas regras de segurança que um driver WDM. Para obter mais informações, consulte Problemas de segurança para códigos de controle de E/S.

  • Os drivers de miniporto NDIS bem projetados não devem depender de serem chamados em um contexto de processo específico, nem interagir muito de perto com o modelo de usuário (com IOCTLs & OIDs sendo a exceção). Seria um sinalizador vermelho ver um miniporto que abriu identificadores de modelo de usuário, realizou esperas de usermode ou memória alocada em relação à cota de usermode. Esse código deve ser investigado.

  • A maioria dos drivers de miniporta NDIS não deve estar envolvida na análise de cargas de pacotes. Em alguns casos, porém, pode ser necessário. Nesse caso, esse código deve ser auditado com muito cuidado, pois o driver está analisando dados de uma fonte não confiável.

  • Como é padrão ao alocar memória no modo kernel, os drivers NDIS devem usar mecanismos de Opt-In do pool de NX apropriados. No WDK 8 e mais recente, a NdisAllocate* família de funções é aceita corretamente.