[インデックス 18370] ファイルの概要
このコミットは、Goランタイムにおけるmake(chan)
関数に存在していたバッファオーバーフローの脆弱性を修正するものです。特に32ビットシステムにおいて、細工されたmake(chan)
の引数によって、チャネルのバッファが予期せぬメモリ領域にアクセスできるようになる問題に対処しています。これにより、システム全体のメモリへの不正なアクセスが可能になる潜在的なセキュリティリスクが排除されました。
コミット
runtime: fix buffer overflow in make(chan)
On 32-bits one can arrange make(chan) params so that
the chan buffer gives you access to whole memory.
LGTM=r
R=golang-codereviews, r
CC=bradfitz, golang-codereviews, iant, khr
https://golang.org/cl/50250045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d409e44cfb7b2a323658f4b6fd6d5bb3a9104889
元コミット内容
runtime: fix buffer overflow in make(chan)
On 32-bits one can arrange make(chan) params so that
the chan buffer gives you access to whole memory.
変更の背景
Go言語のmake(chan)
関数は、チャネルを作成するために使用されます。チャネルは、Goルーチン間で値を送受信するためのパイプのようなものです。make(chan Type, capacity)
のように、要素の型とオプションでバッファ容量を指定して作成できます。
このコミットが行われた背景には、32ビットシステムにおけるメモリ管理の特性と、make(chan)
が内部でメモリを割り当てる際の計算ミスがありました。具体的には、32ビット環境ではアドレス空間が4GBに制限されており、ポインタやサイズを表す整数型が32ビット幅になります。この制限されたアドレス空間において、チャネルのバッファサイズを計算する際にオーバーフローが発生する可能性がありました。
攻撃者がmake(chan)
のcapacity
引数を巧妙に操作することで、チャネルのバッファが本来割り当てられるべきサイズよりもはるかに小さいサイズで割り当てられ、その結果、チャネルのバッファがヒープ上の他の重要なデータ構造や、さらにはシステム全体のメモリ領域にまでアクセスできてしまう「バッファオーバーフロー」の状態を引き起こすことができました。これは、任意のメモリ読み書きを可能にする深刻な脆弱性につながる可能性がありました。
この脆弱性は、特に32ビットシステムで顕著であり、MaxMem
(システムが利用可能な最大メモリ量)とチャネル構造体自体のサイズを考慮せずにバッファ容量のチェックを行っていたことが原因でした。
前提知識の解説
- Goチャネル (
chan
): Goルーチン間で安全にデータをやり取りするための同期プリミティブです。make(chan Type, capacity)
で作成され、capacity
はチャネルが保持できる要素の数を指定します。capacity
が0の場合は非バッファチャネル、正の値の場合はバッファチャネルとなります。 - バッファオーバーフロー: プログラムが、割り当てられたバッファの境界を超えてデータを書き込もうとしたときに発生する脆弱性です。これにより、隣接するメモリ領域が上書きされ、プログラムのクラッシュ、予期せぬ動作、または悪意のあるコードの実行につながる可能性があります。
- 32ビットシステムと64ビットシステム:
- 32ビットシステム: メモリアドレスを32ビットの数値で表現します。これにより、最大で2^32バイト(4GB)のメモリ空間しか直接アドレス指定できません。
- 64ビットシステム: メモリアドレスを64ビットの数値で表現します。これにより、はるかに広大なメモリ空間(2^64バイト)をアドレス指定できます。
MaxMem
: Goランタイムが利用できる最大メモリ量を示す内部定数または変数です。これはシステムアーキテクチャやOSによって異なる場合があります。sizeof(*c)
: C言語の構文で、GoランタイムのCコード内でチャネル構造体自体のサイズをバイト単位で取得するために使用されます。チャネルのバッファとは別に、チャネルのメタデータや内部状態を保持するためのメモリ領域です。elem->size
: チャネルが保持する要素の型(Type
)のサイズをバイト単位で示します。例えば、make(chan int)
の場合、elem->size
はint
のサイズ(通常4バイトまたは8バイト)になります。hint
:make(chan)
に渡されるcapacity
引数に対応する内部変数です。これは、チャネルバッファの要素数を表します。
技術的詳細
このコミットの核心は、src/pkg/runtime/chan.c
ファイル内のruntime·makechan_c
関数におけるバッファ容量のチェックロジックの修正です。
元のコードでは、チャネルのバッファ容量hint
がMaxMem / elem->size
を超えていないかをチェックしていました。これは、elem->size
が0でない場合に、hint * elem->size
がMaxMem
を超えないようにするための基本的なチェックです。しかし、この計算には重要な考慮事項が欠けていました。それは、チャネルのバッファだけでなく、チャネル構造体自体(sizeof(*c)
)もメモリを消費するという点です。
32ビットシステムでは、MaxMem
の値が4GBに近くなることがあり、hint * elem->size
の計算がMaxMem
の範囲内に収まっていても、チャネル構造体自体のサイズを考慮すると、合計のメモリ要求量がMaxMem
を超えてしまう可能性がありました。この場合、メモリ割り当てが失敗するか、あるいは不正なメモリ領域が返されることで、バッファオーバーフローが発生する余地が生まれていました。
修正後のコードでは、この問題を解決するために、MaxMem
からチャネル構造体自体のサイズsizeof(*c)
を差し引いた値でhint
のチェックを行うように変更されました。
具体的には、以下の行が変更されました。
変更前:
if(hint < 0 || (intgo)hint != hint || (elem->size > 0 && hint > MaxMem / elem->size))
変更後:
if(hint < 0 || (intgo)hint != hint || (elem->size > 0 && hint > (MaxMem - sizeof(*c)) / elem->size))
この変更により、チャネルのバッファに割り当てられるメモリとチャネル構造体自体のメモリの合計が、システムが利用可能な最大メモリ量MaxMem
を超えないように、より厳密なチェックが行われるようになりました。MaxMem - sizeof(*c)
とすることで、チャネルバッファに利用できる実質的な最大メモリ量を正確に計算し、その範囲内でhint
が適切であるかを検証しています。これにより、32ビットシステムにおける整数オーバーフローや、それに起因するバッファオーバーフローの脆弱性が効果的に防止されます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/chan.c
+++ b/src/pkg/runtime/chan.c
@@ -104,7 +104,7 @@ runtime·makechan_c(ChanType *t, int64 hint)
if((sizeof(*c)%MAXALIGN) != 0 || elem->align > MAXALIGN)
runtime·throw("makechan: bad alignment");
- if(hint < 0 || (intgo)hint != hint || (elem->size > 0 && hint > MaxMem / elem->size))
+ if(hint < 0 || (intgo)hint != hint || (elem->size > 0 && hint > (MaxMem - sizeof(*c)) / elem->size))
runtime·panicstring("makechan: size out of range");
// allocate memory in one call
コアとなるコードの解説
変更された行は、runtime·makechan_c
関数内で、hint
(チャネルのバッファ容量)が有効な範囲内にあるかをチェックする条件式です。
hint < 0
:hint
が負の値でないことを確認します。負の容量は無効です。(intgo)hint != hint
:hint
がintgo
型(Goのポインタサイズに合わせた整数型)にキャストしても値が変わらないことを確認します。これは、hint
がintgo
の範囲に収まっているか、つまりオーバーフローしていないかをチェックします。(elem->size > 0 && hint > (MaxMem - sizeof(*c)) / elem->size)
: この部分が修正の核心です。elem->size > 0
: 要素サイズが0より大きい場合(非ゼロサイズの要素を持つチャネルの場合)にのみ、このチェックを行います。要素サイズが0のチャネル(例:chan struct{}
)はバッファにメモリを必要としないためです。(MaxMem - sizeof(*c))
: ここが重要な変更点です。システムが利用可能な最大メモリ量MaxMem
から、チャネル構造体自体が消費するメモリ量sizeof(*c)
を差し引いています。これにより、チャネルのバッファに実際に割り当て可能な残りのメモリ量を計算しています。/ elem->size
: 残りのメモリ量を要素サイズで割ることで、そのメモリ量で最大いくつの要素をバッファに格納できるかを計算します。hint > ...
: 最終的に、指定されたhint
が、計算された最大要素数を超えていないかをチェックします。もし超えている場合、それはメモリ割り当ての範囲外であり、makechan: size out of range
というパニックを引き起こします。
この修正により、チャネルのバッファとチャネル構造体自体の両方が、利用可能なメモリ空間内に適切に収まることが保証され、特に32ビットシステムでのバッファオーバーフローの可能性が排除されました。
関連リンク
- Go CL 50250045: https://golang.org/cl/50250045
参考にした情報源リンク
- コミットメッセージと差分 (
d409e44cfb7b2a323658f4b6fd6d5bb3a9104889
) - Go言語のチャネルに関する公式ドキュメントやチュートリアル(一般的な知識として)
- バッファオーバーフローに関する一般的なセキュリティ情報(一般的な知識として)
- 32ビット/64ビットアーキテクチャのメモリモデルに関する一般的な情報(一般的な知識として)