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

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

このコミットは、Goコンパイラ(cmd/gc)とリンカ(cmd/ld)におけるシンボル命名規則の改善と、予約済みインポートパスの保護に関するものです。具体的には、弱いシンボル(weak symbols)のプレフィックスをweakからgo.weakに変更し、さらにgotypeといった内部的なシンボルプレフィックスがインポートパスとして誤用されることを防ぐための防御機構を導入しています。これにより、シンボル名の衝突や予期せぬ動作を防ぎ、Goツールチェインの堅牢性を向上させています。

コミット

  • コミットハッシュ: 80dbe74360e8576337208ec68baca7a63946a72a
  • Author: Russ Cox rsc@golang.org
  • Date: Tue Oct 23 11:16:08 2012 -0400

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

https://github.com/golang/go/commit/80dbe74360e857633720ec68baca7a63946a72a

元コミット内容

cmd/gc, cmd/ld: use go.weak instead of weak as the weak symbol prefix

Also defend our symbol prefixes (now just "go" and "type")
from use as import paths.

Fixes #4257.

R=ken2
CC=golang-dev
https://golang.org/cl/6744072

変更の背景

この変更は、Go言語のコンパイラとリンカにおけるシンボル管理の堅牢性を高めるために行われました。主な背景は以下の2点です。

  1. 弱いシンボルプレフィックスの明確化: 以前は弱いシンボルに対してweakというプレフィックスが使用されていましたが、これは一般的な単語であり、将来的に他の目的で使用される可能性や、意図しない衝突を引き起こす可能性がありました。Go言語の内部的なシンボルであることを明確にするため、より具体的なgo.weakというプレフィックスに変更する必要がありました。
  2. 予約済みインポートパスの保護: Go言語のコンパイラやリンカは、内部的にgotypeといった特定の文字列をシンボルや型情報のプレフィックスとして使用しています。もしユーザーがこれらの文字列をパッケージのインポートパスとして使用した場合、内部的なシンボルとインポートパスが衝突し、コンパイルエラーやリンカエラー、あるいはより深刻な予期せぬ動作を引き起こす可能性がありました。このコミットは、このような衝突を未然に防ぐための防御機構を導入し、ツールチェインの安定性とセキュリティを向上させることを目的としています。

特に、Fixes #4257という記述から、この変更がGoのIssue 4257を解決するために行われたことがわかります。このIssueでは、goという名前のパッケージをインポートしようとするとコンパイルエラーが発生するという問題が報告されていました。これは、コンパイラが内部的にgoを予約語として扱っているため、インポートパスとして使用できないという問題でした。

前提知識の解説

1. シンボルとリンカ

プログラミングにおいて、シンボルとは、変数、関数、クラスなどのプログラム要素を一意に識別するための名前です。コンパイラはソースコードを機械語に変換する際に、これらのシンボルを生成し、リンカは複数のコンパイル済みオブジェクトファイルやライブラリを結合して実行可能ファイルを生成する際に、これらのシンボルを解決(参照を実際のメモリ位置に紐付け)します。

2. 弱いシンボル (Weak Symbols)

弱いシンボル(weak symbols)は、リンカにおける特殊なシンボルの一種です。通常、同じ名前のシンボルが複数定義されている場合、リンカはエラーを報告します。しかし、弱いシンボルを使用すると、同じ名前の強いシンボル(通常のシンボル)が存在する場合、リンカは強いシンボルを優先し、弱いシンボルを無視します。強いシンボルが存在しない場合は、弱いシンボルが使用されます。

これは、ライブラリがデフォルトの実装を提供しつつ、ユーザーがその実装を独自のバージョンでオーバーライドできるようにする場合などに便利です。Go言語の内部では、特定の最適化やランタイムの挙動を制御するために弱いシンボルが利用されることがあります。

3. Go言語のコンパイラ (cmd/gc) とリンカ (cmd/ld)

  • cmd/gc (Go Compiler): Go言語のソースコードをコンパイルし、オブジェクトファイルを生成する役割を担います。この過程で、Goの構文解析、型チェック、中間コード生成などが行われます。
  • cmd/ld (Go Linker): cmd/gcによって生成されたオブジェクトファイルや、Goの標準ライブラリなどを結合し、最終的な実行可能ファイルを生成します。シンボル解決やアドレスの割り当てなどを行います。

4. Bison (Yacc)

src/cmd/gc/go.ysrc/cmd/gc/y.tab.csrc/cmd/gc/y.tab.hといったファイルは、Goコンパイラの構文解析器(パーサー)に関連しています。これらは、通常、Bison(またはYacc)というパーサー生成ツールによって生成されます。

  • .yファイル: Bisonの入力ファイルで、文法規則とそれに対応するアクション(Goのコード)が記述されています。
  • y.tab.c: Bisonが.yファイルから生成するC言語のソースファイルで、実際のパーサーの実装が含まれます。
  • y.tab.h: Bisonが生成するヘッダーファイルで、トークン定義などが含まれます。

このコミットでは、y.tab.cy.tab.hに大きな変更が見られますが、これはgo.yの変更によってBisonが再生成された結果である可能性が高いです。

技術的詳細

このコミットの技術的な変更は、主に以下の2つの側面に焦点を当てています。

1. 弱いシンボルプレフィックスの変更 (weak -> go.weak)

Goコンパイラとリンカは、内部的に特定のシンボルにweakというプレフィックスを付けていました。このコミットでは、このプレフィックスをgo.weakに変更しています。

  • src/cmd/gc/lex.c:

    • weaktypepkgという内部パッケージのパスが"weak.type"から"go.weak.type"に変更されています。これは、Goの型システムに関連する弱いシンボルを管理するための内部パッケージです。
    • コメントも// not weak%2etypeから// not go%2eweak%2etypeに変更されており、URLエンコードされた形式でのパスの解釈に関する注意が更新されています。
  • src/cmd/ld/go.c:

    • リンカ側でも、弱いシンボルを識別するためのロジックが更新され、go.weakプレフィックスを認識するように変更されています。これにより、コンパイラとリンカの間で弱いシンボルの命名規則の一貫性が保たれます。

この変更の目的は、weakという一般的な単語が他の目的で使用される可能性を排除し、Goの内部的なシンボルであることをより明確にすることです。これにより、将来的なシンボル名の衝突を防ぎ、ツールチェインの安定性を向上させます。

2. 予約済みインポートパスの保護

Goコンパイラは、gotypeといった特定の文字列を内部的なシンボルプレフィックスとして使用しています。このコミットでは、これらの文字列がユーザーのインポートパスとして使用されることを防ぐための検証ロジックが追加されています。

  • src/cmd/gc/subr.c:

    • isbadimportという新しい関数が導入されています。この関数は、与えられたインポートパスが予約済みの文字列("go""type")と一致するかどうかをチェックします。
    • reservedimportsという静的配列が追加され、予約済みのインポートパスが定義されています。
    • mkpkg関数(パッケージを作成する関数)からisbadimportの呼び出しが削除されています。これは、mkpkgがパッケージパスを正規化する前にisbadimportを呼び出すと、正規化されていないパスで誤ってエラーを報告する可能性があるためです。代わりに、importimportimportfileといった、より適切なタイミングでインポートパスの検証を行う場所でisbadimportが呼び出されるようになります。
  • src/cmd/gc/export.c:

    • importimport関数(インポートされたパッケージのシンボルを処理する関数)の冒頭でisbadimport(z)が呼び出され、インポートパスzが不正でないかチェックされます。不正な場合はerrorexit()で終了します。
  • src/cmd/gc/go.y:

    • Go言語の文法定義ファイルにおいて、hidden_importsymルール内でインポートパスの検証が追加されています。mkpkgを呼び出す前にisbadimportがチェックされ、不正なインポートパスが使用された場合にエラーを発生させます。
  • src/cmd/gc/lex.c:

    • importfile関数(ファイルからインポートを処理する関数)内で、正規化されたインポートパスに対してisbadimportが呼び出されるようになっています。これにより、ファイルシステム上のパスが予約済みインポートパスと衝突しないかどうかが検証されます。

これらの変更により、Goコンパイラは、gotypeといった内部的に重要な文字列がインポートパスとして使用されることを積極的に防ぐことができます。これにより、シンボル名の衝突によるコンパイルエラーやリンカエラー、さらには予期せぬランタイムの挙動を防ぎ、Goツールチェインの安定性とセキュリティが向上します。

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

1. src/cmd/gc/lex.c における weaktypepkg のパス変更

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -216,9 +216,9 @@ main(int argc, char *argv[])
 	typepkg = mkpkg(strlit("type"));
 	typepkg->name = "type";
 
-	weaktypepkg = mkpkg(strlit("weak.type"));
-	weaktypepkg->name = "weak.type";
-	weaktypepkg->prefix = "weak.type";  // not weak%2etype
+	weaktypepkg = mkpkg(strlit("go.weak.type"));
+	weaktypepkg->name = "go.weak.type";
+	weaktypepkg->prefix = "go.weak.type";  // not go%2eweak%2etype
 
 	unsafepkg = mkpkg(strlit("unsafe"));
 	unsafepkg->name = "unsafe";

2. src/cmd/gc/subr.c における isbadimport 関数の追加と reservedimports の定義

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -3572,9 +3572,6 @@ mkpkg(Strlit *path)
 	Pkg *p;
 	int h;
 
-	if(isbadimport(path))
-		errorexit();
-	
 	h = stringhash(path->s) & (nelem(phash)-1);
 	for(p=phash[h]; p; p=p->link)
 		if(p->path->len == path->len && memcmp(path->s, p->path->s, path->len) == 0)
@@ -3623,9 +3620,15 @@ addinit(Node **np, NodeList *init)
 	n->ullman = UINF;
 }
 
+static char* reservedimports[] = {
+	"go",
+	"type",
+};
+
 int
 isbadimport(Strlit *path)
 {
+	int i;
 	char *s;
 	Rune r;
 
@@ -3633,6 +3636,13 @@ isbadimport(Strlit *path)
 		yyerror("import path contains NUL");
 		return 1;
 	}
+	
+	for(i=0; i<nelem(reservedimports); i++) {
+		if(strcmp(path->s, reservedimports[i]) == 0) {
+			yyerror("import path \"%s\" is reserved and cannot be used", path->s);
+			return 1;
+		}
+	}
 
 	s = path->s;
 	while(*s) {

3. src/cmd/gc/export.c における importimport での isbadimport の呼び出し

--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -393,6 +393,8 @@ importimport(Sym *s, Strlit *z)
 	// human-readable messages.
 	Pkg *p;
 
+	if(isbadimport(z))
+		errorexit();
 	p = mkpkg(z);
 	if(p->name == nil) {
 		p->name = s->name;

4. src/cmd/gc/go.y における hidden_importsym での isbadimport の呼び出し

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1030,10 +1030,16 @@ sym:
 hidden_importsym:
 	'@' LLITERAL '.' LNAME
 	{
+		Pkg *p;
+
 		if($2.u.sval->len == 0)
-			$$ = pkglookup($4->name, importpkg);
-		else
-			$$ = pkglookup($4->name, mkpkg($2.u.sval));
+			p = importpkg;
+		else {
+			if(isbadimport($2.u.sval))
+				errorexit();
+			p = mkpkg($2.u.sval);
+		}
+		$$ = pkglookup($4->name, p);
 	}
 
 name:

コアとなるコードの解説

1. src/cmd/gc/lex.c の変更

weaktypepkgは、Goコンパイラが内部的に使用する「弱い型」に関連するパッケージを表す構造体です。この変更は、そのパッケージのパスとプレフィックスをweak.typeからgo.weak.typeに更新しています。これは、Goの内部的なシンボルであることをより明確にし、一般的なweakという単語との衝突を避けるための命名規則の変更です。コメントもそれに合わせて更新され、URLエンコードされた形式での解釈に関する注意が反映されています。

2. src/cmd/gc/subr.c の変更

  • reservedimports配列: この配列は、Goコンパイラがインポートパスとして使用することを禁止する文字列("go""type")を定義しています。これらの文字列は、Goの内部的なシンボルや型システムで特別な意味を持つため、ユーザーがパッケージ名として使用すると衝突が発生する可能性があります。
  • isbadimport関数: この関数は、引数として渡されたインポートパスがreservedimports配列内のいずれかの文字列と一致するかどうかをチェックします。一致した場合、yyerror(Bisonによって生成されるエラー報告関数)を呼び出してエラーメッセージを出力し、1を返して不正なインポートパスであることを示します。また、インポートパスにNULL文字が含まれていないかの基本的なチェックも行います。
  • mkpkgからのisbadimport呼び出し削除: mkpkgはパッケージパスを正規化する前に呼び出される可能性があるため、正規化されていないパスでisbadimportを呼び出すと誤ったエラーを報告する可能性があります。そのため、より適切なタイミング(インポート処理の初期段階)でisbadimportが呼び出されるように変更されました。

3. src/cmd/gc/export.c の変更

importimport関数は、他のパッケージからエクスポートされたシンボルを現在のパッケージにインポートする際に呼び出されます。この関数内でisbadimport(z)が呼び出されることで、インポートしようとしているパッケージのパスzが予約済みの文字列でないかどうかがチェックされます。もし不正なパスであれば、errorexit()が呼び出され、コンパイルが中断されます。これにより、不正なインポートパスがシステムに持ち込まれることを防ぎます。

4. src/cmd/gc/go.y の変更

go.yはGo言語の文法定義ファイルであり、hidden_importsymは隠れたインポートシンボルを処理する文法ルールです。このルール内で、インポートパスを処理する前にisbadimport($2.u.sval)が呼び出されるようになりました。$2.u.svalはインポートパスの文字列リテラルを表します。これにより、構文解析の段階で、予約済みの文字列がインポートパスとして使用されていないかどうかが検証され、不正な場合はerrorexit()でコンパイルが停止されます。

これらの変更は、Goコンパイラとリンカの内部的な整合性を保ちつつ、ユーザーが意図せず内部予約語をインポートパスとして使用してしまうことによる問題を未然に防ぐための重要なセキュリティおよび安定性向上策です。

関連リンク

参考にした情報源リンク