更新履歴(1件・最終更新 2026年09月05日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の主張を変えず、接続・メッセージ・サーバー構成・権限・障害対応の順に整理し、比較表と小見出しを追加した。
- 初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.22170910)
以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。
小村 豪(2026)「名前付きパイプの実務 ── Windowsプロセス間通信の定番を設計からセキュリティまで」合同会社小村ソフト. https://comcomponent.com/blog/windows-named-pipes-practical-guide/
- DOI(登録済みアーカイブ)
- 10.5281/zenodo.22170910
- DOI(前回登録した版)
- 10.5281/zenodo.22170911
「常駐サービスに設定画面から命令を送りたい」「管理者権限が必要な処理だけを別プロセスに分けたい」── Windowsでこうしたプロセス間通信(IPC)を作るとき、最初に検討したいのが名前付きパイプ(named pipe)です。
同一PC内の通信で第一候補になる理由は、読み書きの手軽さだけではありません。WindowsのACLで接続者を絞り、必要なら相手のWindowsアカウントを確認して、その権限で処理できることが大きな利点です。ただし、パイプを作るだけで安全になるわけではなく、接続と権限の設定が必要です。123
この記事では、接続の形 → メッセージの区切り → サーバー構成 → セキュリティ → 障害時の動作の順で設計を整理します。対象は、Windowsの業務アプリやサービスを書く開発者です。プロセス間通信の判断表の記事で示した「同一マシンIPCの第一候補」を、実装時の判断まで掘り下げます。
1. まず結論:設計で決めることは5つ
APIを選ぶ前に、通信相手・データの単位・同時接続・権限・失敗時の扱いを決めておきます。
| 決めること | 基本の選び方 | 詳しく読む |
|---|---|---|
| どこと通信するか | 同一PC内のWindowsプロセスなら名前付きパイプを第一候補にする。リモート展開・他OS・既存プロトコルの利用を重視するならTCPベースを検討する | 2章 |
| 何を1件とするか | 1回の書き込みを1件として扱うならメッセージモード。既存のフレーミングがあるならバイトモード | 3章 |
| 複数クライアントをどう受けるか | 同じ名前のインスタンスを複数用意する。新規の.NET実装なら非同期I/Oとasync/awaitが素直 | 4章 |
| 誰に接続・権限借用を許すか | リモート拒否、ACL、最初のインスタンスの保証、クライアントの偽装レベルをセットで設計する | 5・6章 |
| 通信が失敗したらどうするか | 起動待ち・再接続・メッセージ上限・応答確認をプロトコルに含める | 7章 |
名前付きパイプは、ACLによる接続制御とクライアントの偽装をWindowsの仕組みとして利用できます。localhostのTCPを使う場合は、通信相手が誰かを確かめる認証を別に設計する必要があります。一方、将来リモート通信へ広げたい、他OSとも通信したい、gRPCなどの既存資産を使いたい場合は、TCPベースが有利です。23
とくに重要なのは、偽装に失敗した要求を実行しないことです。失敗を無視すると、クライアントではなくサーバー自身の権限で処理が続いてしまいます。特権サービスを作る場合は、コード例だけでなく5・6章まで確認してください。4
2. 接続の仕組み:名前は共通、通信路はクライアントごと
2.1 作成・接続・読み書きの役割を分ける
名前付きパイプは、\\.\pipe\MyCompany.MyApp.Control のような名前で識別する、一方向または双方向の通信路です。サーバーとクライアントでは、最初に使うAPIが違います。1
| 段階 | サーバー側 | クライアント側 |
|---|---|---|
| 通信路を用意する | CreateNamedPipe でインスタンスを作る |
サーバーが用意したパイプ名を使う |
| 接続する | ConnectNamedPipe でクライアントを待つ |
CreateFile で同じ名前を開く |
| データをやり取りする | ReadFile / WriteFile で読み書きする |
ReadFile / WriteFile で読み書きする |
接続後は両者ともファイルI/Oと同じ形で扱えます。名前の指定だけでなく、次のインスタンスと方向の考え方を押さえると、複数接続や接続エラーも理解しやすくなります。
2.2 1インスタンスが1クライアントを受け持つ
同じ名前のパイプを複数作成でき、1インスタンスが1クライアントとの通信路になります。同じ名前だからといって、全クライアントが1本の通信路を共有するわけではありません。最初の CreateNamedPipe で最大インスタンス数を指定します。上限指定には PIPE_UNLIMITED_INSTANCES も用意されています。15
flowchart TB
accTitle: 名前付きパイプの基本構造
accDescr: サーバーは同じ名前のパイプインスタンスを複数作成してConnectNamedPipeで接続を待ち、各クライアントはCreateFileで名前を開いて1つのインスタンスと1対1の双方向通信路を持つ
s["サーバー"] --> i1["インスタンス1"]
s --> i2["インスタンス2"]
s --> i3["インスタンス3"]
c1["クライアントA"] <--> i1
c2["クライアントB"] <--> i2
c3["クライアントC"] <--> i3
図1: 双方向パイプの例。同じ名前のインスタンスを複数用意することで、サーバーは複数のクライアントとそれぞれ1対1で通信する。
2.3 「方向」はサーバー側から見た名前
クライアントのアクセス指定は、サーバーが作ったパイプの方向と合わせます。食い違うと CreateFile が失敗します。6
| サーバーが作る方向 | サーバーの動作 | クライアントが指定するアクセス |
|---|---|---|
| 双方向 | 読み書きする | 読み取り・書き込み・その両方で開ける |
| アウトバウンド | 書き込むだけ | 読み取り専用で開く |
| インバウンド | 読み取るだけ | 書き込み専用で開く |
全インスタンスが使用中なら、クライアントの CreateFile は ERROR_PIPE_BUSY になります。WaitNamedPipe で空きを待ち、もう一度 CreateFile を呼びます。サーバーがまだパイプを作っていない場合とは別の失敗なので、起動順の競合と合わせて7.1で整理します。6
2.4 ローカル用途でも、リモート接続の扱いを決める
名前付きパイプは \\server\pipe\名前 の形で、SMB経由のリモート接続にも使えます。ただし、現代の設計でこの経路を積極的に使う理由はほぼなく、ローカルIPCでは使わない経路を開いたままにしないことが重要です。5.1の PIPE_REJECT_REMOTE_CLIENTS で明示的に拒否します。17
3. モードの選び方:「どこまでが1件か」を決める
3.1 要求・応答ならメッセージ、既存の区切りがあるならバイト
転送モードの違いは、書き込みの区切りをパイプが保存するかどうかです。5
| モード | 受信側から見えるもの | 向いている使い方 |
|---|---|---|
バイトモード (PIPE_TYPE_BYTE) |
TCPと同じ、切れ目のないバイト列 | 長さプレフィックスなどのフレーミングを持つデータやストリーム転送 |
メッセージモード (PIPE_TYPE_MESSAGE とメッセージ読み取り) |
1回の書き込みを1メッセージとする単位 | 1件ずつの要求と応答 |
flowchart TB
accTitle: バイトモードとメッセージモードの違い
accDescr: バイトモードでは3回の書き込みが切れ目のないバイト列になり受信側が自前で区切る必要があるが、メッセージモードでは書き込み1回ごとの単位が保存されて受信側にそのまま届く
bw["バイトモード:AAA・BB・CCCCを書き込み"] --> br["受信はAAABBCCCCのバイト列"]
br --> bf["区切り(フレーミング)は自前で設計"]
mw["メッセージモード:同じ3回の書き込み"] --> mr["受信はAAA・BB・CCCCの3件"]
mr --> mf["書き込みの単位が保存される"]
図2: バイトモードでは受信側が区切りを管理する。メッセージモードでは書き込みの単位を保存できるが、1回の読み取りで全体を受け取れるとは限らない。
自前の区切りをまだ持たず、「1回の書き込み=1件の要求」と扱いたいならメッセージモードが便利です。すでに長さ付きのシリアライズ形式などを使うなら、バイトモードで問題ありません。.NETでは PipeTransmissionMode.Message がメッセージ型の指定に当たります。8
メッセージ型の双方向パイプには、要求の送信と応答の受信を1回の呼び出しで行う TransactNamedPipe もあります。9
3.2 メッセージ型と読み取りモードは別の設定
読み取りモードはハンドルごとに設定します。CreateNamedPipe で PIPE_READMODE_MESSAGE を指定しても、それはサーバー側の設定です。クライアントが CreateFile で開いたハンドルは、既定ではバイト読み取りになります。6
| 実装 | サーバー側 | クライアント側 |
|---|---|---|
| Win32 | PIPE_TYPE_MESSAGE と PIPE_READMODE_MESSAGE を指定する |
接続後に SetNamedPipeHandleState で PIPE_READMODE_MESSAGE を指定する |
| .NET | PipeTransmissionMode.Message を指定する |
接続後に NamedPipeClientStream.ReadMode を Message に設定する |
サーバーだけをメッセージ型にして、クライアントも同じ単位で読めると考えないことがポイントです。
3.3 メッセージモードでも、読み足しが必要になる
メッセージの境界が保たれることと、受信バッファに全体が収まることは別です。バッファが小さければ ReadFile は ERROR_MORE_DATA を返し、メッセージは分割されます。受け取れた部分を保持し、残りを読み足して連結するループが必要です。56
| 読み取り結果 | 扱い |
|---|---|
| 成功 | メッセージが完成したものとして処理する |
ERROR_MORE_DATA |
まだ残りがあるので、読み足して連結する |
| それ以外のエラー | 要求の処理を続けず、切断などの通信失敗として扱う |
「小さい要求は通るのに、大きい要求だけ壊れる」場合は、この読み足し処理を確認します。また、無制限に連結してよいわけではありません。1メッセージの最大サイズもプロトコルとして決め、上限を超えたら切断する方針にします。巨大なメッセージによるメモリ浪費を防ぐためです。
4. サーバー構成:受付とクライアント処理を分ける
4.1 同期スレッド型とoverlapped型を比較する
サーバーの基本動作は、インスタンス作成 → 接続待ち → 読み書き → 切断して次への繰り返しです。複数クライアントを同時に受けるには、この流れを複数インスタンスで進めます。
| 構成 | 仕組み | 利点と注意点 |
|---|---|---|
| 同期スレッド型 | インスタンスごとにスレッドを1本割り当て、同期I/Oで処理する | コードは素直。ただしクライアント数ぶんスレッドを消費し、停止時にブロッキングI/Oを抜けさせる工夫が必要 |
| overlapped(非同期)型 | FILE_FLAG_OVERLAPPED で作成し、接続・読み取り・書き込みを非同期で発行する |
少数のスレッドで複数インスタンスの完了を処理できる |
Microsoftの公式サンプルは、各インスタンスのイベントを配列にして WaitForMultipleObjects で待ち、単一スレッドで複数の接続を処理する構成です。クライアント数とスレッド数を切り離せます。規模が大きければ、IOCPやスレッドプールI/Oへの接続も選べます。10
非同期I/Oそのものの仕組みは、同期I/Oと非同期I/Oの記事を参照してください。
4.2 新規の.NET実装ならasync/awaitが素直
.NETでは NamedPipeServerStream の WaitForConnectionAsync / ReadAsync / WriteAsync と async/await を使えます。新規実装で特別な理由がなければ、この非同期型をおすすめします。受付ループは接続を受けたら個別の処理へ渡し、次のインスタンスの受付へ戻ります。8
次は、その受付ループの骨格です。同じユーザーのプロセス間で使う例として、PipeOptions.Asynchronous と PipeOptions.CurrentUserOnly を指定しています。Windowsでは CurrentUserOnly は同一ユーザーかつ同一の昇格レベルに限定する指定です。別アカウントのサービスとUIをつなぐ例ではありません。その場合は5.2のACL設計が必要です。11
// C#: 複数クライアントを受けるサーバーの骨格
while (!token.IsCancellationRequested)
{
var server = new NamedPipeServerStream(
"MyCompany.MyApp.Control",
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
try
{
await server.WaitForConnectionAsync(token);
}
catch
{
await server.DisposeAsync(); // 接続前に抜けるときは自分で破棄
throw;
}
_ = HandleClientAsync(server, token); // 接続後の所有権はハンドラー側へ
}
接続前に例外が起きた場合は受付側で破棄し、接続後は HandleClientAsync 側がストリームの所有権を引き受けます。後者には、読み書きだけでなく、終了時の破棄も含めます。
このコードは通信プロトコルやハンドラー本体を省いた骨格です。実運用では切り離したタスクの例外・終了を管理し、3章の読み足しとサイズ上限、5・6章のセキュリティ、7章の切断・応答確認を組み合わせます。CurrentUserOnly はACLを自分で書かずに接続者を限定する手軽な選択肢ですが、それだけで特権サービス向けの設計が完成するわけではありません。
5. セキュリティ:サーバー側3点とクライアント側1点
名前付きパイプのセキュリティ上の利点は、正しく設定して初めて得られるものです。とくに「管理者権限のサービス+低権限のUIアプリ」というブローカー構成では、パイプが権限の境界になります。
5.1 サーバー側:不要なリモート接続を拒否する
ローカルIPCとして使うなら、CreateNamedPipe に PIPE_REJECT_REMOTE_CLIENTS を指定します。リモートクライアントは自動的に拒否されるため、「同じPCのアプリ向けに作ったつもりなのに、ネットワークからも開ける」という経路を閉じられます。7
5.2 サーバー側:ACLで接続者と権利を絞る
SECURITY_ATTRIBUTES にセキュリティ記述子を渡し、接続してよいユーザー・グループを明示します。既定のACLが、その用途に十分厳しいとは限りません。2
ここで見落としやすいのが、クライアントに与える書き込み権限です。ACLで汎用書き込み(GENERIC_WRITE / FILE_GENERIC_WRITE)を与えると、FILE_CREATE_PIPE_INSTANCE に相当する権利まで含まれます。すると、許可されたクライアント自身が同名のサーバーインスタンスを作り、後続の接続を横取りできてしまいます。2
読み書きは必要な個別の権利で与え、クライアントにはインスタンス作成権を渡さないようにします。「誰に許すか」だけでなく、「何を許すか」までがACL設計です。
5.3 サーバー側:最初のインスタンスを先取りされていないか確認する
悪意あるプロセスが先に同じ名前のパイプを作ると、クライアントはその偽サーバーに接続してしまいます。サーバーは最初のインスタンスを作成するときだけ FILE_FLAG_FIRST_PIPE_INSTANCE を指定し、自分が最初であることを保証します。失敗したら先取りを疑い、処理を止めます。5
このフラグは名前を確保する1本目専用です。2本目以降のインスタンスにも付けると作成が失敗します。複数クライアント用の受付ループで、すべての作成に同じ指定を繰り返さないようにしてください。5
ただし、これは正規サーバーが起動時に異常を検出する仕組みであって、クライアントの接続先認証ではありません。正規サービスが不在なら、攻撃者が先に同名のパイプを作って待ち構える余地は残ります。
5.4 クライアント側:サーバーにどこまで権限を借用させるか決める
偽サーバーへの備えとして、クライアントは偽装レベルを必要最小限にします。身元確認だけを許す設計なら、CreateFile で SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION を指定します。サーバーはアカウントを確認できても、その権限を借りて実際のアクセスを行うことはできなくなります。3
| サーバーにさせたいこと | クライアント側の方針 |
|---|---|
| 接続者の身元を確認するだけ | 識別レベル(SECURITY_IDENTIFICATION)に絞る |
| クライアント権限でファイルを開くなど、実アクセスを行う | 偽装レベル(SECURITY_IMPERSONATION)の許可が必要。ただし正規サーバーへの接続確認とセットにする |
識別レベルのままでは、クライアント権限で実アクセスするブローカーの処理はできません。反対に、接続先を確認せずに偽装を許すと、偽サーバーに権限を借用される危険があります。
サービスの起動保証や、接続後の相互認証によって正規の接続先を確認できる場合に限って、必要な偽装を許可するのが原則です。5.3の先取り検出があるからクライアント側の確認は不要、とは考えないでください。
6. 偽装の使い方:失敗した要求は実行しない
6.1 接続しただけではなく、要求を読んでから偽装する
サーバーがクライアントの身元を確認し、その権限で処理するためのAPIが ImpersonateNamedPipeClient です。対象は、そのパイプから最後に読んだメッセージの送り主のセキュリティ文脈です。まず要求を読み取り、その後に呼び出します。4
偽装は呼び出したスレッドに適用されます。その状態でファイルを開けば、アクセスの可否はクライアントの権限を基準に判定されます。特権サービスであっても、「頼まれた操作を、頼んだ人の権限で実行する」ための仕組みです。3
6.2 読み取り・成功確認・元に戻す処理をワンセットにする
必要な順序は、要求を読む → 偽装を試みる → 成功を確認する → クライアント権限で操作する → 元の文脈へ戻すです。
sequenceDiagram
accTitle: 偽装を使った要求処理の流れ
accDescr: サーバーはパイプから要求を読み、ImpersonateNamedPipeClientの成功を確認してからクライアントの権限で操作を行い、RevertToSelfで自分の文脈に戻る。偽装に失敗した場合は要求を実行せず拒否する
participant C as クライアント
participant S as サーバー
C->>S: 要求を送信
S->>S: 要求を読み取る
S->>S: ImpersonateNamedPipeClient
Note over S: 失敗なら要求を実行せず拒否
S->>S: クライアント権限で操作を実行
S->>S: RevertToSelfで元に戻す
S->>C: 結果を応答
図3: 偽装に失敗したら要求を実行しない。成功した場合も、作業後にRevertToSelfで元へ戻すところまでが一連の処理。
ImpersonateNamedPipeClient の戻り値を確認せず、要求の処理へ進んではいけません。失敗時にはクライアントの権限へ切り替わっておらず、サーバープロセス自身の権限が使われます。サーバーが特権アカウントなら、クライアントには許されない操作まで通してしまいます。Microsoftのドキュメントも、失敗した場合はクライアントの要求を実行しないよう明記しています。4
作業が終わったら、RevertToSelf で確実に元の文脈へ戻します。成功確認と後始末を両方そろえてください。トークン、偽装レベル、SeImpersonatePrivilege などの詳細は、Windowsの偽装トークンの記事で扱っています。
7. 実務の落とし穴:通信が途切れる前提で設計する
7.1 起動待ちと満員待ちは、別の失敗として扱う
接続できないときは、サーバーがまだパイプを作っていないのか、作られたインスタンスがすべて使用中なのかを分けます。69
| 状態 | クライアントの対応 |
|---|---|
| パイプが存在しない | サーバーの起動待ちを織り込み、少し待って再試行する |
ERROR_PIPE_BUSY |
WaitNamedPipe で空きを待ち、CreateFile を再試行する |
| 接続できた | 通信へ進む。必要なら3.2の読み取りモードも設定する |
flowchart TB
accTitle: クライアント接続の再試行フロー
accDescr: CreateFileでパイプを開き、パイプが存在しなければ少し待って再試行し、全インスタンス使用中のERROR_PIPE_BUSYならWaitNamedPipeで空きを待ってから再試行し、成功したら通信に入る
cf["CreateFileで開く"] --> ok{"結果は?"}
ok -->|"成功"| go["通信開始"]
ok -->|"パイプが存在しない"| wait1["少し待つ(サーバー未起動)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["WaitNamedPipeで空きを待つ"]
wait1 --> cf
wnp --> cf
図4: 「存在しない」と「満員」は異なる状態。どちらも、状態に応じた待機の後に接続を再試行する。
サーバー側は、クライアントが起動する前に ConnectNamedPipe で待ち受けを始めておくのが原則です。それでも起動順の競合は起こり得るので、クライアント側にも再試行を組み込みます。9
7.2 切断は異常事態ではなく、通常の処理経路にする
相手のプロセスが終了すると、読み書きは ERROR_BROKEN_PIPE などで失敗します。これは通信の日常として扱います。
サーバーは切断を検知したら DisconnectNamedPipe でインスタンスを切り離し、次の接続に備えます。クライアントは再接続します。スリープ復帰の記事で説明した、繰り返しても状態を壊さない「冪等な再接続」の考え方がここでも有効です。
7.3 大きいメッセージには「読み足し」と「上限」の両方が必要
3.3の ERROR_MORE_DATA は、メッセージを分割して読むための処理です。一方、最大メッセージサイズは、受け付けるデータ量を制限するための約束です。この2つを混同しないでください。
悪意ある相手や、バグのある相手が巨大なメッセージを送ると、際限なく読み足す実装ではメモリを浪費します。上限をプロトコルに定め、超えたら切断する方針にします。
7.4 書き込み成功と、相手の処理完了を区別する
WriteFile の成功は、相手のアプリがデータを処理したことを意味しません。確実に実行されたかを知る必要がある操作は、応答メッセージで確認します。
また、どの要求に対する応答なのかを対応付けられるよう、要求と応答の関係をプロトコルに含めます。「送れた」と「処理が終わった」を分けることが、業務上の完了確認につながります。
8. まとめ:通信路・データ・権限を別々に決める
名前付きパイプは、ファイルI/Oと同じ扱いやすさと、ACL・偽装というWindowsのセキュリティモデルを組み合わせられる、同一マシンIPCの第一候補です。ただし、通信路を作ることと、安全で壊れにくいプロトコルを作ることは別です。
接続とデータの設計では、1インスタンスが1クライアントを受け持つことを出発点にします。要求・応答ならメッセージモード、既存のフレーミングがあるならバイトモードを選び、メッセージ読み取りはクライアント側にも設定します。分割読みへの対応と最大サイズも必要です。複数接続を受ける新規の.NET実装なら、非同期I/Oとasync/awaitが素直です。
権限の設計では、リモート拒否・ACLの明示・最初のインスタンスの保証・クライアント側の偽装レベル最小化をセットにします。偽装を使う場合は、要求を読んでから呼び出し、失敗したら実行せず、作業後は RevertToSelf で戻します。
最後に、起動順・切断・メッセージ上限・応答確認を、自分のプロトコルとして書き出してください。「同じPCの中だから失敗しない」ではなく、通信が途切れても、相手の権限を越えずに復旧できる形を決めてから実装に入るのが実務の要点です。
関連記事
- Windowsのプロセス間通信をどう選ぶか ── 名前付きパイプ / TCP / gRPC / 共有メモリ / COM 判断表
- Windowsアプリで「管理者権限が必要な処理だけ」を分離する具体的な書き方
- Windowsの偽装トークンを正しく扱う ── スレッド単位の権限借用と安全な戻し方
- Windows I/Oの深層(第2回) ── 同期I/Oと非同期I/O:OVERLAPPEDの本当の意味
- 共有メモリの落とし穴と実務ベストプラクティス
関連する相談領域
合同会社小村ソフトでは、サービスとUIアプリの分離・管理者権限の分離といったプロセス間通信を伴う設計と実装、既存IPC(共有メモリ・独自ソケット・COMなど)の名前付きパイプへの置き換え、特権サービスのパイプ通信のセキュリティレビューを扱っています。プロトコル設計の壁打ちからでもご相談ください。
参考リンク
-
Microsoft Learn, Named Pipes. 名前付きパイプがパイプサーバーと1つ以上のパイプクライアントの間の一方向または双方向の通信路であること、すべてのインスタンスが同じ名前を共有しつつ独立したバッファとハンドルを持つこと、ローカルとリモートのプロセスから利用できることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Security and Access Rights. 名前付きパイプのアクセス権の構成、GENERIC_WRITEにFILE_CREATE_PIPE_INSTANCEが含まれるため、クライアントに汎用書き込みを与えるとサーバーインスタンスの作成まで許してしまうこと、データの読み書きは個別のアクセス権で付与すべきことについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Impersonating a Named Pipe Client. 偽装によりサーバースレッドがクライアントの権限の範囲で動作できること、既定の偽装レベルがSecurityImpersonationであること、クライアントがCreateFile時にSECURITY_SQOS_PRESENTフラグで偽装レベルを制御できる(SECURITY_IDENTIFICATIONなら身元確認のみ許す)ことについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). サーバー側スレッドがパイプから最後に読んだメッセージのクライアントのセキュリティ文脈で偽装を開始すること、完了後はRevertToSelfで戻すこと、偽装に失敗した場合に処理を続けるとサーバープロセス自身の(特権的な)文脈で実行されるため、戻り値を必ず確認し失敗時はクライアントの要求を実行してはならないことについて。 ↩ ↩2 ↩3
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). パイプの方向(インバウンド・アウトバウンド・双方向)、バイト型とメッセージ型(PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE)および読み取りモード(PIPE_READMODE_MESSAGE)、最大インスタンス数(PIPE_UNLIMITED_INSTANCES)、FILE_FLAG_OVERLAPPEDによる非同期モード、FILE_FLAG_FIRST_PIPE_INSTANCEによる最初のインスタンス保証、WaitNamedPipe用の既定タイムアウトについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Named Pipe Client. クライアントがCreateFileでパイプを開くこと、全インスタンスが使用中の場合はERROR_PIPE_BUSYとなりWaitNamedPipeで空きを待つこと、開いたハンドルの既定がバイト読み取り・ブロッキング・非overlappedでありSetNamedPipeHandleStateでメッセージ読み取りモードへ変更できることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). PIPE_ACCEPT_REMOTE_CLIENTS(リモート接続を受け付けセキュリティ記述子で検査する)とPIPE_REJECT_REMOTE_CLIENTS(リモートクライアントの接続を自動的に拒否する)の2つのリモートクライアントモードについて。 ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). NamedPipeServerStream / NamedPipeClientStreamによる接続と読み書き、PipeTransmissionMode.Messageによるメッセージ単位の転送、非同期メソッドによる複数クライアントの処理について。 ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. ReadFileEx / WriteFileExによるoverlapped操作、PeekNamedPipeによる取り出さない読み取り、メッセージ型双方向パイプで要求送信と応答受信を1回で行うTransactNamedPipe、クライアント起動前のブロッキング読み取りが競合を招きうることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. 単一スレッドのサーバーがoverlapped操作で複数クライアントとの同時接続を処理する公式サンプルについて。各インスタンスのOVERLAPPED構造体とイベントをWaitForMultipleObjectsで待ち、完了したインスタンスの状態機械を進める構成、GetOverlappedResultによる保留I/Oの完了確認について。 ↩
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Asynchronousによる非同期I/Oの有効化と、CurrentUserOnlyにより同一ユーザー(かつ同一の昇格レベル)のプロセスとの接続だけを許可できることについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
引数はなぜ壊れるか ── Windowsのコマンドライン引数の規則
Windowsでは引数の配列は存在せず、CreateProcessに渡るのは1本の文字列で、分割は受け取り側が行います。CommandLineToArgvW・CRT・.NETの分割規則と、.NETのArgumentList・C++での正しい組み立て方を解説します。
親が落ちたあとに何が残るか ── Job Objectで子プロセスを飼う
UIを強制終了してもSDKのヘルパーが残り、カメラやCOMポートを握ったままになるのはなぜか。Job Objectでプロセスツリーを一つの単位にし、KillOnJobCloseと完了ポートで子プロセスの寿命を設計する方法を計測アプリ目線で解説します。
Win32スレッドプールAPI ── CreateThreadpoolWorkで「スレッドを作らない」並行処理
ネイティブコードでCreateThreadを乱立させていませんか。Vistaで刷新されたWin32スレッドプールAPIのwork・timer・wait・ioの4オブジェクト、クリーンアップグループ、コールバックでの禁止事項までを一次情報にもとづいて解説します。
DllMainとローダーロック ── 「DLLの初期化で何もするな」と言われる本当の理由
DllMainでLoadLibraryやスレッド同期をしてはいけないのはなぜか。全DLL通知を直列化するローダーロックの仕組みから、デッドロックが成立する典型シナリオ、遅延初期化などの正しい設計、ハング調査の手順までを一次情報で解説します。
スプリアスウェイクアップ ── 条件変数が「通知なしに目覚める」理由とWindowsでの正しい待ち方
条件変数のwaitは通知が来ていなくても目覚めることがあります(スプリアスウェイクアップ)。仕様がそれを許す理由をWindowsの実装から解き明かし、whileと述語で書く正しい待ち方をWin32・C++・C#のコードで示します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 名前付きパイプとTCP(localhostソケット)はどう使い分ければよいですか?
- 同一マシン内のプロセス間通信であれば、名前付きパイプが第一候補です。理由はセキュリティモデルにあります。パイプはWindowsのセキュリティ記述子(ACL)で「誰が接続できるか」をOSレベルで制御でき、サーバー側はImpersonateNamedPipeClientで接続相手のWindowsアカウントを確認・借用できます。localhostのTCPポートには誰でも接続でき、相手が誰かを自前の認証で確かめる必要があるのと対照的です。一方、将来リモート通信に発展する可能性が高い、他OSのプロセスとも話す、gRPCなど既存のプロトコル資産を使いたい、といった場合はTCPベースが有利です。この判断はプロセス間通信の判断表の記事でも整理しています。
- バイトモードとメッセージモードはどちらを使うべきですか?
- 「1回の書き込み=1件の意味の単位」として扱いたいならメッセージモード(PIPE_TYPE_MESSAGE+PIPE_READMODE_MESSAGE)が便利です。受信側は送信側が書いた単位で読み取れるため、自前で区切りを管理する必要がありません。バイトモードはTCPと同じ「切れ目のないバイト列」で、長さプレフィックスなどの区切り(フレーミング)を自分で設計する必要があります。既にフレーミングを持つプロトコル(例えば長さ付きのシリアライズ形式)を流すならバイトモードで問題ありません。注意点として、メッセージモードでも受信バッファがメッセージより小さいと分割読み(ERROR_MORE_DATA)が発生するため、その処理は必要です。また読み取りモードはハンドルごとの設定で、CreateNamedPipeで設定されるのはサーバー側だけです。クライアント側はCreateFileの後にSetNamedPipeHandleStateでPIPE_READMODE_MESSAGEを指定する必要があります。.NETではサーバーはPipeTransmissionMode.Messageを指定し、クライアントは接続後にNamedPipeClientStream.ReadModeをMessageに設定します。
- 複数のクライアントを同時に相手にするサーバーはどう作りますか?
- 名前付きパイプは同じ名前で複数のインスタンスを作成でき、1インスタンスが1クライアントを受け持ちます。構成は2通りあります。ひとつはクライアントごとにスレッドを割り当てる同期型で、実装は素直ですがクライアント数分のスレッドを消費します。もうひとつはFILE_FLAG_OVERLAPPEDで非同期I/Oにし、少数のスレッドで全インスタンスのConnectNamedPipe・ReadFile・WriteFileを捌く形で、Microsoftの公式サンプルにも単一スレッドで複数インスタンスを処理する実装が示されています。.NETならNamedPipeServerStreamのWaitForConnectionAsyncとasync/awaitで、非同期型を同期型並みの素直さで書けます。新規実装で特に理由がなければ.NETの非同期型をおすすめします。
- 名前付きパイプのセキュリティで最低限やるべきことは何ですか?
- 4点あります。第一に、リモート接続が不要ならPIPE_REJECT_REMOTE_CLIENTSを指定してネットワーク越しの接続を明示的に拒否する。第二に、SECURITY_ATTRIBUTESで適切なACLを設定し、接続してよいユーザー・グループを絞る(既定のACLは用途によっては緩すぎます)。第三に、最初のインスタンスの作成時にFILE_FLAG_FIRST_PIPE_INSTANCEを指定し、同名パイプを先に作られる「名前の乗っ取り」を検出する(2本目以降のインスタンスには付けません)。第四に、サーバーに身元確認だけをさせたいクライアントは、CreateFileでSECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATIONを指定して、偽サーバーに自分の権限を借用(偽装)されないよう制限する。サーバーがクライアント権限で実アクセスを行うブローカー構成では偽装の許可が必要になるため、この制限は「サーバーに権限借用をさせない設計かどうか」で使い分けます。
- ImpersonateNamedPipeClientを使うときの注意点はありますか?
- 最重要なのは戻り値の確認です。偽装に失敗したのに処理を続けると、以後の操作はサーバープロセス自身の(多くは高い)権限で実行されてしまい、クライアントに許されないはずの操作が通ってしまいます。公式ドキュメントも、失敗時はクライアントの要求を実行してはならないと明記しています。また、偽装は「最後にパイプから読んだメッセージ」の文脈で行われるため、必ず何かを読み取ってから呼ぶこと、作業が終わったらRevertToSelfで確実に元の文脈へ戻すことも必要です。偽装まわりの仕組み(トークン、偽装レベル、SeImpersonatePrivilege)は偽装トークンの記事で詳しく解説しています。