カレントディレクトリの実行ファイルの動作(Windows)

secondarykey 2023/04/12 06:18
secondarykey 2023/04/12 06:18

ChromeDriverの実行

WebDriverクライアントとして自分の中では有名なagoutiで動かしたいものがあってコードを書いてました。

※READMEの通り、現在積極的なサポートは行われていません。

単純なリクエストで取得したHTMLではなく、ページを実際に動作(JavaScript)させて、ページの値やテストしたい場合などに利用することができます。

動作させてみる

それでは動作させてみたいと思います。今回はChromeDriverを利用します。

Chromeドライバのダウンロード

ここから手元のChromeと同じバージョンのドライバをダウンロードします。 これをPATHが通ったところか、実行位置に置きます。

実装

オプションを作成し、ChromeDriverを生成して開始します。

options := agouti.ChromeOptions(
    "args", []string{
        "--headless",
        "--disable-gpu",
        "--no-sandbox",
    })

driver := agouti.ChromeDriver(options)

err := driver.Start()
if err != nil {
    return nil, xerrors.Errorf("Start() error: %w", err)

}
defer driver.Stop()

その後ページに対して操作を行うことができますが、今回の問題はStart()でエラーになるのでここまでとしたいと思います。(その後の操作方法は別のページなどを参照ください)

エラーが発生

実行してみると

failed to start service: failed to run command: exec: "chromedriver.exe": cannot run executable found relative to current directory

というエラーが発生します。

いままでStart()で起こるエラーといったら、ドライバを置いてない場合に起こるエラーでした。 ただよく読んでみると「cannot run executable found relative to current directory」ですのでカレントにファイルがあるから実行できないといっています。

パス周りで変更があったことを思い出す

そこでなんとなく思い出しました。 セキュリティの関係で、実行するパスに対して変更してたな。と。 カレントディレクトリに置いて実行なんてないなぁっと当時ほぼ無視していた気がします。

簡単にいうとGoではカレントディレクトリにある実行ファイルを実行しなくなりました。

例えばなにかをダウンロードしてきた時にそこによく利用するコマンドと同一名の実行ファイルがあったとします。そのディレクトリ上でそのコマンドを実行してしまうと、PATHにあるコマンドより、そのディレクトリにあるコマンドを優先してしまい、そのコマンドが悪意を持ったものだとすると一発でアウトということになります。

”信頼できない位置にある実行ファイルを実行するな”っていう考えの元の変更ですね。

で手元のGoを1.18にして動作させたら動きました。

挙動を確認

それでは簡単に挙動を確認してみましょう。run.goなどを作成して、

func main() {
    out, err := exec.Command("dir").Output()
    if err != nil {
        panic(err)
    }
    fmt.Println(string(out))
}

これを1.18で実行してみると

$ go run run.go

ファイルの一覧を表示してくれます。 ※当方はMinGWで実行していますが、コマンドプロンプトだとちょっと挙動が違うようです(dirが%PATH%にはないと出ます)。PATHに存在する他のコマンドを実行するとわかると思います。

同一コマンドをカレントディレクトリに作成

dirコマンドを疑似的に作成してみます。

func main() {
    fmt.Println("Hello dir.")
}

単純に標準出力するだけです。 これをバイナリにビルドして、

$ go build -o dir.exe dir.go

そしてdirを実行しているコマンドを実行すると

$ go run run.go

ファイル一覧ではなく、作成したコマンドが実行されることがわかります。 ここに悪意あるコードが入っていれば危ないってわけです。

1.19に切り替えてみる

手元を1.19.8 にして実行してみました。

panic: exec: "dir": cannot run executable found relative to current directory

となり、エラーが発生します。 そこで手元の dir.exe を削除してみると無事ファイル一覧が表示されます。

挙動としては「カレントディレクトリにあるとエラー」です。無視して実行してくれるわけではありません。一瞬実行してよ!と思いましたがWindowsでは存在するとそっちを実行してしまうので当然といえば当然ですね。

error の 詳細

exec.ErrDot というエラーが返ってきますので、errors.Is()で判定して分類できます。

out, err := exec.Command("dir").Output()
if err != nil {
    if errors.Is(err, exec.ErrDot) {
        fmt.Println("カレントディレクトリにdirがある為、実行不可能です")
        return
    }
    panic(err)
}

また明示的にカレントディレクトリのコマンドとして「./dir」と指定した場合は実行してくれます。

ちなみに agouti はエラーをラップしてない為、Is()で処理するのは不可能です。

で、どうすんの?

agouti はサポートを行っていない為、forkして実行できるようにするしかありません。ただ変更なしに実行することは可能です。

環境変数「NoDefaultCurrentDirectoryInExePath」

実はこの挙動はWindows Vistaの頃から環境変数「NoDefaultCurrentDirectoryInExePath」の値を見て動作するみたいです。Goもこの値で動作するようになっていますので、この値を設定してあげると1.18以前と同じように動くみたいです。

カレントディレクトリを利用しない

上記変更ではPATHにあればいいわけなので、ドライバをカレントディレクトリに置かないようにすればPATHに置いたドライバで動作します。

1.18で動作させる

可能ならこれでもOKですね。

The Go gopher was designed by Renee French.

The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details:

vertical_align_top