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

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

このコミットは、Goランタイムのチャネル実装における潜在的なダングリングポインタの問題を解消するためのものです。具体的には、チャネルのキューから要素が取り除かれる際に、q->lastポインタが不正なメモリを指し続ける可能性を防ぐための変更が加えられています。

コミット

commit 8422d1ea65e4722098ec940e202a0aa33efa3309
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Jul 15 10:27:36 2014 +0400

    runtime: zero dangling pointer
    I don't see how it can lead to bad things today.
    But it's better to kill it before it does.

    LGTM=khr
    R=golang-codereviews, khr
    CC=golang-codereviews, rsc
    https://golang.org/cl/111130045

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

https://github.com/golang/go/commit/8422d1ea65e4722098ec940e202a0aa33efa3309

元コミット内容

runtime: zero dangling pointer
I don't see how it can lead to bad things today.
But it's better to kill it before it does.

LGTM=khr
R=golang-codereviews, khr
CC=golang-codereviews, rsc
https://golang.org/cl/111130045

変更の背景

このコミットは、Goランタイムのチャネル処理において、将来的に問題を引き起こす可能性のある「ダングリングポインタ」の発生を防ぐために導入されました。ダングリングポインタとは、解放されたメモリ領域を指し続けるポインタのことです。Goのガベージコレクタはこのようなポインタを適切に処理しますが、特定の状況下でポインタが古い、無効なメモリを指し続けることで、予期せぬ動作やメモリリーク、あるいはクラッシュに繋がる可能性があります。

コミットメッセージにある「I don't see how it can lead to bad things today. But it's better to kill it before it does.」という記述は、この問題が現在のGoランタイムの動作に直接的な悪影響を与えているわけではないものの、将来的な変更や特定のシナリオにおいて潜在的な脆弱性となることを懸念し、未然に防ぐための予防的な修正であることを示唆しています。Goランタイムは非常に低レベルなメモリ管理を行うため、このような細かなポインタの健全性を保つことは、システムの安定性と信頼性を確保する上で極めて重要です。

前提知識の解説

1. Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ(GC)、スケジューラ(ゴルーチンの管理)、メモリ割り当て、チャネルの実装などが含まれます。Goプログラムは、OS上で直接実行されるのではなく、このランタイム上で動作します。ランタイムはGo言語の並行性モデル(ゴルーチンとチャネル)を支える基盤であり、その効率性と安全性がGoプログラム全体のパフォーマンスと信頼性に直結します。

2. チャネル (Channels)

チャネルは、Goにおけるゴルーチン間の通信と同期のための主要なプリミティブです。チャネルを通じて値を送受信することで、ゴルーチンは安全にデータを共有し、処理の順序を調整できます。チャネルは内部的にキュー(待ち行列)構造を持っており、送信されたデータや、データを送受信しようとしているゴルーチン(sudog構造体で表現されることが多い)を一時的に保持します。

3. ダングリングポインタ (Dangling Pointer)

ダングリングポインタとは、既に解放されたメモリ領域を指しているポインタのことです。このメモリ領域は、オペレーティングシステムやメモリマネージャによって別の用途に再割り当てされる可能性があります。ダングリングポインタを逆参照(ポインタが指すアドレスの値を読み書き)すると、未定義の動作を引き起こし、プログラムのクラッシュ、データの破損、セキュリティ脆弱性につながる可能性があります。Goにはガベージコレクタがあるため、C/C++のような手動メモリ管理言語に比べてダングリングポインタの問題は発生しにくいですが、ランタイムの低レベルなコードでは、このような問題に注意を払う必要があります。

4. chan.goc

src/pkg/runtime/chan.gocは、Goランタイムにおけるチャネルのコア実装が含まれるファイルです。Goのランタイムコードの多くはC言語とGo言語のハイブリッドで書かれており、.goc拡張子はC言語で書かれたGoランタイムのソースファイルを示します。このファイルには、チャネルの作成、送受信、クローズ、select文の処理など、チャネル操作の低レベルなロジックが記述されています。

5. sudog構造体

Goランタイム内部では、チャネル操作でブロックされたゴルーチンはsudogという構造体で表現され、チャネルの内部キューに格納されます。sudogは、ゴルーチンが待機しているチャネル、送受信するデータ、次のsudogへのリンクなど、ゴルーチンがチャネル操作を完了するために必要な情報を含んでいます。

技術的詳細

このコミットの技術的詳細は、チャネルの内部キュー管理におけるポインタの健全性に関わります。Goのチャネルは、送受信操作でブロックされたゴルーチン(sudog構造体)をキューで管理します。このキューは通常、first(先頭)とlast(末尾)のポインタによって管理されるリンクリストとして実装されています。

チャネルから要素(sudog)を取り出す際、通常はキューの先頭(q->first)から取り出されます。このとき、q->firstは次の要素を指すように更新されます。しかし、もしキューに要素が一つしかなく、その要素がq->firstq->lastの両方が指している唯一の要素であった場合、その要素がキューから取り除かれた後、q->lastは解放されたメモリを指し続ける「ダングリングポインタ」となる可能性があります。

このコミットは、この特定のケースを捕捉し、q->lastがダングリングポインタになることを防ぐためのものです。具体的には、q->firstが更新された後、もし取り除かれたsgpsudogポインタ)が以前のq->lastと同じであった場合、つまりキューから最後の要素が取り除かれた場合、q->lastを明示的にnil(ヌルポインタ)に設定します。これにより、q->lastが不正なメモリを指すことを防ぎ、メモリの健全性を保ちます。

この修正は、Goのガベージコレクタがダングリングポインタを最終的に回収するとしても、一時的にでも不正なポインタが存在する状態を避けることで、ランタイムの堅牢性を高めることを目的としています。特に、低レベルなランタイムコードでは、このような細かなポインタの管理が、将来的なバグや予期せぬ動作を防ぐ上で非常に重要になります。

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

変更はsrc/pkg/runtime/chan.gocファイル内のloop:ラベルの近く、チャネルからsudogを取り出すロジックにあります。

--- a/src/pkg/runtime/chan.goc
+++ b/src/pkg/runtime/chan.goc
@@ -1105,6 +1105,8 @@ loop:
 	if(sgp == nil)
 		return nil;
 	q->first = sgp->link;
+	if(q->last == sgp)
+		q->last = nil;
 
 	// if sgp participates in a select and is already signaled, ignore it
 	if(sgp->selectdone != nil) {

コアとなるコードの解説

変更が加えられたコードブロックは、チャネルのキューからsudogsgp)を取り出す部分です。

  1. q->first = sgp->link; この行は、キューの先頭ポインタq->firstを、現在取り出されようとしているsgpの次の要素(sgp->link)に更新しています。これにより、sgpはキューの先頭から論理的に削除されます。

  2. if(q->last == sgp) この新しい条件文は、現在キューから取り除かれようとしているsgpが、同時にキューの末尾(q->last)が指している要素でもあるかどうかをチェックしています。これは、キューにsgpという要素が一つしか存在しなかった場合に真となります。

  3. q->last = nil; 上記の条件が真であった場合(つまり、キューから最後の要素が取り除かれた場合)、q->lastポインタをnilに設定します。これにより、q->lastが以前にsgpが占めていたメモリ領域を指し続けることを防ぎます。sgpが指していたメモリは、この操作の後、ガベージコレクタによって解放されるか、別の用途に再利用される可能性があります。q->lastnilにすることで、ダングリングポインタの発生を未然に防ぎ、メモリの安全性を確保します。

この修正は、チャネルのキューが空になったときにq->lastが常にnilになることを保証し、ランタイムのメモリ管理における潜在的なエッジケースを解消します。

関連リンク

  • Go言語公式ウェブサイト: https://golang.org/
  • Goのチャネルに関するドキュメント: https://go.dev/tour/concurrency/2 (Go Tourのチャネルのセクション)
  • Goのガベージコレクションに関する情報: Goの公式ドキュメントやブログ記事に詳細があります。

参考にした情報源リンク