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

[インデックス 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_RMAXREGALLOC_FMAX: これらはGoコンパイラの内部で使用される定数で、それぞれ汎用レジスタ(Integer Registers)と浮動小数点レジスタ(Floating-Point Registers)の最大数を定義しています。コンパイラはこれらの定数に基づいて、レジスタの状態を管理するための内部配列(例: regpcreg)を宣言します。
  • 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ファイル内のレジスタ割り当て関連のコードに存在していました。

  1. regpc配列のサイズ定義の誤り:

    • 変更前: uintptr regpc[REGALLOC_RMAX+1];
    • 変更後: uintptr regpc[REGALLOC_FMAX+1];
    • regpc配列は、レジスタのプログラムカウンタ(PC)値を記録するために使用されます。この配列のサイズが、汎用レジスタの最大数REGALLOC_RMAXに基づいて定義されていました。しかし、実際には浮動小数点レジスタも含む可能性があり、REGALLOC_FMAXの方がより広い範囲をカバーしていました。REGALLOC_RMAXREGALLOC_FMAXよりも小さい場合、浮動小数点レジスタに関連する操作が行われた際にregpc配列へのアクセスが範囲外となり、メモリ破損を引き起こす可能性がありました。修正では、より大きな範囲をカバーするREGALLOC_FMAXを使用することで、この問題を解決しています。
  2. regfree関数における範囲チェックの不備:

    • regfree関数は、割り当てられたレジスタを解放する際に呼び出されます。この関数内で、解放しようとしているレジスタのインデックスiが有効な範囲内にあるかどうかのチェックが行われていました。
    • 変更前: if(i < 0 || i >= sizeof(reg))
    • 変更後: if(i < 0 || i >= nelem(reg) || i >= nelem(regpc))
    • 元のチェックでは、sizeof(reg)を使用していましたが、これはreg配列のバイトサイズを返すため、配列の要素数とは異なります。例えば、regint型の配列であれば、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箇所です。

  1. regpc配列の宣言の変更:

    -uintptr regpc[REGALLOC_RMAX+1];
    +uintptr regpc[REGALLOC_FMAX+1];
    

    この変更は、regpcというuintptr型の配列のサイズ定義を修正しています。以前はREGALLOC_RMAX+1(汎用レジスタの最大数に基づく)でサイズが決定されていましたが、これをREGALLOC_FMAX+1(浮動小数点レジスタの最大数に基づく)に変更しました。これにより、regpc配列が、汎用レジスタと浮動小数点レジスタの両方を含む可能性のある、より広い範囲のレジスタインデックスに対応できるようになり、浮動小数点レジスタ関連の操作による範囲外アクセスを防ぎます。

  2. regfree関数内のレジスタインデックスチェックの変更:

    -	if(i < 0 || i >= sizeof(reg))
    +	if(i < 0 || i >= nelem(reg) || i >= nelem(regpc))
    

    regfree関数は、レジスタを解放する際に呼び出されます。この行は、解放しようとしているレジスタのインデックスiが有効な範囲内にあるかをチェックしています。

    • 変更前は、sizeof(reg)を使用していました。sizeof(reg)reg配列全体のバイトサイズを返すため、配列の要素数とは異なります。例えば、regint型の配列であれば、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配列の境界を越えるアクセスを防ぎます。この厳密なチェックにより、レジスタインデックスの不正な使用によるメモリ破損が防止されます。

これらの変更は、コンパイラのレジスタ割り当てロジックの堅牢性を高め、メモリ破損という深刻なバグを修正するために不可欠でした。

関連リンク

参考にした情報源リンク

  • 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 and REGALLOC_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 affected regalloc arrays to RMAX+1 and FMAX+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.
  • Go言語のコンパイラに関する一般的な情報
  • C言語におけるsizeofと配列の要素数に関する情報
  • レジスタ割り当てに関する一般的なコンパイラ理論