プロンプトとコンテキスト
2つのローカルプロセスがUnixドメインソケット経由で連携しています。特権プロセスがファイルをオープンするかリスニングソケットを作成し、それを低特権のワーカープロセスに引き渡します。ディスクリプタの受け渡し方法、レシーバーが受け取るもの、そして切り捨てや認可リスクへの対処法を説明してください。
これはLinux/UnixのIPC、ファイルディスクリプタテーブルとオープンファイル記述(open file description)の違い、およびsendmsg/recvmsgの補助データ(ancillary data)プロトコルをテストするものです。Linuxのunix(7)は、プロセス間で開かれたファイルディスクリプタのセットを送受信するためにSCM_RIGHTSを定義しています。
面接官がテストしているポイント
- プロセスローカルなfd整数値とカーネルのオープンファイル記述を区別できているか。
SCM_RIGHTSが通常のペイロードに整数を配置するのではなく、補助データを使用することを理解しているか。cmsghdr、CMSG_SPACE、CMSG_LENのサイズを正しく設定し、MSG_CTRUNCをチェックできるか。- ソケットパスのパーミッション、送信者のアイデンティティ、リソース制限、closeのタイミング、および失敗時のクリーンアップを網羅できているか。
最初に明確にすべき質問
- プロセスはユーザーを共有しているか、また特権降格や信頼境界はどこにあるか?
- ディスクリプタは通常のファイル、接続済みソケット、リスニングソケット、epoll fd、またはデバイスfdのどれか?
- チャネルは
SOCK_STREAMかSOCK_DGRAMか、またプロトコルにはメッセージ境界や確認応答(acknowledgements)が必要か? - レシーバーは送信者のクレデンシャル、リソースタイプ、読み取り専用プロパティ、および予期されるfd数を検証できるか?
30秒で答える要約
Unixドメインソケットペアを作成し、sendmsgからSOL_SOCKET/SCM_RIGHTS制御メッセージでディスクリプタを転送し、実際のペイロードにプロトコルバージョンとリクエストIDを含めます。レシーバー側は十分な大きさのCMSG_SPACEバッファを指定してrecvmsgを呼び出し、レベル、タイプ、長さ、MSG_CTRUNCを確認した後、受信したfdを自身のプロセス内の新しい整数として使用します。境界を越えるのはオープンファイル記述への参照であるため、通常レシーバーは異なるfd番号を取得します。プロトコルではピアのクレデンシャル検証、件数の上限設定、close-on-execの設定を行い、エラーパスでは受け入れられなかった、または未使用のディスクリプタをすべてクローズします。
ステップバイステップの詳細解説
fd番号とオープンファイル記述を区別する
fdはプロセスのfdテーブル内の整数インデックスです。オープンファイル記述は、ファイルオフセットやステータスフラグなどのオープン状態を保持するカーネルオブジェクトです。SCM_RIGHTSは後者への参照をコピーします。レシーバーは通常異なるfd整数値を取得し、これは意味論的に別のプロセスのfdテーブルへfdを複製(duplicate)するのと同様です。
通常のバイトではなく補助データを使用する
sendmsgとrecvmsgは、msghdr.msg_controlを介してcmsghdrレコードのチェーンを伝送します。cmsg_levelをSOL_SOCKETに、cmsg_typeをSCM_RIGHTSに設定し、データ領域に整数fd配列を配置します。通常のペイロードはバージョン、用途、確認応答IDを運ぶことができますが、制御メッセージを置き換えることはできません。
制御バッファのサイズを正しく設定する
実際のカウントに対して、送信者はcmsg_len用にCMSG_LEN(n * sizeof(int))を使用し、受信者は少なくともCMSG_SPACE(n * sizeof(int))以上のアライメントされた空間を割り当てます。CMSG_FIRSTHDRおよびCMSG_NXTHDRを使用して解析し、短い長さや予期しないタイプを拒否します。
切り捨てとストリーム境界の処理
受信制御バッファが小さすぎる場合、補助データが切り捨てられるか破棄され、MSG_CTRUNCがセットされます。レシーバーは不完全なディスクリプタリストを使用してはなりません。LinuxではSOCK_STREAMでの補助データ送信時に少なくとも1バイトの実際のデータが必要であり、補助データは受信バリアを形成するため、バイトストリームの位置に依存するのではなく、制御メッセージをリクエストIDにバインドします。
アイデンティティと認可境界の確立
ファイルシステムソケットにおいて、ディレクトリとソケットのパーミッションが最初の境界となります。また、サーバーはSO_PEERCREDまたはSCM_CREDENTIALSを使用し、アプリケーション層でテナント、用途、リソースタイプを確認する必要があります。fdを受信すること自体が追加の権限を付与するわけではありません。送信者は認可された参照のみを転送する必要があります。
ライフタイムとリソース制限の管理
送信者は送信後に自身のfdをクローズできますが、カーネルはレシーバーが受け入れるまでインフライトの参照を保持します。Linuxはこの操作をRLIMIT_NOFILEとSCM_MAX_FDで制限しています。現在のmanページではSCM_MAX_FDは通常253と記載されており、古いバージョンでは255でした。メッセージごとおよびワーカーごとのディスクリプタ数を制限し、拒否されたことが観測できるようにします。
struct msghdr msg = {0};
struct iovec iov = {.iov_base = "F", .iov_len = 1};
union { char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr align; } control;
msg.msg_iov = &iov; msg.msg_iovlen = 1;
msg.msg_control = control.buf; msg.msg_controllen = sizeof(control.buf);
struct cmsghdr *c = CMSG_FIRSTHDR(&msg);
c->cmsg_level = SOL_SOCKET; c->cmsg_type = SCM_RIGHTS;
c->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(c), &fd, sizeof(fd));
sendmsg(sock, &msg, MSG_NOSIGNAL);優れた回答例
これを確認応答付きのローカルIPCプロトコルとして構築します。送信側はUnixドメインソケット上でSOL_SOCKET/SCM_RIGHTS制御メッセージを指定したsendmsgを使用し、実際のペイロードにはプロトコルバージョン、用途、リクエストIDを含めます。受信側はCMSG_SPACEで制御領域を割り当て、タイプ、長さ、MSG_CTRUNCをチェックし、ピアのクレデンシャル、ディスクリプタ数、リソースタイプを検証して初めてワーカーに引き渡します。重要なセマンティクスは、この転送がオープンファイル記述を参照することであり、そのためレシーバー側の整数値は通常異なり、ファイルオフセットやオープン状態が共有される可能性がある点です。close-on-execを設定し、インフライトのディスクリプタを制限し、RLIMIT_NOFILEとSCM_MAX_FDを処理し、すべての障害パスをクローズおよび監査します。
よくある間違い
- fd整数値をJSONやバイトペイロードに書き込み、相手プロセスがそれを直接使用できると思い込む。
CMSG_SPACEのアライメントを無視し、制御データ用にsizeof(int)分しか割り当てない。MSG_CTRUNCのチェックをスキップし、切り捨てられたディスクリプタリストを使用してしまう。- ピアのクレデンシャルや用途を検証せず、Unixソケットのパスのみをチェックする。
- レシーバーが新しいfd番号を取得することを見落としたり、共有オフセットやステータスのセマンティクスを指定しなかったりする。
- close-on-exec、fd制限、送信失敗の処理、および未使用ディスクリプタのクローズを怠る。
フォローアップ質問と回答
レシーバーは同じファイルディスクリプタを取得しますか?
通常、同じ整数値ではありません。カーネルはレシーバーのfdテーブルに同じオープンファイル記述への参照をコピーするため、ファイルオフセットや一部のオープン状態が共有される可能性があります。独立したオフセットが必要な場合は、同じfd番号を前提とせず、再オープンするかデータをコピーしてください。
なぜ実際のバイトを送信するのですか?
LinuxのUnixストリームソケットでは、補助データを送信する際に同じsendmsg内に少なくとも1バイトの実際のデータが必要です。また、これによりプロトコルが制御メッセージとリクエストを関連付けることができます。Linuxデータグラムでは省略可能ですが、ポータブルなコードでは常に1バイトの実データを含めるべきです。
制御バッファが小さすぎる場合はどうなりますか?
補助データが切り捨てられるか破棄され、MSG_CTRUNCがセットされます。無効または過剰なディスクリプタをクローズし、プロトコルエラーを返し、イベントを記録してください。部分的なリストを完全な認可として扱ってはなりません。
特権プロセスが誤ったリソースを渡さないようにするにはどうすればよいですか?
ソケットパーミッションとピアクレデンシャルを使用して接続を制限し、アプリケーション層でリクエストID、テナント、用途、リソースタイプをバインドします。送信側は許可リストからのみ選択し、受信側は読み取り専用プロパティ、パスやソケットの状態を確認し、すべての認可とクローズを監査します。