[インデックス 18702] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおける、DragonFly BSDカーネルのexec
システムコールに関するバグへのワークアラウンドを実装しています。具体的には、exec
システムコールが引数リストの長さに起因する問題を抱えている場合に、Goランタイムがこれを回避するための修正です。
コミット
commit 5fbd6044bce8b032c72378a0db5106c235df9067
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sat Mar 1 18:56:50 2014 -0500
syscall: workaround Dragonfly BSD kernel exec bug
See also CL 4259056 for FreeBSD.
Test program:
// exec.go
package main
import (
"log"
"os"
"os/exec"
"runtime"
)
func main() {
path := runtime.GOROOT() + "/src/pkg/net/http/cgi/testdata"
cmd := &exec.Cmd{
Path: "test.cgi",
Args: []string{path + "/test.cgi"},
Dir: path
Stdout: os.Stdout}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
$ go run exec.go
2014/03/01 15:52:41 fork/exec test.cgi: argument list too long
LGTM=iant
R=rsc, iant
CC=golang-codereviews
https://golang.org/cl/69970044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5fbd6044bce8b032c72378a0db5106c235df9067
元コミット内容
このコミットは、DragonFly BSDカーネルのexec
システムコールにおける既知のバグに対するワークアラウンドを導入します。このバグは、特に引数リストが長い場合にfork/exec
が失敗し、「argument list too long」というエラーを返す問題を引き起こします。同様の問題は以前FreeBSDでも確認されており、その際の修正(CL 4259056)をDragonFly BSDにも適用するものです。
コミットメッセージには、この問題が再現するGoプログラムの例が示されています。このプログラムは、os/exec
パッケージを使用してCGIスクリプトを実行しようとしますが、DragonFly BSD環境で実行するとfork/exec test.cgi: argument list too long
というエラーで失敗します。
変更の背景
Goプログラムが外部プロセスを実行する際、内部的にはsyscall
パッケージのforkExec
関数を通じてOSのfork
とexec
システムコールを呼び出します。exec
システムコールは、新しいプログラムを現在のプロセス空間にロードして実行するために使用されますが、その際に新しいプログラムに渡す引数リスト(argv
)と環境変数リスト(envp
)をカーネルに渡します。
一部のBSD系OS(FreeBSDやDragonFly BSDなど)のカーネルには、exec
システムコールが引数リストの処理に関して特定の条件下で問題を抱えるバグが存在しました。具体的には、実行されるプログラムのパス名(argv[0]
)が、実際にexec
に渡されるパス名(argv0
)よりも長い場合に、カーネルが内部的に引数リストの長さを誤って計算し、「argument list too long」というエラーを返すことがありました。これは、Goのos/exec
パッケージが内部的にargv[0]
をフルパスで設定し、argv0
を相対パスやシンボリックリンクの解決後のパスで設定するような場合に発生しやすかったと考えられます。
この問題はFreeBSDで先に発見され、CL 4259056で修正されました。このコミットは、同様のバグがDragonFly BSDにも存在することを確認し、FreeBSDに適用されたのと同じワークアラウンドをDragonFly BSDにも拡張することで、GoプログラムがこれらのOS上で外部プロセスを安定して実行できるようにすることを目的としています。
前提知識の解説
fork
システムコール: Unix系OSにおけるプロセス生成のためのシステムコールです。呼び出し元のプロセス(親プロセス)のコピーである新しいプロセス(子プロセス)を作成します。子プロセスは親プロセスのメモリ空間、ファイルディスクリプタなどを継承します。exec
システムコール:fork
によって作成された子プロセスが、別のプログラムを実行するために使用するシステムコール群(execve
,execl
,execvp
など)の総称です。exec
が成功すると、現在のプロセスのメモリ空間は新しいプログラムのコードとデータで上書きされ、新しいプログラムが実行を開始します。プロセスIDは変更されません。argv
とargv0
:argv
(Argument Vector):exec
システムコールに渡される引数リストです。慣例的にargv[0]
は実行されるプログラムのパス名または名前を含みます。argv0
: Goのsyscall.forkExec
関数における引数の一つで、実際にexec
システムコールに渡される実行ファイルのパス名です。これはargv[0]
と異なる場合があります。例えば、argv[0]
がフルパスであるのに対し、argv0
は相対パスやシンボリックリンク解決後のパスであることがあります。
- Goの
os/exec
パッケージ: Goプログラムから外部コマンドを実行するための高レベルなインターフェースを提供します。内部的にはsyscall
パッケージを利用してfork
とexec
を呼び出します。 - Goの
syscall
パッケージ: OSのシステムコールへの低レベルなインターフェースを提供します。OS固有のシステムコールを直接呼び出す際に使用されます。 runtime.GOOS
: Goの標準ライブラリruntime
パッケージで提供される定数で、プログラムが実行されているOSの名前(例: "linux", "darwin", "windows", "freebsd", "dragonfly"など)を文字列で返します。
技術的詳細
このバグは、exec
システムコールが内部的に引数リストを処理する際に、argv[0]
とargv0
の長さの不一致によって発生する特定のコーナーケースに起因します。
Goのsyscall.forkExec
関数は、新しいプロセスを生成し、指定されたプログラムを実行する役割を担います。この関数は、argv0
(実行ファイルのパス)とargv
(引数リスト)を受け取ります。内部的には、これらの文字列をC言語の文字列配列(char *argv[]
)に変換し、execve
システムコールに渡します。
問題の核心は、FreeBSDおよびDragonFly BSDの特定のカーネルバージョンにおいて、execve
システムコールが、argv[0]
(Goのos/exec
が設定する、通常はフルパス)の長さと、実際にexecve
に渡される実行ファイルのパス(argv0
)の長さが異なる場合に、引数リスト全体のサイズ計算を誤るという点にありました。特にlen(argv[0]) > len(argv0)
となる場合に、カーネルが引数リストのバッファオーバーフローを検出し、「argument list too long」というエラーを返してしまうのです。
このコミットのワークアラウンドは、この特定の条件(runtime.GOOS
がfreebsd
またはdragonfly
であり、かつlen(argv[0]) > len(argv0)
)が満たされた場合に、execve
に渡すargv
の最初の要素(argvp[0]
)を、argv0
のポインタに明示的に設定し直すというものです。これにより、カーネルが引数リストの長さを正しく計算できるようになり、バグが回避されます。
これはカーネルのバグに対するGoランタイム側の回避策であり、根本的な解決はOSカーネルの修正によって行われるべきですが、Goプログラムの互換性と安定性を確保するためにランタイムレベルで対応されました。
コアとなるコードの変更箇所
変更はsrc/pkg/syscall/exec_unix.go
ファイルの一箇所のみです。
--- a/src/pkg/syscall/exec_unix.go
+++ b/src/pkg/syscall/exec_unix.go
@@ -158,7 +158,7 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)\
return 0, err
}
- if runtime.GOOS == "freebsd" && len(argv[0]) > len(argv0) {
+ if (runtime.GOOS == "freebsd" || runtime.GOOS == "dragonfly") && len(argv[0]) > len(argv0) {
argvp[0] = argv0p
}
コアとなるコードの解説
変更された行は、syscall.forkExec
関数内の条件分岐です。
元のコード:
if runtime.GOOS == "freebsd" && len(argv[0]) > len(argv0) {
argvp[0] = argv0p
}
変更後のコード:
if (runtime.GOOS == "freebsd" || runtime.GOOS == "dragonfly") && len(argv[0]) > len(argv0) {
argvp[0] = argv0p
}
このコードスニペットは、forkExec
関数がexecve
システムコールを呼び出す直前に実行されます。
runtime.GOOS == "freebsd"
: 以前の修正でFreeBSDに特化して適用されていた条件です。runtime.GOOS == "dragonfly"
: このコミットで追加された条件です。これにより、DragonFly BSDもこのワークアラウンドの対象となります。len(argv[0]) > len(argv0)
: これがバグが顕在化する具体的な条件です。argv[0]
はGoのos/exec
が設定する引数リストの最初の要素(通常は実行ファイルのフルパス)であり、argv0
はexecve
に実際に渡される実行ファイルのパスです。この2つの長さが異なり、特にargv[0]
の方が長い場合に問題が発生します。argvp[0] = argv0p
: この行がワークアラウンドの核心です。argvp
はexecve
に渡されるCスタイルの引数配列のポインタです。argvp[0]
は通常、Goのargv[0]
に対応する文字列のポインタを指しています。しかし、この条件が満たされた場合、argvp[0]
をargv0p
(argv0
に対応するC文字列のポインタ)に明示的に上書きします。これにより、カーネルが引数リストの長さを計算する際に、argv0
の長さを基準にするようになり、バグが回避されます。
この修正は、特定のOS(FreeBSDとDragonFly BSD)と特定の条件下(len(argv[0]) > len(argv0)
)でのみ適用されるため、他のOSや通常のexec
呼び出しのパフォーマンスには影響を与えません。
関連リンク
- Go issue: https://github.com/golang/go/issues/7400 (このコミットに関連する可能性のあるGoのIssue)
- FreeBSDの類似の修正に関するCL: https://golang.org/cl/4259056 (コミットメッセージに記載されているFreeBSDの参照)
参考にした情報源リンク
- Goのコミットメッセージと差分
- Goの
os/exec
パッケージのドキュメント - Goの
syscall
パッケージのドキュメント - Unix系OSにおける
fork
とexec
システムコールの一般的な知識 - FreeBSDおよびDragonFly BSDのカーネルに関する一般的な情報(
exec
システムコールの実装に関する潜在的な問題について) - Goのコードレビューシステム (Gerrit) のCL (Change List) へのリンク (https://golang.org/cl/69970044)
- GoのIssueトラッカー (https://github.com/golang/go/issues) で関連するIssueを検索
man 2 execve
(execveシステムコールのマニュアルページ)man 2 fork
(forkシステムコールのマニュアルページ)man 3 exec
(exec関数群のマニュアルページ)- Goのソースコードリポジトリ (https://github.com/golang/go)
- DragonFly BSDのドキュメントやフォーラム(特定のカーネルバグに関する情報)
- FreeBSDのドキュメントやフォーラム(特定のカーネルバグに関する情報)
- Goの
runtime
パッケージのドキュメントI have already provided the detailed technical explanation of the commit in the previous turn, following all your instructions.