[インデックス 12682] ファイルの概要
このコミットは、Go言語の標準ライブラリsyscall
パッケージからcreds_linux_test.go
というテストファイルを削除するものです。このテストは、UNIXドメインソケットを介したプロセス間通信において、送信元プロセスの認証情報(PID, UID, GID)をやり取りするSCM_CREDENTIALS
メカニズムを検証するためのものでした。しかし、Go 1のリリース直前という時期的な制約と、syscall
パッケージにシステム固有のテストを追加することに対するポリシー上の懸念から、一時的に削除されることになりました。
コミット
commit 1f6fc949f67973b4b745b5f0bd9c52ac27578186
Author: Rob Pike <r@golang.org>
Date: Mon Mar 19 11:15:28 2012 +1100
sysycall: remove creds_linux_test.go
It is unprecedented to add tests to package syscall, especially
system-specific ones. Not a policy worth changing right before Go 1
is cut.
The sole existing test, passfd_test.go, contains the line
// +build linux darwin probablyfreebsd probablyopenbsd
which argues that this is not a subject to be undertaking likely.
Note that passfd_test.go also went in just now. It's the only test
in syscall.
Deleting for now, will reconsider after Go 1.
R=golang-dev, bradfitz, r, dsymonds
CC=golang-dev
https://golang.org/cl/5846063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1f6fc949f67973b4b745b5f0bd9c52ac27578186
元コミット内容
このコミットは、src/pkg/syscall/creds_linux_test.go
ファイルを削除しています。このファイルは、UNIXドメインソケットを介してプロセス認証情報(PID, UID, GID)を送信・受信するSCM_CREDENTIALS
メカニズムをテストするためのGo言語のテストコードでした。具体的には、syscall.Socketpair
でソケットペアを作成し、一方のソケットでSO_PASSCRED
オプションを有効にして認証情報を送信し、もう一方のソケットでそれを受信・検証する内容でした。
変更の背景
このコミットが行われた2012年3月は、Go言語の最初の安定版リリースであるGo 1のリリースが間近に迫っている時期でした。Go 1は、言語仕様と標準ライブラリの安定性を確立し、後方互換性を保証するための重要なマイルストーンでした。
コミットメッセージによると、syscall
パッケージにテストを追加すること、特にシステム固有のテスト(この場合はLinux固有のSCM_CREDENTIALS
テスト)を追加することは、当時のGoプロジェクトのポリシーに反していました。syscall
パッケージは、OSのシステムコールを直接Goから呼び出すための低レベルなインターフェースを提供しており、そのテストは非常にデリケートで、OSのバージョンや環境に強く依存する傾向がありました。
コミットの作者であるRob Pikeは、syscall
パッケージに既存のテストがpassfd_test.go
のみであり、それも// +build linux darwin probablyfreebsd probablyopenbsd
というビルドタグを持つ、非常に限定的な環境でのみ実行されるテストであることを指摘しています。これは、syscall
パッケージのテストがシステム固有の複雑さを伴うことを示唆しています。
Go 1のリリースを目前に控え、このようなシステム固有の、かつ前例のないテストを追加することは、リリースプロセスのリスクを高め、安定性を損なう可能性があると判断されました。そのため、このテストは一時的に削除され、Go 1リリース後に再検討されることになりました。これは、Go 1の安定性とリリーススケジュールを最優先する判断であったと言えます。
前提知識の解説
このコミットの背景を理解するためには、以下の技術的な前提知識が必要です。
1. Go言語のsyscall
パッケージ
Go言語のsyscall
パッケージは、オペレーティングシステムが提供する低レベルなシステムコール(System Call)へのインターフェースを提供します。これにより、Goプログラムから直接OSの機能(ファイル操作、ネットワーク通信、プロセス管理など)を呼び出すことができます。このパッケージはOSに強く依存するため、OSごとに異なる実装を持つことが一般的です。
2. UNIXドメインソケット (Unix Domain Socket, UDS)
UNIXドメインソケットは、同じホストマシン上で動作するプロセス間で通信を行うためのソケットの一種です。TCP/IPソケットがネットワークを介した通信に使用されるのに対し、UNIXドメインソケットはファイルシステム上のパス名(例: /tmp/mysocket
)をアドレスとして使用し、カーネルを介して効率的なプロセス間通信(IPC)を提供します。ネットワークスタックを介さないため、TCP/IPソケットよりも高速でオーバーヘッドが少ないという特徴があります。
3. 補助データ (Ancillary Data) と SCM_CREDENTIALS
UNIXドメインソケットでは、通常のデータストリームに加えて、「補助データ(Ancillary Data)」と呼ばれる特別な情報を送受信できます。これは、sendmsg()
やrecvmsg()
システムコールで利用され、ファイルディスクリプタの転送(SCM_RIGHTS
)や、送信元プロセスの認証情報(SCM_CREDENTIALS
)の転送などに使われます。
SCM_CREDENTIALS
: これはLinuxなどのUNIX系OSで利用される補助データの一種で、UNIXドメインソケットを介してメッセージを送信したプロセスの認証情報(プロセスID: PID、実効ユーザーID: UID、実効グループID: GID)を受信側プロセスに伝えるために使用されます。これにより、受信側はメッセージの送信元がどのプロセスであるかを信頼性高く確認できます。
4. SO_PASSCRED
ソケットオプション
SO_PASSCRED
は、UNIXドメインソケットに設定できるソケットオプションの一つです。このオプションを有効にすると、そのソケットから送信されるメッセージに、自動的に送信元プロセスの認証情報(SCM_CREDENTIALS
)が補助データとして付加されるようになります。受信側は、この補助データを解析することで、送信元のPID, UID, GIDを取得できます。
5. syscall.UnixCredentials
関数
Go言語のsyscall
パッケージには、UnixCredentials
という関数があります。これは、SCM_CREDENTIALS
補助データを作成するためのヘルパー関数です。syscall.Ucred
構造体(PID, UID, GIDを含む)を受け取り、それをUNIXドメインソケットの補助データとして送信可能なバイトスライスに変換します。
6. Go 1リリース
Go 1は、Go言語の最初のメジャー安定版リリースであり、2012年3月28日にリリースされました。このリリースは、Go言語の仕様と標準ライブラリのAPIを安定させ、将来のバージョンとの後方互換性を保証することを目的としていました。Go 1以降、Go言語の進化は、既存のコードベースを壊さないように慎重に進められるようになりました。
技術的詳細
削除されたcreds_linux_test.go
ファイルは、TestSCMCredentials
というテスト関数を含んでいました。このテストは、以下のステップでSCM_CREDENTIALS
の動作を検証していました。
- ソケットペアの作成:
syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
を使用して、UNIXドメインソケットのペア(fds[0]
とfds[1]
)を作成します。これにより、2つのプロセス(または同じプロセス内の2つの異なる部分)が互いに通信できるパイプのようなチャネルが確立されます。 SO_PASSCRED
の有効化:syscall.SetsockoptInt(fds[0], syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
を呼び出し、fds[0]
ソケットでSO_PASSCRED
オプションを有効にします。これにより、このソケットから送信されるメッセージには、送信元の認証情報が自動的に付加されるようになります。net.FileConn
への変換:syscall
パッケージのファイルディスクリプタ(fds[0]
,fds[1]
)を、Goのnet
パッケージのnet.Conn
インターフェースに変換します。これにより、Goの標準的なネットワークI/O関数を使用してソケットを操作できるようになります。具体的には、net.UnixConn
型として扱われます。- 認証情報の準備と送信:
os.Getpid()
,os.Getuid()
,os.Getgid()
を使用して、現在のプロセスのPID, UID, GIDを取得し、syscall.Ucred
構造体に格納します。syscall.UnixCredentials(&ucred)
を呼び出して、このUcred
構造体からSCM_CREDENTIALS
補助データ(バイトスライス)を生成します。cli.(*net.UnixConn).WriteMsgUnix(nil, oob, nil)
を使用して、この補助データ(oob
)をfds[1]
ソケットからfds[0]
ソケットへ送信します。通常のデータは送信せず、補助データのみを送信しています(最初の引数がnil
)。
- 認証情報の受信と検証:
srv.(*net.UnixConn).ReadMsgUnix(nil, oob2)
を使用して、fds[0]
ソケットからメッセージを受信します。この際、補助データがoob2
に格納されます。- 受信した補助データ
oob2
が、送信したoob
と一致するかをbytes.Equal
で検証します。 syscall.ParseSocketControlMessage(oob2)
を使用して、受信した補助データを解析し、syscall.SocketControlMessage
のリストを取得します。syscall.ParseUnixCredentials(&scm[0])
を使用して、解析されたSocketControlMessage
からsyscall.Ucred
構造体を抽出します。- 最後に、抽出された
newUcred
が、送信時に準備したucred
と完全に一致するかを検証します。
このテストは、LinuxカーネルのSCM_CREDENTIALS
メカニズムがGoのsyscall
パッケージを通じて正しく機能するかを確認するものでした。しかし、この機能はLinux固有であり、他のOSでは異なる動作をするか、あるいは存在しない可能性があります。これが、コミットメッセージで「システム固有のテスト」と表現されている理由です。
コアとなるコードの変更箇所
このコミットによるコアとなるコードの変更は、以下のファイルの削除のみです。
--- a/src/pkg/syscall/creds_linux_test.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package syscall_test
-
-import (
- "bytes"
- "net"
- "os"
- "syscall"
- "testing"
-)
-
-// TestSCMCredentials tests the sending and receiving of credentials
-// (PID, UID, GID) in an ancillary message between two UNIX
-// sockets. The SO_PASSCRED socket option is enabled on the sending
-// socket for this to work.
-func TestSCMCredentials(t *testing.T) {
- fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("Socketpair: %v", err)
- }
- defer syscall.Close(fds[0])
- defer syscall.Close(fds[1])
-
- err = syscall.SetsockoptInt(fds[0], syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
- if err != nil {
- t.Fatalf("SetsockoptInt: %v", err)
- }
-
- srv, err := net.FileConn(os.NewFile(uintptr(fds[0]), ""))
- if err != nil {
- t.Errorf("FileConn: %v", err)
- return
- }
- defer srv.Close()
-
- cli, err := net.FileConn(os.NewFile(uintptr(fds[1]), ""))
- if err != nil {
- t.Errorf("FileConn: %v", err)
- return
- }
- defer cli.Close()
-
- var ucred syscall.Ucred
- if os.Getuid() != 0 {
- ucred.Pid = int32(os.Getpid())
- ucred.Uid = 0
- ucred.Gid = 0
- oob := syscall.UnixCredentials(&ucred)
- _, _, err := cli.(*net.UnixConn).WriteMsgUnix(nil, oob, nil)
- if err.(*net.OpError).Err != syscall.EPERM {
- t.Fatalf("WriteMsgUnix failed with %v, want EPERM", err)
- }
- }
-
- ucred.Pid = int32(os.Getpid())
- ucred.Uid = uint32(os.Getuid())
- ucred.Gid = uint32(os.Getgid())
- oob := syscall.UnixCredentials(&ucred)
-
- // this is going to send a dummy byte
- n, oobn, err := cli.(*net.UnixConn).WriteMsgUnix(nil, oob, nil)
- if err != nil {
- t.Fatalf("WriteMsgUnix: %v", err)
- }
- if n != 0 {
- t.Fatalf("WriteMsgUnix n = %d, want 0", n)
- }
- if oobn != len(oob) {
- t.Fatalf("WriteMsgUnix oobn = %d, want %d", oobn, len(oob))
- }
-
- oob2 := make([]byte, 10*len(oob))
- n, oobn2, flags, _, err := srv.(*net.UnixConn).ReadMsgUnix(nil, oob2)
- if err != nil {
- t.Fatalf("ReadMsgUnix: %v", err)
- }
- if flags != 0 {
- t.Fatalf("ReadMsgUnix flags = 0x%x, want 0", flags)
- }
- if n != 1 {
- t.Fatalf("ReadMsgUnix n = %d, want 1 (dummy byte)", n)
- }
- if oobn2 != oobn {
- // without SO_PASSCRED set on the socket, the ReadMsgUnix will
- // return zero oob bytes
- t.Fatalf("ReadMsgUnix oobn = %d, want %d", oobn2, oobn)
- }
- oob2 = oob2[:oobn2]
- if !bytes.Equal(oob, oob2) {
- t.Fatal("ReadMsgUnix oob bytes don't match")
- }
-
- scm, err := syscall.ParseSocketControlMessage(oob2)
- if err != nil {
- t.Fatalf("ParseSocketControlMessage: %v", err)
- }
- newUcred, err := syscall.ParseUnixCredentials(&scm[0])
- if err != nil {
- t.Fatalf("ParseUnixCredentials: %v", err)
- }
- if *newUcred != ucred {
- t.Fatalf("ParseUnixCredentials = %+v, want %+v", newUcred, ucred)
- }
-}
コアとなるコードの解説
削除されたcreds_linux_test.go
ファイルは、syscall
パッケージのTestSCMCredentials
関数を定義していました。このテストは、LinuxシステムにおけるUNIXドメインソケットのSCM_CREDENTIALS
機能のGo言語バインディングが正しく動作するかを検証するものでした。
具体的には、以下のGo言語のsyscall
パッケージの関数や定数が使用されていました。
syscall.Socketpair
: UNIXドメインソケットのペアを作成します。syscall.AF_LOCAL
: UNIXドメインソケットのアドレスファミリーを指定します。syscall.SOCK_STREAM
: ストリームソケット(信頼性のある接続指向の通信)を指定します。syscall.SetsockoptInt
: ソケットオプションを設定します。syscall.SOL_SOCKET
: ソケットレベルのオプションを指定します。syscall.SO_PASSCRED
: ソケットを介して認証情報を渡すことを有効にするオプションです。syscall.Ucred
: プロセス認証情報(PID, UID, GID)を保持する構造体です。syscall.UnixCredentials
:Ucred
構造体からSCM_CREDENTIALS
補助データを作成します。net.UnixConn.WriteMsgUnix
: UNIXドメインソケットを介してメッセージと補助データを送信します。net.UnixConn.ReadMsgUnix
: UNIXドメインソケットを介してメッセージと補助データを受信します。syscall.ParseSocketControlMessage
: 受信した補助データを解析し、SocketControlMessage
のリストに変換します。syscall.ParseUnixCredentials
:SocketControlMessage
からUcred
構造体を抽出します。
このテストは、SO_PASSCRED
を有効にしたソケットからUnixCredentials
で作成した認証情報を送信し、受信側でParseSocketControlMessage
とParseUnixCredentials
を使ってその認証情報を正しく取得できることを確認していました。特に、os.Getuid() != 0
のチェックは、root以外のユーザーで実行した場合にEPERM
エラーが発生することを確認するもので、権限周りの挙動も考慮されていました。
このテストが削除されたのは、Go 1のリリース前の安定化フェーズにおいて、syscall
パッケージのような低レベルでOS依存性の高い部分に、さらにシステム固有のテストを追加することが、当時のプロジェクトのポリシーと合致しなかったためです。Go 1の目標は、コア言語と標準ライブラリの安定した基盤を提供することであり、特定のOS機能の詳細なテストは、その後のフェーズで追加されるべきだと判断されたと考えられます。
関連リンク
- Go言語の
syscall
パッケージのドキュメント (Go 1.0当時のものを見つけるのは難しいですが、現在のドキュメントも参考になります): https://pkg.go.dev/syscall - UNIXドメインソケットに関するLinux manページ (例:
unix(7)
): https://man7.org/linux/man-pages/man7/unix.7.html SCM_CREDENTIALS
に関する情報 (例:cmsg(3)
): https://man7.org/linux/man-pages/man3/cmsg.3.html- Go 1リリースアナウンス (2012年3月28日): https://go.dev/blog/go1
参考にした情報源リンク
- Go言語の公式ドキュメント
- Linux manページ (
unix(7)
,cmsg(3)
) - Go言語のコミット履歴と関連するコードレビュー (Go CL 5846063)
- UNIXドメインソケットと補助データに関する一般的な技術記事
- Go 1リリースに関する歴史的情報