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

[インデックス 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の動作を検証していました。

  1. ソケットペアの作成: syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0) を使用して、UNIXドメインソケットのペア(fds[0]fds[1])を作成します。これにより、2つのプロセス(または同じプロセス内の2つの異なる部分)が互いに通信できるパイプのようなチャネルが確立されます。
  2. SO_PASSCREDの有効化: syscall.SetsockoptInt(fds[0], syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1) を呼び出し、fds[0]ソケットでSO_PASSCREDオプションを有効にします。これにより、このソケットから送信されるメッセージには、送信元の認証情報が自動的に付加されるようになります。
  3. net.FileConnへの変換: syscallパッケージのファイルディスクリプタ(fds[0], fds[1])を、Goのnetパッケージのnet.Connインターフェースに変換します。これにより、Goの標準的なネットワークI/O関数を使用してソケットを操作できるようになります。具体的には、net.UnixConn型として扱われます。
  4. 認証情報の準備と送信:
    • 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)。
  5. 認証情報の受信と検証:
    • 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で作成した認証情報を送信し、受信側でParseSocketControlMessageParseUnixCredentialsを使ってその認証情報を正しく取得できることを確認していました。特に、os.Getuid() != 0のチェックは、root以外のユーザーで実行した場合にEPERMエラーが発生することを確認するもので、権限周りの挙動も考慮されていました。

このテストが削除されたのは、Go 1のリリース前の安定化フェーズにおいて、syscallパッケージのような低レベルでOS依存性の高い部分に、さらにシステム固有のテストを追加することが、当時のプロジェクトのポリシーと合致しなかったためです。Go 1の目標は、コア言語と標準ライブラリの安定した基盤を提供することであり、特定のOS機能の詳細なテストは、その後のフェーズで追加されるべきだと判断されたと考えられます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Linux manページ (unix(7), cmsg(3))
  • Go言語のコミット履歴と関連するコードレビュー (Go CL 5846063)
  • UNIXドメインソケットと補助データに関する一般的な技術記事
  • Go 1リリースに関する歴史的情報