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

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のsock_cloexec.goファイルに対する変更です。このファイルは、ネットワークソケットの作成と受け入れ(accept)処理において、CLOEXECフラグ(Close-on-exec)を適切に設定するためのロジックを扱っています。特に、Accept4システムコールを使用する際の挙動、および特定の環境下でのエラーハンドリングの改善が目的です。

コミット

commit 5fb39cc6a2621602d33c6b226742795318a279ea
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 3 16:10:45 2014 -0400

    net: accept a few more errors in Accept4 wrapper
    
    Fixes #7271.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/84170043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/5fb39cc6a2621602d33c6b226742795318a279ea

元コミット内容

net: accept a few more errors in Accept4 wrapper

Fixes #7271.

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/84170043

変更の背景

このコミットは、Go言語のネットワークパッケージがAccept4システムコールを使用する際に発生する特定のエラーをより適切に処理するために導入されました。特に、古いLinuxカーネル(例: Linux 2.6)や特定のシステム構成において、Accept4システムコールが期待されるENOSYS(システムコールが存在しない)エラーではなく、EINVAL(不正な引数)、EACCES(アクセス拒否)、またはEFAULT(不正なアドレス)といった異なるエラーを返す場合があるという問題が報告されていました。

Go言語は、新しいソケットを作成する際に、子プロセスにファイルディスクリプタが漏洩するのを防ぐためにCLOEXEC(Close-on-exec)フラグを自動的に設定しようとします。Accept4システムコールは、このCLOEXECフラグをアトミックに設定できるため、競合状態を避ける上で推奨されます。しかし、Accept4が利用できない環境では、Goは従来のacceptシステムコールにフォールバックし、その後にfcntlシステムコールを使ってCLOEXECフラグを設定します。

問題は、一部の環境でAccept4が利用できない場合に、ENOSYS以外のエラーが返されると、Goのランタイムがacceptへのフォールバックを正しく行えず、ネットワーク接続の受け入れに失敗するというものでした。このコミットは、これらの追加のエラーコード(EINVAL, EACCES, EFAULT)もAccept4が利用できないことを示すものとして扱い、安全にacceptへのフォールバックを可能にすることで、堅牢性を向上させています。これはGo issue #7271で報告された問題を解決します。

前提知識の解説

1. Accept4システムコール

Accept4は、Linuxカーネル2.6.28で導入されたシステムコールで、既存のacceptシステムコールの拡張版です。主な違いは、ソケットを受け入れる際に、新しいソケットのファイルディスクリプタに対して追加のフラグ(特にSOCK_NONBLOCKSOCK_CLOEXEC)をアトミックに設定できる点です。

  • accept: クライアントからの接続要求を受け入れ、新しいソケットのファイルディスクリプタを返します。このソケットは、クライアントとの通信に使用されます。
  • Accept4の利点:
    • アトミックなフラグ設定: SOCK_CLOEXECフラグをacceptと同時に設定できるため、acceptでソケットを取得し、その後にfcntl(F_SETFD, FD_CLOEXEC)を呼び出すという2段階の操作で発生しうる競合状態(TOCTOU: Time-of-check to time-of-use)を回避できます。これにより、ファイルディスクリプタが意図せず子プロセスに漏洩するリスクを低減します。
    • 効率性: システムコール呼び出しの回数を減らすことで、わずかながらパフォーマンスの向上が期待できます。

2. CLOEXECフラグ (Close-on-exec)

CLOEXEC(Close-on-exec)は、ファイルディスクリプタに設定できるフラグの一つです。このフラグが設定されたファイルディスクリプタは、execve(または関連するexecファミリーの関数)システムコールによって新しいプログラムが実行される際に、自動的に閉じられます。

  • 目的:
    • セキュリティ: 子プロセスに不要なファイルディスクリプタが漏洩するのを防ぎます。例えば、親プロセスがデータベース接続やネットワークソケットを開いている場合、子プロセスにそれらが引き継がれると、意図しないアクセスやリソースリークにつながる可能性があります。
    • リソース管理: 子プロセスが親プロセスのリソースを不必要に保持するのを防ぎ、リソースのクリーンアップを容易にします。
  • 重要性: 現代の堅牢なアプリケーション開発において、CLOEXECフラグの適切な使用は、セキュリティと安定性の観点から非常に重要です。

3. システムコールエラーコード

システムコールが失敗した場合、通常はエラーコードが返され、errno変数(Goではsyscallパッケージのエラー型)に設定されます。このコミットで言及されている主要なエラーコードは以下の通りです。

  • syscall.ENOSYS (No such system call):
    • 意味: 要求されたシステムコールがカーネルによって実装されていないか、現在のシステムでは利用できないことを示します。
    • Accept4の文脈では、カーネルがAccept4をサポートしていない場合に返されることが期待されます。
  • syscall.EINVAL (Invalid argument):
    • 意味: システムコールに渡された引数が無効であることを示します。
    • Accept4の文脈では、一部の古いLinuxカーネルで、Accept4がサポートされていないにもかかわらず、ENOSYSではなくEINVALが返されるケースがありました。これは、カーネルが未知のフラグを受け取った際に、引数エラーとして処理するためです。
  • syscall.EACCES (Permission denied):
    • 意味: 要求された操作を実行するためのアクセス権がないことを示します。
    • 通常、Accept4で返されることは稀ですが、特定のセキュリティ設定やサンドボックス環境下で発生する可能性があります。このコミットでは、ENOSYSの代替として扱われるケースがあることを示唆しています。
  • syscall.EFAULT (Bad address):
    • 意味: システムコールに渡されたポインタが、プロセスのアドレス空間外を指しているか、無効なメモリ領域を指していることを示します。
    • これもAccept4で直接返されることは稀ですが、一部のLinux環境でENOSYSの代替として返されるケースがあるため、このコミットで考慮されています。

4. Goのnetパッケージとsyscallパッケージ

  • netパッケージ: Go言語の標準ライブラリで、TCP/IP、UDP、UnixドメインソケットなどのネットワークI/O機能を提供します。高レベルの抽象化を提供し、クロスプラットフォームで動作します。
  • syscallパッケージ: 低レベルのシステムコールインターフェースを提供します。OS固有の機能にアクセスするために使用され、netパッケージのような高レベルのライブラリの内部で利用されることがあります。このコミットの変更は、netパッケージがsyscallパッケージを介してOSとどのようにやり取りするかに直接関係しています。

技術的詳細

このコミットの核心は、Accept4システムコールが失敗した場合のGoランタイムのフォールバックロジックの改善にあります。

Goは、まずAccept4システムコールを試行します。これは、CLOEXECフラグをアトミックに設定できるため、推奨される方法です。しかし、もしAccept4が利用できない(例えば、古いLinuxカーネル)場合、Goは従来のacceptシステムコールにフォールバックし、その後でfcntlシステムコールを使ってCLOEXECフラグを明示的に設定します。

変更前のコードでは、Accept4が失敗してエラーを返した場合、そのエラーがsyscall.ENOSYSまたはsyscall.EINVALのいずれかである場合にのみ、acceptへのフォールバックが行われていました。それ以外のエラー(例えば、EACCESEFAULT)が返された場合は、フォールバックせずにエラーをそのまま返していました。

しかし、一部のLinuxディストリビューションやカーネルバージョンでは、Accept4がサポートされていないにもかかわらず、ENOSYSEINVALではなく、EACCESEFAULTといったエラーコードを返すことが判明しました。これは、カーネルがAccept4に渡された未知のフラグを、アクセス違反や不正なメモリアドレスとして誤って解釈するためと考えられます。

このコミットでは、この問題を解決するために、Accept4が返す可能性のあるエラーコードのリストにsyscall.EACCESsyscall.EFAULTを追加しました。これにより、これらのエラーが発生した場合でも、GoランタイムはAccept4が利用できないと判断し、安全にacceptシステムコールへのフォールバックを実行できるようになります。結果として、より多くの環境でGoのネットワーク機能が安定して動作するようになります。

具体的には、accept関数内のエラーハンドリングがif文からswitch文に変更され、ENOSYSEINVALに加えて、EACCESEFAULTもフォールバックのトリガーとなるエラーとして明示的に扱われるようになりました。これにより、Goのネットワークスタックの堅牢性と互換性が向上しています。

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

diff --git a/src/pkg/net/sock_cloexec.go b/src/pkg/net/sock_cloexec.go
index 18ff64388c..dec81855b6 100644
--- a/src/pkg/net/sock_cloexec.go
+++ b/src/pkg/net/sock_cloexec.go
@@ -49,8 +49,13 @@ func accept(s int) (int, syscall.Sockaddr, error) {
 	// kernel and on FreeBSD it was introduced in 10 kernel. If we
 	// get an ENOSYS error on both Linux and FreeBSD, or EINVAL
 	// error on Linux, fall back to using accept.
-	if err == nil || (err != syscall.ENOSYS && err != syscall.EINVAL) {
+	switch err {
+	default: // nil and errors other than the ones listed
 		return ns, sa, err
+	case syscall.ENOSYS: // syscall missing
+	case syscall.EINVAL: // some Linux use this instead of ENOSYS
+	case syscall.EACCES: // some Linux use this instead of ENOSYS
+	case syscall.EFAULT: // some Linux use this instead of ENOSYS
 	}
 
 	// See ../syscall/exec_unix.go for description of ForkLock.

コアとなるコードの解説

変更は、src/pkg/net/sock_cloexec.goファイル内のaccept関数にあります。この関数は、内部的にAccept4システムコールを呼び出し、その結果を処理します。

元のコードでは、Accept4の呼び出し結果のエラーerrnil(成功)であるか、またはsyscall.ENOSYSsyscall.EINVALのどちらでもない場合に、そのまま結果を返していました。これは、ENOSYSまたはEINVALの場合にのみ、acceptシステムコールへのフォールバックロジックに進むことを意味します。

// 変更前
if err == nil || (err != syscall.ENOSYS && err != syscall.EINVAL) {
    return ns, sa, err
}

このif文は、以下のように解釈できます。

  • err == nil: Accept4が成功した場合、そのまま結果を返す。
  • err != syscall.ENOSYS && err != syscall.EINVAL: Accept4が失敗し、そのエラーがENOSYSでもEINVALでもない場合、そのままエラーを返す(フォールバックしない)。

変更後のコードでは、このif文がswitch文に置き換えられました。

// 変更後
switch err {
case syscall.ENOSYS: // syscall missing
case syscall.EINVAL: // some Linux use this instead of ENOSYS
case syscall.EACCES: // some Linux use this instead of ENOSYS
case syscall.EFAULT: // some Linux use this instead of ENOSYS
default: // nil and errors other than the ones listed
    return ns, sa, err
}

このswitch文のロジックは以下の通りです。

  • case syscall.ENOSYS, case syscall.EINVAL, case syscall.EACCES, case syscall.EFAULT: これらのいずれかのエラーコードがAccept4から返された場合、switch文のブロック内にはreturn文がないため、実行はswitch文の直後のコード(つまり、acceptシステムコールへのフォールバックロジック)に進みます。これは、これらのエラーがAccept4が利用できないことを示すものとして扱われることを意味します。
  • default: errnilAccept4が成功)である場合、または上記のcaseで指定されたエラー以外のエラーが返された場合、defaultブロックが実行され、return ns, sa, errによって結果が即座に返されます。これにより、Accept4が成功した場合はもちろん、予期せぬ他のエラーが発生した場合も、フォールバックせずにエラーを上位に伝播させます。

この変更により、GoはAccept4システムコールが利用できないことを示す可能性のある、より広範なエラーコードを認識し、堅牢なフォールバックメカニズムを提供できるようになりました。

関連リンク

参考にした情報源リンク