Windows ドライバーの開発者向けセキュリティ ベスト プラクティス
このトピックでは、Windows ドライバー コードの悪用や不正使用につながる可能性がある安全でない開発パターンについて説明します。 このトピックでは、開発に関する推奨事項とコード サンプルを取り上げています。 これらのベスト プラクティスに従うことで、Windows カーネルで特権動作を実行する際の安全性を高めることができます。
安全でないドライバーの動作の概要
Windows ドライバーがカーネル モードで、高い特権レベルが要求される動作を実行するのは当然ではありますが、セキュリティ チェックを実行せず、特権を与えられた振る舞いに制約を加えない、ということはセキュリティ上、受け入れられません。 Windows ハードウェア互換性プログラム (WHCP) (以前の WHQL) では、新たに申請されるドライバーに、この要件への準拠を求めています。
安全でない危険な動作には次のケースが該当しますが、あくまで一例であり、これらに限定されません。
- 任意の MSR (マシン固有のレジスタ: Machine Specific Register) の読み取りと書き込みを行う機能を提供する
- ポートの入出力に対する読み取りと書き込みを行う機能を提供する
- カーネル メモリ、物理メモリ、デバイス メモリの読み取りと書き込みを行う機能を提供する
MSR の読み取りと書き込みの機能を提供する
MSR からの読み取りのセキュリティを強化する
1 つ目の ReadMsr は、あらゆるレジスタを自由に読み取ることができるという、安全でない動作をドライバーに許可する例です。 ユーザー モードの悪意のあるプロセスによる不正使用につながる可能性があります。
Func ReadMsr(int dwMsrIdx)
{
int value = __readmsr(dwMsrIdx); // Unsafe, can read from any MSR
return value;
}
実際のシナリオで MSR からの読み取りが必要な場合、ドライバーは常に、読み取るレジスタが、想定されるインデックスまたは範囲に制限されていることをチェックする必要があります。 安全な読み取り操作を実装する 2 つの例を次に示します。
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;
}
MSR への書き込みのセキュリティを強化する
1 つ目の WriteMsr は、あらゆるレジスタに自由に書き込むことができるという、安全でない動作をドライバーに許可する例です。 悪意のあるプロセスによって不正に使用され、ユーザー モードで特権が昇格されて、すべての MSR に書き込まれる可能性があります。
Func WriteMsr(int dwMsrIdx)
{
int value = __writemsr(dwMsrIdx); // Unsafe, can write to any MSR
return value;
}
実際のシナリオで MSR への書き込みが必要な場合、ドライバーは常に、書き込み先のレジスタが、想定されるインデックスまたは範囲に制限されていることをチェックする必要があります。 安全な書き込み操作を実装する 2 つの例を次に示します。
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;
}
ポートの入出力に対する読み取りと書き込みを行う機能を提供する
ポート IO からの読み取りのセキュリティを強化する
ポート入出力 (I/O) の読み取り機能を提供する場合は注意が必要です。 このコード例は安全ではありません。
Func ArbitraryInputPort(int inPort)
{
dwResult = __indword(inPort); // Unsafe, allows for arbitrary reading from Input Port
return dwResult;
}
ドライバーの不正使用や悪用を防ぐには、想定される入力ポートを、使用上必要な境界に制限する必要があります。
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;
}
ポート IO への書き込みのセキュリティを強化する
ポート入出力 (I/O) への書き込み機能を提供する場合は注意が必要です。 このコード例は安全ではありません。
Func ArbitraryOutputPort(int outPort, DWORD dwValue)
{
__outdword(OutPort, dwValue); // Unsafe, allows for arbitrary writing to Output Port
}
ドライバーの不正使用や悪用を防ぐには、想定される入力ポートを、使用上必要な境界に制限する必要があります。
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;
}
}
カーネル メモリ、物理メモリ、デバイス メモリの読み取りと書き込みを行う機能を提供する
Memcpy のセキュリティを強化する
このサンプル コードは、制約がなく安全でない物理メモリの使用を示しています。
Func ArbitraryMemoryCopy(src, dst, length)
{
memcpy(dst, src, length); // Unsafe, can read and write anything from physical memory
}
実際のシナリオでカーネル メモリ、物理メモリ、デバイス メモリの読み取りと書き込みが必要な場合、ドライバーは常に、コピー元とコピー先が、想定されるインデックスまたは範囲に制約されていることをチェックする必要があります。
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;
}
}
ZwMapViewOfSection のセキュリティを強化する
次の例は、ZwOpenSection API と ZwMapViewOfSection API を使用して、ユーザー モードから物理メモリを読み書きする安全でない不適切な方法を示しています。
Func ArbitraryMap(PHYSICAL_ADDRESS Address)
{
ZwOpenSection(&hSection, ... ,"\Device\PhysicalMemory");
ZwMapViewOfSection(hSection, -1, 0, 0, 0, Address, ...);
}
悪意のあるユーザー モード プロセスによるドライバーの読み取り/書き込み動作の不正使用と悪用を防ぐには、ドライバーで入力アドレスを検証し、シナリオに必要な使用境界にのみメモリ マッピングを制限する必要があります。
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;
}
}
MmMapLockedPagesSpecifyCache のセキュリティを強化する
次の例は、MmMapIoSpace、IoAllocateMdl、MmMapLockedPagesSpecifyCache の各 API を使用して、ユーザー モードから物理メモリを読み書きする安全でない不適切な方法を示しています。
Func ArbitraryMap(PHYSICAL_ADDRESS paAddress)
{
lpAddress = MmMapIoSpace(paAddress, qwSize, ...);
pMdl = IoAllocateMdl( lpAddress, ...);
MmMapLockedPagesSpecifyCache(pMdl, UserMode, ... );
}
悪意のあるユーザー モード プロセスによるドライバーの読み取り/書き込み動作の不正使用と悪用を防ぐには、ドライバーで入力アドレスを検証し、シナリオに必要な使用境界にのみメモリ マッピングを制限する必要があります。
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;
}
}
参照
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示