Compartir vía


Problemas de seguridad de los controladores de red

Para obtener una explicación general sobre cómo escribir controladores seguros, consulte Creating Reliable Kernel-Mode Drivers.

Más allá de las siguientes prácticas de codificación seguras y la guía general del controlador de dispositivo, los controladores de red deben hacer lo siguiente para mejorar la seguridad:

  • Todos los controladores de red deben validar los valores que leen del Registro. En concreto, el autor de la llamada de NdisReadConfiguration o NdisReadNetworkAddress no debe realizar ninguna suposición sobre los valores leídos del Registro y debe validar cada valor del Registro que lea. Si el autor de la llamada de NdisReadConfiguration determina que un valor está fuera de los límites, debe usar un valor predeterminado en su lugar. Si el autor de la llamada de NdisReadNetworkAddress determina que un valor está fuera de los límites, debe usar la dirección de control de acceso medio permanente (MAC) o una dirección predeterminada en su lugar.

Problemas específicos de OID

Consultar instrucciones de seguridad de OID

La mayoría de los OID de consulta pueden ser emitidos por cualquier aplicación de modo de usuario del sistema. Siga estas instrucciones específicas para los OID de consulta.

  1. Valide siempre el tamaño del búfer es lo suficientemente grande como para la salida. Cualquier controlador de OID de consulta sin una comprobación de tamaño de búfer de salida tiene un error de seguridad.

    if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG);
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Escriba siempre un valor correcto y mínimo en BytesWritten. Se trata de una marca roja para asignar oid->BytesWritten = oid->InformationBufferLength como hace el ejemplo siguiente.

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

    El sistema operativo copiará bytes escritos de nuevo en una aplicación de modo de usuario. Si BytesWritten es mayor que el número de bytes que el controlador escribió realmente, el sistema operativo podría terminar copiando la memoria del kernel sin inicializar en modo de usuario, lo que sería una vulnerabilidad de divulgación de información. En su lugar, use código similar al siguiente:

    oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);
    
  3. Nunca lea los valores del búfer. En algunos casos, el búfer de salida de un OID se asigna directamente a un proceso de modo de usuario hostil. El proceso hostil puede cambiar el búfer de salida después de haber escrito en él. Por ejemplo, se puede atacar el código siguiente, porque un atacante puede cambiar NumElements después de escribirlo:

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

    Para evitar volver a leer desde el búfer, mantenga una copia local. Por ejemplo, para corregir el ejemplo anterior, introduzca una nueva variable de pila:

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

    Con este enfoque, el bucle for vuelve a leer de la variable num de pila del controlador y no de su búfer de salida. El controlador también debe marcar el búfer de salida con la volatile palabra clave para evitar que el compilador deshaga esta corrección de forma silenciosa.

Establecimiento de directrices de seguridad de OID

La mayoría de los identificadores de configuración se pueden emitir mediante una aplicación de modo de usuario que se ejecuta en los administradores o grupos de seguridad del sistema. Aunque normalmente son aplicaciones de confianza, el controlador de minipuerto todavía no debe permitir daños en la memoria ni la inyección de código kernel. Siga estas reglas específicas para Establecer OID:

  1. Valide siempre que la entrada sea lo suficientemente grande. Cualquier controlador de conjunto de OID sin una comprobación de tamaño de búfer de entrada tiene una vulnerabilidad de seguridad.

    if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Siempre que valide un OID con un desplazamiento incrustado, debe validar que el búfer incrustado está dentro de la carga del OID. Esto requiere varias comprobaciones. Por ejemplo, OID_PM_ADD_WOL_PATTERN puede proporcionar un patrón incrustado, que debe comprobarse. La validación correcta requiere la comprobación:

    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> + PatternSize> no se desborda

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

      Estas dos comprobaciones se pueden combinar mediante código como el ejemplo siguiente:

      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> no se desborda

      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> + PatternLength>< = InformationBufferSize

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

Directrices de seguridad de OID de método

Una aplicación de modo de usuario que se ejecuta en administradores o grupos de seguridad del sistema puede emitir identificadores de método. Son una combinación de un conjunto y una consulta, por lo que ambas listas anteriores de instrucciones también se aplican a los OID de método.

Otros problemas de seguridad del controlador de red

  • Muchos controladores de minipuerto de NDIS exponen un dispositivo de control mediante NdisRegisterDeviceEx. Los que hacen esto deben auditar sus controladores IOCTL, con todas las mismas reglas de seguridad que un controlador WDM. Para obtener más información, consulte Problemas de seguridad para códigos de control de E/S.

  • Los controladores de miniporte NDIS bien diseñados no deben depender de llamarse en un contexto de proceso determinado, ni interactuar muy estrechamente con el modo de usuario (con ioCTLs & identificadores de interfaz de usuario que son la excepción). Sería una marca roja ver un minipuerto que abrió los identificadores de modo de usuario, realizó esperas de modo de usuario o memoria asignada con la cuota de modo de usuario. Ese código debe investigarse.

  • La mayoría de los controladores de miniporte NDIS no deben participar en el análisis de cargas de paquetes. Sin embargo, en algunos casos puede ser necesario. Si es así, este código se debe auditar con mucho cuidado, ya que el controlador analiza los datos de un origen que no es de confianza.

  • Como es estándar al asignar memoria en modo kernel, los controladores NDIS deben usar los mecanismos adecuados de NX Pool Opt-In. En WDK 8 y versiones posteriores, la NdisAllocate* familia de funciones se ha optado por participar correctamente.