Partilhar via


Problemas de segurança para drivers de rede

Para uma discussão geral sobre como escrever drivers seguros, consulte Criação de Drivers Kernel-Mode Confiáveis.

Além de seguir as práticas de codificação segura e as orientações gerais do driver de dispositivo, os drivers de rede devem fazer o seguinte para aumentar a segurança:

  • Todos os drivers de rede devem validar valores que eles leem do registro. Especificamente, o chamador de NdisReadConfiguration ou NdisReadNetworkAddress não deve fazer nenhuma suposição 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 deve usar um valor padrão em vez disso. Se o chamador de NdisReadNetworkAddress determinar que um valor está fora dos limites, ele deve usar o endereço MAC (controle de acesso médio) permanente ou um endereço padrão.

Questões específicas do OID

Consultar diretrizes de segurança do OID

A maioria dos OIDs de consulta pode ser emitida por qualquer aplicação em modo de utilizador no sistema. Siga estas diretrizes específicas para identificadores de consulta (OIDs).

  1. Sempre valide se o tamanho do buffer é grande o suficiente para a saída. Qualquer manipulador OID de consulta sem uma verificação de tamanho do buffer de saída 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 para BytesWritten. É um sinal de alerta atribuir oid->BytesWritten = oid->InformationBufferLength como o exemplo a seguir faz.

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

    O SO copiará BytesWritten de volta para uma aplicação em modo de utilizador. Se BytesWritten for maior do que o número de bytes que o driver realmente escreveu, então o sistema operacional pode acabar copiando de volta a memória do kernel não inicializada para o modo 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 novamente valores do buffer. Em alguns casos, o buffer de saída de um OID é diretamente mapeado em um processo de modo de usuário hostil. O processo hostil pode alterar seu buffer de saída depois que você tiver escrito nele. Por exemplo, o código abaixo pode ser atacado, porque um invasor pode alterar NumElements depois que ele é escrito:

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

    Para evitar ler novamente 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 a partir 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 a volatile palavra-chave, para evitar que o compilador desfaça silenciosamente essa correção.

Definir diretrizes de segurança OID

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

  1. Sempre valide se a entrada é grande o suficiente. Qualquer manipulador de conjunto OID sem uma verificação de tamanho do buffer de entrada 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 incorporado, você deve validar se o buffer incorporado está dentro da carga útil do OID. Isto requer várias verificações. Por exemplo, OID_PM_ADD_WOL_PATTERN pode fornecer um padrão incorporado, que precisa ser verificado. A validação correta requer a verificação:

    1. InformationBufferSize >= tamanho de (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 transborda

      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 um 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 do Method OID

Os OIDs de método podem ser emitidos por uma aplicação em modo de utilizador em execução nos grupos de segurança de Administradores ou Sistema. Estes são uma combinação de um conjunto e uma consulta, portanto, ambas as listas anteriores de orientação também se aplicam aos OIDs do método.

Outros problemas de segurança do controlador de rede

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

  • Drivers de miniporta NDIS bem projetados não devem depender de serem chamados em um contexto de processo específico, nem interagir muito de perto com o modo de usuário (com IOCTLs e OIDs sendo a exceção). Seria um alerta vermelho ver uma miniporta que abrisse identificadores do modo utilizador, realizasse esperas do modo utilizador ou alocasse memória contra a quota do modo utilizador. Esse código deve ser investigado.

  • A maioria dos drivers miniport NDIS não deve estar envolvida na análise dos dados dos pacotes. Em alguns casos, porém, pode ser necessário. Em caso afirmativo, 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 de modo kernel, os drivers NDIS devem usar mecanismos de pool Opt-In NX apropriados. No WDK 8 e em versões mais recentes, a família de funções NdisAllocate* está devidamente optada.