.NET 11 Preview 4では、Unix系OS上でNamedPipeServerStreamをPipeOptions.CurrentUserOnly付きで作成した場合、裏側で使われるUnixドメインソケットのファイル権限が0600に固定されるようになります。結論から言うと、同じユーザーだけが使う名前付きパイプなら基本的に対応不要です。一方で、別ユーザーで動くヘルパープロセス、監視ツール、CLI、サービス間連携が同じパイプへ接続している場合は、接続失敗や権限エラーが起きる可能性があります。(GitHub)
今回の.NET documentation updateは、単なるドキュメント追記ではなく、Unix環境における.NETの名前付きパイプの実行時挙動に関わるBreaking changeです。Linuxコンテナ、macOS開発環境、Unix上で動く.NETサービスを運用しているチームは、PipeOptions.CurrentUserOnlyの指定箇所と、パイプへ接続するプロセスの実行ユーザーを確認しておくべきです。
.NET 11で何が変わるのか
今回の変更は、.NET 11 Preview 4で導入される予定のCore .NET librariesの挙動変更です。対象は、Unixプラットフォーム上でNamedPipeServerStreamを作成し、オプションにPipeOptions.CurrentUserOnlyを指定しているケースです。Microsoft LearnのPipeOptions.CurrentUserOnlyは、サーバー側では「同じユーザーが作成したクライアントにのみそのパイプを接続できる」ことを示すオプションとして説明されています。(Microsoft Learn)
これまでUnixでは、.NETの名前付きパイプAPIの実装にUnix Domain Socketが使われ、ソケットファイルの権限はプロセスのumaskに依存していました。たとえば0644や0755のように、所有者以外からも見える権限になることがありました。CurrentUserOnlyによる制限は、接続時のピア資格情報チェックに寄っており、ソケットファイル自体の権限を必ず所有者限定にするものではありませんでした。(GitHub)
.NET 11 Preview 4以降は、PipeOptions.CurrentUserOnlyを指定したNamedPipeServerStreamをUnix上で作成すると、bind()直後にソケットファイルへchmodが実行され、ファイルモードが0600になります。つまり、所有ユーザーだけが読み書きでき、グループやその他ユーザーはOSレベルで開いたり接続したりできなくなります。(GitHub)
| 観点 | 以前の挙動 | .NET 11 Preview 4以降の挙動 |
|---|---|---|
| 対象OS | Unix系OS | Unix系OS |
| 対象API | NamedPipeServerStream | NamedPipeServerStream |
| 条件 | PipeOptions.CurrentUserOnly指定時 | PipeOptions.CurrentUserOnly指定時 |
| ソケットファイル権限 | プロセスのumaskに依存 | 0600に設定 |
| 他ユーザーからの見え方 | statや接続試行ができる場合がある | root以外の他ユーザーはOSレベルで接続できない |
| 変更種別 | 従来挙動 | Behavioral change |
なぜBreaking changeなのか
この変更はセキュリティ上は自然な改善です。CurrentUserOnlyという名前の通り、現在のユーザーだけに接続を限定する意図にUnix上のソケットファイル権限も合わせるためです。Windowsでは同一ユーザーに関する検証が行われるため、Unix側の挙動をより近づける意味もあります。(GitHub)
ただし、既存アプリが「CurrentUserOnlyを付けているが、実際には別ユーザーのプロセスからもソケットパスへ到達できる」ことに依存していた場合、.NET 11で実行時の動作が変わります。ソースコードがコンパイルできなくなる変更ではなくても、本番環境で接続エラーとして表面化する可能性があるため、Breaking changeとして扱われています。関連するruntime側のPRでは、Unix named pipe、つまりUnixドメインソケットのファイルシステム権限をCurrentUserOnly利用時に0600へ厳格化し、回帰テストも追加されています。(GitHub)
重要なのは、これは「脆弱化」ではなく「意図どおりに厳しくなる」変更だという点です。問題が起きるのは、セキュリティ制限として指定したCurrentUserOnlyを、実務上は別ユーザー接続のある共有パイプで使っていた場合です。
影響を受けるアプリケーション
影響を受ける可能性が高いのは、LinuxやmacOSで.NETの名前付きパイプを使い、かつサーバー側でPipeOptions.CurrentUserOnlyを指定しているアプリケーションです。Microsoftの名前付きパイプ解説でも、Linux上の.NETではこれらのAPIの実装にUnix Domain Socketsが使われると説明されています。(Microsoft Learn)
特に次のような構成は確認が必要です。
| 構成 | 影響の可能性 | 確認ポイント |
|---|---|---|
| 同一ユーザーでサーバーとクライアントを実行 | 低い | 基本的にはそのままでよい |
| systemdサービスと一般ユーザーCLIが接続 | 高い | 実行ユーザーが異なる場合、接続失敗の可能性 |
| rootまたは専用ユーザーで動く常駐サービスに別ユーザーのツールが接続 | 高い | CurrentUserOnlyを使う設計が適切か見直す |
| コンテナ内で同一ユーザーのみが接続 | 低い | UID/GID、実行ユーザーを確認 |
| 監視・診断ツールがソケットファイルを探索 | 中〜高 | statや接続試行が権限エラーになる可能性 |
| 同じプロセス内で同じパイプ名を複数インスタンスが共有 | 中 | 後述のラチェット挙動を確認 |
たとえば、次のようなケースでは注意が必要です。
using var server = new NamedPipeServerStream(
"mypipe",
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
PipeOptions.CurrentUserOnly);
このコードをLinux上で実行すると、実際のソケットパスは環境や実装に応じた場所に作成されます。ドキュメント例では/tmp/CoreFxPipe_mypipeのようなパスで権限確認する例が示されています。従来はumask次第で所有者以外にも見える権限になることがありましたが、.NET 11ではCurrentUserOnly指定時に0600へ固定されます。(GitHub)
対応が不要なケース
次の条件に当てはまる場合、今回の変更はむしろ望ましい改善です。
| 条件 | 判断 |
|---|---|
| サーバーとクライアントが同じOSユーザーで動く | 対応不要の可能性が高い |
CurrentUserOnlyを「同一ユーザー限定」のために使っている | 意図と一致する |
| 他ユーザーからの接続や探索を想定していない | 影響は限定的 |
| Unix環境で名前付きパイプを使っていない | 影響なし |
| Windowsのみで動作するアプリ | 今回のUnixソケット権限変更の直接影響はなし |
CurrentUserOnlyは、そもそも同じユーザーに接続を限定するためのオプションです。したがって、アプリの設計が「同じユーザーだけが接続する」で一貫しているなら、基本的にはコード変更ではなく回帰テストで確認する程度で十分です。
対応が必要になりやすいケース
問題になりやすいのは、CurrentUserOnlyを指定しているにもかかわらず、実際の運用では別ユーザー接続を許していたケースです。
たとえば、次のような構成です。
常駐サービス: app-service ユーザーで実行
CLIツール: developer ユーザーで実行
通信経路: NamedPipeServerStream + CurrentUserOnly
実行環境: Linux
この場合、.NET 10以前や一部の既存環境では、ソケットファイルの権限やディレクトリ権限によってはCLI側が接続を試みられた可能性があります。しかし.NET 11では、CurrentUserOnly付きで作成されたソケットファイルが0600になるため、developerユーザーからOSレベルで接続できなくなる可能性があります。(GitHub)
この構成で本当に別ユーザー接続を許可したいなら、サーバー側でPipeOptions.CurrentUserOnlyを指定しない設計に変更する必要があります。ただし、オプションを外すだけで必ず接続できるわけではありません。ソケットファイルはumaskの影響を受け、さらにソケットを置くディレクトリへの実行権限や書き込み権限も関係します。ドキュメントでも、クロスユーザーアクセスを意図する場合は、実効的なソケットファイルモードとディレクトリ権限の確認が推奨されています。(GitHub)
移行前に確認すべきチェックリスト
.NET 11への移行やPreview検証を始める前に、次の順で確認すると影響範囲を絞り込みやすくなります。
| 確認項目 | 確認方法 | 判断基準 |
|---|---|---|
PipeOptions.CurrentUserOnlyを使っているか | コード検索 | 使用箇所がなければ今回の変更影響は限定的 |
| Unix環境で実行しているか | CI、本番、開発環境を確認 | Linux、macOS、コンテナは対象 |
| 接続元プロセスのユーザーは同じか | id、systemd unit、コンテナ設定を確認 | 別ユーザーなら要検証 |
| 同じパイプ名を複数インスタンスで使うか | サーバー生成処理を確認 | ラチェット挙動の影響あり |
| 監視ツールがソケットを探索するか | 運用スクリプトやヘルスチェックを確認 | 権限エラーの可能性 |
umaskやディレクトリ権限に依存していないか | 実行環境でstat確認 | クロスユーザー接続では特に重要 |
コード検索では、次のようなキーワードを探します。
PipeOptions.CurrentUserOnly
NamedPipeServerStream
NamedPipeClientStream
System.IO.Pipes
NamedPipeClientStream側だけを見ても不十分です。今回のファイル権限変更は、サーバー側のNamedPipeServerStreamがCurrentUserOnlyを指定してソケットを作成する時点で発生します。
実環境での権限確認方法
Unix環境では、実際にソケットファイルの権限を確認するのが最も確実です。アプリがパイプを作成して待機している状態で、対象パスに対してls -lやstatを実行します。
ls -l /tmp/CoreFxPipe_mypipe
stat /tmp/CoreFxPipe_mypipe
.NET 11 Preview 4以降でCurrentUserOnlyを指定している場合、権限は次のようになることが期待されます。
srw------- owner group ... /tmp/CoreFxPipe_mypipe
srw-------のうち、先頭のsはソケットを示し、rw-------が所有者のみ読み書き可能であることを示します。数値表現では0600です。
C#側で確認する場合は、File.GetUnixFileModeを使ってモードを確認できます。
using System.IO;
using System.IO.Pipes;
using var server = new NamedPipeServerStream(
"mypipe",
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
PipeOptions.CurrentUserOnly);
var mode = File.GetUnixFileMode("/tmp/CoreFxPipe_mypipe");
Console.WriteLine(mode);
実務では、単体テストだけでなく、サービス起動後に別ユーザーから接続する統合テストを用意しておくと安全です。今回の変更はコンパイルエラーではなく、接続時の権限エラーとして現れやすいためです。
同じパイプ名を共有する場合のラチェット挙動に注意
今回の変更で特に見落としやすいのが、同一プロセス内で複数のNamedPipeServerStreamインスタンスが同じパイプ名を使う場合の挙動です。
ドキュメントでは、同じパイプ名を使うインプロセスの共有サーバーキャッシュにおいて、いずれかのインスタンスがCurrentUserOnlyを指定すると、その時点でソケットファイルが0600へ締められ、その共有ライフタイム中は後続のインスタンスがCurrentUserOnlyを指定しなくても権限は緩められないと説明されています。(GitHub)
イメージとしては次のような動きです。
| 作成順 | オプション | ソケット権限の結果 |
|---|---|---|
| 1つ目 | PipeOptions.None | umaskに依存 |
| 2つ目 | PipeOptions.CurrentUserOnly | 0600へ変更 |
| 3つ目 | PipeOptions.None | 0600のまま |
つまり、後からCurrentUserOnlyなしのインスタンスを作っても、同じ共有サーバーのライフタイム中は権限が元に戻りません。
これは「一度締めた権限は勝手に緩めない」という安全側の設計です。ただし、実装上は次のような混在があると原因調査が難しくなります。
// 先に通常のサーバーを作成
using var server1 = new NamedPipeServerStream(
"mypipe",
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
PipeOptions.None);
// 後からCurrentUserOnly付きで同じ名前を使用
using var server2 = new NamedPipeServerStream(
"mypipe",
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
PipeOptions.CurrentUserOnly);
このようなコードが同じアプリ内にある場合、「CurrentUserOnlyを指定していない箇所なのに、なぜ別ユーザーから接続できないのか」というトラブルにつながります。対策として、同じパイプ名ではセキュリティ方針を統一し、同一パスでCurrentUserOnlyあり・なしを混在させない設計にするのが安全です。
移行時の判断基準
対応方針は、名前付きパイプを「誰に使わせたいか」で決めます。
| 目的 | 推奨対応 |
|---|---|
| 同一ユーザーだけに接続させたい | PipeOptions.CurrentUserOnlyを維持 |
| 別ユーザーのローカルプロセスにも接続させたい | CurrentUserOnlyを外すことを検討 |
| セキュリティを強めたい | .NET 11の新挙動を前提にテスト |
| 監視ツールだけがソケットを見たい | 権限エラーを前提に監視方法を見直す |
| 複数ユーザーで共有するIPCを作りたい | ソケット配置先ディレクトリ、umask、実行ユーザーを明示的に設計 |
CurrentUserOnlyを外す場合は、代替のアクセス制御を必ず検討してください。単にオプションを削除すると、環境によっては想定以上に広いユーザーから接続できる可能性があります。
たとえば、共有用のIPCを作るなら次の観点を決めておきます。
| 設計項目 | 例 |
|---|---|
| 接続を許可するユーザー | 同一グループのユーザーのみ |
| ソケット配置先 | /run/myapp/など、権限管理しやすいディレクトリ |
| ディレクトリ権限 | 所有者・グループを明示 |
umask | systemd unitや起動スクリプトで管理 |
| 認証 | パイプ接続後にアプリケーション層で検証 |
| ログ | 権限エラー、接続拒否、ユーザーIDを記録 |
.NETのオプションだけにアクセス制御を任せるのではなく、OS権限とアプリケーション側の認証を組み合わせて設計することが重要です。
よくあるトラブルと対処法
.NET 11へ上げたらLinuxのCLIツールが接続できない
まず、サーバー側がPipeOptions.CurrentUserOnly付きで起動していないか確認します。次に、CLIツールとサーバーの実行ユーザーを確認します。
ps -eo user,pid,cmd | grep myapp
id
別ユーザーで動いているなら、今回の変更によりOSレベルで接続できなくなっている可能性があります。別ユーザー接続が仕様なら、CurrentUserOnlyを外すことを検討し、ソケットファイルと配置ディレクトリの権限を明示的に設計してください。
CurrentUserOnlyを外したのに接続できない
CurrentUserOnlyを外しても、接続許可が自動的に広がるとは限りません。ソケットファイルはumaskの影響を受けます。また、ソケットファイルが置かれるディレクトリに対して、接続元ユーザーが必要な権限を持っている必要があります。ドキュメントでも、クロスユーザーアクセスを意図する場合は、実効的なソケットファイルモードとディレクトリ権限を確認する必要があるとされています。(GitHub)
確認例は次の通りです。
stat /tmp/CoreFxPipe_mypipe
stat /tmp
namei -l /tmp/CoreFxPipe_mypipe
namei -lを使うと、パス上の各ディレクトリ権限を追えるため、ソケットファイルそのものではなく親ディレクトリで詰まっているケースを見つけやすくなります。
同じコードなのに環境によって再現しない
従来の挙動はumaskやディレクトリ権限に依存していたため、開発環境、CI、本番コンテナで結果が違うことがあります。.NET 11の新挙動ではCurrentUserOnly指定時に0600へ寄せられるため、むしろ環境差は減ります。ただし、別ユーザー接続に依存している場合は、環境差が「接続できる・できない」の差として現れます。
確認では、次の3点を揃えて見ます。
| 確認対象 | コマンド例 |
|---|---|
| .NETバージョン | dotnet --info |
| 実行ユーザー | id、ps -eo user,pid,cmd |
| ソケット権限 | stat、ls -l |
実務での移行手順
.NET 11への移行を進める場合は、次の順序で対応すると抜け漏れを減らせます。
| 手順 | 作業 | 目的 |
|---|---|---|
| 1 | PipeOptions.CurrentUserOnlyをコード検索 | 影響箇所を洗い出す |
| 2 | Unix環境で実行される箇所を分類 | Windows専用コードを除外 |
| 3 | サーバーとクライアントの実行ユーザーを確認 | 接続失敗リスクを判定 |
| 4 | 同じパイプ名の混在を確認 | ラチェット挙動の影響を防ぐ |
| 5 | Preview環境で接続テスト | 実行時エラーを検出 |
| 6 | 必要ならCurrentUserOnlyを外す | クロスユーザー接続を許可 |
| 7 | OS権限とアプリ認証を再設計 | セキュリティ低下を防ぐ |
特にCIでは、「同一ユーザー接続」と「別ユーザー接続」の両方をテストすると効果的です。サービスアカウントとCLIユーザーが異なる構成は、本番ではよくありますが、開発環境では見落とされがちです。
コード修正の考え方
同一ユーザー限定が仕様なら、コードはそのままで構いません。
var options = PipeOptions.CurrentUserOnly;
using var server = new NamedPipeServerStream(
"mypipe",
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
options);
別ユーザーからの接続を意図しているなら、CurrentUserOnlyを外します。
var options = PipeOptions.None;
using var server = new NamedPipeServerStream(
"mypipe",
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
options);
ただし、この変更だけで完了と考えないでください。CurrentUserOnlyを外すと、同一ユーザー限定という.NET側の保護も外れます。必要に応じて、接続後にクライアントの識別、トークン検証、プロトコル上の認証、操作権限のチェックを追加します。
また、同じパイプ名でCurrentUserOnlyあり・なしを切り替える設計は避けるべきです。読みやすいコードにするなら、用途別にパイプ名を分けるのも有効です。
mypipe-user-only
mypipe-shared
このように名前から用途が分かるようにしておくと、将来の保守で誤ってセキュリティ方針を混在させるリスクを減らせます。
セキュリティ面で押さえるべきポイント
今回の変更は、Unix上の.NET名前付きパイプにおいて「接続時に拒否する」だけでなく、「ソケットファイル自体を他ユーザーから扱えないようにする」方向の強化です。これは、ローカルユーザーが多いサーバーや共有開発環境ではメリットが大きい変更です。
一方で、運用上の都合で別ユーザーから接続させていた場合は、これまで曖昧だった設計を明文化する必要があります。
| セキュリティ観点 | 推奨 |
|---|---|
| 同一ユーザー専用IPC | CurrentUserOnlyを維持 |
| 管理者用CLIからの操作 | CLIとサービスの実行ユーザーを合わせるか、共有設計を明示 |
| 複数ユーザー共有 | CurrentUserOnlyに頼らず、OS権限と認証を設計 |
| 監視・診断 | ソケット接続ではなく専用エンドポイントやログを検討 |
| コンテナ | 実行UID、ボリューム、ソケット配置先を確認 |
「動いていたから問題ない」ではなく、「どのユーザーに接続を許すのか」を明確にすることが、今回の変更への最も重要な対応です。
まとめ:まずCurrentUserOnlyの利用箇所と実行ユーザーを確認する
.NET 11 Preview 4では、Unix上のNamedPipeServerStreamでPipeOptions.CurrentUserOnlyを指定すると、裏側のUnixドメインソケットが0600に設定されます。これは、同一ユーザー限定というオプションの意図に沿ったセキュリティ強化です。
対応の優先順位は明確です。まずコードベースからPipeOptions.CurrentUserOnlyを検索し、Unix環境で実行される箇所を特定してください。次に、サーバーとクライアントが同じユーザーで動いているかを確認します。同一ユーザーなら多くの場合は対応不要です。別ユーザー接続を前提にしているなら、CurrentUserOnlyを外すか、実行ユーザーを統一するか、共有IPCとしてOS権限と認証を再設計する必要があります。
最後に、同じパイプ名でCurrentUserOnlyあり・なしを混在させていないか確認しましょう。一度0600に締められた共有サーバーの権限は、その共有ライフタイム中に後続インスタンスで緩められません。Preview段階の情報であるため最終リリースまでに細部が変わる可能性はありますが、LinuxやmacOSで.NETの名前付きパイプを使うチームは、早めに移行テストへ組み込んでおくのが安全です。(GitHub)

コメント