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

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

このコミットは、Go言語のリンカ(liblink)におけるsymgrow関数の潜在的なバグ修正に関するものです。具体的には、symgrow関数に渡されるサイズ引数がint32型からvlong型に変更され、vlongで渡された値がint32の範囲を超える場合に暗黙的な切り捨てが発生するのを防ぎ、代わりにシステムエラーを発生させるように改善されています。これにより、リンカがシンボルのサイズを誤って解釈し、予期せぬ動作やクラッシュを引き起こす可能性が排除されます。

コミット

  • コミットハッシュ: 6111dc4e71ffd843ac8029df4fd67d32689f8e36
  • 作者: Ian Lance Taylor iant@golang.org
  • コミット日時: 2014年1月21日 火曜日 06:12:54 -0800

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

https://github.com/golang/go/commit/6111dc4e71ffd843ac8029df4fd67d32689f8e36

元コミット内容

liblink: check for symgrow size too large

Many calls to symgrow pass a vlong value.  Change the function
to not implicitly truncate, and to instead give an error if
the value is too large.

R=golang-codereviews, gobot, rsc
CC=golang-codereviews
https://golang.org/cl/54010043

変更の背景

symgrow関数は、Goリンカの内部でシンボル(LSym)のメモリ領域を拡張するために使用されます。この関数は元々、サイズ引数としてint32型を受け取っていました。しかし、コミットメッセージによると、symgrowを呼び出す多くの箇所で、より大きな値を保持できるvlong型の値が渡されていました。

C言語のような型システムでは、より大きな型(例: vlong、通常は64ビット整数)の値を、より小さな型(例: int32、通常は32ビット整数)の変数に代入しようとすると、値が小さすぎる場合に上位ビットが暗黙的に切り捨てられる(truncate)可能性があります。この暗黙的な切り捨ては、特にリンカのような低レベルのシステムプログラミングにおいて、非常に危険なバグの温床となります。

もしsymgrowに渡されるvlong値がint32の最大値を超えていた場合、symgrow関数内でその値がint32に切り捨てられ、シンボルのサイズが意図したものよりもはるかに小さく解釈されてしまう可能性がありました。これにより、メモリの破損、バッファオーバーフロー、またはリンカのクラッシュといった深刻な問題が発生する恐れがありました。

このコミットは、このような潜在的なデータ損失とそれに伴うバグを防ぐために、symgrow関数の引数型を変更し、明示的なサイズチェックを追加することで、堅牢性を向上させることを目的としています。

前提知識の解説

  • Goリンカ (liblink): Go言語のコンパイラツールチェーンの一部であり、コンパイルされたオブジェクトファイルを結合して実行可能ファイルを生成する役割を担います。liblinkは、Goプログラムの実行に必要なすべてのコードとデータを適切に配置し、シンボル解決を行う低レベルのコンポーネントです。
  • LSym (Linker Symbol): リンカが扱うシンボルを表す構造体です。関数、グローバル変数、文字列リテラルなど、プログラム内の名前付きエンティティはすべてLSymとして表現され、リンカがこれらのメモリ配置や参照を管理します。LSymには、そのシンボルが占めるメモリサイズなどの情報が含まれます。
  • symgrow関数: LSym構造体に関連付けられたメモリ領域(通常はシンボルのデータやコードを格納するバッファ)を、指定されたサイズまで拡張するために使用されるリンカ内部の関数です。シンボルに新しいデータが追加される際などに呼び出されます。
  • vlong: Goのリンカコードベース(C言語で書かれている部分)で使われるカスタム型で、通常は64ビット符号付き整数(long longint64_tに相当)を表します。これは、大きなメモリサイズやオフセットを扱うために使用されます。
  • int32: 32ビット符号付き整数型です。最大値は約20億(2^31 - 1)です。
  • 暗黙的な型変換と切り捨て (Implicit Type Conversion and Truncation): C言語では、異なる型の間で値を代入する際に、コンパイラが自動的に型変換を行うことがあります。この際、変換先の型が変換元の型よりも表現できる値の範囲が狭い場合、値が切り捨てられる(上位ビットが失われる)可能性があります。例えば、vlong(64ビット)の値をint32(32ビット)変数に代入し、元のvlong値がint32の最大値を超えていた場合、値が正しく保持されず、予期せぬ小さな値になってしまいます。
  • sysfatal関数: Goのランタイムやツールチェーンのコードベースで使われる関数で、回復不可能な致命的なエラーが発生した場合にプログラムを終了させるために使用されます。通常、エラーメッセージを出力して終了します。

技術的詳細

このコミットの技術的な核心は、symgrow関数のシグネチャ変更と、それに伴うサイズチェックの追加です。

  1. include/link.h の変更:

    • symgrow関数のプロトタイプが変更されました。
    • 変更前: void symgrow(Link *ctxt, LSym *s, int32 siz);
    • 変更後: void symgrow(Link *ctxt, LSym *s, vlong siz);
    • これにより、symgrow関数がサイズ引数をint32ではなくvlongとして直接受け取れるようになり、呼び出し元から渡されるvlong値が暗黙的に切り捨てられることを防ぎます。
  2. src/liblink/data.c の変更:

    • symgrow関数の実装が変更されました。
    • 変更前はint32 sizとして引数を受け取っていましたが、変更後はvlong lsizとして引数を受け取ります。
    • 関数内部で、このvlong lsizint32 sizにキャストしています (siz = (int32)lsiz;)。
    • 重要な変更点として、このキャストの直後に以下のチェックが追加されました。
      if((vlong)siz != lsiz)
          sysfatal("symgrow size %lld too long", lsiz);
      
      このチェックは、vlong型のlsizint32型のsizにキャストした後、そのint32型のsizを再度vlong型にキャストし、元のlsizと比較しています。もしlsizint32の範囲を超えていた場合、int32へのキャスト時に値が切り捨てられるため、 (vlong)sizは元のlsizとは異なる値になります。この不一致を検出した場合、sysfatalを呼び出して致命的なエラーとしてプログラムを終了させます。エラーメッセージには、切り捨てられようとした元のvlong値が表示されます。
    • このチェックにより、symgrowが処理できるサイズの上限(int32の最大値)を超える値が渡された場合に、リンカが不正な状態で動作を続けるのではなく、早期にエラーを検出して停止するようになります。

この変更は、Goリンカの堅牢性を高め、潜在的なメモリ関連のバグを防ぐ上で非常に重要です。

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

include/link.h:

-void	symgrow(Link *ctxt, LSym *s, int32 siz);
+void	symgrow(Link *ctxt, LSym *s, vlong siz);

src/liblink/data.c:

 void
-symgrow(Link *ctxt, LSym *s, int32 siz)
+symgrow(Link *ctxt, LSym *s, vlong lsiz)
 {
+\tint32 siz;
+\n \tUSED(ctxt);
+\n+\tsiz = (int32)lsiz;
+\tif((vlong)siz != lsiz)
+\t\tsysfatal("symgrow size %lld too long", lsiz);
+\n \tif(s->np >= siz)
 \t\treturn;

コアとなるコードの解説

  1. include/link.h の変更:

    • symgrow関数の第3引数の型がint32からvlongに変更されました。これは、この関数がより大きなサイズ値を直接受け取れるようにするための変更です。これにより、呼び出し元がvlongでサイズを渡しても、関数シグネチャの段階で暗黙的な切り捨てが発生するのを防ぎます。
  2. src/liblink/data.c の変更:

    • 引数名の変更: 関数定義の引数名がsizからlsiz(おそらく "long size" の略)に変更され、vlong型であることを明確にしています。
    • ローカル変数sizの導入: 関数内部で、int32型のローカル変数sizが宣言されます。これは、既存のsymgrow関数のロジックがint32型のサイズを前提としているため、そのロジックを最小限の変更で維持するためです。
    • 明示的なキャストとチェック:
      siz = (int32)lsiz;
      if((vlong)siz != lsiz)
          sysfatal("symgrow size %lld too long", lsiz);
      
      この部分がこのコミットの最も重要な変更点です。
      • まず、vlong型のlsizint32型のsizに明示的にキャストします。
      • 次に、if((vlong)siz != lsiz)という条件で、キャストによって値が失われたかどうかをチェックします。もしlsizint32の範囲内に収まっていれば、int32にキャストしても値は変わらず、再度vlongにキャストしても元のlsizと同じ値になります。しかし、lsizint32の範囲を超えていた場合、int32へのキャストで値が切り捨てられ、その結果 (vlong)sizは元のlsizとは異なる値になります。
      • 値が異なる場合、つまり切り捨てが発生した場合は、sysfatalを呼び出してプログラムを終了させます。これにより、リンカが不正なサイズで処理を続行するのを防ぎ、問題を早期に発見できます。エラーメッセージには、切り捨てられようとした元の大きなサイズ値が表示され、デバッグに役立ちます。

この変更により、symgrow関数は、int32の範囲を超えるサイズが渡された場合に、静かに誤った動作をするのではなく、明確なエラーを報告するようになり、リンカの信頼性が向上しました。

関連リンク

参考にした情報源リンク