[インデックス 15924] ファイルの概要
このコミットは、Go言語のコンパイラ (cmd/cc
) とリンカ (cmd/ld
) におけるシンボルルックアップ処理のバグ修正に関するものです。具体的には、シンボル名の比較において memcmp
関数が誤った長さで呼び出されていたために発生する可能性があった文字列のオーバーフロー(境界外読み取り)を修正し、より安全な strcmp
関数に置き換えることで問題を解決しています。
コミット
commit 77e7e4c329f44353d6d11eb0adee2d83437ce5ea
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Mon Mar 25 08:20:22 2013 +0100
cmd/cc, cmd/ld: do not overflow strings in symbol lookup.
R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/7876044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/77e7e4c329f44353d6d11eb0adee2d83437ce5ea
元コミット内容
このコミットの元の内容は、Go言語のコンパイラ (src/cmd/cc/lexbody
) とリンカ (src/cmd/ld/lib.c
) のシンボルルックアップ関数において、文字列比較に memcmp
を使用していた箇所を strcmp
に変更するというものです。
具体的には、以下の変更が行われています。
src/cmd/cc/lexbody
のlookup
関数内で、memcmp(s->name, symb, l) == 0
をstrcmp(s->name, symb) == 0
に変更。src/cmd/ld/lib.c
の_lookup
関数内で、l
変数の宣言とl = (p - symb) + 1;
の計算を削除し、memcmp(s->name, symb, l) == 0
をstrcmp(s->name, symb) == 0
に変更。
変更の背景
この変更の背景には、Go言語のコンパイラとリンカがシンボルテーブル内でシンボル名を検索する際に使用していた文字列比較ロジックに潜在的なバグが存在したことが挙げられます。
従来のコードでは、memcmp
関数を使用してシンボル名を比較していました。memcmp
は指定されたバイト数だけメモリ領域を比較する関数であり、比較するバイト数 (l
) が正確に計算されていない場合、または比較対象の文字列がその長さよりも短い場合に、メモリの境界を越えて読み取ってしまう(オーバーフロー)可能性がありました。これは、セキュリティ上の脆弱性やプログラムのクラッシュにつながる可能性があります。
特に、src/cmd/ld/lib.c
の _lookup
関数では、l = (p - symb) + 1;
という計算で比較長を決定していましたが、この計算が常に正しい文字列長を保証するものではなかった可能性があります。シンボル名がヌル終端されていることを前提とするならば、strcmp
を使用する方がより安全で意図に沿った比較方法となります。
このコミットは、このような潜在的な問題を解消し、シンボルルックアップの堅牢性を向上させることを目的としています。
前提知識の解説
Go言語のツールチェイン
Go言語は、コンパイラ、リンカ、アセンブラなどのツールチェインを自身で実装しています。
cmd/cc
: Go言語のコンパイラの一部であり、C言語のコードをコンパイルする際に使用されることがあります。Goの初期のツールチェインはC言語で書かれており、その名残が見られます。cmd/ld
: Go言語のリンカです。コンパイルされたオブジェクトファイルを結合し、実行可能なバイナリを生成する役割を担います。シンボル解決(どの関数や変数がどこにあるかを特定するプロセス)はリンカの主要な機能の一つです。
シンボルルックアップ
シンボルルックアップとは、プログラム内で使用される関数名、変数名などの「シンボル」が、メモリ上のどこに配置されているかを特定するプロセスです。コンパイラやリンカは、このシンボルルックアップを通じて、コード内の参照を実際のメモリアドレスに解決します。シンボルテーブルと呼ばれるデータ構造にシンボル名とそのアドレスが格納されており、ルックアップ時にはこのテーブルを検索します。
C言語の文字列比較関数
-
memcmp(const void *s1, const void *s2, size_t n)
:s1
とs2
が指すメモリ領域の最初のn
バイトを比較します。- ヌル終端文字 (
\0
) を文字列の終端とはみなしません。指定されたn
バイトを厳密に比較します。 - バイナリデータの比較に適しています。
n
の値が不正確だと、バッファオーバーフロー(境界外読み取り)を引き起こす可能性があります。
-
strcmp(const char *s1, const char *s2)
:s1
とs2
が指すヌル終端文字列を比較します。- ヌル終端文字 (
\0
) を文字列の終端とみなし、その文字までを比較します。 - 文字列の比較に適しています。
- 比較する文字列がヌル終端されていることを前提とします。
オーバーフロー(境界外読み取り)
プログラミングにおける「オーバーフロー」は、通常、数値がデータ型の最大値を超えてしまうことを指しますが、この文脈では「バッファオーバーフロー」の一種である「境界外読み取り (Out-of-bounds read)」を指します。これは、プログラムが割り当てられたメモリ領域の境界を越えてデータを読み取ろうとするときに発生します。
memcmp
の場合、比較するバイト数 l
が実際の文字列の長さよりも大きいと、文字列の終端を超えてメモリを読み取ってしまう可能性があります。これにより、予期せぬデータが比較に含まれたり、不正なメモリアクセスが発生してプログラムがクラッシュしたり、セキュリティ上の脆弱性につながったりすることがあります。
技術的詳細
このコミットの技術的な核心は、シンボルルックアップにおける文字列比較の安全性の向上です。
src/cmd/cc/lexbody
の lookup
関数と src/cmd/ld/lib.c
の _lookup
関数は、どちらもハッシュテーブルを使用してシンボルを検索するロジックを持っています。ハッシュ衝突が発生した場合、リンクリストを辿って目的のシンボルを見つける必要があります。この際、リスト内の各シンボルの名前 (s->name
) と検索対象のシンボル名 (symb
) を比較して一致するかどうかを確認します。
変更前は、この比較に memcmp(s->name, symb, l)
が使用されていました。ここで l
は比較するバイト数を指定します。
src/cmd/cc/lexbody
のlookup
関数では、l
の値は明示的に計算されていませんが、おそらくsymb
の長さとして使用されていたと考えられます。src/cmd/ld/lib.c
の_lookup
関数では、l = (p - symb) + 1;
という計算でl
が決定されていました。p
はsymb
の終端(ヌル終端文字の次)を指すポインタであり、symb
からp
までの距離に1を加えることで、symb
の長さ(ヌル終端文字を含む)を計算しようとしています。
しかし、この l
の計算が常に正確である保証はありませんでした。特に、symb
がヌル終端されていない場合や、p
の計算が何らかの理由で誤っていた場合、l
が実際の文字列長を超えてしまう可能性があります。その結果、memcmp
は s->name
や symb
のバッファ境界を越えてメモリを読み取り、比較を行ってしまうことになります。これが「overflow strings in symbol lookup」という問題の本質です。
この問題を解決するために、開発者は memcmp
を strcmp
に置き換えました。strcmp
はヌル終端文字列を比較するように設計されているため、比較する文字列が適切にヌル終端されていれば、比較長を明示的に指定する必要がなく、境界外読み取りのリスクがなくなります。この変更は、s->name
と symb
が常にヌル終端文字列であることを前提としています。これはC言語の標準的な文字列の扱いに合致しており、より安全で堅牢な比較方法と言えます。
コアとなるコードの変更箇所
src/cmd/cc/lexbody
--- a/src/cmd/cc/lexbody
+++ b/src/cmd/cc/lexbody
@@ -263,7 +263,7 @@ lookup(void)
for(s = hash[h]; s != S; s = s->link) {
if(s->name[0] != c)
continue;
- if(memcmp(s->name, symb, l) == 0)
+ if(strcmp(s->name, symb) == 0)
return s;
}
s = alloc(sizeof(*s));
src/cmd/ld/lib.c
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -846,17 +846,16 @@ _lookup(char *symb, int v, int creat)\n \tSym *s;\n \tchar *p;\n \tint32 h;\n-\tint l, c;\n+\tint c;\n \n \th = v;\n \tfor(p=symb; c = *p; p++)\n \t\th = h+h+h + c;\n-\tl = (p - symb) + 1;\n \t// not if(h < 0) h = ~h, because gcc 4.3 -O2 miscompiles it.\n \th &= 0xffffff;\n \th %= NHASH;\n \tfor(s = hash[h]; s != S; s = s->hash)\n-\t\tif(memcmp(s->name, symb, l) == 0)\n+\t\tif(strcmp(s->name, symb) == 0)\n \t\t\treturn s;\n \tif(!creat)\n \t\treturn nil;\
コアとなるコードの解説
src/cmd/cc/lexbody
の lookup
関数
この関数は、コンパイラの字句解析器の一部で、シンボルテーブルからシンボルを検索する役割を担っています。
for(s = hash[h]; s != S; s = s->link)
: ハッシュ値h
に対応するハッシュテーブルのエントリから、リンクリストを辿ってシンボルを検索します。if(s->name[0] != c) continue;
: 最初の文字が一致しない場合は、すぐに次のシンボルへスキップします。これは、比較の最適化です。if(memcmp(s->name, symb, l) == 0)
(変更前):s->name
とsymb
をl
バイトだけ比較していました。l
の値がどこから来るのかは、このスニペットだけでは不明ですが、おそらくsymb
の長さに関連する値が渡されていたと考えられます。if(strcmp(s->name, symb) == 0)
(変更後):memcmp
をstrcmp
に置き換えることで、s->name
とsymb
がヌル終端文字列として比較されるようになりました。これにより、比較長を明示的に指定する必要がなくなり、l
の誤った値による境界外読み取りのリスクが排除されました。
src/cmd/ld/lib.c
の _lookup
関数
この関数は、リンカのライブラリの一部で、シンボルテーブルからシンボルを検索する役割を担っています。
int l, c;
(変更前): 比較長を格納するl
変数と、文字を格納するc
変数が宣言されていました。int c;
(変更後):l
変数が不要になったため、宣言から削除されました。for(p=symb; c = *p; p++) h = h+h+h + c;
: シンボル名symb
を走査しながらハッシュ値を計算しています。p
はsymb
のポインタです。l = (p - symb) + 1;
(変更前):symb
の開始ポインタからp
の現在位置までの距離(文字列長)に1を加えることで、ヌル終端文字を含む文字列の長さを計算しようとしていました。この計算が、前述の通り、潜在的な問題の原因となっていました。if(memcmp(s->name, symb, l) == 0)
(変更前):s->name
とsymb
を計算されたl
バイトだけ比較していました。if(strcmp(s->name, symb) == 0)
(変更後):memcmp
をstrcmp
に置き換えることで、s->name
とsymb
がヌル終端文字列として比較されるようになりました。これにより、l
の計算と使用が不要になり、コードが簡潔かつ安全になりました。
この変更は、Go言語のツールチェインがC言語で書かれていた時代のコードベースにおける、C言語の文字列操作のベストプラクティスへの移行を示しています。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Gerrit Change 7876044: https://golang.org/cl/7876044 (Goプロジェクトのコードレビューシステム)
参考にした情報源リンク
- C言語
memcmp
関数: https://www.cplusplus.com/reference/cstring/memcmp/ - C言語
strcmp
関数: https://www.cplusplus.com/reference/cstring/strcmp/ - バッファオーバーフロー(境界外読み取り)に関する一般的な情報: https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC
- Go言語のツールチェインに関する情報 (Goの公式ドキュメントやブログ記事など)