Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18695] ファイルの概要

このコミットは、Go言語のosパッケージにおけるプロセス管理に関する重要な修正を導入しています。具体的には、os.Process.Releaseが呼び出された後に、解放済みのProcessオブジェクトに対してProcess.Killが誤って呼び出されることを防ぐための変更です。この修正は、特にUnix系システムにおいて、解放済みプロセスのPIDが-1になった場合に、意図せずシステム上の全プロセスにシグナルが送られてしまうという深刻な問題("killing -1 kills everything")を回避することを目的としています。

コミット

commit 07e2b4049be2f99ff3dca57b942769c017b12360
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Feb 28 08:31:12 2014 -0800

    os: don't allow Process.Kill after Process.Release
    
    This is a user error, but killing -1 kills everything, which
    is a pretty bad failure mode.
    
    Fixes #7434
    
    LGTM=iant
    R=iant
    CC=golang-codereviews
    https://golang.org/cl/70140043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/07e2b4049be2f99ff3dca57b942769c017b12360

元コミット内容

os: don't allow Process.Kill after Process.Release

このコミットは、os.Process.Releaseが呼び出された後に、そのProcessオブジェクトに対してProcess.Killが呼び出されることを許可しないようにするものです。これはユーザー側の誤った使用方法ではありますが、もし解放されたプロセスのPIDが-1であった場合、Unix系システムではkill -1がシステム上のほぼ全てのプロセスを終了させてしまうという非常に危険な挙動を引き起こすため、これを防ぐための修正です。

変更の背景

Go言語のosパッケージは、オペレーティングシステムとの低レベルなインタラクションを提供し、プロセスの生成や管理を行う機能を含んでいます。os.Process構造体は、実行中の外部プロセスを表します。

os.Process.Release()メソッドは、Processオブジェクトに関連付けられたシステムリソースを解放するために使用されます。このメソッドが呼び出されると、Processオブジェクトはそれ以降の操作(例えば、KillWait)には使用できなくなります。特にUnix系システムでは、Releaseが呼び出された後、内部的にプロセスのPIDが-1に設定されることがあります。

問題は、ユーザーがProcess.Release()を呼び出した後も、誤ってそのProcessオブジェクトに対してProcess.Kill()を呼び出してしまう可能性があったことです。通常、これは単なるエラーで済みますが、もし解放されたプロセスのPIDが-1になっていた場合、syscall.Kill(-1, sig)のような内部呼び出しが発生し、これはUnix系OSのkillコマンドにおける特殊な挙動である「PIDが-1の場合、呼び出し元プロセスとinitプロセスを除く全てのプロセスにシグナルを送る」という動作に繋がってしまいます。これはシステム全体に壊滅的な影響を与える可能性のある、非常に危険な「失敗モード」でした。

このコミットは、このようなユーザーエラーがシステム全体に及ぼす潜在的な破壊を防ぐために導入されました。

前提知識の解説

Go言語のosパッケージとプロセス管理

Go言語のosパッケージは、オペレーティングシステムが提供する機能へのプラットフォーム非依存なインターフェースを提供します。これには、ファイルシステム操作、環境変数、そしてプロセス管理が含まれます。

  • os.Process: 実行中の外部プロセスを表す構造体です。このオブジェクトを通じて、プロセスのPID(プロセスID)を取得したり、シグナルを送ったり、プロセスの終了を待ったりすることができます。
  • os.Process.Kill(): プロセスに終了シグナル(Unix系ではSIGKILL)を送るメソッドです。これにより、プロセスを強制的に終了させることができます。
  • os.Process.Release(): Processオブジェクトが保持しているシステムリソースを解放するメソッドです。このメソッドが呼び出されると、Processオブジェクトは無効になり、それ以降の操作はできません。通常、os.Process.Wait()を呼び出さない場合に、リソースリークを防ぐために明示的に呼び出す必要があります。Wait()を呼び出すと、自動的にリソースが解放されます。
  • os.Process.Wait(): プロセスの終了を待ち、その終了ステータスを取得するメソッドです。このメソッドは、プロセスが終了するまでブロックします。

Unix系OSにおけるkillコマンドとPID -1の特殊性

Unix系オペレーティングシステムには、プロセスにシグナルを送るためのkillコマンドがあります。このコマンドは、指定されたPIDを持つプロセスにシグナルを送ります。

  • PID (Process ID): 各プロセスに一意に割り当てられる識別子です。
  • シグナル: プロセスに対して特定のイベント(例えば、終了、一時停止、再開など)を通知するためのソフトウェア割り込みです。
  • kill -1の特殊性: Unix系システムでは、killコマンドにPIDとして-1を指定すると、特別な意味を持ちます。これは、**「呼び出し元のプロセス(killコマンドを実行しているプロセス)と、システム起動時に最初に実行されるinitプロセス(PID 1)を除く、システム上の全てのプロセスにシグナルを送る」**という動作を意味します。これは非常に強力で危険な操作であり、誤って実行されるとシステム全体がシャットダウンしたり、不安定になったりする可能性があります。

ゴルーチンと並行処理

Go言語はゴルーチン(goroutine)と呼ばれる軽量なスレッドと、チャネル(channel)による通信を特徴とする並行処理モデルを持っています。これにより、複数の処理を同時に実行することが容易になります。しかし、並行処理はリソースの競合や状態の不整合といった問題を引き起こす可能性があり、特にプロセス管理のようなシステムリソースを扱う際には、注意深い設計とエラーハンドリングが求められます。

技術的詳細

このコミットの技術的詳細は、os.Process構造体の内部状態管理と、signalメソッドにおけるPIDのチェックに集約されます。

Goのos.Processは、内部的にプロセスのPIDを保持しています。os.Process.Release()が呼び出されると、このPIDはUnix系システムでは慣習的に-1に設定され、オブジェクトが無効になったことを示します。

コミット前のコードでは、Process.signal()メソッド(Process.Kill()から内部的に呼び出される)は、プロセスが既に終了しているかどうか(p.done())はチェックしていましたが、プロセスがReleaseされているかどうかは明示的にチェックしていませんでした。

このため、以下のシナリオで問題が発生する可能性がありました。

  1. Goプログラムが外部プロセスを起動し、os.Processオブジェクトpを取得する。
  2. プログラムが何らかの理由でp.Release()を呼び出す。このとき、p.Pid-1に設定される。
  3. プログラムが、解放済みであるにもかかわらず、誤ってp.Kill()を呼び出す。
  4. p.Kill()は内部的にp.signal()を呼び出す。
  5. p.signal()p.done()をチェックするが、プロセスが終了していなくてもReleaseされているだけなので、このチェックはパスする。
  6. p.signal()は、p.Pid-1であることに気づかず、syscall.Kill(-1, sig)のようなシステムコールを試みる。
  7. Unix系OSのカーネルは、PID -1を特殊な意味として解釈し、システム上のほぼ全てのプロセスにシグナルを送ってしまう。

このコミットは、p.signal()メソッドにp.Pid == -1というチェックを追加することで、この脆弱性を解消しています。これにより、Releaseされたプロセスに対してKillが呼び出された場合、即座にエラーを返すようになり、危険なkill -1の呼び出しが防止されます。

コアとなるコードの変更箇所

変更はsrc/pkg/os/exec_unix.goファイルに集中しています。

--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/exec_unix.go
@@ -38,6 +38,9 @@ func (p *Process) signal(sig Signal) error {
 	if p.done() {
 		return errors.New("os: process already finished")
 	}
+	if p.Pid == -1 {
+		return errors.New("os: process already released")
+	}
 	s, ok := sig.(syscall.Signal)
 	if !ok {
 		return errors.New("os: unsupported signal type")

コアとなるコードの解説

変更はfunc (p *Process) signal(sig Signal) error関数内で行われています。この関数は、os.Processオブジェクトに対してシグナルを送るための内部ヘルパー関数です。

  1. if p.done() { return errors.New("os: process already finished") }

    • この行は既存のチェックで、プロセスが既に終了している場合はエラーを返します。
  2. if p.Pid == -1 { return errors.New("os: process already released") }

    • この行が今回のコミットで追加された新しいチェックです。
    • p.Pid-1であるかどうかを確認します。前述の通り、Unix系システムではos.Process.Release()が呼び出された後、p.Pid-1に設定されることがあります。
    • もしp.Pid-1であれば、それはプロセスが既に解放されていることを意味します。この場合、"os: process already released"というエラーを返して処理を中断します。これにより、解放済みのプロセスに対してシグナルを送ろうとする試みが安全にブロックされます。

このシンプルな3行の追加により、Process.Release後のProcess.Kill呼び出しが引き起こす可能性のある、システム全体への破壊的な影響が効果的に防止されています。

関連リンク

参考にした情報源リンク

  • Go issue 7434 (コミットメッセージに記載されているが、公開されているGoのIssueトラッカーでは見つからなかったため、内部的なトラッカーの可能性が高い。関連する公開情報は見つからず。)
  • Unix kill command documentation (man pages, online resources)
  • Stack OverflowやGoコミュニティの議論(os.Process.Releasekill -1に関するもの)
  • Go言語のソースコード(src/pkg/os/exec_unix.go
  • Web検索結果:
    • Go os.Process.Releaseに関する情報
    • Go Process.Kill -1に関する情報
    • Go issue 7434に関する情報(ただし、関連する公開Issueは見つからず)I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. The explanation is in Japanese and covers the background, prerequisite knowledge, technical details, core code changes, and related links. I have also addressed the Fixes #7434 part by noting that the issue number might be internal or a typo, as no public Go issue with that number was found.

I will now output the generated explanation to standard output.

# [インデックス 18695] ファイルの概要

このコミットは、Go言語の`os`パッケージにおけるプロセス管理に関する重要な修正を導入しています。具体的には、`os.Process.Release`が呼び出された後に、解放済みの`Process`オブジェクトに対して`Process.Kill`が誤って呼び出されることを防ぐための変更です。この修正は、特にUnix系システムにおいて、解放済みプロセスのPIDが`-1`になった場合に、意図せずシステム上の全プロセスにシグナルが送られてしまうという深刻な問題("killing -1 kills everything")を回避することを目的としています。

## コミット

commit 07e2b4049be2f99ff3dca57b942769c017b12360 Author: Brad Fitzpatrick bradfitz@golang.org Date: Fri Feb 28 08:31:12 2014 -0800

os: don't allow Process.Kill after Process.Release

This is a user error, but killing -1 kills everything, which
is a pretty bad failure mode.

Fixes #7434

LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/70140043

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/07e2b4049be2f99ff3dca57b942769c017b12360](https://github.com/golang/go/commit/07e2b4049be2f99ff3dca57b942769c017b12360)

## 元コミット内容

`os: don't allow Process.Kill after Process.Release`

このコミットは、`os.Process.Release`が呼び出された後に、その`Process`オブジェクトに対して`Process.Kill`が呼び出されることを許可しないようにするものです。これはユーザー側の誤った使用方法ではありますが、もし解放されたプロセスのPIDが`-1`であった場合、Unix系システムでは`kill -1`がシステム上のほぼ全てのプロセスを終了させてしまうという非常に危険な挙動を引き起こすため、これを防ぐための修正です。

## 変更の背景

Go言語の`os`パッケージは、オペレーティングシステムとの低レベルなインタラクションを提供し、プロセスの生成や管理を行う機能を含んでいます。`os.Process`構造体は、実行中の外部プロセスを表します。

`os.Process.Release()`メソッドは、`Process`オブジェクトに関連付けられたシステムリソースを解放するために使用されます。このメソッドが呼び出されると、`Process`オブジェクトはそれ以降の操作(例えば、`Kill`や`Wait`)には使用できなくなります。特にUnix系システムでは、`Release`が呼び出された後、内部的にプロセスのPIDが`-1`に設定されることがあります。

問題は、ユーザーが`Process.Release()`を呼び出した後も、誤ってその`Process`オブジェクトに対して`Process.Kill()`を呼び出してしまう可能性があったことです。通常、これは単なるエラーで済みますが、もし解放されたプロセスのPIDが`-1`になっていた場合、`syscall.Kill(-1, sig)`のような内部呼び出しが発生し、これはUnix系OSの`kill`コマンドにおける特殊な挙動である「PIDが-1の場合、呼び出し元プロセスとinitプロセスを除く全てのプロセスにシグナルを送る」という動作に繋がってしまいます。これはシステム全体に壊滅的な影響を与える可能性のある、非常に危険な「失敗モード」でした。

このコミットは、このようなユーザーエラーがシステム全体に及ぼす潜在的な破壊を防ぐために導入されました。

## 前提知識の解説

### Go言語の`os`パッケージとプロセス管理

Go言語の`os`パッケージは、オペレーティングシステムが提供する機能へのプラットフォーム非依存なインターフェースを提供します。これには、ファイルシステム操作、環境変数、そしてプロセス管理が含まれます。

*   **`os.Process`**: 実行中の外部プロセスを表す構造体です。このオブジェクトを通じて、プロセスのPID(プロセスID)を取得したり、シグナルを送ったり、プロセスの終了を待ったりすることができます。
*   **`os.Process.Kill()`**: プロセスに終了シグナル(Unix系では`SIGKILL`)を送るメソッドです。これにより、プロセスを強制的に終了させることができます。
*   **`os.Process.Release()`**: `Process`オブジェクトが保持しているシステムリソースを解放するメソッドです。このメソッドが呼び出されると、`Process`オブジェクトは無効になり、それ以降の操作はできません。通常、`os.Process.Wait()`を呼び出さない場合に、リソースリークを防ぐために明示的に呼び出す必要があります。`Wait()`を呼び出すと、自動的にリソースが解放されます。
*   **`os.Process.Wait()`**: プロセスの終了を待ち、その終了ステータスを取得するメソッドです。このメソッドは、プロセスが終了するまでブロックします。

### Unix系OSにおける`kill`コマンドとPID `-1`の特殊性

Unix系オペレーティングシステムには、プロセスにシグナルを送るための`kill`コマンドがあります。このコマンドは、指定されたPIDを持つプロセスにシグナルを送ります。

*   **PID (Process ID)**: 各プロセスに一意に割り当てられる識別子です。
*   **シグナル**: プロセスに対して特定のイベント(例えば、終了、一時停止、再開など)を通知するためのソフトウェア割り込みです。
*   **`kill -1`の特殊性**: Unix系システムでは、`kill`コマンドにPIDとして`-1`を指定すると、特別な意味を持ちます。これは、**「呼び出し元のプロセス(`kill`コマンドを実行しているプロセス)と、システム起動時に最初に実行される`init`プロセス(PID 1)を除く、システム上の全てのプロセスにシグナルを送る」**という動作を意味します。これは非常に強力で危険な操作であり、誤って実行されるとシステム全体がシャットダウンしたり、不安定になったりする可能性があります。

### ゴルーチンと並行処理

Go言語はゴルーチン(goroutine)と呼ばれる軽量なスレッドと、チャネル(channel)による通信を特徴とする並行処理モデルを持っています。これにより、複数の処理を同時に実行することが容易になります。しかし、並行処理はリソースの競合や状態の不整合といった問題を引き起こす可能性があり、特にプロセス管理のようなシステムリソースを扱う際には、注意深い設計とエラーハンドリングが求められます。

## 技術的詳細

このコミットの技術的詳細は、`os.Process`構造体の内部状態管理と、`signal`メソッドにおけるPIDのチェックに集約されます。

Goの`os.Process`は、内部的にプロセスのPIDを保持しています。`os.Process.Release()`が呼び出されると、このPIDはUnix系システムでは慣習的に`-1`に設定され、オブジェクトが無効になったことを示します。

コミット前のコードでは、`Process.signal()`メソッド(`Process.Kill()`から内部的に呼び出される)は、プロセスが既に終了しているかどうか(`p.done()`)はチェックしていましたが、プロセスが`Release`されているかどうかは明示的にチェックしていませんでした。

このため、以下のシナリオで問題が発生する可能性がありました。

1.  Goプログラムが外部プロセスを起動し、`os.Process`オブジェクト`p`を取得する。
2.  プログラムが何らかの理由で`p.Release()`を呼び出す。このとき、`p.Pid`は`-1`に設定される。
3.  プログラムが、解放済みであるにもかかわらず、誤って`p.Kill()`を呼び出す。
4.  `p.Kill()`は内部的に`p.signal()`を呼び出す。
5.  `p.signal()`は`p.done()`をチェックするが、プロセスが終了していなくても`Release`されているだけなので、このチェックはパスする。
6.  `p.signal()`は、`p.Pid`が`-1`であることに気づかず、`syscall.Kill(-1, sig)`のようなシステムコールを試みる。
7.  Unix系OSのカーネルは、PID `-1`を特殊な意味として解釈し、システム上のほぼ全てのプロセスにシグナルを送ってしまう。

このコミットは、`p.signal()`メソッドに`p.Pid == -1`というチェックを追加することで、この脆弱性を解消しています。これにより、`Release`されたプロセスに対して`Kill`が呼び出された場合、即座にエラーを返すようになり、危険な`kill -1`の呼び出しが防止されます。

## コアとなるコードの変更箇所

変更は`src/pkg/os/exec_unix.go`ファイルに集中しています。

```diff
--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/exec_unix.go
@@ -38,6 +38,9 @@ func (p *Process) signal(sig Signal) error {
 	if p.done() {
 		return errors.New("os: process already finished")
 	}
+	if p.Pid == -1 {
+		return errors.New("os: process already released")
+	}
 	s, ok := sig.(syscall.Signal)
 	if !ok {
 		return errors.New("os: unsupported signal type")

コアとなるコードの解説

変更はfunc (p *Process) signal(sig Signal) error関数内で行われています。この関数は、os.Processオブジェクトに対してシグナルを送るための内部ヘルパー関数です。

  1. if p.done() { return errors.New("os: process already finished") }

    • この行は既存のチェックで、プロセスが既に終了している場合はエラーを返します。
  2. if p.Pid == -1 { return errors.New("os: process already released") }

    • この行が今回のコミットで追加された新しいチェックです。
    • p.Pid-1であるかどうかを確認します。前述の通り、Unix系システムではos.Process.Release()が呼び出された後、p.Pid-1に設定されることがあります。
    • もしp.Pid-1であれば、それはプロセスが既に解放されていることを意味します。この場合、"os: process already released"というエラーを返して処理を中断します。これにより、解放済みのプロセスに対してシグナルを送ろうとする試みが安全にブロックされます。

このシンプルな3行の追加により、Process.Release後のProcess.Kill呼び出しが引き起こす可能性のある、システム全体への破壊的な影響が効果的に防止されています。

関連リンク

参考にした情報源リンク

  • Go issue 7434 (コミットメッセージに記載されているが、公開されているGoのIssueトラッカーでは見つからなかったため、内部的なトラッカーの可能性が高い。関連する公開情報は見つからず。)
  • Unix kill command documentation (man pages, online resources)
  • Stack OverflowやGoコミュニティの議論(os.Process.Releasekill -1に関するもの)
  • Go言語のソースコード(src/pkg/os/exec_unix.go
  • Web検索結果:
    • Go os.Process.Releaseに関する情報
    • Go Process.Kill -1に関する情報
    • Go issue 7434に関する情報(ただし、関連する公開Issueは見つからず)

[インデックス 18695] ファイルの概要

このコミットは、Go言語のosパッケージにおけるプロセス管理に関する重要な修正を導入しています。具体的には、os.Process.Releaseが呼び出された後に、解放済みのProcessオブジェクトに対してProcess.Killが誤って呼び出されることを防ぐための変更です。この修正は、特にUnix系システムにおいて、解放済みプロセスのPIDが-1になった場合に、意図せずシステム上の全プロセスにシグナルが送られてしまうという深刻な問題("killing -1 kills everything")を回避することを目的としています。

コミット

commit 07e2b4049be2f99ff3dca57b942769c017b12360
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Feb 28 08:31:12 2014 -0800

    os: don't allow Process.Kill after Process.Release
    
    This is a user error, but killing -1 kills everything, which
    is a pretty bad failure mode.
    
    Fixes #7434
    
    LGTM=iant
    R=iant
    CC=golang-codereviews
    https://golang.org/cl/70140043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/07e2b4049be2f99ff3dca57b942769c017b12360

元コミット内容

os: don't allow Process.Kill after Process.Release

このコミットは、os.Process.Releaseが呼び出された後に、そのProcessオブジェクトに対してProcess.Killが呼び出されることを許可しないようにするものです。これはユーザー側の誤った使用方法ではありますが、もし解放されたプロセスのPIDが-1であった場合、Unix系システムではkill -1がシステム上のほぼ全てのプロセスを終了させてしまうという非常に危険な挙動を引き起こすため、これを防ぐための修正です。

変更の背景

Go言語のosパッケージは、オペレーティングシステムとの低レベルなインタラクションを提供し、プロセスの生成や管理を行う機能を含んでいます。os.Process構造体は、実行中の外部プロセスを表します。

os.Process.Release()メソッドは、Processオブジェクトに関連付けられたシステムリソースを解放するために使用されます。このメソッドが呼び出されると、Processオブジェクトはそれ以降の操作(例えば、KillWait)には使用できなくなります。特にUnix系システムでは、Releaseが呼び出された後、内部的にプロセスのPIDが-1に設定されることがあります。

問題は、ユーザーがProcess.Release()を呼び出した後も、誤ってそのProcessオブジェクトに対してProcess.Kill()を呼び出してしまう可能性があったことです。通常、これは単なるエラーで済みますが、もし解放されたプロセスのPIDが-1になっていた場合、syscall.Kill(-1, sig)のような内部呼び出しが発生し、これはUnix系OSのkillコマンドにおける特殊な挙動である「PIDが-1の場合、呼び出し元プロセスとinitプロセスを除く全てのプロセスにシグナルを送る」という動作に繋がってしまいます。これはシステム全体に壊滅的な影響を与える可能性のある、非常に危険な「失敗モード」でした。

このコミットは、このようなユーザーエラーがシステム全体に及ぼす潜在的な破壊を防ぐために導入されました。

前提知識の解説

Go言語のosパッケージとプロセス管理

Go言語のosパッケージは、オペレーティングシステムが提供する機能へのプラットフォーム非依存なインターフェースを提供します。これには、ファイルシステム操作、環境変数、そしてプロセス管理が含まれます。

  • os.Process: 実行中の外部プロセスを表す構造体です。このオブジェクトを通じて、プロセスのPID(プロセスID)を取得したり、シグナルを送ったり、プロセスの終了を待ったりすることができます。
  • os.Process.Kill(): プロセスに終了シグナル(Unix系ではSIGKILL)を送るメソッドです。これにより、プロセスを強制的に終了させることができます。
  • os.Process.Release(): Processオブジェクトが保持しているシステムリソースを解放するメソッドです。このメソッドが呼び出されると、Processオブジェクトは無効になり、それ以降の操作はできません。通常、os.Process.Wait()を呼び出さない場合に、リソースリークを防ぐために明示的に呼び出す必要があります。Wait()を呼び出すと、自動的にリソースが解放されます。
  • os.Process.Wait(): プロセスの終了を待ち、その終了ステータスを取得するメソッドです。このメソッドは、プロセスが終了するまでブロックします。

Unix系OSにおけるkillコマンドとPID -1の特殊性

Unix系オペレーティングシステムには、プロセスにシグナルを送るためのkillコマンドがあります。このコマンドは、指定されたPIDを持つプロセスにシグナルを送ります。

  • PID (Process ID): 各プロセスに一意に割り当てられる識別子です。
  • シグナル: プロセスに対して特定のイベント(例えば、終了、一時停止、再開など)を通知するためのソフトウェア割り込みです。
  • kill -1の特殊性: Unix系システムでは、killコマンドにPIDとして-1を指定すると、特別な意味を持ちます。これは、**「呼び出し元のプロセス(killコマンドを実行しているプロセス)と、システム起動時に最初に実行されるinitプロセス(PID 1)を除く、システム上の全てのプロセスにシグナルを送る」**という動作を意味します。これは非常に強力で危険な操作であり、誤って実行されるとシステム全体がシャットダウンしたり、不安定になったりする可能性があります。

ゴルーチンと並行処理

Go言語はゴルーチン(goroutine)と呼ばれる軽量なスレッドと、チャネル(channel)による通信を特徴とする並行処理モデルを持っています。これにより、複数の処理を同時に実行することが容易になります。しかし、並行処理はリソースの競合や状態の不整合といった問題を引き起こす可能性があり、特にプロセス管理のようなシステムリソースを扱う際には、注意深い設計とエラーハンドリングが求められます。

技術的詳細

このコミットの技術的詳細は、os.Process構造体の内部状態管理と、signalメソッドにおけるPIDのチェックに集約されます。

Goのos.Processは、内部的にプロセスのPIDを保持しています。os.Process.Release()が呼び出されると、このPIDはUnix系システムでは慣習的に-1に設定され、オブジェクトが無効になったことを示します。

コミット前のコードでは、Process.signal()メソッド(Process.Kill()から内部的に呼び出される)は、プロセスが既に終了しているかどうか(p.done())はチェックしていましたが、プロセスがReleaseされているかどうかは明示的にチェックしていませんでした。

このため、以下のシナリオで問題が発生する可能性がありました。

  1. Goプログラムが外部プロセスを起動し、os.Processオブジェクトpを取得する。
  2. プログラムが何らかの理由でp.Release()を呼び出す。このとき、p.Pid-1に設定される。
  3. プログラムが、解放済みであるにもかかわらず、誤ってp.Kill()を呼び出す。
  4. p.Kill()は内部的にp.signal()を呼び出す。
  5. p.signal()p.done()をチェックするが、プロセスが終了していなくてもReleaseされているだけなので、このチェックはパスする。
  6. p.signal()は、p.Pid-1であることに気づかず、syscall.Kill(-1, sig)のようなシステムコールを試みる。
  7. Unix系OSのカーネルは、PID -1を特殊な意味として解釈し、システム上のほぼ全てのプロセスにシグナルを送ってしまう。

このコミットは、p.signal()メソッドにp.Pid == -1というチェックを追加することで、この脆弱性を解消しています。これにより、Releaseされたプロセスに対してKillが呼び出された場合、即座にエラーを返すようになり、危険なkill -1の呼び出しが防止されます。

コアとなるコードの変更箇所

変更はsrc/pkg/os/exec_unix.goファイルに集中しています。

--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/exec_unix.go
@@ -38,6 +38,9 @@ func (p *Process) signal(sig Signal) error {
 	if p.done() {
 		return errors.New("os: process already finished")
 	}
+	if p.Pid == -1 {
+		return errors.New("os: process already released")
+	}
 	s, ok := sig.(syscall.Signal)
 	if !ok {
 		return errors.New("os: unsupported signal type")

コアとなるコードの解説

変更はfunc (p *Process) signal(sig Signal) error関数内で行われています。この関数は、os.Processオブジェクトに対してシグナルを送るための内部ヘルパー関数です。

  1. if p.done() { return errors.New("os: process already finished") }

    • この行は既存のチェックで、プロセスが既に終了している場合はエラーを返します。
  2. if p.Pid == -1 { return errors.New("os: process already released") }

    • この行が今回のコミットで追加された新しいチェックです。
    • p.Pid-1であるかどうかを確認します。前述の通り、Unix系システムではos.Process.Release()が呼び出された後、p.Pid-1に設定されることがあります。
    • もしp.Pid-1であれば、それはプロセスが既に解放されていることを意味します。この場合、"os: process already released"というエラーを返して処理を中断します。これにより、解放済みのプロセスに対してシグナルを送ろうとする試みが安全にブロックされます。

このシンプルな3行の追加により、Process.Release後のProcess.Kill呼び出しが引き起こす可能性のある、システム全体への破壊的な影響が効果的に防止されています。

関連リンク

参考にした情報源リンク

  • Go issue 7434 (コミットメッセージに記載されているが、公開されているGoのIssueトラッカーでは見つからなかったため、内部的なトラッカーの可能性が高い。関連する公開情報は見つからず。)
  • Unix kill command documentation (man pages, online resources)
  • Stack OverflowやGoコミュニティの議論(os.Process.Releasekill -1に関するもの)
  • Go言語のソースコード(src/pkg/os/exec_unix.go
  • Web検索結果:
    • Go os.Process.Releaseに関する情報
    • Go Process.Kill -1に関する情報
    • Go issue 7434に関する情報(ただし、関連する公開Issueは見つからず)