ShellExecuteの罠
罠大杉ですね。
先日WinFileのWinExecの話にちらっと触れたので、GUIからの行儀良いプログラムの実行方法のお話です。IssuesにLoadLibraryのセキュリティ取り扱いに注意とか書かれているけど、ガン無視なのでWinFile3.0時代のセキュリティなのでしょう。
まず歴史
ファイラーってご存知ですか?PC-88/98時代のプロテクトを外すカタログファイルのことではありません。
DOS系のコマンドライン
CUIです。指定したファイルの拡張子は勝手に付与。
ファイラー
コマンドラインをGUI化したものです。指定したファイルの拡張子は勝手に付与します。ちなみに、欧米ではマイナーですが日本やロシアではメジャーでした。
Explorer
GUIで指定したファイルを実行するものです。指定したファイルの拡張子を勝手に付与したりしません。NTや2000までは良くわかりませんが、Windows XPはこれ。多分、Windows95 OSR2あたりからこれなのじゃないかな。
そんなShellExecuteで大丈夫か?問題あり
UACの導入以来、プログラム実行はCreateProcessから権限昇格可能なShellExecuteに移ってきました。ShellExecute便利ですよね。けど、使い方のお作法ご存知ですか?というか、はっきり言えばShellExecuteの引数で許されるプログラムは固定の絶対パスです。UIから指定されたファイルをShellExecuteで実行するなんて論外です、蛮勇です、死にたいんでうかワレです。ShellExecuteExを使いましょう
よくあるShellExecuteのよろしくない使い方
このコード片はWinFileからもってきたものです。繰り返しますが懐古主義で過去の仕様を再現したプログラムなのでこれによる動作は仕様です。脆弱性ではないはずです。
よくあるコード片
DWOR ret = (DWORD)ShellExecute(hwnd, bRunAs? L"runas" : NULL, lPath, lpParams, lpDir, bLoadIt ? SW_SHORWINNOACTIVE : SW_SHOWNORMAL);
実はこれって、怖いんです。これってファイラーなのです。
lPathが"hoge"だとすると、"hoge.exe"みたいな実行可能ファイル(拡張子はEXTPATHのもの。他にBATやCMDなども検索対象)があるとそちらを実行してしまいます。これはCreateProcessと一緒です。
そこでShellExecuteExさん登場
エクストラはすごいんです。先の例だとこんな書き方ができます。
#define STRICT_TYPED_ITEMIDS
#include <shlobj.h>
DWORD ret;
SHELLINFO info;
ZeroMemory(&info, sizeof(info));
info.cbSize = sizeof(info);
info.hwnd = hwnd;
info.lpVerb = bRunAs ? L"runas" : NULL;
info.lpFile = lpPath
info.lpParameters = lpParms;
info.lpDirectory = lpDir;
info.nShow = bLoadIt ? SW_SHOWINNOACTIVE : SW_SHOWNORMAL;
info.lpIDList = SHSimpleIDListFromPath(lpPath)
info.fMask = SEE_MASK_INVOKEIDLIST; // includes SEE_MASK_IDLIST
if (ShellExecuteEx(&info)) {
ret = (DWORD)info.hInstApp;
}
else {
ret = GetLastError();
}
ちょっと長ったらしいですね。エラーハンドリングは手抜きしてます*1。重要なのは、fMaskとlpIDListの指定。この組み合わせならlpFileより優先されます。この書き方では"hoge"に対応するファイルのITEMIDLISTを取得してセットしているのがミソ。こうすれば別のITEMIDを持った"hoge"と"hoge.exe"を同一視するような情弱なことにはなりません。もちろん?info.lpIDListの指定を忘れるとShellExecuteと同じような情弱な動きをするので注意。そんなミスしないでしょうけどね。
みんな使ってねーだろ
そう思うでしょ。そう思っていた時代もありましたが、人気ソフトのExpLzhの更新履歴にはなぜか
- ShellExecuteEx() の実行では実行ファイルの ITEMIDLIST を指定して実行するように変更。
という項目があります。脆弱性対策の意識がとてもとても高くて気付いたのでしょう。拡張子なしファイル選んだら拡張子補完はWindowsの仕様だ、そんなことも知らんのか。なんて言いたくなるところですが、こんなのまで対応しているOSSは偉いですね。
ほ・そ・く
ここではShellExecuteExによる実行だけを書きましたが、もう一つメジャーな使い道としてファイルプロパティの表示があります。その場合はlpVerbに"properties"をセットします。
やっぱり同じような問題があって、ITEMIDLISTを使わないと、"hoge"を指定しても勝手に"hoge.exe"のプロパティが出てきたりします。ShellExecuteやShellExecuteExでlpFileだけ指定してプロパティ表示しようとせずに、ちゃんとITEMIDLISTを使いましょうね。
魔法の呪文
いかがでしたか、いかがでしょうか、いかがでしたでしょうか、調べてみました。
*1:SHSimpleIDListFromPathで拾えない場合など