Problèmes de sécurité pour les pilotes réseau
Pour une discussion générale sur l’écriture de pilotes sécurisés, consultez Création de pilotes fiables Kernel-Mode.
Au-delà des pratiques de codage sécurisées et des instructions générales relatives aux pilotes de périphérique, les pilotes réseau doivent effectuer les actions suivantes pour améliorer la sécurité :
- Tous les pilotes réseau doivent valider les valeurs qu’ils lisent à partir du Registre. Plus précisément, l’appelant de NdisReadConfiguration ou NdisReadNetworkAddress ne doit pas faire d’hypothèses sur les valeurs lues à partir du Registre et doit valider chaque valeur de Registre qu’il lit. Si l’appelant de NdisReadConfiguration détermine qu’une valeur est hors limites, il doit utiliser une valeur par défaut à la place. Si l’appelant de NdisReadNetworkAddress détermine qu’une valeur est hors limites, il doit utiliser l’adresse MAC (Permanent Medium Access Control) ou une adresse par défaut à la place.
Problèmes spécifiques à l’OID
Un pilote miniport, dans ses fonctions MiniportOidRequest ou MiniportCoOidRequest , doit valider toute valeur d’identificateur d’objet (OID) que le pilote est invité à définir. Si le pilote détermine que la valeur à définir est hors limites, la demande de jeu doit échouer. Pour plus d’informations sur les identificateurs d’objet, consultez Obtention et définition des informations sur le pilote Miniport et Prise en charge NDIS pour WMI.
Si la fonction MiniportOidRequest d’un pilote intermédiaire ne transmet pas d’opération définie à un pilote miniport sous-jacent, la fonction doit valider la valeur OID. Pour plus d’informations, consultez Opérations de définition et de requête de pilote intermédiaires.
Instructions de sécurité OID de requête
La plupart des OID de requête peuvent être émis par n’importe quelle application usermode sur le système. Suivez ces instructions spécifiques pour les OID de requête.
Vérifiez toujours que la taille de la mémoire tampon est suffisamment grande pour la sortie. Tout gestionnaire d’OID de requête sans taille de mémoire tampon de sortie case activée présente un bogue de sécurité.
if (oid->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(ULONG)) { oid->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG); return NDIS_STATUS_INVALID_LENGTH; }
Écrivez toujours une valeur correcte et minimale dans BytesWritten. Il s’agit d’un indicateur rouge à affecter
oid->BytesWritten = oid->InformationBufferLength
comme le fait l’exemple suivant.// ALWAYS WRONG oid->DATA.QUERY_INFORMATION.BytesWritten = DATA.QUERY_INFORMATION.InformationBufferLength;
Le système d’exploitation copiera octets écrits vers une application usermode. Si BytesWritten est supérieur au nombre d’octets que le pilote a réellement écrit, le système d’exploitation peut finir par copier la mémoire du noyau non initialisée vers le mode utilisateur, ce qui constituerait une vulnérabilité de divulgation d’informations. Utilisez plutôt un code semblable à ce qui suit :
oid->DATA.QUERY_INFORMATION.BytesWritten = sizeof(ULONG);
Ne lisez jamais les valeurs à partir de la mémoire tampon. Dans certains cas, la mémoire tampon de sortie d’un OID est mappée directement dans un processus usermode hostile. Le processus hostile peut modifier votre mémoire tampon de sortie une fois que vous y avez écrit. Par exemple, le code ci-dessous peut être attaqué, car un attaquant peut modifier NumElements après son écriture :
output->NumElements = 4; for (i = 0 ; i < output->NumElements ; i++) { output->Element[i] = . . .; }
Pour éviter la lecture à partir de la mémoire tampon, conservez une copie locale. Par exemple, pour corriger l’exemple ci-dessus, introduisez une nouvelle variable de pile :
ULONG num = 4; output->NumElements = num; for (i = 0 ; i < num; i++) { output->Element[i] = . . .; }
Avec cette approche, la boucle for lit à partir de la variable
num
de pile du pilote et non de sa mémoire tampon de sortie. Le pilote doit également marquer la mémoire tampon de sortie avec levolatile
mot clé, pour empêcher le compilateur d’annuler ce correctif en mode silencieux.
Définir les instructions de sécurité OID
La plupart des OID set peuvent être émis par une application usermode s’exécutant dans les groupes de sécurité Administrateurs ou Système. Bien qu’il s’agisse d’applications généralement approuvées, le pilote miniport ne doit toujours pas autoriser l’altération de la mémoire ou l’injection de code du noyau. Suivez ces règles spécifiques pour définir des OID :
Vérifiez toujours que l’entrée est suffisamment grande. Tout gestionnaire de jeux d’OID sans taille de mémoire tampon d’entrée case activée présente une vulnérabilité de sécurité.
if (oid->DATA.SET_INFORMATION.InformationBufferLength < sizeof(ULONG)) { return NDIS_STATUS_INVALID_LENGTH; }
Chaque fois que vous validez un OID avec un décalage incorporé, vous devez vérifier que la mémoire tampon incorporée se trouve dans la charge utile OID. Cela nécessite plusieurs vérifications. Par exemple, OID_PM_ADD_WOL_PATTERN peut fournir un modèle incorporé qui doit être vérifié. Une validation correcte nécessite une vérification :
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; }
Pattern-PatternOffset> + Pattern-PatternSize> ne dépasse pas
ULONG TotalSize = 0; if (!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternSize, &TotalSize) || TotalSize > InformationBufferLength) { return NDIS_STATUS_INVALID_LENGTH; }
Ces deux vérifications peuvent être combinées à l’aide de code comme dans l’exemple suivant :
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; }
InformationBuffer + Pattern-PatternOffset> + Pattern-PatternLength> ne déborde pas
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; }
Pattern-PatternOffset> + Pattern-PatternLength <>= InformationBufferSize
ULONG TotalSize = 0; if(!NT_SUCCESS(RtlUlongAdd(Pattern->PatternOffset, Pattern->PatternLength, &TotalSize) || TotalSize > InformationBufferLength)) { return NDIS_STATUS_INVALID_LENGTH; }
Instructions de sécurité OID de méthode
Les OID de méthode peuvent être émis par une application usermode s’exécutant dans les groupes de sécurité Administrateurs ou Système. Comme il s’agit d’une combinaison d’un jeu et d’une requête, les deux listes d’instructions précédentes s’appliquent également aux OID de méthode.
Autres problèmes de sécurité des pilotes réseau
De nombreux pilotes miniportS NDIS exposent un périphérique de contrôle à l’aide de NdisRegisterDeviceEx. Ceux qui effectuent cette opération doivent auditer leurs gestionnaires IOCTL, avec toutes les mêmes règles de sécurité qu’un pilote WDM. Pour plus d’informations, consultez Problèmes de sécurité pour les codes de contrôle d’E/S.
Les pilotes de miniport NDIS bien conçus ne doivent pas s’appuyer sur l’appel dans un contexte de processus particulier, ni interagir très étroitement avec le mode utilisateur (les IOCTL & les OID étant l’exception). Il s’agirait d’un indicateur rouge pour voir un miniport qui a ouvert des handles de mode utilisateur, effectué des attentes de mode utilisateur ou alloué de la mémoire sur le quota de mode utilisateur. Ce code doit être examiné.
La plupart des pilotes miniportS NDIS ne doivent pas être impliqués dans l’analyse des charges utiles de paquets. Dans certains cas, cependant, cela peut être nécessaire. Si c’est le cas, ce code doit être audité très soigneusement, car le pilote analyse les données d’une source non approuvée.
Comme il est standard lors de l’allocation de mémoire en mode noyau, les pilotes NDIS doivent utiliser les mécanismes de pool NX Opt-In appropriés. Dans WDK 8 et versions ultérieures, la
NdisAllocate*
famille de fonctions est correctement choisie.