[インデックス 16114] ファイルの概要
このコミットは、Goランタイムのsrc/pkg/runtime/proc.cファイルに1行の変更を加えるものです。具体的には、runtime·dropm関数内でm->sehポインタをnilにリセットする処理が追加されています。
コミット
commit 77354c39f9b9b35537951c4d98868b88214bf8dd
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Apr 6 20:00:45 2013 -0700
runtime: reset typed dangling pointer
If for whatever reason seh points into Go heap region,
the dangling pointer will cause memory corruption during GC.
Update #5193.
R=golang-dev, alex.brainman, iant
CC=golang-dev
https://golang.org/cl/8402045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/77354c39f9b9b35537951c4d98868b88214bf8dd
元コミット内容
runtime: reset typed dangling pointer
If for whatever reason seh points into Go heap region,
the dangling pointer will cause memory corruption during GC.
Update #5193.
変更の背景
このコミットは、Goランタイムにおける潜在的なメモリ破損の問題を解決するために導入されました。コミットメッセージによると、seh(Structured Exception Handling)ポインタが何らかの理由でGoのヒープ領域を指している場合、そのポインタがダングリングポインタ(無効なメモリ領域を指すポインタ)となり、ガベージコレクション(GC)の実行中にメモリ破損を引き起こす可能性がありました。
Goのガベージコレクタは、ヒープ上のオブジェクトを追跡し、不要になったメモリを解放する役割を担っています。このプロセス中に、無効なポインタがヒープ内の有効なオブジェクトを指しているかのように見せかけたり、あるいはGCがアクセスしてはならない領域を指したりすると、GCの整合性が損なわれ、結果としてプログラムのクラッシュや予期せぬ動作につながるメモリ破損が発生します。
コミットメッセージにある「Update #5193」は、この問題がGoのイシュートラッカーで報告された問題5193に関連していることを示唆しています。具体的なイシューの内容は不明ですが、このコミットがその問題に対する修正であることを示しています。
前提知識の解説
Goランタイム (Go Runtime)
Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。Goランタイムは、ガベージコレクション、ゴルーチン(軽量スレッド)のスケジューリング、メモリ管理、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルな機能を提供します。src/pkg/runtime/proc.cのようなファイルは、このランタイムのC言語で書かれた部分であり、Go言語自体では実装が難しい、あるいはパフォーマンスが要求される部分を担っています。
ガベージコレクション (Garbage Collection, GC)
Goは自動メモリ管理を採用しており、開発者が手動でメモリを解放する必要はありません。その代わりに、ガベージコレクタが定期的に実行され、どのメモリがまだ使用されているかを判断し、使用されていないメモリを自動的に解放します。GoのGCは、並行マーク&スイープ方式をベースにしており、プログラムの実行と並行して動作することで、GCによる一時停止(ストップ・ザ・ワールド)を最小限に抑えるように設計されています。
ポインタ (Pointer)
ポインタは、メモリ上の特定のアドレスを指す変数です。Goでは、C言語のようなポインタ演算は制限されていますが、ポインタ自体は存在し、値型ではなく参照型としてデータを扱う際に使用されます。
ダングリングポインタ (Dangling Pointer)
ダングリングポインタとは、既に解放されたメモリ領域を指しているにもかかわらず、そのポインタ自体はまだ有効な状態にあるポインタのことです。このようなポインタを介してメモリにアクセスしようとすると、未定義の動作を引き起こし、メモリ破損やセキュリティ上の脆弱性につながる可能性があります。
m と g (M and G)
Goランタイムの内部では、m (machine) と g (goroutine) という2つの重要な構造体があります。
m(Machine): オペレーティングシステムのスレッドを表します。Goランタイムは、OSスレッド上でゴルーチンを実行します。m構造体は、そのスレッド固有の情報を保持します。g(Goroutine): Go言語の軽量スレッドであるゴルーチンを表します。g構造体は、ゴルーチンのスタック、状態、スケジューリング情報などを保持します。
Goランタイムは、これらのmとgを多対多でマッピングし、効率的なゴルーチンのスケジューリングと実行を実現しています。
seh (Structured Exception Handling)
sehは、Windowsオペレーティングシステムで提供される例外処理メカニズムです。ハードウェア例外(例:ゼロ除算、無効なメモリアクセス)やソフトウェア例外を捕捉し、処理するためのフレームワークを提供します。GoランタイムがWindows上で動作する場合、低レベルなエラーハンドリングのためにsehを利用することがあります。この文脈でのsehは、Goランタイムが内部的に使用する、例外処理に関連するコンテキストやポインタを保持するフィールドであると推測されます。
技術的詳細
この問題の核心は、Goランタイムが管理するメモリ(Goヒープ)と、OSレベルの例外処理メカニズム(seh)との間の相互作用にあります。
通常、Goのガベージコレクタは、Goヒープ内の有効なGoオブジェクトのみをスキャンし、参照されていないオブジェクトを特定して解放します。しかし、もしm構造体(OSスレッドのコンテキストを保持)内のsehフィールドが、Goヒープ内の解放済み、あるいは無効なメモリ領域を指すダングリングポインタになっていた場合、以下の問題が発生する可能性があります。
- GCのスキャン対象外のポインタ:
sehフィールドがGoの型システムによって「型付けされたポインタ」として認識されていない場合、GCは通常、そのポインタが指す先をGoヒープ内の有効なオブジェクトとしてスキャンしません。しかし、もしsehが偶然にもGoヒープ領域を指していた場合、GCはその領域を誤って有効なオブジェクトの一部と見なしたり、あるいはその領域にアクセスしようとして不正なメモリアクセスを引き起こしたりする可能性があります。 - メモリ破損: GCがダングリングポインタを追跡しようとすると、既に解放されたメモリや、Goランタイムが管理していないメモリ領域にアクセスする可能性があります。これにより、GCの内部状態が破壊されたり、他の有効なGoオブジェクトのデータが上書きされたりする「メモリ破損」が発生します。メモリ破損は、プログラムのクラッシュ、データの破壊、セキュリティ上の脆弱性など、深刻な問題を引き起こします。
- タイミングの問題: この問題は、
m構造体が再利用される際に顕在化する可能性があります。runtime·dropm関数は、m構造体がOSスレッドから切り離される際に呼び出されます。このとき、以前のスレッドのコンテキストで設定されたsehポインタが、Goヒープ内の古い、既に解放されたメモリを指したままになっていると、そのmが別のゴルーチンに再利用された際に問題を引き起こす可能性があります。
このコミットは、このような状況を防ぐために、m構造体がOSスレッドから切り離される際に、sehポインタを明示的にnil(ヌルポインタ)にリセットすることで、ダングリングポインタの問題を解消しようとしています。これにより、GCが不正なポインタを追跡することを防ぎ、メモリ破損のリスクを低減します。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -695,6 +695,7 @@ runtime·dropm(void)\
// Undo whatever initialization minit did during needm.
runtime·unminit();
+ m->seh = nil; // reset dangling typed pointer
// Clear m and g, and return m to the extra list.
// After the call to setmg we can only call nosplit functions.
コアとなるコードの解説
変更はsrc/pkg/runtime/proc.cファイルのruntime·dropm関数内で行われています。
runtime·dropm関数は、GoランタイムがOSスレッド(m)を解放またはプールに戻す際に呼び出される関数です。これは、特定のOSスレッドがゴルーチンの実行を終え、他のゴルーチンに利用可能になるか、完全にシステムに返却される準備ができたときに実行されます。
追加された行は以下の通りです。
m->seh = nil; // reset dangling typed pointer
この行は、現在のm構造体(OSスレッドのコンテキスト)のsehフィールドをnilに設定しています。
m: 現在処理中のm構造体へのポインタ。seh:m構造体内のフィールドで、Structured Exception Handlingに関連するポインタまたはコンテキストを保持していると推測されます。nil: C言語におけるNULLポインタに相当し、ポインタが何も指していないことを示します。
この変更の目的は、mが再利用される前に、sehが指していた可能性のある古い、無効なメモリ参照をクリアすることです。これにより、sehがGoヒープ内のダングリングポインタとして残り、ガベージコレクション中に問題を引き起こすことを防ぎます。コメントにある「reset dangling typed pointer」は、このsehがGoランタイムによって特定の型を持つポインタとして扱われる可能性があり、そのダングリング状態がGCにとって特に問題となることを示唆しています。
runtime·unminit()の直後にこの行が追加されているのは、unminitがmの初期化を解除する処理を行った後、mがクリーンな状態であることを保証するためです。これにより、mがプールに戻され、将来的に別のゴルーチンによって再利用される際に、以前のsehの状態が引き継がれることによる潜在的な問題を排除します。
関連リンク
- Go言語の公式GitHubリポジトリ
- このコミットのGitHubページ
- Go CL 8402045 (Gerrit Code Reviewへのリンク)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goランタイムのソースコード (
src/pkg/runtime/) - ガベージコレクションに関する一般的な情報
- ポインタとメモリ管理に関する一般的な情報
- Structured Exception Handling (SEH) に関する一般的な情報 (特にWindows環境)
- Goイシュートラッカー (問題 #5193 は直接見つかりませんでしたが、Goのバグ報告と追跡のメカニズムとして)
- Go runtime: m, p, g の関係 (一般的なGoの並行処理モデルの理解に役立つ)
- Goのガベージコレクションについて (GoのGCの進化と仕組みに関する公式ブログ記事)
- Goのメモリモデル (Goにおけるメモリの振る舞いに関する公式ドキュメント)