[インデックス 10919] ファイルの概要
このコミットは、Go言語の標準ライブラリ os
パッケージにおけるファイルディスクリプタのクローズオンエグゼック (Close-on-exec) 動作に関する修正です。特にmacOS (旧称 OS X) 環境において、O_CLOEXEC
フラグの挙動がOSのバージョンによって異なる問題に対応しています。
コミット
commit 1dfe3d1f6e510e7c62cf74240a53d26131042049
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Dec 20 15:41:37 2011 -0800
os: don't trust O_CLOEXEC on OS X
OS X 10.6 doesn't do O_CLOEXEC.
OS X 10.7 does.
For now, always fall back to using syscall.CloseOnExec on darwin.
This can removed when 10.6 is old news, or if we find a
way to cheaply & reliably detect 10.6 vs 10.7 at runtime.
Fixes #2587
R=golang-dev, rsc, iant
CC=golang-dev
https://golang.org/cl/5500053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1dfe3d1f6e510e7c62cf74240a53d26131042049
元コミット内容
このコミットの元のメッセージは以下の通りです。
os: don't trust O_CLOEXEC on OS X
OS X 10.6 doesn't do O_CLOEXEC. OS X 10.7 does.
For now, always fall back to using syscall.CloseOnExec on darwin.
This can removed when 10.6 is old news, or if we find a way to cheaply & reliably detect 10.6 vs 10.7 at runtime.
Fixes #2587
変更の背景
この変更の背景には、macOSの異なるバージョン間での O_CLOEXEC
フラグの挙動の不一致があります。O_CLOEXEC
は、ファイルディスクリプタをオープンする際に、そのディスクリプタが exec
システムコール(新しいプログラムを実行する際に使用される)によって子プロセスに継承されないように設定するためのフラグです。
具体的には、OS X 10.6 (Snow Leopard) では O_CLOEXEC
フラグが正しく機能せず、ファイルディスクリプタが意図せず子プロセスに継承されてしまう問題がありました。一方、OS X 10.7 (Lion) ではこの問題が修正され、O_CLOEXEC
が期待通りに動作するようになりました。
Go言語の os
パッケージは、ファイル操作を行う際にこの O_CLOEXEC
フラグを利用して、セキュリティとリソース管理を最適化しようとします。しかし、OS X 10.6のような古いバージョンで O_CLOEXEC
が信頼できない場合、子プロセスが予期せぬファイルディスクリプタを継承し、潜在的なセキュリティ脆弱性やリソースリークを引き起こす可能性があります。
このコミットは、このようなOSのバージョン間の差異を吸収し、macOS環境全体でファイルディスクリプタが確実にクローズオンエグゼックされるようにするための暫定的な対策として導入されました。
前提知識の解説
ファイルディスクリプタ (File Descriptor, FD)
ファイルディスクリプタは、Unix系オペレーティングシステムにおいて、開かれたファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。プロセスはファイルディスクリプタを通じてこれらのリソースにアクセスします。
fork()
と exec()
システムコール
Unix系システムでは、新しいプロセスを作成する際に主に二つのシステムコールが使われます。
fork()
: 現在のプロセス(親プロセス)のほぼ完全なコピーである新しいプロセス(子プロセス)を作成します。子プロセスは親プロセスのメモリ空間、ファイルディスクリプタ、その他のリソースのコピーを受け取ります。exec()
(例:execve()
): 現在のプロセスを、指定された新しいプログラムで置き換えます。exec
が成功すると、現在のプロセスのメモリ空間は新しいプログラムのコードとデータで上書きされ、実行が開始されます。この際、デフォルトでは、exec
を呼び出す前のプロセスが持っていた開いているファイルディスクリプタは、新しいプログラムに継承されます。
クローズオンエグゼック (Close-on-exec, FD_CLOEXEC)
クローズオンエグゼックは、ファイルディスクリプタに設定できるフラグの一つです。このフラグが設定されたファイルディスクリプタは、exec
システムコールが成功して新しいプログラムが実行される際に、自動的に閉じられます。これにより、子プロセスが親プロセスから不要なファイルディスクリプタを継承するのを防ぎ、以下のような利点があります。
- セキュリティの向上: 機密性の高いファイルディスクリプタ(例: 認証情報を含むソケット)が意図せず子プロセスに漏洩するのを防ぎます。
- リソース管理の簡素化: 子プロセスが不要なファイルディスクリプタを保持しないため、リソースリークを防ぎ、ファイルディスクリプタの枯渇を回避します。
- プログラミングの容易さ: 子プロセス側で不要なファイルディスクリプタを明示的に閉じる手間が省けます。
O_CLOEXEC
フラグ
O_CLOEXEC
は、open()
や socket()
などのシステムコールでファイルディスクリプタをオープンする際に指定できるフラグです。このフラグを指定すると、ファイルディスクリプタがオープンされると同時にクローズオンエグゼック属性が設定されます。これにより、ファイルディスクリプタをオープンした後に別途 fcntl(fd, F_SETFD, FD_CLOEXEC)
を呼び出す必要がなくなり、アトミックな操作が可能になります。これは、ファイルディスクリプタをオープンしてから FD_CLOEXEC
を設定するまでの間に fork()
と exec()
が発生する「競合状態 (race condition)」を防ぐ上で非常に重要です。
syscall.CloseOnExec
Go言語の syscall
パッケージには、特定のファイルディスクリプタに対してクローズオンエグゼック属性を設定するための CloseOnExec
関数が提供されています。これは、C言語の fcntl(fd, F_SETFD, FD_CLOEXEC)
に相当する機能を提供します。
技術的詳細
このコミットは、Go言語の src/pkg/os/file_unix.go
ファイル内の OpenFile
関数に修正を加えています。OpenFile
関数は、Unix系システムでファイルを開くための内部的なヘルパー関数です。
元のコードでは、syscall.O_CLOEXEC == 0
(つまり、システムが O_CLOEXEC
フラグをサポートしていない場合) にのみ syscall.CloseOnExec(r)
を呼び出して、明示的にクローズオンエグゼック属性を設定していました。これは、O_CLOEXEC
がサポートされているシステムでは、open
時にこのフラグが使用されることを期待していたためです。
しかし、macOS環境において、OS X 10.6では O_CLOEXEC
がサポートされていると報告されるにもかかわらず、実際には正しく機能しないという問題がありました。OS X 10.7ではこの問題が修正されましたが、Goランタイムが実行されているOSのバージョンを安価かつ確実に検出する方法がなかったため、GoプログラムはOS X 10.6上で O_CLOEXEC
を信頼してしまい、ファイルディスクリプタが子プロセスに継承されてしまう可能性がありました。
このコミットでは、この問題を解決するために、OpenFile
関数内の条件式を以下のように変更しました。
if syscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin" { // O_CLOEXEC not supported
syscall.CloseOnExec(r)
}
この変更により、以下のいずれかの条件が満たされる場合に syscall.CloseOnExec(r)
が呼び出されるようになります。
syscall.O_CLOEXEC == 0
: システムがO_CLOEXEC
フラグをサポートしていない場合。runtime.GOOS == "darwin"
: 実行環境がmacOSである場合。
特に runtime.GOOS == "darwin"
の条件が追加されたことで、macOS上ではOSのバージョンに関わらず、常に syscall.CloseOnExec
を呼び出してファイルディスクリプタにクローズオンエグゼック属性を明示的に設定するようになりました。これにより、OS X 10.6のような O_CLOEXEC
が信頼できない環境でも、ファイルディスクリプタが確実にクローズオンエグゼックされることが保証されます。
コミットメッセージにもあるように、これはOS X 10.6が「古いニュース」になるか、または10.6と10.7を安価かつ確実に実行時に検出する方法が見つかるまでの暫定的な対策です。将来的には、この追加の syscall.CloseOnExec
呼び出しは不要になる可能性があります。
また、src/pkg/os/exec/exec_test.go
のテストファイルにもコメントが追加されており、この変更がビルド環境に反映された後にテストのTODOコメントを削除できることが示唆されています。これは、O_CLOEXEC
の問題が解決されることで、関連するテストの失敗が解消されることを期待しているためです。
コアとなるコードの変更箇所
src/pkg/os/file_unix.go
--- a/src/pkg/os/file_unix.go
+++ b/src/pkg/os/file_unix.go
@@ -68,8 +68,13 @@ func OpenFile(name string, flag int, perm uint32) (file *File, err error) {
}
// There's a race here with fork/exec, which we are
- // content to live with. See ../syscall/exec.go
- // if syscall.O_CLOEXEC == 0 { // O_CLOEXEC not supported
+ // content to live with. See ../syscall/exec_unix.go.
+ // On OS X 10.6, the O_CLOEXEC flag is not respected.
+ // On OS X 10.7, the O_CLOEXEC flag works.
+ // Without a cheap & reliable way to detect 10.6 vs 10.7 at
+ // runtime, we just always call syscall.CloseOnExec on Darwin.
+ // Once >=10.7 is prevalent, this extra call can removed.
+ if syscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin" { // O_CLOEXEC not supported
syscall.CloseOnExec(r)
}
src/pkg/os/exec/exec_test.go
--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -256,8 +256,9 @@ func TestHelperProcess(*testing.T) {
fmt.Printf("ReadAll from fd 3: %v", err)
os.Exit(1)
}
- // TODO(bradfitz,iant): the rest of this test is disabled
- // for now. remove this block once we figure out why it fails.
+ // TODO(bradfitz): remove this block once the builders are restarted
+ // with a new binary including be47ea17bea0 (set CLOEXEC on epoll/kqueue fds)
+ // and 5500053 (don't trust O_CLOEXEC on OS X).
{
os.Stderr.Write(bs)
os.Exit(0)
コアとなるコードの解説
src/pkg/os/file_unix.go
の変更
このファイルの OpenFile
関数は、Goプログラムがファイルを開く際に内部的に呼び出される関数です。変更の核心は、ファイルディスクリプタ r
に対して syscall.CloseOnExec(r)
を呼び出す条件の変更です。
-
変更前:
if syscall.O_CLOEXEC == 0 { // O_CLOEXEC not supported syscall.CloseOnExec(r) }
この条件は、「システムが
O_CLOEXEC
フラグをサポートしていない場合のみ、明示的にCloseOnExec
を呼び出す」ことを意味していました。これは、O_CLOEXEC
がサポートされているシステムでは、open
時にこのフラグが使用され、自動的にクローズオンエグゼック属性が設定されることを前提としていました。 -
変更後:
if syscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin" { // O_CLOEXEC not supported syscall.CloseOnExec(r) }
この変更により、条件に
|| runtime.GOOS == "darwin"
が追加されました。runtime.GOOS
はGoプログラムが実行されているオペレーティングシステムを示す定数で、"darwin"
はmacOSを指します。 この新しい条件は、「システムがO_CLOEXEC
をサポートしていない場合」または「実行環境がmacOSである場合」にsyscall.CloseOnExec(r)
を呼び出すことを意味します。 これにより、macOS上ではOSのバージョン(特にOS X 10.6のようにO_CLOEXEC
が信頼できないバージョン)に関わらず、常にsyscall.CloseOnExec
が実行され、ファイルディスクリプタが確実にクローズオンエグゼックされるようになります。これは、O_CLOEXEC
の挙動が不安定なmacOS環境での堅牢性を高めるための重要な変更です。
src/pkg/os/exec/exec_test.go
の変更
このファイルは os/exec
パッケージのテストコードです。変更はコードの機能には影響せず、コメントの追加のみです。
- 追加されたコメント:
このコメントは、このテストブロックが一時的に無効化されている理由と、将来的に削除できる条件を示しています。具体的には、// TODO(bradfitz): remove this block once the builders are restarted // with a new binary including be47ea17bea0 (set CLOEXEC on epoll/kqueue fds) // and 5500053 (don't trust O_CLOEXEC on OS X).
be47ea17bea0
(epoll/kqueue FDにCLOEXECを設定するコミット) と5500053
(このコミット、OS XでO_CLOEXECを信頼しない) の両方の変更を含む新しいGoバイナリでビルド環境が再起動された後、このテストブロックを削除できることを示しています。これは、これらの変更によって関連するバグが修正され、テストが正しくパスするようになることを期待しているためです。
関連リンク
- Go Issue #2587: https://github.com/golang/go/issues/2587
- Go CL 5500053: https://golang.org/cl/5500053
参考にした情報源リンク
open(2)
man page (O_CLOEXEC): https://man7.org/linux/man-pages/man2/open.2.htmlfcntl(2)
man page (FD_CLOEXEC): https://man7.org/linux/man-pages/man2/fcntl.2.htmlfork(2)
man page: https://man7.org/linux/man-pages/man2/fork.2.htmlexecve(2)
man page: https://man7.org/linux/man-pages/man2/execve.2.html- Go
syscall
package documentation: https://pkg.go.dev/syscall - Go
runtime
package documentation: https://pkg.go.dev/runtime - Stack Overflow: What is the purpose of O_CLOEXEC?: https://stackoverflow.com/questions/10095396/what-is-the-purpose-of-o-cloexec