突然ですが、皆さんは、左ハンドルの車を運転したことはありますか?・・・このお話にご興味のある方は本文の最後の【閑話休題】までどうぞ。
さて、今回は、I/O コントロール発行に使うハンドルに必要なアクセス権限についてお話ししようと思います。結論から言いますと、
「CreateFile() の 2nd parameter の accessMode には、 I/O コントロールコードを定義する CTL_CODE() マクロの 4th parameter の Access に指定された権限が必要」
ということです。言い換えると、 I/O コントロールコードの定義に指定されたアクセス権 (Access) を指定せずに、 CreateFile() で取得したハンドルを使って、 DeviceIoControl() でその I/O コントロールを発行しても、アクセスは拒否される、ということです。
このことについて、以前の記事「アプリケーションから SCSI コマンドを発行する方法」でご案内した、 WDK の SPTI サンプルを使った、 I/O コントロールの発行方法を題材にご説明したいと思います。この記事から少し抜粋しますと、まず、以下のように CreateFile() を使って、オープンしたデバイスのハンドルを取得します。
137 fileHandle = CreateFile(string, 138 accessMode, 139 shareMode, 140 NULL, 141 OPEN_EXISTING, 142 0, 143 NULL); |
続いて、この CreateFile() で得たハンドル (fileHandle) を使って、以下の通り、 IOCTL_SCSI_PASS_THROUGH を発行しています。
195 status = DeviceIoControl(fileHandle, 196 IOCTL_SCSI_PASS_THROUGH, 197 &sptwb, 198 sizeof(SCSI_PASS_THROUGH), 199 &sptwb, 200 length, 201 &returned, 202 FALSE); |
ここで、 CreateFile () の 2nd parameter の accessMode について、以下のように書きました。
―――抜粋:ここから―――――――――――――
2nd parameterのaccessModeには、
110 accessMode = GENERIC_WRITE | GENERIC_READ; // default |
のように入れます。
―――抜粋:ここまで―――――――――――――
ここでセットしている、GENERIC_WRITE や GENERIC_READ は、 wdm.h で以下のように定義されています。
#define GENERIC_READ (0x80000000L) #define GENERIC_WRITE (0x40000000L) #define GENERIC_EXECUTE (0x20000000L) #define GENERIC_ALL (0x10000000L) |
それに対し、 ntddscsi.h にある IOCTL_SCSI_PASS_THROUGH の定義を見てみましょう。
#define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) |
4th parameter に指定されている、FILE_READ_ACCESS と FILE_WRITE_ACCESS は、 wdm.h で以下のように定義されています。
#define FILE_ANY_ACCESS 0 #define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS) #define FILE_READ_ACCESS ( 0x0001 ) // file & pipe #define FILE_WRITE_ACCESS ( 0x0002 ) // file & pipe |
I/O マネージャでは、これらの値を比較して、I/O コントロールの定義で指定されたアクセス権を、ハンドルが持っているか ( 適切に CreateFile() で指定しているか ) をチェックしています。もし、ハンドルが適切な権限を持っていないと、STATUS_ACCESS_DENIED が返されます。つまり、 DeviceIoControl() が Win32 エラーコード 5 (= ERROR_ACCESS_DENIED) 「アクセスが拒否されました。」で返ることになります。
このような背景があるため、 SPTI サンプルでは、 IOCTL_SCSI_PASS_THROUGH を発行するのに必要な権限として、 CreateFile() に GENERIC_WRITE | GENERIC_READ を指定しています。
他に例を挙げますと、 CTL_CODE() マクロの 4th parameter で FILE_ANY_ACCESS と定義されている I/O コントロールがあれば、それを発行するための CreateFile() の 2nd parameter は、何も指定していなくても構いません。ただ、実際には、 I/O コントロールの種類によって、必ずしも CTL_CODE の 4th parameter に応じた値だけを指定しておけばよいわけではなく、 wdm.h に別途定義された権限も必要な場合もあります。そのため、既存の I/O コントロールをご利用される場合には、公開ドキュメントをよくご参照いただくことをお勧めいたします。
また、ご自身で、独自の I/O コントロールコードを作成する場合には、 CTL_CODE() マクロの 4th parameter を FILE_ANY_ACCESS にしてしまうと、どのようなアクセス権を持ったハンドルでもアクセスできることになってしまうので、ご注意ください。
このことは、以下のドキュメントにも書かれていますので、ご参照いただければ幸いです。
Windows セキュリティモデル:ドライバー作成者向け情報
http://msdn.microsoft.com/ja-jp/windows/hardware/gg487483
CreateFile function
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
では、また。
――――――――――――――――
【閑話休題】突然ですが、皆さんは、左ハンドルの車を運転したことはありますか?
・・・いいえ、外国車を買ったわけではありません。以前、このブログで、グアムでダイビング体験を���た時に魚に指を噛まれた話をしましたが、その時の話です。入社した際、先輩から「米国に出張する時は、レンタカーを運転する場合があるよ」と言われたので、左ハンドルの車を運転したことがなかった私は、グアムに遊びに行った際、左ハンドルのレンタカーを借り、運転の練習をしました。初めて左ハンドルの車を運転するので、多少の緊張はありましたが、道路の右側を走ればいいということも頭では理解していたつもりでしたし、同乗している友人たちも、右側を走るように念を押してくれていました。しかし、やってしまいました!最初の信号で左折をする際に、対向車線に信号待ちの車がいなかったため、日本での運転の癖でつい対向車線に入ってしまったのです。すぐに真正面に対向車がけたたましいクラクションを鳴らしながら現れて、すごく驚きました。慌てて右側にハンドルを切り、無事にやり過ごすことができましたが、今でも真正面に車が現れた光景は忘れません。出発して早々、大変なミスをしてしまったと思いましたが、その後は、特に大きなミスもなく、順調に行程を進めていました。その日は、グアムを一周する計画でした。グアムは、淡路島と同じくらいの面積だそうで、車で一周するのは半日もかからないという話を聞いていました。おまけに、海岸沿いは基地があるなど、民間人が行ける道がなく、当初想定していたよりも内陸部を走ることになり、早く帰ることができるはずでした。しかし、そうはいきませんでした。とある小高い山の頂上が海辺を一望できる観光場所になっており、そこで車を止めたところ、エンストか何かで、二度と動かなくなってしまいました。周りを見渡しても、観光地の説明の看板はあっても、公衆電話もないようなところでした。しかし、たまたま友人が、自分の携帯を海外で使えるように手配してきてくれていたおかげで、何とかレンタカー会社に連絡がつきました。しかし、結構レンタカー会社から離れたところまで運転していたこともあり、代わりの車が到着するまで数時間かかりました。それに乗って残りの行程をこなしましたが、慣れない運転に加え、一日に 2 つも上記のようなイベントが起こると、さすがに全員くたくたになりました。ちなみに、その後、出張でもプライベートでも、左ハンドルの車を運転したことはありません。皆さんも、予定もないのに、海外で左ハンドルの車を運転する際には、ご注意ください。