[インデックス 15157] ファイルの概要
このコミットは、Go言語の標準ライブラリos/exec
パッケージ内のLookPath
関数の挙動に関する修正です。具体的には、Unix系システムにおいてPATH
環境変数が空の場合に、LookPath
が誤ってカレントワーキングディレクトリ (cwd) を検索してしまう問題を解決します。この変更により、セキュリティと予測可能性が向上します。
コミット
commit fe7dbea00e36d649f072d38070630c53c4efab17
Author: Péter Surányi <speter.go1@gmail.com>
Date: Thu Feb 7 06:41:35 2013 -0800
os/exec: LookPath on Unix shouldn't look in cwd when PATH is empty
R=golang-dev, dave, bradfitz
CC=golang-dev
https://golang.org/cl/7305053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fe7dbea00e36d649f072d38070630c53c4efab17
元コミット内容
os/exec: LookPath on Unix shouldn't look in cwd when PATH is empty
このコミットは、Unix系システムにおけるLookPath
関数の動作を修正します。PATH
環境変数が空の場合、LookPath
はカレントワーキングディレクトリ (cwd) を検索すべきではありません。
変更の背景
os/exec
パッケージのLookPath
関数は、指定された実行可能ファイルがシステム上のどこにあるかをPATH
環境変数に基づいて検索する役割を担っています。Unix系システムでは、伝統的にPATH
環境変数が空の場合、シェルはカレントワーキングディレクトリ (cwd) を検索パスに含めません。これは、悪意のある実行ファイルがカレントディレクトリに置かれている場合に、意図しないプログラムが実行されるのを防ぐためのセキュリティ上の慣習です。
しかし、GoのLookPath
関数は、PATH
環境変数が空であっても、内部的にカレントワーキングディレクトリを検索パスの一部として扱ってしまうバグがありました。この挙動は、Unixシェルの標準的な動作と異なり、予期せぬプログラムの実行やセキュリティ上の脆弱性につながる可能性がありました。例えば、ユーザーがPATH
を意図的に空に設定していても、LookPath
を使用するプログラムがカレントディレクトリにある同名のスクリプトや実行ファイルを誤って実行してしまうリスクがありました。
このコミットは、この不整合を解消し、LookPath
がUnixシェルの標準的なセキュリティプラクティスに準拠するように修正することを目的としています。
前提知識の解説
os/exec
パッケージ
Go言語の標準ライブラリであるos/exec
パッケージは、外部コマンドを実行するための機能を提供します。このパッケージを使用することで、Goプログラムからシェルコマンドや他の実行可能ファイルを起動し、その入出力を制御することができます。
LookPath
関数
os/exec
パッケージ内のLookPath
関数は、与えられたファイル名(実行可能ファイル名)がPATH
環境変数で指定されたディレクトリ群のどこに存在するかを検索し、その絶対パスを返します。例えば、LookPath("ls")
と呼び出すと、システム上のls
コマンドの絶対パス(例: /bin/ls
)を返します。
PATH
環境変数
PATH
環境変数は、オペレーティングシステムが実行可能ファイルを検索するディレクトリのリストを定義する環境変数です。Unix系システムでは、コロン (:
) で区切られたディレクトリパスのリストとして表現されます。例えば、PATH=/usr/local/bin:/usr/bin:/bin
のような値が設定されている場合、システムはまず/usr/local/bin
、次に/usr/bin
、最後に/bin
の順で実行可能ファイルを検索します。
カレントワーキングディレクトリ (cwd)
カレントワーキングディレクトリ (Current Working Directory, cwd) は、現在作業しているディレクトリを指します。シェルやプログラムがファイルや他のプログラムを検索する際に、相対パスが指定された場合の基準となるディレクトリです。Unix系システムでは、PATH
環境変数に明示的に.
(カレントディレクトリ)が含まれていない限り、通常はcwdは実行可能ファイルの検索パスには含まれません。これは、セキュリティ上の理由から推奨される慣行です。
技術的詳細
このコミットの技術的な核心は、LookPath
関数がPATH
環境変数の値を評価するロジックにあります。修正前は、os.Getenv("PATH")
でPATH
環境変数の値を取得した後、その値が空文字列 (""
) であっても、後続のループ処理でパス要素として空文字列 (""
) が現れた場合に、それをカレントワーキングディレクトリ (.
) として解釈していました。
Unixシェルのセマンティクスでは、PATH
内の空の要素はカレントディレクトリを意味しますが、これはPATH
自体が空でない場合に適用されるルールです。PATH
全体が空である場合は、実行可能ファイルの検索は行われないか、あるいは特定のシステムディレクトリのみに限定されるべきです。
このコミットでは、os.Getenv("PATH")
で取得したpathenv
が空文字列である場合に、即座にErrNotFound
を返して処理を終了するチェックを追加しています。これにより、PATH
が空の場合に誤ってカレントワーキングディレクトリを検索してしまうという、Unixシェルの慣習に反する挙動が修正されます。
また、この変更を検証するために、新しいテストケースTestLookPathUnixEmptyPath
が追加されました。このテストは、一時ディレクトリを作成し、そのディレクトリに移動した後、PATH
環境変数を一時的に空に設定します。そして、そのディレクトリ内に実行可能ファイルを作成し、LookPath
がそのファイルを見つけないことを確認します。これにより、修正が正しく機能していることが保証されます。
コアとなるコードの変更箇所
変更は以下の2つのファイルで行われています。
src/pkg/os/exec/lp_unix.go
:LookPath
関数の実装ファイルsrc/pkg/os/exec/lp_unix_test.go
:LookPath
関数のテストファイル(新規追加)
src/pkg/os/exec/lp_unix.go
の変更
--- a/src/pkg/os/exec/lp_unix.go
+++ b/src/pkg/os/exec/lp_unix.go
@@ -42,6 +42,9 @@ func LookPath(file string) (string, error) {
return "", &Error{file, err}
}
pathenv := os.Getenv("PATH")
+ if pathenv == "" {
+ return "", &Error{file, ErrNotFound}
+ }
for _, dir := range strings.Split(pathenv, ":") {
if dir == "" {
// Unix shell semantics: path element "" means "."
src/pkg/os/exec/lp_unix_test.go
の新規追加
--- /dev/null
+++ b/src/pkg/os/exec/lp_unix_test.go
@@ -0,0 +1,52 @@
+// Copyright 2013 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.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package exec
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestLookPathUnixEmptyPath(t *testing.T) {
+ tmp, err := ioutil.TempDir("", "TestLookPathUnixEmptyPath")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmp)
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatal("Getwd failed: ", err)
+ }
+ err = os.Chdir(tmp)
+ if err != nil {
+ t.Fatal("Chdir failed: ", err)
+ }
+ defer os.Chdir(wd)
+
+ f, err := os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700)
+ if err != nil {
+ t.Fatal("OpenFile failed: ", err)
+ }
+ defer f.Close()
+
+ pathenv := os.Getenv("PATH")
+ defer os.Setenv("PATH", pathenv)
+
+ err = os.Setenv("PATH", "")
+ if err != nil {
+ t.Fatal("Setenv failed: ", err)
+ }
+
+ path, err := LookPath("exec_me")
+ if err == nil {
+ t.Fatal("LookPath found exec_me in empty $PATH")
+ }
+ if path != "" {
+ t.Fatalf("LookPath path == %q when err != nil", path)
+ }
+}
コアとなるコードの解説
src/pkg/os/exec/lp_unix.go
の変更点
追加された3行のコードは、LookPath
関数の冒頭近くに挿入されています。
pathenv := os.Getenv("PATH")
if pathenv == "" {
return "", &Error{file, ErrNotFound}
}
pathenv := os.Getenv("PATH")
: まず、現在のPATH
環境変数の値を取得し、pathenv
変数に格納します。if pathenv == ""
: 取得したpathenv
が空文字列であるかどうかをチェックします。return "", &Error{file, ErrNotFound}
: もしpathenv
が空であれば、指定されたファイルが見つからなかったことを示すエラーErrNotFound
を返して、関数の実行を即座に終了します。
このシンプルなチェックにより、PATH
が空の場合に後続のstrings.Split(pathenv, ":")
が空のスライスを返し、その結果としてカレントディレクトリが検索されてしまうという誤った挙動が防止されます。これにより、Unixシェルの標準的なセキュリティ慣行に準拠した動作が実現されます。
src/pkg/os/exec/lp_unix_test.go
の新規追加
このテストファイルは、+build
ディレクティブによりUnix系システム(darwin, freebsd, linux, netbsd, openbsd)でのみビルドされるように設定されています。
TestLookPathUnixEmptyPath
関数は以下の手順でテストを実行します。
- 一時ディレクトリの作成と移動:
ioutil.TempDir
で一時ディレクトリを作成し、テスト終了時にdefer os.RemoveAll(tmp)
でクリーンアップします。os.Getwd()
で現在のワーキングディレクトリを保存し、os.Chdir(tmp)
で一時ディレクトリに移動します。テスト終了時にdefer os.Chdir(wd)
で元のディレクトリに戻ります。これにより、テストが他のテストやシステムの状態に影響を与えないようにします。
- テスト用実行可能ファイルの作成:
os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700)
で、一時ディレクトリ内にexec_me
という名前の実行可能ファイルを作成します。パーミッション0700
は、所有者のみが読み書き実行できることを意味します。
PATH
環境変数の操作:pathenv := os.Getenv("PATH")
で現在のPATH
環境変数の値を保存し、defer os.Setenv("PATH", pathenv)
でテスト終了時に元の値に戻します。os.Setenv("PATH", "")
でPATH
環境変数を意図的に空に設定します。これがテストの肝となる部分です。
LookPath
の呼び出しと検証:path, err := LookPath("exec_me")
で、PATH
が空の状態でexec_me
を検索します。if err == nil { t.Fatal("LookPath found exec_me in empty $PATH") }
:PATH
が空であるため、LookPath
はexec_me
を見つけるべきではありません。もしエラーがnil
(つまり見つかった)であれば、テストは失敗します。if path != "" { t.Fatalf("LookPath path == %q when err != nil", path) }
: エラーが発生した場合、返されるパスは空文字列であるべきです。もし空でなければ、テストは失敗します。
このテストは、PATH
が空の場合にLookPath
がカレントディレクトリを検索しないという、修正後の正しい挙動を厳密に検証しています。
関連リンク
- Go言語の
os/exec
パッケージのドキュメント: https://pkg.go.dev/os/exec - Go言語の
os
パッケージのドキュメント: https://pkg.go.dev/os
参考にした情報源リンク
- Go CL 7305053: https://golang.org/cl/7305053
- Unixの
PATH
環境変数とセキュリティに関する一般的な情報 (例:.
をPATH
に含めない理由など)- https://www.gnu.org/software/bash/manual/html_node/Command-Search-Path.html (BashのドキュメントにおけるPATHの検索パスに関する記述)
- https://en.wikipedia.org/wiki/PATH_(variable) (PATH環境変数に関するWikipedia記事)