Unixドメインソケットとfd受け渡し
特権プロセスが開いたソケットやファイルを非特権ワーカーへ安全に手渡す――その仕組みがSCM_RIGHTSです。双方向通信からfd受け渡し、権限移譲、abstract socketまで、権限分離設計の土台が腑に落ちます。
- 1.Unixドメインソケットは同一ホスト内の双方向通信路で、SOCK_STREAM/SOCK_DGRAM/SOCK_SEQPACKETを選べる。ネットワークソケットと同じAPIだがTCP/IPスタックを通さず、カーネル内でバッファをコピーするだけなので高速。
- 2.sendmsg/recvmsgの補助データSCM_RIGHTSで、fdそのもの(が指すopen file description)を別プロセスへ渡せる。受信側ではfd番号が新たに割り当てられ、オフセットやO_NONBLOCK等の状態を共有した複製になる。
- 3.abstract socketはsun_path先頭がヌルバイトの名前空間で、ファイルシステムに痕跡を残さずunlink不要。ただしパーミッションで守れずネットワーク名前空間に閉じる点に注意。
Unixドメインソケットとは何か
Unixドメインソケット(AF_UNIX、別名 AF_LOCAL)は、同一ホスト内のプロセス間 で双方向にデータをやり取りするための通信路です。APIはネットワークソケットと完全に同一で、socket(AF_UNIX, type, 0) で生成し、bind/listen/accept(または connect)で接続を確立し、read/write や sendmsg/recvmsg でやり取りします。
決定的な違いは、TCP/IPスタックを一切通さない ことです。ネットワークソケットがチェックサム・順序制御・輻輳制御・ルーティングを経由するのに対し、Unixドメインソケットは送信側のバッファから受信側のカーネルバッファへ データをコピーするだけ で完結します。アドレスの解決もポート割り当ても要らず、ループバック(127.0.0.1)経由のTCPより明確に低レイテンシ・高帯域になります。同一ホスト内通信における定番が他IPCを差し置いてこれである理由は、IPCの設計比較 で論じたとおり、双方向・多重化・後述のfd受け渡しを1つのAPIで束ねている点にあります。
3つの型:ストリーム・データグラム・シーケンスドパケット
Unixドメインソケットは3種類のソケット型を選べ、メッセージ境界の扱いが異なります。
| 型 | 境界 | 信頼性 | 性質 |
|---|---|---|---|
| SOCK_STREAM | なし(バイトストリーム) | 順序保証・無損失 | TCP相当。read 1回が write 1回と一致する保証はない |
| SOCK_DGRAM | あり(メッセージ単位) | 順序保証・無損失 | UDP相当だがローカルなので欠落しない。1 send = 1 recv |
| SOCK_SEQPACKET | あり(メッセージ単位) | 順序保証・無損失・接続型 | 境界を保ちつつ接続を張る。両者の良いとこ取り |
ネットワークのUDPと違い、AF_UNIX の SOCK_DGRAM は カーネル内転送なのでパケットが失われません(バッファが満杯なら送信側がブロックまたは EAGAIN で待たされるだけ)。境界を保ちたいがコネクション指向の管理(接続/切断の検知)も欲しい場合は SOCK_SEQPACKET が最適で、後述のfd受け渡しを伴う制御プロトコルでは境界保持が効くため選ばれることが多い型です。
名前空間:パス名 vs abstract socket
サーバー側がアドレスに bind する際、struct sockaddr_un の sun_path をどう埋めるかで2つの名前空間に分かれます。
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* パス名 or 抽象名 */
};
- パス名ソケット(pathname):
sun_pathに/run/foo.sockのようなパスを入れる。bindするとファイルシステム上にソケット型の特殊ファイルが作られ、クライアントは同じパスへconnectする。ディレクトリとファイルのパーミッションでアクセス制御 できる反面、プロセス終了後もファイルが残るため明示的なunlinkが要る(残骸があると次回bindがEADDRINUSEで失敗する)。 - abstract socket(抽象名前空間、Linux固有):
sun_path[0]を ヌルバイト(\0)にして 残りを名前として使う。ファイルシステムに痕跡を残さず、全fdが閉じた瞬間に自動消滅 するのでunlinkも後始末も不要。名前の衝突管理が要らないコンテナや一時的なデーモンで重宝します。
abstract socketは便利ですが、ファイルシステムのパーミッションが効きません。パス名ソケットならディレクトリの実行権限やソケットファイルのモードで接続元を絞れますが、abstract名はネームスペース内のどのプロセスからも connect できてしまいます。アクセス制御は SO_PEERCRED(後述)で接続相手のUID/PID/GIDを検証して自前で行う必要があります。なお、abstract socketは ネットワーク名前空間(netns)に閉じる ため、別netnsのコンテナからは見えません。これは隔離の利点でもあり、netns越しに繋げないという制約でもあります。
SCM_RIGHTS:fdそのものを渡す
Unixドメインソケットが他のIPCと一線を画す能力が、ファイルディスクリプタの受け渡し です。通常のデータとは別に、sendmsg/recvmsg の 補助データ(ancillary data、別名 control message) に SCM_RIGHTS というレベルでfd配列を載せて送ります。
/* 送信側:fd を1つ補助データに載せる */
struct msghdr msg = {0};
char buf[1] = {'F'}; /* 通常データは最低1バイト要る */
struct iovec iov = { buf, sizeof(buf) };
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
char cmsgbuf[CMSG_SPACE(sizeof(int))];
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int)); /* 送るfd番号 */
sendmsg(sock, &msg, 0);
ここで本質的に重要なのは、渡されるのはfd「番号」ではなく、その番号が指すカーネル内のオブジェクト だという点です。具体的には ファイルディスクリプタテーブル のエントリが指す open file description(カーネル内の struct file)への参照が複製されます。
- 受信側では、recvmsg時に 新しいfd番号がそのプロセスで自動的に割り当てられ、補助データから読み出せます。送信側の番号(例:3)と受信側の番号(例:7)は無関係で構いません。
- 複製されるのは参照なので、送信側と受信側は 同じ open file description を共有 します。つまり ファイルオフセット・
O_NONBLOCKなどのステータスフラグ・ファイルロックの所有を共有 します。一方がlseekで位置を動かせば他方にも見えます。これはdupを別プロセス越しに行ったのと同じ意味で、fork後の親子がfdを共有するのと同じ構造です。 - ソケット・パイプ・通常ファイル・eventfd・epoll fd・memfd など、fdで表せるものは何でも渡せます。
sendmsg で SCM_RIGHTS を送った後、送信側が元のfdを close しても問題ありません。open file descriptionは 参照カウント で管理され、ソケット送信バッファ内の補助データも1つの参照を握るため、受信側が recvmsg で受け取るまでオブジェクトは解放されません。逆に、受信側が補助データを取りこぼす(バッファ不足で MSG_CTRUNC が立つ等)と、渡されたfdが宙に浮いてリーク します。受信時は必ず CMSG_SPACE で十分な領域を確保し、MSG_CTRUNC を検査することが鉄則です。
アクセス権の移譲という意味
fd受け渡しの真価は、アクセス権そのものを移譲できる ことにあります。open file descriptionは「openした瞬間の権限チェックの結果」を保持しています。Unixでは パーミッションチェックはopen時に1回だけ 行われ、以後の read/write ではfdが有効であれば再チェックされません。
この性質とfd受け渡しを組み合わせると、強力な権限分離パターンが成立します。
特権プロセス(root) 非特権ワーカー(nobody)
open("/etc/secret", O_RDONLY)
→ fd が「読める権利」を内包
sendmsg(SCM_RIGHTS, fd) ─────→ recvmsg → 新fd取得
read(新fd) ← root権限なしでも読める
ワーカーは自分の権限では /etc/secret を open できなくても、特権プロセスがopen済みのfdを受け取れば中身を読めます。逆方向にも使え、特権プロセスが特権ポート(1024未満)を bind した listening socket をワーカーへ配り、複数ワーカーで accept を分担させる構成(古典的なpre-fork型サーバーやsystemdのsocket activation)が組めます。
fd受け渡しは「最小権限の原則」を実装する基盤です。危険な操作(特権ポートのbind、機微ファイルのopen)は短命な特権ヘルパーに閉じ込め、長時間動くワーカーは Linuxケーパビリティ を落とし seccompシステムコールフィルタ で open 系すら禁止しておく――それでも必要なfdは受け渡しで供給できます。攻撃者がワーカーを乗っ取っても、渡されたfd以上のものは触れません。ブラウザのサンドボックスやコンテナランタイムが多用する構造です。
受け渡しの落とし穴と接続相手の確認
fd受け渡しには実装上の罠がいくつかあります。第一に、補助データの送受信には通常データが最低1バイト必要 です。iovec を空にして補助データだけ送ろうとすると、受信側で正しく受け取れないことがあるため、ダミーの1バイトを必ず添えます。第二に、1メッセージで複数のfdをまとめて渡せます(SCM_RIGHTS のデータ部にint配列を並べる)が、受信バッファが足りないと一部だけ受信し残りがリークします。
そしてabstract socketのようにパーミッションで守れない場合、接続してきた相手が信頼できるか を確かめる必要があります。Linuxでは getsockopt(SO_PEERCRED) で接続相手の PID・UID・GID をカーネルから直接取得できます。これはアプリが自己申告する値ではなく カーネルが接続確立時に記録した検証済みの値 なので偽装できません。SCM_RIGHTS を悪用されないためには、fdを送る前にこのpeer credentialで相手を認証するのが定石です。
fd受け渡しは「権利の移譲」であるがゆえに、相手の選定を誤ると致命的です。信頼できない接続元へ書き込み可能なfdや特権ファイルのfdを渡せば、そのまま権限昇格の経路になります。逆に、信頼できないソケットからfdを 受け取る のも危険で、攻撃者が用意した予期せぬ種別のfd(例:/proc/self/mem への書き込みfd)を掴まされる恐れがあります。SO_PEERCRED での相手認証と、受け取ったfdの種別・モードの検証(fstat で st_mode や、必要なら fcntl(F_GETFL) を確認)を省略してはいけません。
まとめ
Unixドメインソケット(AF_UNIX)は TCP/IPスタックを通さずカーネル内コピーで完結する 同一ホスト内の双方向通信路で、SOCK_STREAM/SOCK_DGRAM/SOCK_SEQPACKET の3型でメッセージ境界を選べます。名前空間は パス名(パーミッションで守れるがunlink要) と abstract socket(sun_path先頭がヌル、痕跡を残さず自動消滅、ただしパーミッション無効でnetnsに閉じる) の2種。最大の特徴は sendmsg/recvmsg の補助データ SCM_RIGHTS によるfd受け渡し で、渡るのはfd番号ではなく open file description への参照――オフセットやステータスフラグを共有する dup 相当の複製です。openは1回しか権限チェックされない性質と組み合わせ、特権プロセスがopen済みのfdを非特権ワーカーへ手渡す権限移譲 が実装でき、ケーパビリティ剥奪やseccompと併せた最小権限設計の土台になります。受信時は MSG_CTRUNC でのリーク回避と SO_PEERCRED での相手認証が必須です。前提の通信抽象は ソケットとTCP接続のカーネル状態管理、他方式との位置づけは IPCの設計比較 を併読してください。
- Unixドメインソケットは AF_UNIX、TCP/IPを通さず同一ホスト内で双方向通信。
SOCK_DGRAMでもローカルなので 欠落しない。 SCM_RIGHTSはsendmsg/recvmsgの 補助データ(ancillary data) でfdを渡す機構。渡るのは open file description への参照(dup相当)で、オフセット・ステータスフラグを共有 する。- 受信側では fd番号が新規割り当て され、送信側と番号は無関係。送信後に送信側が
closeしても 参照カウント で生き残る。 - 補助データには 通常データ最低1バイトが必要。受信バッファ不足で
MSG_CTRUNCが立つと fdがリーク する。 - abstract socket は
sun_path[0]がヌルバイトで ファイルシステムに痕跡を残さず自動消滅、unlink不要。ただし パーミッションで守れず netns に閉じる。 - 相手認証は
SO_PEERCRED(PID/UID/GID、カーネル記録で偽装不可)。openは 1回しか権限チェックされない ためfd受け渡しで権限移譲が成立する。
OS Article
Unixドメインソケットとfd受け渡しを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Unixドメインソケット
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
sendmsg/recvmsgの補助データSCM_RIGHTSで、fdそのもの(が指すopen file description)を別プロセスへ渡せる。受信側ではfd番号が新たに割り当てられ、オフセットやO_NONBLOCK等の状態を共有した複製になる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Unixドメインソケット / SCM_RIGHTS」に近いか確認する。
- 強みである「Unixドメインソケットは同一ホスト内の双方向通信路で、SOCK_STREAM/SOCK_DGRAM/SOCK_SEQPACKETを選べる。ネットワークソケットと同じAPIだがTCP/IPスタックを通さず、カーネル内でバッファをコピーするだけなので高速。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。