[インデックス 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_NONBLOCK
とSOCK_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
へのフォールバックが行われていました。それ以外のエラー(例えば、EACCES
やEFAULT
)が返された場合は、フォールバックせずにエラーをそのまま返していました。
しかし、一部のLinuxディストリビューションやカーネルバージョンでは、Accept4
がサポートされていないにもかかわらず、ENOSYS
やEINVAL
ではなく、EACCES
やEFAULT
といったエラーコードを返すことが判明しました。これは、カーネルがAccept4
に渡された未知のフラグを、アクセス違反や不正なメモリアドレスとして誤って解釈するためと考えられます。
このコミットでは、この問題を解決するために、Accept4
が返す可能性のあるエラーコードのリストにsyscall.EACCES
とsyscall.EFAULT
を追加しました。これにより、これらのエラーが発生した場合でも、GoランタイムはAccept4
が利用できないと判断し、安全にaccept
システムコールへのフォールバックを実行できるようになります。結果として、より多くの環境でGoのネットワーク機能が安定して動作するようになります。
具体的には、accept
関数内のエラーハンドリングがif
文からswitch
文に変更され、ENOSYS
、EINVAL
に加えて、EACCES
、EFAULT
もフォールバックのトリガーとなるエラーとして明示的に扱われるようになりました。これにより、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
の呼び出し結果のエラーerr
がnil
(成功)であるか、またはsyscall.ENOSYS
とsyscall.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
:err
がnil
(Accept4
が成功)である場合、または上記のcase
で指定されたエラー以外のエラーが返された場合、default
ブロックが実行され、return ns, sa, err
によって結果が即座に返されます。これにより、Accept4
が成功した場合はもちろん、予期せぬ他のエラーが発生した場合も、フォールバックせずにエラーを上位に伝播させます。
この変更により、GoはAccept4
システムコールが利用できないことを示す可能性のある、より広範なエラーコードを認識し、堅牢なフォールバックメカニズムを提供できるようになりました。
関連リンク
- Go GitHubコミットページ: https://github.com/golang/go/commit/5fb39cc6a2621602d33c6b226742795318a279ea
- Go Gerrit Code Review: https://golang.org/cl/84170043
- Go Issue #7271: https://github.com/golang/go/issues/7271 (このコミットが解決した問題)
参考にした情報源リンク
- Go issue #7271のGitHubページ
Accept4
システムコールに関するLinux manページCLOEXEC
フラグに関するLinux manページ- Go言語の
net
およびsyscall
パッケージのドキュメント - 各種技術ブログやフォーラムでの
Accept4
およびCLOEXEC
に関する議論 - https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFth6t8Lg5AbF_2XiEu7mw6Z5K_CNRZNGd_kNYty1-NDeQSBmaCgml020c4ETNmWWDizKJ0NDTkSfNF4QbZ7VvG2LTo5ph6kh1eAvuB2ndP4ZZ1tH4LE_LeEL6HaiBNKH0A5A==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEwQY_wGeBtfYn910FaYIZeWpC2L158mW498gc911ZKZM_HAGx5p1oiST1T0VJbmDyI16YGZ8IJcn9ICs0Edw4pQixAWZjmKtf0bsbKG2op2OiD6-uvR-4=