知識マップ: 引数はなぜ壊れるか ── Windowsのコマンドライン引数の規則

記事「引数はなぜ壊れるか ── Windowsのコマンドライン引数の規則」の主張を、概念と関係(エッジ)に分解した知識グラフの全体です。各関係には根拠・確認日・確度が付いています。

Windowsのプロセスには引数の配列は渡らず、CreateProcessに渡した1本のコマンドライン文字列が新しいプロセスに届き(先頭の実行ファイル名はOSがフルパスを補うことがある)、子はGetCommandLineWでそれを取り出す。argvの配列は受け取り側が自分で作るもので、Cランタイムの起動コード、CommandLineToArgvW、.NETランタイムの分割コードが、空白とタブで区切る・二重引用符で囲んだ範囲は区切らない・バックスラッシュは直後に二重引用符が来るときだけ特別扱いする(2n個ならn個と囲みの開閉、2n+1個ならn個と文字としての引用符)という同じ骨格の規則を別々に実装している。先頭のargv[0]だけは引用符で囲めるがエスケープの効かない別規則で、lpApplicationNameを省略すると空白を含むパスの解釈が曖昧になりC:\Program.exeのような別の実行ファイルが起動し得る。この規則の帰結として、末尾がバックスラッシュのパスを引用符で囲むと閉じ引用符が文字に化けて次の引数を巻き込む。組み立て側は、空白か引用符を含むか空の引数を引用符で囲み、引用符の直前のバックスラッシュを2倍+1、末尾のバックスラッシュを2倍にし、引用符にバックスラッシュを前置する1つの関数で足り、.NET Core 2.1以降のProcessStartInfo.ArgumentListがこれを内部で行う。ArgumentListとArgumentsは同時に使えず、.NET Frameworkでは同じ規則の自前関数でArgumentsを組み立てる。中身のある引数の内側で引用符を隣接させる形は受け取り側で解釈が分かれるため生成しない(空の引数を表す引用符2つだけの形は別で、正しい書き方)。往復が保証されるのは、受け取り側がCommandLineToArgvW・Cランタイム・.NETランタイムと同じ分割規則でワイド文字のまま分割し(独自の文法で解釈する相手や途中にシェルのパーサが挟まる場合は対象外)、setargv.obj/wsetargv.objによるワイルドカード展開を有効にしておらず、引数にNUL文字を含まず、組み立てた全体がlpCommandLineの上限に収まる場合で、狭い文字列のmainではコードページで表せない文字が失われる。cmd.exeは独自の構文と引用符の剥がし方を持ち、バッチファイルは生の文字列を受け取るため、外から来た値をバッチに渡すとコマンド注入につながる。コマンドライン長にはCreateProcessの32,767 UTF-16コード単位(終端のnull文字を含む)、cmd.exeの8,191文字などの上限があり、件数に比例して伸びる引数は、相手が読める場合に限り応答ファイルに切り替える。届いた引数は、呼び出し側で組み立てた文字列のログ、Process Explorerや相手の起動時ログで見える相手側の文字列(先頭の実行ファイル名はOSがフルパスを補うことがあり、cmd.exeが挟まればその段が作り直した文字列になる)、相手と同じランタイムで作った引数表示用exeで見る分割後の配列、の3つを順に突き合わせる。呼び出し側・起動時のどちらでコマンドラインをログに残す場合も、機密になり得る引数の値を伏せ字にしてから記録する。

Windowsのコマンドライン引数の規則の知識マップCreateProcessが引数を1本のコマンドライン文字列として渡し、CommandLineToArgvW・Cランタイム・.NETランタイムがそれぞれ空白・引用符・バックスラッシュの同じ規則で分割すること、その規則が末尾バックスラッシュの巻き込みを生み、組み立て関数とProcessStartInfo.ArgumentListがそれを防ぐこと、lpApplicationNameがProgram.exe問題を防ぐこと、cmd.exeとバッチファイルが引数の変形とコマンド注入の原因になり得ること、応答ファイルが長さの上限を回避すること、引数表示用exeとProcess Explorerで届いた引数を確認できることを含む関係図利用する利用するで確認できる利用する利用する実装を担う実装を担う実装を担うで構成できる前提とする原因になり得る防止する防止する実装を担う前提とする両立しない推奨される対応推奨される対応防止する原因になり得る原因になり得る原因になり得る軽減するで確認できるで確認できる利用する原因になり得る原因になり得るコマンドライン文字列(lpCommandLine)コマンドライン分割の規則(空白・引用符・バックスラッシュ)引数の引用符付け(組み立て関数)ProcessStartInfo.ArgumentListCreateProcesslpApplicationNameGetCommandLineWCommandLineToArgvW.NETランタイムの引数分割Cランタイムの引数分割(argv生成)setargv.objによるワイルドカード展開argv[0](プログラム名)の特別規則末尾バックスラッシュによる引数の巻き込み囲みの内側の引用符2連続(非空の引数)ProcessStartInfo.Arguments(手組み文字列)子プロセスへの引数の受け渡し空白を含む実行ファイルパスの曖昧さ(Program.exe問題)意図しない実行ファイルの起動バッチファイル(bat)バッチファイル経由のコマンド注入リスクコマンドシェル(cmd.exe)引用符消失・空文字列引数の消失応答ファイルコマンドライン長の上限引数表示用の確認exeProcess Explorerコマンドラインのログ記録(呼び出し側・起動時)ログへの機密の漏えいコマンドライン引数への機密の混入

概念間の関係(全28件)

図と同じ関係を文章でも列挙します。表示している文と機械可読な意味データ(RDFa)は同じ要素に載っています。確度が「確立した関係」のものは直接の関係として、「条件付きの関係」のものは成立条件つきの言明(rdf:Statement)として表現しています。

主要概念の定義

コマンドライン文字列(lpCommandLine)
Windowsがプロセスの起動時に渡す、実行ファイル名と引数を空白で並べた1本のnull終端文字列。引数の配列としては渡らず、受け取った側のプロセスが自分で分割する。上限は終端のnull文字を含めて32,767 UTF-16コード単位(サロゲートペアの文字は2つと数える)。
コマンドライン分割の規則(空白・引用符・バックスラッシュ)
Windowsのコマンドライン文字列を引数に分割する規則。空白とタブで区切り、二重引用符で囲んだ範囲は区切らず、バックスラッシュは直後に二重引用符が来るときだけ特別扱いされる(2n個ならn個と囲みの開閉、2n+1個ならn個と文字としての引用符)。
引数の引用符付け(組み立て関数)
引数の配列から、受け取り側の分割規則で元どおりに割れる1本のコマンドライン文字列を組み立てる処理。空白か引用符を含むか空の引数を引用符で囲み(空の引数は引用符2つだけになる)、引用符の直前のバックスラッシュを2倍+1、末尾のバックスラッシュを2倍にし、引用符にはバックスラッシュを前置する。往復が保証されるのは受け取り側がCommandLineToArgvW・Cランタイム・.NETランタイムと同じ分割規則でワイド文字のまま分割し(独自の文法で生のコマンドラインを解釈する相手や、途中にシェルのパーサが挟まる場合は対象外)、かつsetargv.obj/wsetargv.objによるワイルドカード展開を有効にしていない場合で(展開が有効だと*や?を含む引数がファイル名に置き換わる)、組み立てた文字列全体がCreateProcessのlpCommandLineの上限(終端のnull文字を含めて32,767 UTF-16コード単位)に収まることも前提になる。コマンドラインがnull終端文字列であるためNUL文字を含む引数は渡せず、狭い文字列のmainではコードページで表せない文字が失われる。
ProcessStartInfo.ArgumentList
.NET Core 2.1以降で、引数をコレクションとして個別に指定でき、必要な引用符付けをランタイムが行うProcessStartInfoのプロパティ。
CreateProcess
新しいプロセスと主スレッドを作成するWin32 API。実行するモジュールをlpApplicationNameで、子プロセスに渡すコマンドライン文字列をlpCommandLineで指定する。
CommandLineToArgvW
Unicodeのコマンドライン文字列をCランタイムのargv/argcに似た配列に分割するWin32 API。戻り値は1回のLocalFreeで解放する。
.NETランタイムの引数分割
.NETランタイムがWindowsでコマンドラインから引数配列を得る仕組み。apphostやdotnet.exeで起動した通常の構成では、ホスト自身がMSVCのCランタイムで作られたwmainのプログラムなので分割はCランタイム起動コードが行い、ホストは自分のオプションとアプリのパスを除いた残りを、アプリのパスと一緒にランタイムに渡す。ランタイムは起動時に、先頭にプログラム名(ホストから渡された起動名、無ければアセンブリのパス)を置いた配列を作ってEnvironment.GetCommandLineArgs用に保持し、Mainのargsにはプログラム名を除いた引数だけを渡す。起動時の引数を受け取らないホストされたライブラリの構成では、フォールバックとしてランタイム自身の分割コード(SegmentCommandLine)がGetCommandLineWの戻り値を分割する。この分割コードはMSVCのCランタイムの文書に合わせて実装され、CommandLineToArgvWは挙動がわずかに異なるため使っていない。ネイティブホストが独自のargc/argvを渡してMainを呼ぶ構成では、Mainのargsはホストが渡した値になる。
Cランタイムの引数分割(argv生成)
MSVCのCランタイム起動コードが、プログラム開始時にコマンドライン文字列をargc/argvへ分割する処理。Microsoft固有の規則に従い、既定ではワイルドカードを展開しない。
argv[0](プログラム名)の特別規則
コマンドライン先頭のプログラム名だけに適用される分割規則。引用符で囲んで空白を含められるが、バックスラッシュによるエスケープは働かず、引用符そのものを含める方法もない。
ProcessStartInfo.Arguments(手組み文字列)
引用符付けを自分で行った1本のコマンドライン文字列として引数を渡す、ProcessStartInfoの旧来のプロパティ。
lpApplicationName
CreateProcessの第1引数で、実行するモジュールのパスを明示する。NULLにするとlpCommandLineの先頭の空白区切りトークンから実行ファイルが推定され、モジュール名部分はMAX_PATHに制限される。
空白を含む実行ファイルパスの曖昧さ(Program.exe問題)
lpApplicationNameをNULLにして空白を含むパスを引用符なしでコマンドライン先頭に置くと、CreateProcessが短い候補(C:\Program.exeなど)から順に実行ファイルを試すため、意図しないファイルが実行され得る状態。
バッチファイル(bat)
cmd.exeが解釈するコマンドを並べたスクリプトファイル。拡張子.batまたは.cmdを持つ。
コマンドシェル(cmd.exe)
Windowsに搭載される、batファイルなどのバッチスクリプトを実行する伝統的なコマンドラインシェル。
応答ファイル
引数の内容をファイルに書き出し、コマンドラインにはそのファイルのパスだけを渡す方式。コマンドライン長の上限を回避するための公式の回避策の一つだが、CreateProcessやcmd.exeがファイルを展開するわけではなく、受け取るプログラムが@fileのような構文で応答ファイルを読めることが前提になる。
子プロセスへの引数の受け渡し
呼び出し側が子プロセスに引数を渡す行為。Windowsでは配列ではなく1本のコマンドライン文字列として渡るため、受け取り側の分割規則で元どおりに割れる文字列を組み立てることと同義になる。
コマンドラインのログ記録(呼び出し側・起動時)
呼び出し側が組み立てたコマンドライン文字列、または起動したプログラムが受け取ったコマンドライン文字列や分割後の引数をログに書き出す運用。どんな文字列で起動されたかを後から確認できるが、記録の仕方によっては引数に含まれる機密がログに残る。
コマンドライン引数への機密の混入
パスワードやAPIキーをコマンドライン引数として渡す実装。プロセスの引数を読める相手にはその機密が渡る。

機械可読データ

このデータセットについて

作成
合同会社小村ソフト
ライセンス
CC BY 4.0 ── 出典(合同会社小村ソフト)とライセンスへのリンクを明示し、改変した場合はその旨を示すことを条件に、再配布・改変を含めて再利用できます
最終確認日
2026-09-03 ── 収録する関係のうち、最後に出典と照合した日

このページはサイトの知識グラフ(_data/knowledge/)から自動生成されています。誤りの指摘はお問い合わせからお願いします。