デバイス オブジェクトのセキュリティ その2

 

久方ぶりです。まさかたです。
前回の記事で、デバイス オブジェクトのセキュリティという観点から、Security Descriptorの記述についてお話ししました。その時に、これ以外にも、デバイスオブジェクトをセキュリティで保護するためにドライバー側でできることとして、デバイスの名前空間の保護や、デバイスオープンの排他制御といったものがあることに触れましたが、今回はその名前空間の保護と排他制御の 2 点について簡単にお話ししたいと思います。

1. デバイスの名前空間の保護
デバイスの名前空間とは、例えば DeviceName という名前のデバイス オブジェクトがあった場合、CreateFile() で指定できるファイル パスの先頭に、”\Device\DeviceName” を持つパス全体を指します。
CreateFile() で名前付きのデバイス オブジェクトをオープンする場合、単純な “\Device\DeviceName” の他に、実は名前空間上にあるディレクトリやファイルを指すパスも指定することができ、この場合も、デバイスオブジェクトをオープンすることができてしまいます。

表 ファイルパスによるオブジェクトとセキュリティチェックの主体の違い

ファイルパス (例)

オブジェクト

既定のセキュリティチェックの主体

(1)

\Device\DeviceName

DeviceName のデバイス オブジェクト

I/O マネージャー

(2)

\Device\DeviceName\

DeviceName の最上位 ディレクトリ オブジェクト

ドライバー

(3)

\Device\DeviceName\File

DeviceName 名前空間上の ファイル オブジェクト

ドライバー

デバイスの名前空間をサポートするドライバーでは、(2) や (3) のようなディレクトリやファイルのオープンにも対応していて、個々のオブジェクトに応じた何らかの処理をすることになります。ただ、多くドライバーではそのような処理は行わず、名前空間をサポートしない側に入るかと思います。そのため、ほとんどのドライバーで関心があるのは、デバイス オブジェクトそのものを指す “\Device\DeviceName” だけだと思います。
しかし、ここで注意しないといけないのは、既定では、(1) であれば I/O マネージャーがデバイス オブジェクトに設定された Security Descriptor を基に、デバイス オブジェクトへのアクセスの可否を判断してくれますが、(2), (3) の場合は、I/O マネージャーではそのようなチェックは行ってくれないため、ドライバー自身がその判断を行う必要があることです。
そのため、デバイス オブジェクトに適切な Security Descriptor を設定していても、もし (2) や (3) のようなオープン リクエスト (IRP_MJ_CREATE) を受けた時に、無条件にリクエストを成功させると、本来ならアクセスする権限がないユーザーでも、デバイス オブジェクトへのアクセスが可能となり、セキュリティ ホールとなり得ますので、注意が必要です。
上記の点に対応するための方法として、以下では、 (ア) FILE_DEVICE_SECURE_OPEN フラグを使用した方法と、 (イ) FILE_DEVICE_SECURE_OPEN フラグを使用しない場合の方法をご紹介します。

(ア) FILE_DEVICE_SECURE_OPEN フラグによる名前空間の保護
デバイス オブジェクトに FILE_DEVICE_SECURE_OPEN フラグを設定すると、(2), (3) の場合でも、自動的にデバイス オブジェクトの Security Descriptorを適用し、I/O マネージャーがセキュリティのチェックを行ってくれるようになります。FILE_DEVICE_SECURE_OPEN フラグは、ドライバーの INF ファイル内の AddReg ディレクティブ の DeviceCharacteristis エントリで、もしくは、IoCreateDevice() 関数、または、IoCreateDeviceSecure() 関数の引数で指定できます。

(イ) FILE_DEVICE_SECURE_OPEN フラグを使用しない場合の名前空間の保護
FILE_DEVICE_SECURE_OPEN を設定していない場合、そのままでは、(2)、(3) の場合は、デバイス オブジェクトに不正にアクセス可能となるため、ドライバー側の責任でリクエストを失敗させる必要があります。つまり、デバイス オブジェクトのオープン要求のみを許可し、それ以外のデバイスの名前空間上のディレクトリ オブジェクトや、ファイル オブジェクトへのアクセスは失敗させることになります。
両者の区別は、下記のように IRP_MJ_CREATE リクエストを受け取った時のファイル名の長さをチェックすることで可能です。

if ( IrpSp->FileObject->Filename.Length != 0 ) return STATUS_ACCESS_DENIED;

つまり、ファイルの長さが 0 以外の場合には、ディレクトリやファイルオブジェクトへのアクセスと判断できますので、このようなリクエストを失敗させます。

[参考]
- How safe is your device namespace?
  <https://msdn.microsoft.com/en-us/library/windows/hardware/gg463228.aspx>
- Controlling Device Namespace Access
  <https://msdn.microsoft.com/en-us/library/windows/hardware/ff542068(v=vs.85).aspx>
- Specifying Device Characteristics
  <https://msdn.microsoft.com/en-us/library/windows/hardware/ff563818(v=vs.85).aspx>
- DEVICE_OBJECT structure
  <https://msdn.microsoft.com/en-us/library/windows/hardware/ff543147(v=vs.85).aspx>

2. オープンの排他制御
デバイスのオープンは、様々なアプリケーションから同時に行うことができますが、一度に複数のハンドルをオープン可能としたくないデバイスもあるかと思います。そのような場合、以下の2つを実施することで、デバイス オブジェクトの重複したオープンを防ぐことができます。

    (1) DO_EXCLUSIVE の設定
(2) RelatedFileObject のチェック

(1) DO_EXCLUSIVE の設定
デバイス オブジェクトの Flags フィールドに DO_EXCLUSIVE ビットを設定することで、排他性を実現します。この設定は、以下のいずれかの設定で行えます。

    ① INF ファイルの AddReg ディレクティブ内で設定
IoCreateDevice() / IoCreateDeviceSecure() 関数呼び出し時に設定
IoCreateDevice() の Exclusive に TRUE を設定しても効果はありませんのでご注意ください。名前付きのデバイス オブジェクトの場合、強制的に Exclusive Access となります。

(2) RelatedFileObject のチェック
(1) で DO_EXCLUSIVE フラグをセットした場合でも、同じハンドルなら追加のオープンはできてしまうので、これを見分ける必要があります。これは、IRP_MJ_CREATE リクエストの RelatedFileObject フィールドの値が NULL かどうかで見分けることができ、NULL 以外の場合には、別のハンドルが既に開いていることを示しているので、ドライバーは要求を失敗させる必要があります。

- Specifying Exclusive Access to Device Objects
  <https://msdn.microsoft.com/en-us/library/windows/hardware/ff563827(v=vs.85).aspx>

それではまた。