Meilleures pratiques en matière de sécurité des pilotes Windows pour les développeurs de pilotes

Cette rubrique résume les modèles de développement non sécurisés qui peuvent conduire à l’exploitation et à l’abus de votre code de pilote Windows. Cette rubrique fournit des recommandations de développement et des exemples de code. Le fait de suivre ces bonnes pratiques permet d’améliorer la sécurité de l’exécution d’un comportement privilégié dans le noyau Windows.

Vue d’ensemble du comportement des pilotes non sécurisés

Bien qu’il soit attendu que les pilotes Windows effectuent un comportement à privilèges élevés en mode noyau, il est inacceptable de ne pas effectuer de vérifications de sécurité et d’ajouter des contraintes sur le comportement privilégié. Le programme de compatibilité matérielle Windows (WHCP), anciennement WHQL, exige que les nouvelles soumissions de pilotes soient conformes à cette exigence.

Les exemples de comportements non sécurisés et dangereux incluent, sans s’y limiter, les éléments suivants :

Fournir la possibilité de lire et d’écrire des fichiers MSR

Amélioration de la sécurité de la lecture à partir des fichiers MSR

Dans le premier exemple ReadMsr, le pilote autorise un comportement non sécurisé en autorisant la lecture arbitraire de tous les registres. Cela peut entraîner des abus par des processus malveillants en mode utilisateur.

Func ReadMsr(int dwMsrIdx) 
{
	int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
	return value;
}

Si votre scénario nécessite la lecture à partir des fichiers MSR, le pilote doit toujours case activée que le registre à lire est limité à l’index ou à la plage attendus. Voici deux exemples d’implémentation de l’opération de lecture sécurisée.

Func ConstrainedReadMsr(int dwMsrIdx) 
{
    int value = 0;
    if (dwMsrIdx == expected_index) // Blocks from reading anything
    {
        value = __readmsr(dwMsrIdx); // Can only read the expected MSR
    }
    else
    {
        return error;
    }
    return value;
}

// OR

Func ConstrainedReadMsr(int dwMsrIdx) 
{
    int value = 0;
    if (min_range <= dwMsrIdx <= max_range) // Blocks from reading anything
    {
        value = __readmsr(dwMsrIdx); // Can only from the expected range of MSRs
    }
    else
    {
        return error;
    }
    return value;
}

Amélioration de la sécurité de l’écriture dans les fichiers MSR

Dans le premier exemple WriteMsr, le pilote autorise un comportement non sécurisé en autorisant l’écriture arbitraire de tous les registres. Cela peut entraîner des abus par des processus malveillants pour élever les privilèges en mode utilisateur et écrire dans tous les fichiers MSR.

Func WriteMsr(int dwMsrIdx) 
{
	int value = __writemsr(dwMsrIdx); // Unsafe, can write to any MSR
	return value;
}

Si votre scénario nécessite l’écriture dans des fichiers MSR, le pilote doit toujours case activée que le registre dans lequel écrire est limité à l’index ou à la plage attendu. Voici deux exemples d’implémentation de l’opération d’écriture sécurisée.

Func ConstrainedWriteMsr(int dwMsrIdx) 
{
    int value = 0;
    if (dwMsrIdx == expected_index) // Blocks from reading anything
    {
        value = __writemsr(dwMsrIdx); // Can only write to the expected constrained MSR
    }
    else
    {
        return error;
    }
    return value;
}

// OR

Func ConstrainedWriteMSR(int dwMsrIdx) 
{
    int value = 0;
    if (min_range <= dwMsrIdx <= max_range) // Blocks from reading anything
    {
        value = __writemsr(dwMsrIdx); // Can only write to the expected constrained MSR
    }
    else
    {
        return error;
    }
    return value;
}

Fournir la possibilité de lire et d’écrire sur l’entrée et la sortie du port

Amélioration de la sécurité de la lecture à partir des E/S de port

La prudence doit être utilisée lorsque vous fournissez la possibilité de lire sur port d’entrée/sortie (E/S). Cet exemple de code n’est pas sécurisé.

Func ArbitraryInputPort(int inPort) 
{
	dwResult = __indword(inPort); // Unsafe, allows for arbitrary reading from Input Port
	return dwResult; 
}

Pour éviter l’abus et l’exploitation du pilote, le port d’entrée attendu doit être limité à la limite d’utilisation requise.

Func ConstrainedInputPort(int inPort) 
{
	// The expected input port must be constrained to the required usage boundary to prevent abuse
	if(inPort == expected_InPort)
	{
		dwResult = __indword(inPort);
	}
	else
	{
		return error; 
	}
	return dwResult; 
}

Amélioration de la sécurité de l’écriture dans les E/S de port

La prudence doit être utilisée lorsque vous fournissez la possibilité d’écrire dans l’entrée/sortie du port (E/S). Cet exemple de code n’est pas sécurisé.

Func ArbitraryOutputPort(int outPort, DWORD dwValue) 
{
	__outdword(OutPort, dwValue); // Unsafe, allows for arbitrary writing to Output Port
}

Pour éviter l’abus et l’exploitation du pilote, le port d’entrée attendu doit être limité à la limite d’utilisation requise.

Func ConstrainedOutputPort(int outPort, DWORD dwValue) 
{
	// The expected output port must be constrained to the required usage boundary to prevent abuse
	if(outPort == expected_OutputPort)
	{
		__outdword(OutPort, dwValue); // checks on InputPort
	}
	else
	{
		return error; 
	}
}

Fournir la possibilité de lire et d’écrire de la mémoire du noyau, physique ou de l’appareil

Amélioration de la sécurité de Memcpy

Cet exemple de code montre une utilisation non contrainte et non sécurisée de la mémoire physique.

Func ArbitraryMemoryCopy(src, dst, length) 
{
	memcpy(dst, src, length); // Unsafe, can read and write anything from physical memory
}

Si votre scénario nécessite la lecture et l’écriture de mémoire de noyau, physique ou de périphérique, le pilote doit toujours case activée que la source et les destinations sont limitées aux index ou plages attendus.

Func ConstrainedMemoryCopy(src, dst, length) 
{
	// valid_src and valid_dst must be constrained to required usage boundary to prevent abuse
	if(src == valid_Src && dst == valid_Dst)
	{
		memcpy(dst, src, length); 
	}
	else
	{
		return error;
	}
}

Amélioration de la sécurité de ZwMapViewOfSection

L’exemple suivant illustre la méthode non sécurisée et incorrecte pour lire et écrire de la mémoire physique à partir du mode utilisateur à l’aide des API ZwOpenSection et ZwMapViewOfSection.

Func ArbitraryMap(PHYSICAL_ADDRESS Address)
{
	ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
	ZwMapViewOfSection(hSection, -1, 0, 0, 0, Address, ...);
}

Pour éviter l’abus et l’exploitation du comportement en lecture/écriture du pilote par des processus malveillants en mode utilisateur, le pilote doit valider l’adresse d’entrée et limiter le mappage de mémoire uniquement à la limite d’utilisation requise pour le scénario.

Func ConstrainedMap(PHYSICAL_ADDRESS paAddress)
{
	// expected_Address must be constrained to required usage boundary to prevent abuse
	if(paAddress == expected_Address)
	{
		ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
		ZwMapViewOfSection(hSection, -1, 0, 0, 0, paAddress, ...);
	}
	else
	{
		return error;
	}
}

Amélioration de la sécurité de MmMapLockedPagesSpecifyCache

L’exemple suivant illustre la méthode non sécurisée et incorrecte pour lire et écrire de la mémoire physique à partir du mode utilisateur à l’aide des API MmMapIoSpace, IoAllocateMdl et MmMapLockedPagesSpecifyCache.

Func ArbitraryMap(PHYSICAL_ADDRESS paAddress)
{
	lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
	pMdl = IoAllocateMdl( lpAddress, ...);
	MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
}

Pour éviter l’abus et l’exploitation du comportement en lecture/écriture du pilote par des processus malveillants en mode utilisateur, le pilote doit valider l’adresse d’entrée et limiter le mappage de mémoire uniquement à la limite d’utilisation requise pour le scénario.

Func ConstrainedMap(PHYSICAL_ADDRESS paAddress)
{
	// expected_Address must be constrained to required usage boundary to prevent abuse
	if(paAddress == expected_Address && qwSize == valid_Size) 
	{
		lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
		pMdl = IoAllocateMdl( lpAddress, ...);
		MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
	}
	else
	{
		return error;
	}
}

Voir aussi

Liste de contrôle de sécurité des pilotes