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

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

このコミットは、Go言語のリンカ(cmd/ld)における「use-after-free」のバグを修正するものです。このバグは、特に重複シンボルエラーメッセージの生成時に顕在化し、メモリの不正利用を引き起こす可能性がありました。修正は、シンボル名が参照される際にその文字列を安全に複製することで、解放済みメモリへのアクセスを防ぐことを目的としています。

コミット

commit 8bbb6d3ed04ed0d79438045bb8d56b8c03a42944
Author: Russ Cox <rsc@golang.org>
Date:   Thu Mar 14 14:35:47 2013 -0400

    cmd/ld: another use-after-free
    
    This only shows up in the duplicate symbol error message.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/7486053

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

https://github.com/golang/go/commit/8bbb6d3ed04ed0d79438045bb8d56b8c03a42944

元コミット内容

cmd/ld: another use-after-free

この問題は、重複シンボルエラーメッセージにのみ現れます。

変更の背景

このコミットは、Go言語のリンカ(cmd/ld)に存在する「use-after-free」(UAF)脆弱性、またはバグを修正するために行われました。UAFは、プログラムが一度解放されたメモリ領域にアクセスしようとしたときに発生するメモリ安全性の問題です。この種のバグは、クラッシュ、予期せぬ動作、さらには悪用された場合にはセキュリティ脆弱性につながる可能性があります。

コミットメッセージによると、この特定のUAFは「重複シンボルエラーメッセージ」の表示時にのみ発生していました。リンカは、複数のオブジェクトファイルが同じ名前のシンボルを定義している場合に、重複シンボルエラーを報告します。このエラーメッセージを構築する際に、リンカが使用するシンボル名(pn変数で表される可能性が高い)が、メッセージの生成が完了する前に解放されてしまう状況があったと考えられます。その結果、リンカが解放済みメモリにアクセスしようとして、未定義の動作を引き起こしていました。

この修正は、リンカの堅牢性を高め、特にエラー処理パスにおけるメモリ安全性を確保するために不可欠でした。

前提知識の解説

Use-after-free (UAF)

Use-after-free(UAF)は、プログラミングにおけるメモリ安全性の脆弱性の一種です。これは、プログラムがメモリを解放(freedeleteなどの操作で)した後も、その解放されたメモリ領域へのポインタを保持し続け、その後、そのポインタを介して解放済みメモリにアクセスしようとしたときに発生します。

UAFが発生する典型的なシナリオは以下の通りです。

  1. メモリ領域Aが割り当てられ、ポインタPがそれを指す。
  2. メモリ領域Aが解放される。この時点で、ポインタPは「ダングリングポインタ」(解放済みメモリを指すポインタ)となる。
  3. 解放されたメモリ領域Aが、別の目的で再割り当てされる(別のデータが書き込まれる)。
  4. プログラムがポインタPを介して、以前解放されたメモリ領域Aにアクセスしようとする。しかし、そこにはもはや期待するデータはなく、別のデータが存在するか、あるいはそのメモリ領域がOSによって完全に回収されている場合、不正なメモリアクセスとなり、クラッシュやセキュリティ脆弱性(例: 任意のコード実行)につながる可能性があります。

Go Linker (cmd/ld)

Go言語のコンパイルプロセスにおいて、リンカ(cmd/ld)は非常に重要な役割を担っています。Goのビルドシステムは、ソースコードをコンパイルしてオブジェクトファイル(.oファイル)を生成し、その後、リンカがこれらのオブジェクトファイルを結合して実行可能バイナリを作成します。

リンカの主な機能は以下の通りです。

  • シンボル解決: 各オブジェクトファイルは、自身が定義するシンボル(関数、変数など)と、他のオブジェクトファイルが定義するシンボル(外部参照)を持っています。リンカは、これらの外部参照を、対応する定義に解決します。
  • アドレス割り当て: 実行可能ファイル内の各セクション(コード、データなど)とシンボルに、最終的なメモリ上のアドレスを割り当てます。
  • 再配置: シンボルが最終的なアドレスに配置された後、コード内の参照(例: 関数呼び出しのアドレス)を更新します。
  • 重複シンボル検出: 複数のオブジェクトファイルが同じ名前のグローバルシンボルを定義している場合、リンカは通常、エラーを報告します。これは、どの定義を使用すべきかリンカが判断できないためです。

シンボルテーブルと重複シンボル

リンカは、プログラム内のすべてのシンボル(関数名、変数名など)とそのアドレス情報を管理するために「シンボルテーブル」を使用します。各オブジェクトファイルには、そのファイル内で定義されているシンボルと、そのファイルが参照する外部シンボルがリストされたローカルシンボルテーブルが含まれています。リンカはこれらのローカルテーブルを結合し、最終的な実行可能ファイルのグローバルシンボルテーブルを構築します。

「重複シンボルエラー」は、リンカが複数のオブジェクトファイルで同じ名前のグローバルシンボル定義を見つけた場合に発生します。これは通常、プログラミングエラーを示しており、リンカはどの定義を使用すべきか決定できないため、ビルドプロセスを停止します。

estrdup

estrdupは、C言語の標準ライブラリ関数strdupに似た関数で、文字列を複製するために使用されます。strdupは、与えられた文字列のコピーをヒープメモリに割り当て、そのコピーへのポインタを返します。元の文字列が解放されても、複製された文字列は独立して存在し続けるため、UAFのような問題を防ぐのに役立ちます。

GoのツールチェインのCコードベースでは、estrdupのような関数がメモリ管理を安全に行うために使用されます。eプレフィックスは、エラーが発生した場合にプログラムを終了する(exit)ことを示す慣例である可能性があります。

技術的詳細

このUAFバグは、リンカが重複シンボルエラーメッセージを生成する際に、シンボル名を表す文字列pnのライフサイクル管理に問題があったことに起因します。

リンカの処理フローでは、オブジェクトファイルからシンボル情報を読み込む際に、シンボル名(pn)が一時的なバッファや、リンカの内部構造体の一部として参照されることがあります。重複シンボルが検出された場合、リンカはそのシンボル名を含むエラーメッセージを構築し、出力します。

問題のシナリオは以下のようであったと推測されます。

  1. リンカがオブジェクトファイルからシンボル名pnを読み込む。このpnは、一時的なメモリ領域を指すポインタである可能性がある。
  2. リンカがシンボルテーブルにpnへの参照を格納する。
  3. 重複シンボルが検出され、エラーメッセージの生成がトリガーされる。
  4. エラーメッセージの生成中に、元のpnが指していた一時的なメモリ領域が解放されるか、別のデータで上書きされる。
  5. リンカがエラーメッセージの構築を完了するために、シンボルテーブルに格納されたpnへの参照を介して、解放済みメモリにアクセスしようとする。

このアクセスがUAFを引き起こし、リンカのクラッシュや不正なエラーメッセージの表示につながっていました。コミットメッセージが「This only shows up in the duplicate symbol error message.」と述べていることから、この問題はエラーパスでのみ顕在化し、通常のリンキングプロセスでは発生しなかったことが示唆されます。

修正は、pn = estrdup(pn);という行を追加することで、この問題を解決しています。これにより、pnが指す文字列の独立したコピーが作成され、そのコピーへのポインタがpnに再割り当てされます。結果として、Sym*(シンボル構造体へのポインタ)が参照する文字列は、元のメモリ領域のライフサイクルに依存しなくなり、エラーメッセージの生成中に解放されることがなくなります。

この修正は、src/cmd/5l/obj.c (ARMアーキテクチャ用リンカ), src/cmd/6l/obj.c (x86-64アーキテクチャ用リンカ), src/cmd/8l/obj.c (x86アーキテクチャ用リンカ) の3つのファイルに適用されています。これは、これらのリンカが共通のコードパスで同様のシンボル処理ロジックを使用しているためです。

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

diff --git a/src/cmd/5l/obj.c b/src/cmd/5l/obj.c
index d25fefebba..f70cb6c336 100644
--- a/src/cmd/5l/obj.c
+++ b/src/cmd/5l/obj.c
@@ -428,6 +428,7 @@ ldobj1(Biobuf *f, char *pkg, int64 len, char *pn)
 	ntext = 0;
 	eof = Boffset(f) + len;
 	src[0] = 0;
+	pn = estrdup(pn); // we keep it in Sym* references
 
 newloop:
 	memset(h, 0, sizeof(h));
diff --git a/src/cmd/6l/obj.c b/src/cmd/6l/obj.c
index 6ea88de273..91569794bf 100644
--- a/src/cmd/6l/obj.c
+++ b/src/cmd/6l/obj.c
@@ -423,6 +423,7 @@ ldobj1(Biobuf *f, char *pkg, int64 len, char *pn)
 	ntext = 0;
 	eof = Boffset(f) + len;
 	src[0] = 0;
+	pn = estrdup(pn); // we keep it in Sym* references
 
 newloop:
 	memset(h, 0, sizeof(h));
diff --git a/src/cmd/8l/obj.c b/src/cmd/8l/obj.c
index ad453064cc..14c6b6aa97 100644
--- a/src/cmd/8l/obj.c
+++ b/src/cmd/8l/obj.c
@@ -444,7 +444,7 @@ ldobj1(Biobuf *f, char *pkg, int64 len, char *pn)
 	ntext = 0;
 	eof = Boffset(f) + len;
 	src[0] = 0;
-
+	pn = estrdup(pn); // we keep it in Sym* references
 
 newloop:
 	memset(h, 0, sizeof(h));

コアとなるコードの解説

変更は、ldobj1関数内のpn = estrdup(pn);という一行の追加です。この変更は、src/cmd/5l/obj.csrc/cmd/6l/obj.csrc/cmd/8l/obj.cの3つのファイルに共通して適用されています。

  • ldobj1関数: この関数は、リンカがオブジェクトファイル(.oファイル)を読み込み、その内容を処理する主要な関数の一つです。pn引数は、おそらく現在処理中のパッケージ名またはシンボル名を表しています。
  • pn = estrdup(pn);: この行が追加されたことで、pnが指す文字列のコピーがヒープメモリに作成され、その新しいコピーへのポインタがpn変数に再割り当てされます。
  • // we keep it in Sym* references: このコメントは、なぜこの複製が必要なのかを明確に説明しています。リンカは、読み込んだシンボル名(pn)をSym*型の参照(シンボル構造体へのポインタ)として内部的に保持します。もしpnが指すメモリが一時的なものであり、後で解放される可能性がある場合、Sym*参照はダングリングポインタとなり、その後のアクセスでUAFを引き起こします。estrdupを使用することで、Sym*参照が指す文字列が独立したメモリ領域に存在することが保証され、元のメモリの解放に影響されなくなります。

この修正により、リンカがシンボル名を安全に管理できるようになり、特に重複シンボルエラーメッセージの生成時におけるUAFバグが解消されました。

関連リンク

  • Go Issue Tracker (CL 7486053): https://golang.org/cl/7486053 (これはコミットメッセージに記載されているGoのコードレビューシステムへのリンクです。現在はGitHubに統合されていますが、当時のレビュー情報が残っている可能性があります。)

参考にした情報源リンク

  • Use-after-free - Wikipedia: https://en.wikipedia.org/wiki/Use-after-free
  • Go言語のリンカに関する一般的な情報 (Goのドキュメントやブログ記事など)
  • C言語のstrdup関数に関する情報 (例: man strdup)