[インデックス 13769] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおいて、Linux上でのSCM (Socket Control Message) credentialsのテストを追加するものです。具体的には、UNIXドメインソケットを介してプロセス間でPID、UID、GIDといった資格情報が正しく送受信できるかを検証するテストケースTestSCMCredentials
がsrc/pkg/syscall/creds_test.go
として新規追加されています。
コミット
commit 412c60f1fa97ae15b625f84ec467227d71ac08ac
Author: Albert Strasheim <fullung@gmail.com>
Date: Fri Sep 7 10:31:17 2012 -0700
syscall: Test SCM credentials on Linux.
This test was previously removed in 087c6e15702e.
R=bradfitz, rsc, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/6506061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/412c60f1fa97ae15b625f84ec467227d71ac08ac
元コミット内容
このコミットの元々の内容は、syscall: Test SCM credentials on Linux.
という簡潔なものです。これは、LinuxシステムコールにおけるSCM資格情報のテストを実装したことを示しています。コミットメッセージには、このテストが以前のコミット087c6e15702e
で削除されたものが再導入されたものであることが明記されています。
変更の背景
コミットメッセージに「This test was previously removed in 087c6e15702e.」とあるように、このテストは過去に一度削除されたものが再追加されたものです。具体的な削除理由や再追加の経緯はこのコミットメッセージからは読み取れませんが、一般的にテストが削除されるケースとしては、テストが不安定であった、特定の環境でのみ失敗する、テスト対象の機能が変更・削除された、あるいはより良いテスト方法が見つかったなどが考えられます。
このテストが再導入されたということは、SCM credentialsの機能がGoのsyscall
パッケージにおいて重要であり、その正確な動作を保証するためのテストが不可欠であると判断されたことを示唆しています。特に、UNIXドメインソケットを介したプロセス間通信において、送信元のプロセス情報(PID, UID, GID)を安全かつ確実に渡す機能は、セキュリティや信頼性の観点から非常に重要です。そのため、この機能のテストカバレッジを確保することが目的であったと推測されます。
前提知識の解説
UNIXドメインソケット (UNIX Domain Sockets)
UNIXドメインソケットは、同じホストマシン上のプロセス間で通信を行うためのプロセス間通信 (IPC) メカニズムです。ネットワークソケット(TCP/IPなど)とは異なり、ファイルシステム上のパス名(例: /tmp/mysocket
)に関連付けられることが多く、ネットワークスタックを介さないため、一般的にネットワークソケットよりも高速です。
補助データ (Ancillary Data / Control Messages)
ソケット通信では、通常のデータ(ペイロード)に加えて、補助データ(または制御メッセージ)を送信することができます。これは、ファイルディスクリプタの受け渡しや、プロセス資格情報(PID, UID, GID)の送信など、通常のデータストリームでは扱えないメタ情報を送るために使用されます。UNIXドメインソケットで特に利用されます。
SCM_CREDENTIALS
と SO_PASSCRED
SCM_CREDENTIALS
: 補助データとしてプロセス資格情報(PID, UID, GID)を送信するために使用される制御メッセージタイプです。SO_PASSCRED
: ソケットオプションの一つで、UNIXドメインソケットでSCM_CREDENTIALS
メッセージの送受信を有効にするために使用されます。このオプションが設定されていないと、資格情報の送受信はできません。
syscall.Ucred
構造体
Go言語のsyscall
パッケージにおけるUcred
構造体は、UNIXの資格情報(User Credentials)を表します。これには通常、以下のフィールドが含まれます。
Pid
: プロセスID (Process ID)Uid
: ユーザーID (User ID)Gid
: グループID (Group ID)
Socketpair
システムコール
socketpair()
は、UNIX系システムコールの一つで、互いに接続されたソケットのペアを作成します。これにより、同じプロセス内または親子プロセス間で双方向の通信チャネルを簡単に確立できます。このテストでは、このソケットペアを使って、送信側と受信側のソケットをシミュレートしています。
SetsockoptInt
SetsockoptInt
は、ソケットのオプションを設定するためのGoのsyscall
パッケージの関数です。このテストでは、SO_PASSCRED
オプションを有効にするために使用されます。
WriteMsgUnix
と ReadMsgUnix
これらはGoのnet
パッケージ(内部でsyscall
を使用)のUnixConn
型が提供するメソッドで、UNIXドメインソケットを介してメッセージを送受信するために使用されます。これらは通常のデータだけでなく、補助データ(oob
バイトスライスとして渡される)も扱うことができます。
ParseSocketControlMessage
と ParseUnixCredentials
Goのsyscall
パッケージの関数で、受信した補助データ(バイトスライス)を解析し、SCM_CREDENTIALS
メッセージからUcred
構造体などの意味のある情報を取り出すために使用されます。
技術的詳細
このコミットで追加されたTestSCMCredentials
テストは、LinuxシステムにおけるUNIXドメインソケットを介したプロセス資格情報(PID, UID, GID)の送受信メカニズムを検証します。
テストの主要な流れは以下の通りです。
- ソケットペアの作成:
syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
を呼び出して、互いに接続された2つのUNIXドメインソケットを作成します。これにより、テスト内で送信側と受信側のソケットをシミュレートできます。 SO_PASSCRED
オプションの有効化: 送信側のソケット(fds[0]
)に対してsyscall.SetsockoptInt(fds[0], syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
を呼び出し、SO_PASSCRED
オプションを有効にします。これにより、このソケットを介して資格情報が補助データとして送信できるようになります。net.Conn
への変換:syscall
パッケージで得られたファイルディスクリプタ(fds[0]
とfds[1]
)を、Goのnet
パッケージのnet.Conn
インターフェースに変換します。これはnet.FileConn
を使用して行われます。これにより、net.UnixConn
のメソッド(WriteMsgUnix
,ReadMsgUnix
)を利用できるようになります。- 資格情報の準備: 現在のプロセスのPID、UID、GIDを取得し、それらを使って
syscall.Ucred
構造体を作成します。この構造体が送信される資格情報となります。 - 資格情報の送信:
cli.(*net.UnixConn).WriteMsgUnix(nil, oob, nil)
を呼び出して、作成したUcred
構造体から生成された補助データ(oob
バイトスライス)を送信します。通常のデータは送信しないため、最初の引数はnil
です。- 非rootユーザーがUID/GIDが0の資格情報を送信しようとした場合に
EPERM
(Permission denied)エラーが発生するかどうかのテストも含まれています。これはセキュリティ上の重要な側面です。
- 非rootユーザーがUID/GIDが0の資格情報を送信しようとした場合に
- 資格情報の受信: 受信側のソケット(
srv.(*net.UnixConn).ReadMsgUnix(nil, oob2)
)を使って、送信された補助データを受信します。 - 受信データの解析と検証:
- 受信した補助データ(
oob2
)が、送信されたデータとバイトレベルで一致するかをbytes.Equal
で確認します。 syscall.ParseSocketControlMessage
とsyscall.ParseUnixCredentials
を順に呼び出して、受信した補助データからUcred
構造体を再構築します。- 再構築された
Ucred
構造体が、送信時に準備した元のUcred
構造体と完全に一致するかを検証します。
- 受信した補助データ(
このテストは、Goのsyscall
パッケージがLinuxのUNIXドメインソケットにおけるSCM_CREDENTIALS
機能を正しくラップし、利用できることを保証するためのものです。特に、SO_PASSCRED
オプションの挙動、WriteMsgUnix
とReadMsgUnix
による補助データの送受信、そしてParseSocketControlMessage
とParseUnixCredentials
によるデータの正確な解析が検証されています。
コアとなるコードの変更箇所
このコミットでは、src/pkg/syscall/creds_test.go
という新しいファイルが追加されています。
--- /dev/null
+++ b/src/pkg/syscall/creds_test.go
@@ -0,0 +1,109 @@
+// 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.
+
+// +build linux
+
+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, 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_test.go
ファイルは、syscall
パッケージのテストスイートの一部として機能します。
- ビルドタグ:
// +build linux
という行は、このファイルがLinuxシステムでのみビルドおよび実行されることを示しています。これは、SCM credentialsの機能がオペレーティングシステムに依存するためです。 - パッケージ:
package syscall_test
は、このテストがsyscall
パッケージの外部テストであることを示します。これにより、syscall
パッケージの内部実装にアクセスすることなく、公開されたAPIのみをテストできます。 - インポート:
bytes
,net
,os
,syscall
,testing
パッケージがインポートされています。これらは、ソケット通信、ファイル操作、システムコール、テストフレームワークの機能を提供するために必要です。 TestSCMCredentials
関数:syscall.Socketpair
でUNIXドメインソケットのペアを作成し、defer syscall.Close
で確実にクローズされるようにします。syscall.SetsockoptInt
で送信側ソケットにSO_PASSCRED
オプションを設定します。これがなければ資格情報は送信されません。net.FileConn
を使って、syscall
のファイルディスクリプタをnet.Conn
オブジェクトに変換します。これにより、net
パッケージのより高レベルなソケット操作関数を利用できます。- 非rootユーザーでの
EPERM
テスト:os.Getuid() != 0
の条件分岐により、現在のユーザーがrootでない場合に、UID/GIDが0の資格情報を送信しようとするとsyscall.EPERM
エラーが発生するかをテストします。これは、特権的な資格情報の送信が制限されていることを確認する重要なセキュリティテストです。 - 資格情報の送信:
syscall.UnixCredentials
で現在のプロセスのPID, UID, GIDからoob
(Out-Of-Band)データを作成し、cli.(*net.UnixConn).WriteMsgUnix
で送信します。n != 0
とoobn != len(oob)
のチェックは、通常のデータは送信されず、補助データのみが正しく送信されたことを確認します。 - 資格情報の受信:
srv.(*net.UnixConn).ReadMsgUnix
で補助データを受信します。flags != 0
のチェックは、予期せぬフラグが設定されていないことを確認します。n != 1
はダミーバイトが1つ送信されたことを確認し、oobn2 != oobn
は補助データが正しく受信されたことを確認します。 - データの検証:
bytes.Equal(oob, oob2)
で、送信された生データと受信された生データが一致するかを確認します。syscall.ParseSocketControlMessage(oob2)
で、受信した補助データから制御メッセージを解析します。syscall.ParseUnixCredentials(&scm[0])
で、解析された制御メッセージからUcred
構造体を抽出します。- 最後に、抽出された
newUcred
が、送信時に準備した元のucred
と完全に一致するかを検証します。これにより、資格情報が破損なく正しく送受信されたことが保証されます。
このテストは、Goのsyscall
パッケージが提供するUNIXドメインソケットと資格情報関連のAPIが、Linux上で期待通りに機能することを包括的に検証しています。
関連リンク
- Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - UNIXドメインソケット (Wikipedia): https://ja.wikipedia.org/wiki/Unix%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88
SCM_CREDENTIALS
に関するLinux manページ (例:man 7 unix
): https://man7.org/linux/man-pages/man7/unix.7.html
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/6506061 (コミットメッセージに記載されているChange-ID)
- UNIXドメインソケットと補助データに関する一般的なプログラミングリソース (例: Beej's Guide to Network Programmingなど)
- Linuxシステムプログラミングに関する書籍やオンラインドキュメント