Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13769] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおいて、Linux上でのSCM (Socket Control Message) credentialsのテストを追加するものです。具体的には、UNIXドメインソケットを介してプロセス間でPID、UID、GIDといった資格情報が正しく送受信できるかを検証するテストケースTestSCMCredentialssrc/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_CREDENTIALSSO_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オプションを有効にするために使用されます。

WriteMsgUnixReadMsgUnix

これらはGoのnetパッケージ(内部でsyscallを使用)のUnixConn型が提供するメソッドで、UNIXドメインソケットを介してメッセージを送受信するために使用されます。これらは通常のデータだけでなく、補助データ(oobバイトスライスとして渡される)も扱うことができます。

ParseSocketControlMessageParseUnixCredentials

Goのsyscallパッケージの関数で、受信した補助データ(バイトスライス)を解析し、SCM_CREDENTIALSメッセージからUcred構造体などの意味のある情報を取り出すために使用されます。

技術的詳細

このコミットで追加されたTestSCMCredentialsテストは、LinuxシステムにおけるUNIXドメインソケットを介したプロセス資格情報(PID, UID, GID)の送受信メカニズムを検証します。

テストの主要な流れは以下の通りです。

  1. ソケットペアの作成: syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)を呼び出して、互いに接続された2つのUNIXドメインソケットを作成します。これにより、テスト内で送信側と受信側のソケットをシミュレートできます。
  2. SO_PASSCREDオプションの有効化: 送信側のソケット(fds[0])に対してsyscall.SetsockoptInt(fds[0], syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)を呼び出し、SO_PASSCREDオプションを有効にします。これにより、このソケットを介して資格情報が補助データとして送信できるようになります。
  3. net.Connへの変換: syscallパッケージで得られたファイルディスクリプタ(fds[0]fds[1])を、Goのnetパッケージのnet.Connインターフェースに変換します。これはnet.FileConnを使用して行われます。これにより、net.UnixConnのメソッド(WriteMsgUnix, ReadMsgUnix)を利用できるようになります。
  4. 資格情報の準備: 現在のプロセスのPID、UID、GIDを取得し、それらを使ってsyscall.Ucred構造体を作成します。この構造体が送信される資格情報となります。
  5. 資格情報の送信: cli.(*net.UnixConn).WriteMsgUnix(nil, oob, nil)を呼び出して、作成したUcred構造体から生成された補助データ(oobバイトスライス)を送信します。通常のデータは送信しないため、最初の引数はnilです。
    • 非rootユーザーがUID/GIDが0の資格情報を送信しようとした場合にEPERM(Permission denied)エラーが発生するかどうかのテストも含まれています。これはセキュリティ上の重要な側面です。
  6. 資格情報の受信: 受信側のソケット(srv.(*net.UnixConn).ReadMsgUnix(nil, oob2))を使って、送信された補助データを受信します。
  7. 受信データの解析と検証:
    • 受信した補助データ(oob2)が、送信されたデータとバイトレベルで一致するかをbytes.Equalで確認します。
    • syscall.ParseSocketControlMessagesyscall.ParseUnixCredentialsを順に呼び出して、受信した補助データからUcred構造体を再構築します。
    • 再構築されたUcred構造体が、送信時に準備した元のUcred構造体と完全に一致するかを検証します。

このテストは、GoのsyscallパッケージがLinuxのUNIXドメインソケットにおけるSCM_CREDENTIALS機能を正しくラップし、利用できることを保証するためのものです。特に、SO_PASSCREDオプションの挙動、WriteMsgUnixReadMsgUnixによる補助データの送受信、そしてParseSocketControlMessageParseUnixCredentialsによるデータの正確な解析が検証されています。

コアとなるコードの変更箇所

このコミットでは、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 != 0oobn != 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言語の公式リポジトリ: https://github.com/golang/go
  • Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/6506061 (コミットメッセージに記載されているChange-ID)
  • UNIXドメインソケットと補助データに関する一般的なプログラミングリソース (例: Beej's Guide to Network Programmingなど)
  • Linuxシステムプログラミングに関する書籍やオンラインドキュメント