[インデックス 15736] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) における「use after free」エラーを修正するものです。特に、新しいオブジェクトコード処理におけるメモリ管理のバグに対処しています。
コミット
commit f7ad816c5c13d334f1c0d350041e95cb507e47bc
Author: Russ Cox <rsc@golang.org>
Date: Tue Mar 12 17:57:13 2013 -0400
cmd/ld: fix 'use after free' error in new obj code
Many thanks to Elias Naur for finding this with Valgrind on Linux.
Perhaps this is what is breaking the windows/amd64 builder.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7595044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f7ad816c5c13d334f1c0d350041e95cb507e47bc
元コミット内容
diff --git a/src/cmd/ld/lib.c b/src/cmd/ld/lib.c
index cbd947dc82..450a83716e 100644
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -528,7 +528,7 @@ ldhostobj(void (*ld)(Biobuf*, char*, int64, char*), Biobuf *f, char *pkg, int64
}\n \th = &hostobj[nhostobj++];\n \th->ld = ld;\n-\th->pkg = pkg;\n+\th->pkg = estrdup(pkg);\n \th->pn = estrdup(pn);\n \th->file = estrdup(file);\n \th->off = Boffset(f);\n```
## 変更の背景
このコミットは、Go言語のリンカ (`cmd/ld`) において発生していた「use after free」エラーを修正するために導入されました。この問題は、Elias Naur氏がLinux上でメモリデバッグツールであるValgrindを使用して発見しました。コミットメッセージには、このバグがWindows/amd64ビルダの不具合の原因である可能性も示唆されています。
「use after free」は、解放されたメモリ領域をプログラムが誤って参照しようとしたときに発生する深刻なメモリ破損バグです。このようなバグは、プログラムのクラッシュ、予期せぬ動作、さらにはセキュリティ上の脆弱性につながる可能性があります。Valgrindのようなツールは、このようなメモリ関連のバグを検出するのに非常に有効です。
## 前提知識の解説
* **Use After Free (UAF)**:
メモリが解放された後も、そのメモリ領域へのポインタが有効なまま残り、プログラムがそのポインタを使って解放済みのメモリにアクセスしようとすると発生する脆弱性です。解放されたメモリは、オペレーティングシステムによって別の目的で再割り当てされる可能性があるため、UAFはデータの破損や、悪意のあるコード実行につながる可能性があります。
* **Valgrind**:
Valgrindは、主にLinux上で動作するプログラミングデバッグおよびプロファイリングツールのスイートです。特に、メモリ管理エラー(メモリリーク、無効な読み書き、use-after-freeなど)の検出に優れています。プログラムをValgrindの下で実行すると、Valgrindはプログラムの実行を監視し、メモリ関連の問題を報告します。
* **Go言語のリンカ (`cmd/ld`)**:
Go言語のコンパイルプロセスにおいて、リンカ (`ld`) は、コンパイラによって生成されたオブジェクトファイル(`.o`ファイル)と、必要なライブラリを結合して、実行可能なバイナリを生成する役割を担います。`cmd/ld`はGoのツールチェインの一部であり、Goプログラムのビルドにおいて不可欠なコンポーネントです。
* **`estrdup` 関数**:
C言語の標準ライブラリには `strdup` という関数がありますが、Goのツールチェイン内で使われる `estrdup` は、エラーハンドリングを強化した文字列複製関数であることが多いです。通常、`strdup` は引数として与えられた文字列をヒープに複製し、その新しい文字列へのポインタを返します。メモリ割り当てに失敗した場合は `NULL` を返します。`estrdup` は、メモリ割り当てに失敗した場合にプログラムを終了させるなど、より堅牢なエラー処理を行うカスタム実装である可能性が高いです。これにより、メモリ割り当ての失敗が未処理のまま進むことを防ぎます。
## 技術的詳細
このコミットの核心は、`src/cmd/ld/lib.c` ファイル内の `ldhostobj` 関数におけるメモリ管理の修正です。
`ldhostobj` 関数は、ホストオブジェクト(Goのビルドシステムが利用する外部のC/C++オブジェクトファイルなど)を処理する際に呼び出されます。この関数内で、`hostobj` という配列に新しいエントリが追加され、そのエントリのメンバー (`h->pkg`, `h->pn`, `h->file`) に文字列が割り当てられます。
元のコードでは、`h->pkg = pkg;` という行がありました。ここで `pkg` は `ldhostobj` 関数に引数として渡される `char*` 型のポインタです。この `pkg` ポインタが指すメモリ領域は、`ldhostobj` の呼び出し元によって管理されている可能性があります。もし `pkg` が指すメモリが `ldhostobj` の実行中に、あるいは `ldhostobj` が返った後に解放されてしまうと、`h->pkg` は解放済みのメモリを指す「ダングリングポインタ」となってしまいます。その後に `h->pkg` を参照しようとすると、「use after free」エラーが発生します。
修正では、`h->pkg = pkg;` を `h->pkg = estrdup(pkg);` に変更しています。`estrdup(pkg)` は、`pkg` が指す文字列の内容を新しいメモリ領域に複製し、その新しいメモリ領域へのポインタを返します。これにより、`h->pkg` は `ldhostobj` の呼び出し元が管理するメモリではなく、`estrdup` によって新しく割り当てられた独立したメモリ領域を指すようになります。この新しいメモリ領域は、`hostobj` エントリのライフサイクルに合わせて管理されるため、元の `pkg` が解放されても `h->pkg` がダングリングポインタになることはありません。
この変更により、`ldhostobj` 関数が処理するパッケージ名 (`pkg`) が、`hostobj` 構造体内で安全に保持されるようになり、メモリの解放タイミングに関する競合状態が解消され、「use after free」エラーが防止されます。
## コアとなるコードの変更箇所
```diff
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -528,7 +528,7 @@ ldhostobj(void (*ld)(Biobuf*, char*, int64, char*), Biobuf *f, char *pkg, int64
}\n \th = &hostobj[nhostobj++];\n \th->ld = ld;\n-\th->pkg = pkg;\n+\th->pkg = estrdup(pkg);\n \th->pn = estrdup(pn);\n \th->file = estrdup(file);\n \th->off = Boffset(f);\
コアとなるコードの解説
変更された行は src/cmd/ld/lib.c
の529行目です。
-
変更前:
h->pkg = pkg;
この行では、pkg
ポインタが指すアドレスをh->pkg
に直接代入していました。これは、h->pkg
がpkg
と同じメモリ領域を共有することを意味します。もしpkg
が指すメモリが、h->pkg
がまだ使用されている間に解放されてしまうと、h->pkg
は無効なメモリを指すことになり、「use after free」エラーが発生します。 -
変更後:
h->pkg = estrdup(pkg);
この行では、estrdup(pkg)
関数を呼び出しています。estrdup
はpkg
が指す文字列の内容をヒープ上の新しいメモリ領域にコピーし、その新しいメモリ領域へのポインタを返します。このポインタがh->pkg
に代入されます。これにより、h->pkg
はpkg
とは独立したメモリ領域を指すようになります。pkg
が指す元のメモリが解放されても、h->pkg
が指すコピーされた文字列は影響を受けないため、安全にアクセスし続けることができます。
この修正は、文字列の所有権とライフサイクルを明確にすることで、メモリ管理の堅牢性を高めています。h->pkg
が指す文字列は、hostobj
エントリが有効である限り、そのメモリが保証されることになります。
関連リンク
- Go CL 7595044: https://golang.org/cl/7595044
参考にした情報源リンク
- Valgrind 公式サイト: https://valgrind.org/
- Use-after-free (CWE-416): https://cwe.mitre.org/data/definitions/416.html
strdup
man page (一般的なC言語の関数): https://man7.org/linux/man-pages/man3/strdup.3.html- Go言語のリンカに関する一般的な情報 (Goのドキュメントやソースコード): https://go.dev/doc/ (Goの公式ドキュメント)
- Goのソースコード (
src/cmd/ld
ディレクトリ): https://github.com/golang/go/tree/master/src/cmd/ld