[インデックス 19350] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるヒープバッファオーバーフローのバグを修正するものです。具体的には、AddressSanitizer
によって報告された、src/cmd/gc/pgen.c
内のmemcmp
関数の誤用による境界外アクセスを修正しています。この修正により、コンパイラの安定性と安全性が向上します。
コミット
commit 8c2fefe89cbc9fa64e600c400d127905e3c375a5
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed May 14 19:24:00 2014 +0400
cmd/gc: fix out of bounds access
AddressSanitizer says:
AddressSanitizer: heap-buffer-overflow on address 0x60200001b6f3
READ of size 6 at 0x60200001b6f3 thread T0
#0 0x46741b in __interceptor_memcmp asan_interceptors.cc:337
#1 0x4b5794 in compile src/cmd/6g/../gc/pgen.c:177
#2 0x509b81 in funccompile src/cmd/gc/dcl.c:1457
#3 0x520fe2 in p9main src/cmd/gc/lex.c:489
#4 0x5e2e01 in main src/lib9/main.c:57
#5 0x7fab81f7976c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
#6 0x4b16dc in _start (pkg/tool/linux_amd64/6g+0x4b16dc)
0x60200001b6f3 is located 0 bytes to the right of 3-byte region [0x60200001b6f0,0x60200001b6f3)
allocated by thread T0 here:
#0 0x493ec8 in __interceptor_malloc asan_malloc_linux.cc:75
#1 0x54d64e in mal src/cmd/gc/subr.c:459
#2 0x5260d5 in yylex src/cmd/gc/lex.c:1605
#3 0x52078f in p9main src/cmd/gc/lex.c:402
#4 0x5e2e01 in main src/lib9/main.c:57
If the memory block happens to be at the end of hunk and page bounadry,
this out-of-bounds can lead to a crash.
LGTM=dave, iant
R=golang-codereviews, dave, iant
CC=golang-codereviews
https://golang.org/cl/93370043
---
src/cmd/gc/pgen.c | 2 +-\
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cmd/gc/pgen.c b/src/cmd/gc/pgen.c
index 2c986bb94c..40620c3dad 100644
--- a/src/cmd/gc/pgen.c
+++ b/src/cmd/gc/pgen.c
@@ -174,7 +174,7 @@ compile(Node *fn)
lno = setlineno(fn);
if(fn->nbody == nil) {
-\t\tif(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
+\t\tif(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
\t\t\tyyerror("missing function body", fn);\n \t\tgoto ret;\n \t}\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/8c2fefe89cbc9fa64e600c400d127905e3c375a5](https://github.com/golang/go/commit/8c2fefe89cbc9fa64e600c400d127905e3c375a5)
## 元コミット内容
Goコンパイラ(`cmd/gc`)における境界外アクセスを修正します。`AddressSanitizer`が報告したヒープバッファオーバーフローは、`memcmp`関数の使用が原因で発生していました。メモリブロックがチャンクやページ境界の終端にある場合、この境界外アクセスはクラッシュを引き起こす可能性がありました。
## 変更の背景
この変更は、`AddressSanitizer` (ASan) によって検出されたGoコンパイラ(`cmd/gc`)内の深刻なメモリ安全性の問題に対処するために行われました。ASanのレポートによると、`src/cmd/gc/pgen.c`ファイルの`compile`関数内で`memcmp`を使用している箇所でヒープバッファオーバーフローが発生していました。
具体的には、`0x60200001b6f3`というアドレスでサイズ6の読み取り操作が行われましたが、このアドレスは割り当てられた3バイトの領域`[0x60200001b6f0,0x60200001b6f3)`の直後に位置していました。これは、割り当てられたメモリ領域を超えてデータを読み取ろうとしたことを意味します。
このような境界外アクセスは、特にメモリブロックがヒープのチャンクやメモリページの境界に位置する場合に、コンパイラのクラッシュや予期せぬ動作、さらにはセキュリティ上の脆弱性につながる可能性があります。コンパイラはソフトウェア開発の基盤であるため、その安定性と信頼性は極めて重要です。このバグは、Goプログラムのコンパイルプロセス自体を不安定にする可能性があったため、早急な修正が必要とされました。
## 前提知識の解説
### Goコンパイラ (`cmd/gc`)
Go言語の公式コンパイラは、通常`gc`と呼ばれます。これはGoツールチェーンの一部であり、Goのソースコードを機械語に変換する主要な役割を担っています。`cmd/gc`は、Go言語のセマンティクスを理解し、型チェック、最適化、コード生成など、コンパイルプロセスの様々な段階を実行します。
`src/cmd/gc/pgen.c`は、Goコンパイラのバックエンドの一部であり、主にプラットフォーム固有のコード生成(`pgen`は"platform generation"の略である可能性が高い)や、関数やシンボルの処理に関連する部分を担当しています。
### AddressSanitizer (ASan)
`AddressSanitizer` (ASan) は、Googleによって開発された高速なメモリエラー検出ツールです。C/C++プログラムにおけるランタイムメモリエラー(例: ヒープバッファオーバーフロー、スタックバッファオーバーフロー、ユースアフターフリー、ダブルフリーなど)を検出するために設計されています。ASanは、コンパイル時にコードにインストルメンテーション(計測コードの挿入)を行い、実行時にメモリアクセスを監視することで、これらのエラーを特定します。エラーが検出されると、詳細なスタックトレースとメモリの状態に関する情報を含むレポートが出力され、デバッグを支援します。このコミットの背景にある問題も、ASanによって正確に特定されました。
### `memcmp` と `strncmp`
これらはC標準ライブラリの関数で、メモリ領域や文字列の比較に使用されます。
* **`memcmp(const void *s1, const void *s2, size_t n)`**:
この関数は、`s1`と`s2`が指すメモリ領域の最初の`n`バイトを比較します。比較はバイト単位で行われ、NULL終端文字は特別な意味を持ちません。`memcmp`は、バイナリデータや、NULL終端されていない可能性のある固定長のバイト配列を比較するのに適しています。しかし、文字列を比較する際に、比較対象の文字列が`n`バイトよりも短い場合、またはNULL終端されていない場合に、指定された`n`バイトを読み取ろうとすると、バッファオーバーフローを引き起こす可能性があります。
* **`strncmp(const char *s1, const char *s2, size_t n)`**:
この関数は、`s1`と`s2`が指す文字列の最初の`n`文字(バイト)を比較します。`memcmp`と異なり、`strncmp`はNULL終端文字を文字列の終端として認識します。つまり、`n`バイトに到達する前にNULL終端文字が見つかった場合、そこで比較を終了します。これにより、文字列の実際の長さが`n`よりも短い場合に、安全に比較を行うことができます。
今回のバグは、文字列(シンボル名)を比較する際に、NULL終端を考慮しない`memcmp`を固定長で使ったために発生しました。比較対象の文字列が指定された長さ(6バイト)よりも短い場合、`memcmp`は割り当てられたメモリ領域を超えて読み取ろうとし、境界外アクセスを引き起こしました。
### `init·` 関数
Go言語には、パッケージの初期化のために自動的に実行される`init`関数という特別な関数があります。各パッケージは複数の`init`関数を持つことができ、これらはパッケージ内の他のどの関数よりも前に、そしてインポートされたすべてのパッケージの`init`関数が実行された後に呼び出されます。コンパイラ内部では、これらの`init`関数は`init·`というプレフィックスを持つシンボル名として扱われることがあります。このコミットでは、まさにこの`init·`プレフィックスを持つ関数名のチェックに関連するバグが修正されました。
## 技術的詳細
`AddressSanitizer`のレポートは、`src/cmd/gc/pgen.c`の`compile`関数、具体的には177行目付近で`__interceptor_memcmp`が呼び出された際に、ヒープバッファオーバーフローが発生したことを明確に示しています。
スタックトレースは以下のようになっています。
* `#0 0x46741b in __interceptor_memcmp asan_interceptors.cc:337`: `memcmp`のインターセプトされたバージョンが呼び出されたことを示します。
* `#1 0x4b5794 in compile src/cmd/6g/../gc/pgen.c:177`: 問題の`memcmp`呼び出しが`src/cmd/gc/pgen.c`の`compile`関数、177行目で発生したことを示します。
* その後のスタックフレームは、`compile`関数が`funccompile`から、さらに`p9main`、`main`へと呼び出されたことを示しており、コンパイラの主要な実行パスの一部であることがわかります。
ASanのレポートはさらに、`0x60200001b6f3`というアドレスでのサイズ6の読み取りが、`0x60200001b6f0`から始まる3バイトの領域の直後(0バイト右)で発生したことを指摘しています。これは、割り当てられたメモリ領域が3バイトしかなかったにもかかわらず、`memcmp`が6バイトを読み取ろうとしたために、3バイトの境界外読み取りが発生したことを意味します。
この3バイトの領域は、`src/cmd/gc/lex.c`の`yylex`関数内で`mal`(おそらく`malloc`のラッパー)によって割り当てられたものです。これは、コンパイラが字句解析(lexing)の過程でシンボル名などの文字列を処理する際に、動的にメモリを割り当てていることを示唆しています。
問題のコードは以下の行でした。
```c
if(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
ここで、fn->nname->sym->name
は関数のシンボル名(文字列)を指します。このコードは、関数がpure_go
モードでコンパイルされているか、またはその名前が"init·"
で始まるかどうかをチェックしています。"init·"
は6文字(バイト)の文字列リテラルです。
memcmp
は、指定されたバイト数(この場合は6)を無条件に比較しようとします。もしfn->nname->sym->name
が指す文字列が6バイトよりも短い場合(例えば、"init"
のような4バイトの文字列や、さらに短い文字列)、memcmp
は割り当てられたメモリ領域を超えて読み取りを試み、これがヒープバッファオーバーフローを引き起こします。
このバグは、特にinit
関数がGoコンパイラによって内部的にどのように扱われるかという点に関連しています。Goのinit
関数は、コンパイラ内部ではinit·
という特殊なプレフィックスを持つシンボル名として表現されることがあります。このチェックは、init
関数が本体を持たない場合にエラーを報告するためのものでした。
修正は、memcmp
をstrncmp
に置き換えることで行われました。
if(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
strncmp
は、比較するバイト数n
(この場合は6)に加えて、比較対象の文字列がNULL終端されているかどうかを考慮します。これにより、fn->nname->sym->name
が指す文字列が6バイトよりも短い場合でも、NULL終端文字が見つかればそこで比較を停止するため、境界外アクセスを防ぐことができます。これにより、コンパイラの堅牢性が向上し、潜在的なクラッシュやセキュリティ上の問題が回避されます。
コアとなるコードの変更箇所
--- a/src/cmd/gc/pgen.c
+++ b/src/cmd/gc/pgen.c
@@ -174,7 +174,7 @@ compile(Node *fn)
lno = setlineno(fn);
if(fn->nbody == nil) {
-\t\tif(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
+\t\tif(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
\t\t\tyyerror("missing function body", fn);\n \t\tgoto ret;\n \t}\n```
## コアとなるコードの解説
変更は`src/cmd/gc/pgen.c`ファイルの`compile`関数内の一行です。
元のコード:
```c
if(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
この行は、Goコンパイラが関数をコンパイルする際に、その関数が本体(nbody
)を持たない場合にエラーを報告するための条件チェックの一部です。具体的には、pure_go
モードであるか、または関数のシンボル名(fn->nname->sym->name
)が文字列リテラル"init·"
と最初の6バイトが一致するかどうかを調べています。
問題は、memcmp
関数が使用されていた点にあります。memcmp
は、指定されたバイト数(この場合は6)を無条件に比較します。もしfn->nname->sym->name
が指す文字列が、実際に割り当てられているメモリ領域が6バイトよりも短い場合、memcmp
は割り当てられた領域を超えて読み取りを試み、これがヒープバッファオーバーフローを引き起こしました。例えば、シンボル名が"init"
(5バイト + NULL終端)のような場合、memcmp
は6バイトを読み取ろうとするため、境界外アクセスが発生します。
修正後のコード:
if(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
memcmp
がstrncmp
に置き換えられました。strncmp
は、memcmp
と同様に最初のn
バイトを比較しますが、文字列のNULL終端文字を考慮します。つまり、比較対象の文字列の実際の長さがn
バイトよりも短い場合でも、NULL終端文字が見つかればそこで比較を停止します。これにより、割り当てられたメモリ領域を超えて読み取ることを防ぎ、ヒープバッファオーバーフローを解消します。
この変更により、Goコンパイラは、init·
で始まる可能性のある関数名を安全にチェックできるようになり、コンパイラの安定性と信頼性が向上しました。
関連リンク
- Go issue tracker (CL 93370043): https://golang.org/cl/93370043
- AddressSanitizer GitHub: https://github.com/google/sanitizers/wiki/AddressSanitizer
参考にした情報源リンク
- AddressSanitizer Documentation: https://clang.llvm.org/docs/AddressSanitizer.html
memcmp
man page: https://man7.org/linux/man-pages/man3/memcmp.3.htmlstrncmp
man page: https://man7.org/linux/man-pages/man3/strncmp.3.html- Go Language Specification - Package initialization: https://go.dev/ref/spec#Package_initialization
- Go Compiler Internals (general reference, specific link not for this commit but for understanding context): https://go.dev/doc/articles/go_compiler_internals.html
# [インデックス 19350] ファイルの概要
このコミットは、Goコンパイラ(`cmd/gc`)におけるヒープバッファオーバーフローのバグを修正するものです。具体的には、`AddressSanitizer`によって報告された、`src/cmd/gc/pgen.c`内の`memcmp`関数の誤用による境界外アクセスを修正しています。この修正により、コンパイラの安定性と安全性が向上します。
## コミット
commit 8c2fefe89cbc9fa64e600c400d127905e3c375a5 Author: Dmitriy Vyukov dvyukov@google.com Date: Wed May 14 19:24:00 2014 +0400
cmd/gc: fix out of bounds access
AddressSanitizer says:
AddressSanitizer: heap-buffer-overflow on address 0x60200001b6f3
READ of size 6 at 0x60200001b6f3 thread T0
#0 0x46741b in __interceptor_memcmp asan_interceptors.cc:337
#1 0x4b5794 in compile src/cmd/6g/../gc/pgen.c:177
#2 0x509b81 in funccompile src/cmd/gc/dcl.c:1457
#3 0x520fe2 in p9main src/cmd/gc/lex.c:489
#4 0x5e2e01 in main src/lib9/main.c:57
#5 0x7fab81f7976c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
#6 0x4b16dc in _start (pkg/tool/linux_amd64/6g+0x4b16dc)
0x60200001b6f3 is located 0 bytes to the right of 3-byte region [0x60200001b6f0,0x60200001b6f3)
allocated by thread T0 here:
#0 0x493ec8 in __interceptor_malloc asan_malloc_linux.cc:75
#1 0x54d64e in mal src/cmd/gc/subr.c:459
#2 0x5260d5 in yylex src/cmd/gc/lex.c:1605
#3 0x52078f in p9main src/cmd/gc/lex.c:402
#4 0x5e2e01 in main src/lib9/main.c:57
If the memory block happens to be at the end of hunk and page bounadry,
this out-of-bounds can lead to a crash.
LGTM=dave, iant
R=golang-codereviews, dave, iant
CC=golang-codereviews
https://golang.org/cl/93370043
src/cmd/gc/pgen.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cmd/gc/pgen.c b/src/cmd/gc/pgen.c index 2c986bb94c..40620c3dad 100644 --- a/src/cmd/gc/pgen.c +++ b/src/cmd/gc/pgen.c @@ -174,7 +174,7 @@ compile(Node *fn) lno = setlineno(fn);
if(fn->nbody == nil) {
-\t\tif(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0) +\t\tif(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0) \t\t\tyyerror("missing function body", fn);\n \t\tgoto ret;\n \t}\n```
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8c2fefe89cbc9fa64e600c400d127905e3c375a5
元コミット内容
Goコンパイラ(cmd/gc
)における境界外アクセスを修正します。AddressSanitizer
が報告したヒープバッファオーバーフローは、memcmp
関数の使用が原因で発生していました。メモリブロックがチャンクやページ境界の終端にある場合、この境界外アクセスはクラッシュを引き起こす可能性がありました。
変更の背景
この変更は、AddressSanitizer
(ASan) によって検出されたGoコンパイラ(cmd/gc
)内の深刻なメモリ安全性の問題に対処するために行われました。ASanのレポートによると、src/cmd/gc/pgen.c
ファイルのcompile
関数内でmemcmp
を使用している箇所でヒープバッファオーバーフローが発生していました。
具体的には、0x60200001b6f3
というアドレスでサイズ6の読み取り操作が行われましたが、このアドレスは割り当てられた3バイトの領域[0x60200001b6f0,0x60200001b6f3)
の直後に位置していました。これは、割り当てられたメモリ領域を超えてデータを読み取ろうとしたことを意味します。
このような境界外アクセスは、特にメモリブロックがヒープのチャンクやメモリページの境界に位置する場合に、コンパイラのクラッシュや予期せぬ動作、さらにはセキュリティ上の脆弱性につながる可能性があります。コンパイラはソフトウェア開発の基盤であるため、その安定性と信頼性は極めて重要です。このバグは、Goプログラムのコンパイルプロセス自体を不安定にする可能性があったため、早急な修正が必要とされました。
前提知識の解説
Goコンパイラ (cmd/gc
)
Go言語の公式コンパイラは、通常gc
と呼ばれます。これはGoツールチェーンの一部であり、Goのソースコードを機械語に変換する主要な役割を担っています。cmd/gc
は、Go言語のセマンティクスを理解し、型チェック、最適化、コード生成など、コンパイルプロセスの様々な段階を実行します。
src/cmd/gc/pgen.c
は、Goコンパイラのバックエンドの一部であり、主にプラットフォーム固有のコード生成(pgen
は"platform generation"の略である可能性が高い)や、関数やシンボルの処理に関連する部分を担当しています。
AddressSanitizer (ASan)
AddressSanitizer
(ASan) は、Googleによって開発された高速なメモリエラー検出ツールです。C/C++プログラムにおけるランタイムメモリエラー(例: ヒープバッファオーバーフロー、スタックバッファオーバーフロー、ユースアフターフリー、ダブルフリーなど)を検出するために設計されています。ASanは、コンパイル時にコードにインストルメンテーション(計測コードの挿入)を行い、実行時にメモリアクセスを監視することで、これらのエラーを特定します。エラーが検出されると、詳細なスタックトレースとメモリの状態に関する情報を含むレポートが出力され、デバッグを支援します。このコミットの背景にある問題も、ASanによって正確に特定されました。
memcmp
と strncmp
これらはC標準ライブラリの関数で、メモリ領域や文字列の比較に使用されます。
-
memcmp(const void *s1, const void *s2, size_t n)
: この関数は、s1
とs2
が指すメモリ領域の最初のn
バイトを比較します。比較はバイト単位で行われ、NULL終端文字は特別な意味を持ちません。memcmp
は、バイナリデータや、NULL終端されていない可能性のある固定長のバイト配列を比較するのに適しています。しかし、文字列を比較する際に、比較対象の文字列がn
バイトよりも短い場合、またはNULL終端されていない場合に、指定されたn
バイトを読み取ろうとすると、バッファオーバーフローを引き起こす可能性があります。 -
strncmp(const char *s1, const char *s2, size_t n)
: この関数は、s1
とs2
が指す文字列の最初のn
文字(バイト)を比較します。memcmp
と異なり、strncmp
はNULL終端文字を文字列の終端として認識します。つまり、n
バイトに到達する前にNULL終端文字が見つかった場合、そこで比較を終了します。これにより、文字列の実際の長さがn
よりも短い場合に、安全に比較を行うことができます。
今回のバグは、文字列(シンボル名)を比較する際に、NULL終端を考慮しないmemcmp
を固定長で使ったために発生しました。比較対象の文字列が指定された長さ(6バイト)よりも短い場合、memcmp
は割り当てられたメモリ領域を超えて読み取ろうとし、境界外アクセスを引き起こしました。
init·
関数
Go言語には、パッケージの初期化のために自動的に実行されるinit
関数という特別な関数があります。各パッケージは複数のinit
関数を持つことができ、これらはパッケージ内の他のどの関数よりも前に、そしてインポートされたすべてのパッケージのinit
関数が実行された後に呼び出されます。コンパイラ内部では、これらのinit
関数はinit·
というプレフィックスを持つシンボル名として扱われることがあります。このコミットでは、まさにこのinit·
プレフィックスを持つ関数名のチェックに関連するバグが修正されました。
技術的詳細
AddressSanitizer
のレポートは、src/cmd/gc/pgen.c
のcompile
関数、具体的には177行目付近で__interceptor_memcmp
が呼び出された際に、ヒープバッファオーバーフローが発生したことを明確に示しています。
スタックトレースは以下のようになっています。
#0 0x46741b in __interceptor_memcmp asan_interceptors.cc:337
:memcmp
のインターセプトされたバージョンが呼び出されたことを示します。#1 0x4b5794 in compile src/cmd/6g/../gc/pgen.c:177
: 問題のmemcmp
呼び出しがsrc/cmd/gc/pgen.c
のcompile
関数、177行目で発生したことを示します。- その後のスタックフレームは、
compile
関数がfunccompile
から、さらにp9main
、main
へと呼び出されたことを示しており、コンパイラの主要な実行パスの一部であることがわかります。
ASanのレポートはさらに、0x60200001b6f3
というアドレスでのサイズ6の読み取りが、0x60200001b6f0
から始まる3バイトの領域の直後(0バイト右)で発生したことを指摘しています。これは、割り当てられたメモリ領域が3バイトしかなかったにもかかわらず、memcmp
が6バイトを読み取ろうとしたために、3バイトの境界外読み取りが発生したことを意味します。
この3バイトの領域は、src/cmd/gc/lex.c
のyylex
関数内でmal
(おそらくmalloc
のラッパー)によって割り当てられたものです。これは、コンパイラが字句解析(lexing)の過程でシンボル名などの文字列を処理する際に、動的にメモリを割り当てていることを示唆しています。
問題のコードは以下の行でした。
if(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
ここで、fn->nname->sym->name
は関数のシンボル名(文字列)を指します。このコードは、関数がpure_go
モードでコンパイルされているか、またはその名前が"init·"
で始まるかどうかをチェックしています。"init·"
は6文字(バイト)の文字列リテラルです。
memcmp
は、指定されたバイト数(この場合は6)を無条件に比較しようとします。もしfn->nname->sym->name
が指す文字列が6バイトよりも短い場合(例えば、"init"
のような4バイトの文字列や、さらに短い文字列)、memcmp
は割り当てられたメモリ領域を超えて読み取りを試み、これがヒープバッファオーバーフローを引き起こします。
このバグは、特にinit
関数がGoコンパイラによって内部的にどのように扱われるかという点に関連しています。Goのinit
関数は、コンパイラ内部ではinit·
という特殊なプレフィックスを持つシンボル名として表現されることがあります。このチェックは、init
関数が本体を持たない場合にエラーを報告するためのものでした。
修正は、memcmp
をstrncmp
に置き換えることで行われました。
if(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
strncmp
は、比較するバイト数n
(この場合は6)に加えて、比較対象の文字列がNULL終端されているかどうかを考慮します。これにより、fn->nname->sym->name
が指す文字列が6バイトよりも短い場合でも、NULL終端文字が見つかればそこで比較を停止するため、境界外アクセスを防ぐことができます。これにより、コンパイラの堅牢性が向上し、潜在的なクラッシュやセキュリティ上の問題が回避されます。
コアとなるコードの変更箇所
--- a/src/cmd/gc/pgen.c
+++ b/src/cmd/gc/pgen.c
@@ -174,7 +174,7 @@ compile(Node *fn)
lno = setlineno(fn);
if(fn->nbody == nil) {
-\t\tif(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
+\t\tif(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
\t\t\tyyerror("missing function body", fn);\n \t\tgoto ret;\n \t}\n```
## コアとなるコードの解説
変更は`src/cmd/gc/pgen.c`ファイルの`compile`関数内の一行です。
元のコード:
```c
if(pure_go || memcmp(fn->nname->sym->name, "init·", 6) == 0)
この行は、Goコンパイラが関数をコンパイルする際に、その関数が本体(nbody
)を持たない場合にエラーを報告するための条件チェックの一部です。具体的には、pure_go
モードであるか、または関数のシンボル名(fn->nname->sym->name
)が文字列リテラル"init·"
と最初の6バイトが一致するかどうかを調べています。
問題は、memcmp
関数が使用されていた点にあります。memcmp
は、指定されたバイト数(この場合は6)を無条件に比較します。もしfn->nname->sym->name
が指す文字列が、実際に割り当てられているメモリ領域が6バイトよりも短い場合、memcmp
は割り当てられた領域を超えて読み取りを試み、これがヒープバッファオーバーフローを引き起こしました。例えば、シンボル名が"init"
(5バイト + NULL終端)のような場合、memcmp
は6バイトを読み取ろうとするため、境界外アクセスが発生します。
修正後のコード:
if(pure_go || strncmp(fn->nname->sym->name, "init·", 6) == 0)
memcmp
がstrncmp
に置き換えられました。strncmp
は、memcmp
と同様に最初のn
バイトを比較しますが、文字列のNULL終端文字を考慮します。つまり、比較対象の文字列の実際の長さがn
バイトよりも短い場合でも、NULL終端文字が見つかればそこで比較を停止します。これにより、割り当てられたメモリ領域を超えて読み取ることを防ぎ、ヒープバッファオーバーフローを解消します。
この変更により、Goコンパイラは、init·
で始まる可能性のある関数名を安全にチェックできるようになり、コンパイラの安定性と信頼性が向上しました。
関連リンク
- Go issue tracker (CL 93370043): https://golang.org/cl/93370043
- AddressSanitizer GitHub: https://github.com/google/sanitizers/wiki/AddressSanitizer
参考にした情報源リンク
- AddressSanitizer Documentation: https://clang.llvm.org/docs/AddressSanitizer.html
memcmp
man page: https://man7.org/linux/man-pages/man3/memcmp.3.htmlstrncmp
man page: https://man7.org/linux/man-pages/man3/strncmp.3.html- Go Language Specification - Package initialization: https://go.dev/ref/spec#Package_initialization
- Go Compiler Internals (general reference, specific link not for this commit but for understanding context): https://go.dev/doc/articles/go_compiler_internals.html