[インデックス 11891] ファイルの概要
このコミットは、Go言語のコンパイラである5g
におけるメモリ破損バグを修正するものです。具体的には、レジスタ割り当てに関連する配列のサイズが不適切であったために発生していた、範囲外書き込みによるメモリ破損を解消しています。
コミット
commit 1c987a321f5013517a0c1987826f9595dde7cb72
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 14 01:13:14 2012 -0500
5g: fix memory corruption
R=ken2
CC=golang-dev
https://golang.org/cl/5666043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1c987a321f5013517a0c1987826f9595dde7cb72
元コミット内容
5g: fix memory corruption
R=ken2
CC=golang-dev
https://golang.org/cl/5666043
変更の背景
このコミットは、Go言語のコンパイラである5g
(ARMアーキテクチャ向けのGoコンパイラ)に存在していた深刻なメモリ破損バグを修正するために行われました。このバグは、コンパイラのレジスタ割り当て処理において、内部で使用される配列のサイズが不適切であったことに起因します。具体的には、レジスタの最大数を示す定数REGALLOC_RMAX
や浮動小数点レジスタの最大数を示すREGALLOC_FMAX
が配列のインデックス範囲を決定する際に誤って使用され、結果として配列の境界を越えた書き込み(out-of-bounds write)が発生していました。このようなメモリ破損は、コンパイラのクラッシュ、誤ったコード生成、または予測不能な動作を引き起こす可能性があり、Goプログラムの安定性と正確性に直接影響を与えるため、早急な修正が必要でした。
前提知識の解説
- Goコンパイラ
5g
: Go言語の初期のコンパイラ群は、ターゲットアーキテクチャごとに名前が付けられていました。5g
はARMアーキテクチャ(ARMv5/v6/v7)向けのGoコンパイラを指します。g
はGoコンパイラを意味し、5
はARMv5アーキテクチャを指す慣例的な数字です。 - レジスタ割り当て (Register Allocation): コンパイラの重要な最適化フェーズの一つで、プログラムの変数や中間結果をCPUの高速なレジスタに割り当てるプロセスです。レジスタはメモリよりもアクセスが非常に速いため、適切にレジスタを割り当てることでプログラムの実行速度を大幅に向上させることができます。レジスタ割り当て器は、どの変数をどのレジスタに、いつ割り当てるかを決定します。
REGALLOC_RMAX
とREGALLOC_FMAX
: これらはGoコンパイラの内部で使用される定数で、それぞれ汎用レジスタ(Integer Registers)と浮動小数点レジスタ(Floating-Point Registers)の最大数を定義しています。コンパイラはこれらの定数に基づいて、レジスタの状態を管理するための内部配列(例:regpc
やreg
)を宣言します。uintptr
: Go言語におけるuintptr
型は、ポインタを保持するのに十分な大きさの符号なし整数型です。主に低レベルのプログラミングやシステムコール、C言語との相互運用などで、ポインタを整数として扱う必要がある場合に使用されます。nelem(array)
: これはGo言語の標準ライブラリ関数ではなく、C言語の慣用句やコンパイラ内部で定義されるマクロに似たものです。配列の要素数を計算するために使用されます。通常はsizeof(array) / sizeof(array[0])
のように実装されます。このコミットの文脈では、reg
配列の要素数を取得するために使われています。fatal(...)
: コンパイラ内部で使用されるエラー報告関数です。致命的なエラーが発生した場合に呼び出され、通常はエラーメッセージを出力してプログラムを終了させます。これは、コンパイラのバグや予期せぬ状態を検出するために使用されます。- メモリ破損 (Memory Corruption): プログラムが意図しないメモリ領域に書き込みを行うことで、データが破壊されたり、プログラムの実行フローが乗っ取られたりする現象です。配列の範囲外アクセス(out-of-bounds access)は、メモリ破損の一般的な原因の一つです。
技術的詳細
このメモリ破損バグは、src/cmd/5g/gsubr.c
ファイル内のレジスタ割り当て関連のコードに存在していました。
-
regpc
配列のサイズ定義の誤り:- 変更前:
uintptr regpc[REGALLOC_RMAX+1];
- 変更後:
uintptr regpc[REGALLOC_FMAX+1];
regpc
配列は、レジスタのプログラムカウンタ(PC)値を記録するために使用されます。この配列のサイズが、汎用レジスタの最大数REGALLOC_RMAX
に基づいて定義されていました。しかし、実際には浮動小数点レジスタも含む可能性があり、REGALLOC_FMAX
の方がより広い範囲をカバーしていました。REGALLOC_RMAX
がREGALLOC_FMAX
よりも小さい場合、浮動小数点レジスタに関連する操作が行われた際にregpc
配列へのアクセスが範囲外となり、メモリ破損を引き起こす可能性がありました。修正では、より大きな範囲をカバーするREGALLOC_FMAX
を使用することで、この問題を解決しています。
- 変更前:
-
regfree
関数における範囲チェックの不備:regfree
関数は、割り当てられたレジスタを解放する際に呼び出されます。この関数内で、解放しようとしているレジスタのインデックスi
が有効な範囲内にあるかどうかのチェックが行われていました。- 変更前:
if(i < 0 || i >= sizeof(reg))
- 変更後:
if(i < 0 || i >= nelem(reg) || i >= nelem(regpc))
- 元のチェックでは、
sizeof(reg)
を使用していましたが、これはreg
配列のバイトサイズを返すため、配列の要素数とは異なります。例えば、reg
がint
型の配列であれば、sizeof(reg)
は要素数 * sizeof(int)
となります。したがって、このチェックは配列の実際の要素数に対する適切な境界チェックになっていませんでした。 - 修正では、
nelem(reg)
(reg
配列の要素数)とnelem(regpc)
(regpc
配列の要素数)の両方に対してインデックスi
が範囲内にあるかをチェックするように変更されました。これにより、レジスタインデックスがreg
配列またはregpc
配列のいずれかの境界を越えてアクセスされることを防ぎ、メモリ破損を未然に防ぐことができます。
これらの変更により、レジスタ割り当て器が使用する内部配列のサイズが適切に設定され、レジスタの解放時にインデックスの範囲チェックが強化されたことで、5g
コンパイラにおけるメモリ破損バグが修正されました。
コアとなるコードの変更箇所
diff --git a/src/cmd/5g/gsubr.c b/src/cmd/5g/gsubr.c
index 94caeb0918..c938f8b00b 100644
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -346,7 +346,7 @@ anyregalloc(void)\n return 0;\n }\n \n-uintptr regpc[REGALLOC_RMAX+1];
+uintptr regpc[REGALLOC_FMAX+1];
\n /*
* allocate register of type t, leave in n.
*
@@ -451,7 +451,7 @@ regfree(Node *n)\n if(n->op != OREGISTER && n->op != OINDREG)\n fatal("regfree: not a register");\n i = n->val.u.reg;\n-\tif(i < 0 || i >= sizeof(reg))\n+\tif(i < 0 || i >= nelem(reg) || i >= nelem(regpc))\n fatal("regfree: reg out of range");\n if(reg[i] <= 0)\n fatal("regfree: reg not allocated");
コアとなるコードの解説
このコミットにおけるコアとなるコードの変更は、src/cmd/5g/gsubr.c
ファイル内の2箇所です。
-
regpc
配列の宣言の変更:-uintptr regpc[REGALLOC_RMAX+1]; +uintptr regpc[REGALLOC_FMAX+1];
この変更は、
regpc
というuintptr
型の配列のサイズ定義を修正しています。以前はREGALLOC_RMAX+1
(汎用レジスタの最大数に基づく)でサイズが決定されていましたが、これをREGALLOC_FMAX+1
(浮動小数点レジスタの最大数に基づく)に変更しました。これにより、regpc
配列が、汎用レジスタと浮動小数点レジスタの両方を含む可能性のある、より広い範囲のレジスタインデックスに対応できるようになり、浮動小数点レジスタ関連の操作による範囲外アクセスを防ぎます。 -
regfree
関数内のレジスタインデックスチェックの変更:- if(i < 0 || i >= sizeof(reg)) + if(i < 0 || i >= nelem(reg) || i >= nelem(regpc))
regfree
関数は、レジスタを解放する際に呼び出されます。この行は、解放しようとしているレジスタのインデックスi
が有効な範囲内にあるかをチェックしています。- 変更前は、
sizeof(reg)
を使用していました。sizeof(reg)
はreg
配列全体のバイトサイズを返すため、配列の要素数とは異なります。例えば、reg
がint
型の配列であれば、sizeof(reg)
は要素数 * sizeof(int)
となります。このため、インデックスi
が配列の要素数を超えていても、sizeof(reg)
の範囲内であればエラーが検出されず、範囲外アクセスが発生する可能性がありました。 - 変更後は、
nelem(reg)
とnelem(regpc)
を使用しています。nelem
は配列の要素数を返すマクロ(または同様の機能)であり、これによりreg
配列とregpc
配列の両方に対して、インデックスi
が実際の要素数の範囲内にあるかを正確にチェックできるようになりました。i < 0
は負のインデックスを防ぎ、i >= nelem(reg)
はreg
配列の境界を越えるアクセスを防ぎ、i >= nelem(regpc)
はregpc
配列の境界を越えるアクセスを防ぎます。この厳密なチェックにより、レジスタインデックスの不正な使用によるメモリ破損が防止されます。
- 変更前は、
これらの変更は、コンパイラのレジスタ割り当てロジックの堅牢性を高め、メモリ破損という深刻なバグを修正するために不可欠でした。
関連リンク
- Go Code Review: https://golang.org/cl/5666043
参考にした情報源リンク
- Web search results for "golang 5g compiler REGALLOC_RMAX REGALLOC_FMAX memory corruption CL 5666043"
- CL 5666043, titled "cmd/compile: fix REGALLOC_RMAX/FMAX memory corruption," addresses a memory corruption bug within the Go 5g compiler. The issue stemmed from the register allocator's use of
REGALLOC_RMAX
andREGALLOC_FMAX
as array sizes. These values were sometimes insufficient, leading to out-of-bounds writes and subsequent memory corruption. The fix involved increasing the size of the affectedregalloc
arrays toRMAX+1
andFMAX+1
to prevent these out-of-bounds writes, thereby resolving the memory corruption. This corruption could have resulted in incorrect code generation or compiler crashes.
- CL 5666043, titled "cmd/compile: fix REGALLOC_RMAX/FMAX memory corruption," addresses a memory corruption bug within the Go 5g compiler. The issue stemmed from the register allocator's use of
- Go言語のコンパイラに関する一般的な情報
- C言語における
sizeof
と配列の要素数に関する情報 - レジスタ割り当てに関する一般的なコンパイラ理論