Condividi tramite


Problemi di sicurezza per i driver di rete

Per una discussione generale sulla scrittura di driver sicuri, vedere Creazione di driver affidabili Kernel-Mode.

Oltre alle procedure di codifica sicure e alle indicazioni generali sui driver di dispositivo, i driver di rete devono eseguire le operazioni seguenti per migliorare la sicurezza:

  • Tutti i driver di rete devono convalidare i valori letti dal Registro di sistema. In particolare, il chiamante di NdisReadConfiguration o NdisReadNetworkAddress non deve fare ipotesi sui valori letti dal Registro di sistema e deve convalidare ogni valore del Registro di sistema letto. Se il chiamante di NdisReadConfiguration determina che un valore è fuori dai limiti, deve usare invece un valore predefinito. Se il chiamante di NdisReadNetworkAddress determina che un valore è fuori dai limiti, deve usare invece l'indirizzo MAC (Permanent Medium Access Control) o un indirizzo predefinito.

Problemi specifici dell'OID

Linee guida per la sicurezza delle query OID

La maggior parte degli OID di query può essere rilasciata da qualsiasi applicazione usermode nel sistema. Seguire queste linee guida specifiche per gli OID delle query.

  1. Verificare sempre che le dimensioni del buffer sia sufficientemente grande per l'output. Qualsiasi gestore OID di query senza un controllo delle dimensioni del buffer di output presenta un bug di sicurezza.

    if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG);
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Scrivere sempre un valore corretto e minimo in BytesScritti. Si tratta di un flag rosso da assegnare oid->BytesWritten = oid->InformationBufferLength come nell'esempio seguente.

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

    Il sistema operativo copierà i BytesWritten in un'applicazione in modalità utente. Se BytesWritten è maggiore del numero di byte effettivamente scritti dal driver, il sistema operativo potrebbe finire per copiare la memoria del kernel non inizializzata in modalità usermode, che costituirebbe una vulnerabilità di divulgazione di informazioni. Usare invece codice simile al seguente:

    oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);
    
  3. Non leggere mai i valori dal buffer. In alcuni casi, il buffer di output di un OID viene mappato direttamente in un processo di modalità utente ostile. Il processo ostile può modificare il buffer di output dopo averlo scritto. Ad esempio, il codice seguente può essere attaccato, perché un utente malintenzionato può modificare NumElements dopo la scrittura:

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

    Per evitare la lettura dal buffer, mantenere una copia locale. Ad esempio, per correggere l'esempio precedente, introdurre una nuova variabile dello stack:

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

    Con questo approccio, il ciclo for legge nuovamente dalla variabile num dello stack del driver e non dal relativo buffer di output. Il driver deve anche contrassegnare il buffer di output con la volatile parola chiave , per impedire al compilatore di annullare automaticamente questa correzione.

Impostare le linee guida per la sicurezza OID

La maggior parte dei set di OID può essere emessa da un'applicazione in modalità utente eseguita nei gruppi di sicurezza Administrators o System. Anche se queste sono in genere applicazioni attendibili, il driver miniport non deve ancora consentire il danneggiamento della memoria o l'inserimento di codice kernel. Seguire queste regole specifiche per set OID:

  1. Verificare sempre che l'input sia sufficientemente grande. Qualsiasi gestore di set OID senza un controllo delle dimensioni del buffer di input presenta una vulnerabilità di sicurezza.

    if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) {
        return NDIS_STATUS_INVALID_LENGTH;
    }
    
  2. Ogni volta che si convalida un OID con un offset incorporato, è necessario verificare che il buffer incorporato si trovi all'interno del payload OID. Questo richiede diversi controlli. Ad esempio, OID_PM_ADD_WOL_PATTERN può fornire un modello incorporato, che deve essere controllato. La convalida corretta richiede il controllo:

    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> non esegue l'overflow

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

      Questi due controlli possono essere combinati usando codice simile all'esempio seguente:

      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 + PatternOffset> + Pattern-PatternLength> non esegue l'overflow

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

Linee guida sulla sicurezza degli OID dei metodi

Gli OID possono essere emessi da un'applicazione usermode in esecuzione nei gruppi di sicurezza Administrators o System. Sono una combinazione di un Set e una Query, quindi entrambi gli elenchi di linee guida precedenti si applicano anche agli OIDs di metodo.

Altri problemi di sicurezza dei driver di rete

  • Molti driver miniport NDIS espongono un dispositivo di controllo usando NdisRegisterDeviceEx. Coloro che fanno ciò devono revisionare i gestori IOCTL, seguendo tutte le stesse regole di sicurezza di un driver WDM. Per altre informazioni, vedere Problemi di sicurezza per i codici di controllo di I/O.

  • I driver miniport NDIS ben progettati non devono basarsi sull'essere chiamati in un contesto di processo specifico, né interagire molto con la modalità utente (con IOCTLs e OID che rappresentano l'eccezione). Sarebbe un segnale di allarme vedere un miniport che ha aperto handle in modalità utente, eseguito attese in modalità utente o allocato memoria rispetto alla quota usermode. Il codice deve essere analizzato.

  • La maggior parte dei driver miniport NDIS non deve essere coinvolta nell'analisi dei payload dei pacchetti. In alcuni casi, tuttavia, potrebbe essere necessario. In tal caso, questo codice deve essere controllato con molta attenzione, perché il driver analizza i dati da un'origine non attendibile.

  • Come standard quando si alloca la memoria in modalità kernel, i driver NDIS devono usare i meccanismi appropriati per il pool NX Opt-In. In WDK 8 e versioni successive, la famiglia di funzioni NdisAllocate* è correttamente integrata.