Freigeben über


Sicherheitsprobleme für Netzwerktreiber

Eine allgemeine Diskussion zum Schreiben sicherer Treiber finden Sie unter Erstellen von zuverlässigen Kernel-Mode Treibern.

Neben den sicheren Codierungsmethoden und der allgemeinen Anleitung für Gerätetreiber sollten Netzwerktreiber folgendes tun, um die Sicherheit zu erhöhen:

  • Alle Netzwerktreiber sollten Werte überprüfen, die sie aus der Registrierung lesen. Insbesondere darf der Aufrufer von NdisReadConfiguration oder NdisReadNetworkAddress keine Annahmen über werte treffen, die aus der Registrierung gelesen werden, und muss jeden gelesenen Registrierungswert überprüfen. Wenn der Aufrufer von NdisReadConfiguration feststellt, dass ein Wert außerhalb der Grenzen liegt, sollte er stattdessen einen Standardwert verwenden. Wenn der Aufrufer von NdisReadNetworkAddress feststellt, dass ein Wert außerhalb der Grenzen liegt, sollte er stattdessen die permanente MAC-Adresse (Medium Access Control) oder eine Standardadresse verwenden.

OID-spezifische Probleme

Sicherheitsrichtlinien für abfragen von OID

Die meisten Abfrage-OIDs können von jeder Benutzermodusanwendung auf dem System ausgegeben werden. Befolgen Sie diese spezifischen Richtlinien für Abfrage-OIDs.

  1. Überprüfen Sie immer, ob die Größe des Puffers groß genug für die Ausgabe ist. Jeder Abfrage-OID-Handler ohne Überprüfung der Ausgabepuffergröße weist einen Sicherheitsfehler auf.

    if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG);
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Schreiben Sie immer einen richtigen und minimalen Wert in BytesWritten. Es ist ein rotes Flag, das wie im folgenden Beispiel zugewiesen oid->BytesWritten = oid->InformationBufferLength werden soll.

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

    Das Betriebssystem kopiert BytesGeschriebene Bytes zurück in eine Benutzermodusanwendung. Wenn BytesWritten größer ist als die Anzahl der Bytes, die der Treiber tatsächlich geschrieben hat, kopiert das Betriebssystem möglicherweise nicht initialisierten Kernelspeicher zurück in den Benutzermodus, was eine Sicherheitsanfälligkeit bei der Offenlegung von Informationen darstellt. Verwenden Sie stattdessen Code ähnlich dem folgenden:

    oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);
    
  3. Lesen Sie niemals Werte aus dem Puffer zurück. In einigen Fällen wird der Ausgabepuffer einer OID direkt einem feindlichen Benutzermodusprozess zugeordnet. Der feindselige Prozess kann Ihren Ausgabepuffer ändern, nachdem Sie ihn geschrieben haben. Beispielsweise kann der folgende Code angegriffen werden, da ein Angreifer NumElements ändern kann, nachdem es geschrieben wurde:

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

    Um das Zurücklesen aus dem Puffer zu vermeiden, behalten Sie eine lokale Kopie bei. Führen Sie beispielsweise eine neue Stapelvariable ein, um das obige Beispiel zu beheben:

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

    Bei diesem Ansatz liest die for-Schleife aus der Stapelvariable num des Treibers zurück und nicht aus dem Ausgabepuffer. Der Treiber sollte auch den Ausgabepuffer mit dem volatile Schlüsselwort (keyword) markieren, um zu verhindern, dass der Compiler diesen Fix automatisch rückgängig machen kann.

Festlegen von OID-Sicherheitsrichtlinien

Die meisten Set-OIDs können von einer Benutzermodusanwendung ausgegeben werden, die in den Sicherheitsgruppen "Administratoren" oder "System" ausgeführt wird. Obwohl es sich um allgemein vertrauenswürdige Anwendungen handelt, darf der Miniporttreiber dennoch keine Speicherbeschädigung oder Einschleusung von Kernelcode zulassen. Befolgen Sie die folgenden spezifischen Regeln für Set OIDs:

  1. Überprüfen Sie immer, ob die Eingabe groß genug ist. Jeder OID-Satzhandler ohne Überprüfung der Eingabepuffergröße weist ein Sicherheitsrisiko auf.

    if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Wenn Sie eine OID mit einem eingebetteten Offset überprüfen, müssen Sie überprüfen, ob sich der eingebettete Puffer innerhalb der OID-Nutzlast befindet. Dies erfordert mehrere Überprüfungen. Beispielsweise können OID_PM_ADD_WOL_PATTERN ein eingebettetes Muster liefern, das überprüft werden muss. Für die richtige Validierung ist eine Überprüfung erforderlich:

    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> überläuft nicht

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

      Diese beiden Überprüfungen können mithilfe von Code wie im folgenden Beispiel kombiniert werden:

      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> überläuft nicht

      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;
      }
      

Methoden-OID-Sicherheitsrichtlinien

Methoden-OIDs können von einer Benutzermodusanwendung ausgegeben werden, die in den Sicherheitsgruppen "Administratoren" oder "System" ausgeführt wird. Sie sind eine Kombination aus einem Set und einer Abfrage. Daher gelten beide vorherigen Leitfadenlisten auch für Methoden-OIDs.

Andere Sicherheitsprobleme des Netzwerktreibers

  • Viele NDIS-Miniporttreiber machen ein Steuergerät mithilfe von NdisRegisterDeviceEx verfügbar. Diejenigen, die dies tun, müssen ihre IOCTL-Handler mit den gleichen Sicherheitsregeln wie ein WDM-Treiber überwachen. Weitere Informationen finden Sie unter Sicherheitsprobleme für E/A-Steuerungscodes.

  • Gut konzipierte NDIS-Miniporttreiber sollten sich nicht darauf verlassen, in einem bestimmten Prozesskontext aufgerufen zu werden, noch sehr eng mit dem Benutzermodus interagieren (wobei IOCTLs & OIDs die Ausnahme sind). Es wäre ein rotes Flag, wenn ein Miniport angezeigt wird, der benutzermode-Handles geöffnet hat, ausgeführte Benutzermoduswartevorgänge oder zugewiesenen Arbeitsspeicher für das Benutzermoduskontingent. Dieser Code sollte untersucht werden.

  • Die meisten NDIS-Miniporttreiber sollten nicht an der Analyse von Paketnutzlasten beteiligt sein. In einigen Fällen kann dies jedoch erforderlich sein. In diesem Fall sollte dieser Code sehr sorgfältig überwacht werden, da der Treiber Daten aus einer nicht vertrauenswürdigen Quelle analysiert.

  • Wie bei der Zuweisung des Kernelmodusspeichers standardmäßig, sollten NDIS-Treiber die entsprechenden NX-Pool-Opt-In-Mechanismen verwenden. In WDK 8 und höher ist die NdisAllocate* Funktionsfamilie ordnungsgemäß aktiviert.